command.go 1.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. package cli
  2. import (
  3. "fmt"
  4. "strings"
  5. "unicode"
  6. )
  7. // splitCommand supports quoted arguments without invoking a system shell.
  8. // Backslashes outside quotes remain literal, preserving passwords and keys.
  9. func splitCommand(input string) ([]string, error) {
  10. var args []string
  11. var word strings.Builder
  12. var quote rune
  13. started := false
  14. runes := []rune(input)
  15. for i := 0; i < len(runes); i++ {
  16. ch := runes[i]
  17. if quote != 0 {
  18. if ch == quote {
  19. quote = 0
  20. } else if ch == '\\' && quote == '"' && i+1 < len(runes) && (runes[i+1] == '"' || runes[i+1] == '\\') {
  21. i++
  22. word.WriteRune(runes[i])
  23. } else {
  24. word.WriteRune(ch)
  25. }
  26. } else if ch == '\'' || ch == '"' {
  27. quote = ch
  28. started = true
  29. } else if unicode.IsSpace(ch) {
  30. if started {
  31. args = append(args, word.String())
  32. word.Reset()
  33. started = false
  34. }
  35. } else {
  36. word.WriteRune(ch)
  37. started = true
  38. }
  39. }
  40. if quote != 0 {
  41. return nil, fmt.Errorf("unterminated quote")
  42. }
  43. if started {
  44. args = append(args, word.String())
  45. }
  46. return args, nil
  47. }
  48. // Existing command handlers mark failures red. Keep interactive rendering while
  49. // supplying an exit status for SSH automation.
  50. func commandExitStatus(output string) uint32 {
  51. if strings.HasPrefix(strings.TrimSpace(output), ColorRed) {
  52. return 1
  53. }
  54. return 0
  55. }