github.com/webx-top/com@v1.2.12/cmd.go (about) 1 //go:build go1.2 2 // +build go1.2 3 4 // Copyright 2013 com authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"): you may 7 // not use this file except in compliance with the License. You may obtain 8 // a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 // License for the specific language governing permissions and limitations 16 // under the License. 17 18 // Package com is an open source project for commonly used functions for the Go programming language. 19 package com 20 21 import ( 22 "bufio" 23 "bytes" 24 "context" 25 "errors" 26 "fmt" 27 "io" 28 "log" 29 "os" 30 "os/exec" 31 "regexp" 32 "runtime" 33 "strconv" 34 "strings" 35 "sync" 36 "time" 37 ) 38 39 var ErrCmdNotRunning = errors.New(`command is not running`) 40 41 // ElapsedMemory 内存占用 42 func ElapsedMemory() (ret string) { 43 memStat := new(runtime.MemStats) 44 runtime.ReadMemStats(memStat) 45 ret = FormatByte(memStat.Alloc, 3) 46 return 47 } 48 49 // ExecCmdDirBytesWithContext executes system command in given directory 50 // and return stdout, stderr in bytes type, along with possible error. 51 func ExecCmdDirBytesWithContext(ctx context.Context, dir, cmdName string, args ...string) ([]byte, []byte, error) { 52 bufOut := new(bytes.Buffer) 53 bufErr := new(bytes.Buffer) 54 55 cmd := exec.CommandContext(ctx, cmdName, args...) 56 cmd.Dir = dir 57 cmd.Stdout = bufOut 58 cmd.Stderr = bufErr 59 60 err := cmd.Run() 61 if err != nil { 62 if e, y := err.(*exec.ExitError); y { 63 OnCmdExitError(append([]string{cmdName}, args...), e) 64 } else { 65 cmd.Stderr.Write([]byte(err.Error() + "\n")) 66 } 67 } 68 return bufOut.Bytes(), bufErr.Bytes(), err 69 } 70 71 // ExecCmdDirBytes executes system command in given directory 72 // and return stdout, stderr in bytes type, along with possible error. 73 func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) { 74 return ExecCmdDirBytesWithContext(context.Background(), dir, cmdName, args...) 75 } 76 77 // ExecCmdBytes executes system command 78 // and return stdout, stderr in bytes type, along with possible error. 79 func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) { 80 return ExecCmdBytesWithContext(context.Background(), cmdName, args...) 81 } 82 83 // ExecCmdBytesWithContext executes system command 84 // and return stdout, stderr in bytes type, along with possible error. 85 func ExecCmdBytesWithContext(ctx context.Context, cmdName string, args ...string) ([]byte, []byte, error) { 86 return ExecCmdDirBytesWithContext(ctx, "", cmdName, args...) 87 } 88 89 // ExecCmdDir executes system command in given directory 90 // and return stdout, stderr in string type, along with possible error. 91 func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) { 92 return ExecCmdDirWithContext(context.Background(), dir, cmdName, args...) 93 } 94 95 // ExecCmdDirWithContext executes system command in given directory 96 // and return stdout, stderr in string type, along with possible error. 97 func ExecCmdDirWithContext(ctx context.Context, dir, cmdName string, args ...string) (string, string, error) { 98 bufOut, bufErr, err := ExecCmdDirBytesWithContext(ctx, dir, cmdName, args...) 99 return string(bufOut), string(bufErr), err 100 } 101 102 // ExecCmd executes system command 103 // and return stdout, stderr in string type, along with possible error. 104 func ExecCmd(cmdName string, args ...string) (string, string, error) { 105 return ExecCmdWithContext(context.Background(), cmdName, args...) 106 } 107 108 // ExecCmdWithContext executes system command 109 // and return stdout, stderr in string type, along with possible error. 110 func ExecCmdWithContext(ctx context.Context, cmdName string, args ...string) (string, string, error) { 111 return ExecCmdDirWithContext(ctx, "", cmdName, args...) 112 } 113 114 // WritePidFile writes the process ID to the file at PidFile. 115 // It does nothing if PidFile is not set. 116 func WritePidFile(pidFile string, pidNumbers ...int) error { 117 if pidFile == "" { 118 return nil 119 } 120 var pidNumber int 121 if len(pidNumbers) > 0 { 122 pidNumber = pidNumbers[0] 123 } else { 124 pidNumber = os.Getpid() 125 } 126 pid := []byte(strconv.Itoa(pidNumber) + "\n") 127 return os.WriteFile(pidFile, pid, 0644) 128 } 129 130 var ( 131 equal = rune('=') 132 space = rune(' ') 133 quote = rune('"') 134 squote = rune('\'') 135 slash = rune('\\') 136 tab = rune('\t') 137 envOS = regexp.MustCompile(`\{\$[a-zA-Z0-9_]+(:[^}]*)?\}`) 138 envWin = regexp.MustCompile(`\{%[a-zA-Z0-9_]+(:[^}]*)?%\}`) 139 ) 140 141 func ParseFields(row string) (fields []string) { 142 item := []rune{} 143 hasQuote := false 144 hasSlash := false 145 hasSpace := false 146 var foundQuote rune 147 maxIndex := len(row) - 1 148 //drwxr-xr-x 1 root root 0 2023-11-19 04:18 'test test2' 149 for k, v := range row { 150 if !hasQuote { 151 if v == space || v == tab { 152 if hasSpace { 153 continue 154 } 155 hasSpace = true 156 fields = append(fields, string(item)) 157 item = []rune{} 158 continue 159 } 160 if hasSpace { 161 if v == quote || v == squote { 162 hasSpace = false 163 hasQuote = true 164 foundQuote = v 165 continue 166 } 167 } 168 hasSpace = false 169 } else { 170 hasSpace = false 171 if !hasSlash && v == foundQuote { 172 hasQuote = false 173 continue 174 } 175 if !hasSlash && v == slash && k+1 <= maxIndex && rune(row[k+1]) == foundQuote { 176 hasSlash = true 177 continue 178 } 179 hasSlash = false 180 } 181 item = append(item, v) 182 } 183 if len(item) > 0 { 184 fields = append(fields, string(item)) 185 } 186 return 187 } 188 189 func ParseArgs(command string, disableParseEnvVar ...bool) (params []string) { 190 item := []rune{} 191 hasQuote := false 192 hasSlash := false 193 hasSpace := false 194 var foundQuote rune 195 maxIndex := len(command) - 1 196 //tower.exe -c tower.yaml -p "eee\"ddd" -t aaaa 197 for k, v := range command { 198 if !hasQuote { 199 if v == space || v == tab { 200 if hasSpace { 201 continue 202 } 203 hasSpace = true 204 params = append(params, string(item)) 205 item = []rune{} 206 continue 207 } 208 hasSpace = false 209 if v == equal { 210 params = append(params, string(item)) 211 item = []rune{} 212 continue 213 } 214 if v == quote || v == squote { 215 hasQuote = true 216 foundQuote = v 217 continue 218 } 219 } else { 220 hasSpace = false 221 if !hasSlash && v == foundQuote { 222 hasQuote = false 223 continue 224 } 225 if !hasSlash && v == slash && k+1 <= maxIndex && rune(command[k+1]) == foundQuote { 226 hasSlash = true 227 continue 228 } 229 hasSlash = false 230 } 231 item = append(item, v) 232 } 233 if len(item) > 0 { 234 params = append(params, string(item)) 235 } 236 if len(disableParseEnvVar) == 0 || !disableParseEnvVar[0] { 237 for k, v := range params { 238 v = ParseWindowsEnvVar(v) 239 params[k] = ParseEnvVar(v) 240 } 241 } 242 return 243 } 244 245 func ParseEnvVar(v string, cb ...func(string) string) string { 246 if len(cb) > 0 && cb[0] != nil { 247 return envOS.ReplaceAllStringFunc(v, cb[0]) 248 } 249 return envOS.ReplaceAllStringFunc(v, getEnv) 250 } 251 252 func ParseWindowsEnvVar(v string, cb ...func(string) string) string { 253 if len(cb) > 0 && cb[0] != nil { 254 return envWin.ReplaceAllStringFunc(v, cb[0]) 255 } 256 return envWin.ReplaceAllStringFunc(v, getWinEnv) 257 } 258 259 func GetWinEnvVarName(s string) string { 260 s = strings.TrimPrefix(s, `{%`) 261 s = strings.TrimSuffix(s, `%}`) 262 return s 263 } 264 265 func getWinEnv(s string) string { 266 s = GetWinEnvVarName(s) 267 return GetenvOr(s) 268 } 269 270 func GetEnvVarName(s string) string { 271 s = strings.TrimPrefix(s, `{$`) 272 s = strings.TrimSuffix(s, `}`) 273 return s 274 } 275 276 func getEnv(s string) string { 277 s = GetEnvVarName(s) 278 return GetenvOr(s) 279 } 280 281 type CmdResultCapturer struct { 282 Do func([]byte) error 283 } 284 285 func (c CmdResultCapturer) Write(p []byte) (n int, err error) { 286 err = c.Do(p) 287 n = len(p) 288 return 289 } 290 291 func (c CmdResultCapturer) WriteString(p string) (n int, err error) { 292 err = c.Do([]byte(p)) 293 n = len(p) 294 return 295 } 296 297 func NewCmdChanReader() *CmdChanReader { 298 return &CmdChanReader{ch: make(chan io.Reader)} 299 } 300 301 type CmdChanReader struct { 302 ch chan io.Reader 303 mu sync.RWMutex 304 } 305 306 func (c *CmdChanReader) getCh() chan io.Reader { 307 c.mu.RLock() 308 ch := c.ch 309 c.mu.RUnlock() 310 return ch 311 } 312 313 func (c *CmdChanReader) setCh(ch chan io.Reader) { 314 c.mu.Lock() 315 c.ch = ch 316 c.mu.Unlock() 317 } 318 319 func (c *CmdChanReader) Read(p []byte) (n int, err error) { 320 ch := c.getCh() 321 if ch == nil { 322 ch = make(chan io.Reader) 323 c.setCh(ch) 324 } 325 r := <-ch 326 if r == nil { 327 return 0, errors.New(`[CmdChanReader] Chan has been closed`) 328 } 329 return r.Read(p) 330 } 331 332 func (c *CmdChanReader) Close() { 333 ch := c.getCh() 334 if ch == nil { 335 return 336 } 337 close(ch) 338 c.setCh(nil) 339 } 340 341 func (c *CmdChanReader) SendReader(r io.Reader) *CmdChanReader { 342 ch := c.getCh() 343 if ch == nil { 344 return c 345 } 346 defer recover() 347 select { 348 case ch <- r: 349 default: 350 } 351 return c 352 } 353 354 func (c *CmdChanReader) SendReaderAndWait(r io.Reader) *CmdChanReader { 355 ch := c.getCh() 356 if ch == nil { 357 return c 358 } 359 defer recover() 360 ch <- r 361 return c 362 } 363 364 func (c *CmdChanReader) Send(b []byte) *CmdChanReader { 365 return c.SendReader(bytes.NewReader(b)) 366 } 367 368 func (c *CmdChanReader) SendString(s string) *CmdChanReader { 369 return c.SendReader(strings.NewReader(s)) 370 } 371 372 func (c *CmdChanReader) SendAndWait(b []byte) *CmdChanReader { 373 return c.SendReaderAndWait(bytes.NewReader(b)) 374 } 375 376 func (c *CmdChanReader) SendStringAndWait(s string) *CmdChanReader { 377 return c.SendReaderAndWait(strings.NewReader(s)) 378 } 379 380 // WatchingStdin Watching os.Stdin 381 // example: go WatchingStdin(ctx,`name`,fn) 382 func WatchingStdin(ctx context.Context, name string, fn func(string) error) { 383 in := bufio.NewReader(os.Stdin) 384 for { 385 select { 386 case <-ctx.Done(): 387 return 388 default: 389 input, err := in.ReadString(LF) 390 if err != nil && err != io.EOF { 391 log.Printf(`watchingStdin(%s): %s`+StrLF, name, err.Error()) 392 return 393 } 394 err = fn(input) 395 if err != nil { 396 log.Printf(`watchingStdin(%s): %s`+StrLF, name, err.Error()) 397 return 398 } 399 } 400 } 401 } 402 403 func NewCmdStartResultCapturer(writer io.Writer, duration time.Duration) *CmdStartResultCapturer { 404 return &CmdStartResultCapturer{ 405 writer: writer, 406 duration: duration, 407 started: time.Now(), 408 buffer: bytes.NewBuffer(nil), 409 } 410 } 411 412 type CmdStartResultCapturer struct { 413 writer io.Writer 414 started time.Time 415 duration time.Duration 416 buffer *bytes.Buffer 417 } 418 419 func (c *CmdStartResultCapturer) Write(p []byte) (n int, err error) { 420 if time.Since(c.started) < c.duration { 421 c.buffer.Write(p) 422 } 423 return c.writer.Write(p) 424 } 425 426 func (c *CmdStartResultCapturer) Buffer() *bytes.Buffer { 427 return c.buffer 428 } 429 430 func (c *CmdStartResultCapturer) Writer() io.Writer { 431 return c.writer 432 } 433 434 func CreateCmdStr(command string, recvResult func([]byte) error) *exec.Cmd { 435 return CreateCmdStrWithContext(context.Background(), command, recvResult) 436 } 437 438 func CreateCmdStrWithContext(ctx context.Context, command string, recvResult func([]byte) error) *exec.Cmd { 439 out := CmdResultCapturer{Do: recvResult} 440 return CreateCmdStrWithWriter(command, out) 441 } 442 443 func CreateCmd(params []string, recvResult func([]byte) error) *exec.Cmd { 444 return CreateCmdWithContext(context.Background(), params, recvResult) 445 } 446 447 func CreateCmdWithContext(ctx context.Context, params []string, recvResult func([]byte) error) *exec.Cmd { 448 out := CmdResultCapturer{Do: recvResult} 449 return CreateCmdWriterWithContext(ctx, params, out) 450 } 451 452 func CreateCmdStrWithWriter(command string, writer ...io.Writer) *exec.Cmd { 453 return CreateCmdStrWriterWithContext(context.Background(), command, writer...) 454 } 455 456 func CreateCmdStrWriterWithContext(ctx context.Context, command string, writer ...io.Writer) *exec.Cmd { 457 params := ParseArgs(command) 458 return CreateCmdWriterWithContext(ctx, params, writer...) 459 } 460 461 func CreateCmdWithWriter(params []string, writer ...io.Writer) *exec.Cmd { 462 return CreateCmdWriterWithContext(context.Background(), params, writer...) 463 } 464 465 func CreateCmdWriterWithContext(ctx context.Context, params []string, writer ...io.Writer) *exec.Cmd { 466 var cmd *exec.Cmd 467 length := len(params) 468 if length == 0 || len(params[0]) == 0 { 469 return cmd 470 } 471 if length > 1 { 472 cmd = exec.CommandContext(ctx, params[0], params[1:]...) 473 } else { 474 cmd = exec.CommandContext(ctx, params[0]) 475 } 476 var wOut, wErr io.Writer = os.Stdout, os.Stderr 477 length = len(writer) 478 if length > 0 { 479 if writer[0] != nil { 480 wOut = writer[0] 481 } 482 if length > 1 && writer[1] != nil { 483 wErr = writer[1] 484 } 485 } 486 cmd.Stdout = wOut 487 cmd.Stderr = wErr 488 return cmd 489 } 490 491 func RunCmdStr(command string, recvResult func([]byte) error) *exec.Cmd { 492 return RunCmdStrWithContext(context.Background(), command, recvResult) 493 } 494 495 func RunCmdStrWithContext(ctx context.Context, command string, recvResult func([]byte) error) *exec.Cmd { 496 out := CmdResultCapturer{Do: recvResult} 497 return RunCmdStrWriterWithContext(ctx, command, out) 498 } 499 500 func RunCmd(params []string, recvResult func([]byte) error) *exec.Cmd { 501 return RunCmdWithContext(context.Background(), params, recvResult) 502 } 503 504 func RunCmdWithContext(ctx context.Context, params []string, recvResult func([]byte) error) *exec.Cmd { 505 out := CmdResultCapturer{Do: recvResult} 506 return RunCmdWriterWithContext(ctx, params, out) 507 } 508 509 func RunCmdStrWithWriter(command string, writer ...io.Writer) *exec.Cmd { 510 return RunCmdStrWriterWithContext(context.Background(), command, writer...) 511 } 512 513 func RunCmdStrWriterWithContext(ctx context.Context, command string, writer ...io.Writer) *exec.Cmd { 514 params := ParseArgs(command) 515 return RunCmdWriterWithContext(ctx, params, writer...) 516 } 517 518 var OnCmdExitError = func(params []string, err *exec.ExitError) { 519 fmt.Printf("[%v]The process exited abnormally: PID(%d) PARAMS(%v) ERR(%v)\n", time.Now().Format(`2006-01-02 15:04:05`), err.Pid(), params, err) 520 } 521 522 func RunCmdReaderWriterWithContext(ctx context.Context, params []string, reader io.Reader, writer ...io.Writer) *exec.Cmd { 523 cmd := CreateCmdWriterWithContext(ctx, params, writer...) 524 cmd.Stdin = reader 525 526 go func() { 527 err := cmd.Run() 528 if err != nil { 529 if e, y := err.(*exec.ExitError); y { 530 OnCmdExitError(params, e) 531 } else { 532 cmd.Stderr.Write([]byte(err.Error() + "\n")) 533 } 534 } 535 }() 536 537 return cmd 538 } 539 540 func RunCmdWithReaderWriter(params []string, reader io.Reader, writer ...io.Writer) *exec.Cmd { 541 return RunCmdReaderWriterWithContext(context.Background(), params, reader, writer...) 542 } 543 544 func RunCmdWithWriter(params []string, writer ...io.Writer) *exec.Cmd { 545 return RunCmdWriterWithContext(context.Background(), params, writer...) 546 } 547 548 func RunCmdWriterWithContext(ctx context.Context, params []string, writer ...io.Writer) *exec.Cmd { 549 cmd := CreateCmdWriterWithContext(ctx, params, writer...) 550 551 go func() { 552 err := cmd.Run() 553 if err != nil { 554 if e, y := err.(*exec.ExitError); y { 555 OnCmdExitError(params, e) 556 } else { 557 cmd.Stderr.Write([]byte(err.Error() + "\n")) 558 } 559 } 560 }() 561 562 return cmd 563 } 564 565 func RunCmdWithWriterx(params []string, wait time.Duration, writer ...io.Writer) (cmd *exec.Cmd, err error, newOut *CmdStartResultCapturer, newErr *CmdStartResultCapturer) { 566 return RunCmdWriterxWithContext(context.Background(), params, wait, writer...) 567 } 568 569 func RunCmdWriterxWithContext(ctx context.Context, params []string, wait time.Duration, writer ...io.Writer) (cmd *exec.Cmd, err error, newOut *CmdStartResultCapturer, newErr *CmdStartResultCapturer) { 570 length := len(writer) 571 var wOut, wErr io.Writer = os.Stdout, os.Stderr 572 if length > 0 { 573 if writer[0] != nil { 574 wOut = writer[0] 575 } 576 if length > 1 { 577 if writer[1] != nil { 578 wErr = writer[1] 579 } 580 } 581 } 582 newOut = NewCmdStartResultCapturer(wOut, wait) 583 newErr = NewCmdStartResultCapturer(wErr, wait) 584 writer = []io.Writer{newOut, newErr} 585 cmd = CreateCmdWriterWithContext(ctx, params, writer...) 586 go func() { 587 err = cmd.Run() 588 if err != nil { 589 if e, y := err.(*exec.ExitError); y { 590 OnCmdExitError(params, e) 591 } else { 592 cmd.Stderr.Write([]byte(err.Error() + "\n")) 593 } 594 } 595 }() 596 time.Sleep(wait) 597 return 598 } 599 600 func CloseProcessFromPidFile(pidFile string) error { 601 if pidFile == `` { 602 return nil 603 } 604 b, err := os.ReadFile(pidFile) 605 if err != nil { 606 if os.IsNotExist(err) { 607 return nil 608 } 609 return err 610 } 611 pid, err := strconv.Atoi(strings.TrimSpace(string(b))) 612 if err != nil { 613 return nil 614 } 615 return CloseProcessFromPid(pid) 616 } 617 618 func CloseProcessFromPid(pid int) error { 619 if pid <= 0 { 620 return nil 621 } 622 procs, err := os.FindProcess(pid) 623 if err == nil { 624 err = procs.Kill() 625 } 626 if err != nil && errors.Is(err, os.ErrProcessDone) { 627 return nil 628 } 629 return err 630 } 631 632 func CloseProcessFromCmd(cmd *exec.Cmd) error { 633 if cmd == nil { 634 return nil 635 } 636 if cmd.Process == nil { 637 return nil 638 } 639 err := cmd.Process.Kill() 640 if err != nil && errors.Is(err, os.ErrProcessDone) { 641 return nil 642 } 643 if cmd.ProcessState == nil || cmd.ProcessState.Exited() { 644 return nil 645 } 646 return err 647 } 648 649 func CmdIsRunning(cmd *exec.Cmd) bool { 650 return cmd != nil && cmd.ProcessState == nil 651 }