英文:
Parse a command line string into flags and arguments in Golang
问题
我正在寻找一个能够将字符串(例如-v --format "some example" -i test
)解析成字符串切片的包,能够正确处理引号、空格等情况:
-v
--format
some example
-i
test
我已经查看了内置的flag
包以及GitHub上的其他标志处理包,但似乎没有一个包能够处理将原始字符串解析为标记的这种特殊情况。在尝试自己实现之前,我更愿意寻找一个现成的包,因为我相信有很多特殊情况需要处理。
有什么建议吗?
英文:
I'm looking for a package that would take a string such as -v --format "some example" -i test
and parse it into a slice of strings, handling quotes, spaces, etc. properly:
-v
--format
some example
-i
test
I've checked the built-in flag
package as well as other flag handling packages on Github but none of them seem to handle this particular case of parsing a raw string into tokens. Before trying to do it myself I'd rather look for a package as I'm sure there are a lot of special cases to handle.
Any suggestion?
答案1
得分: 14
看起来类似于 shlex:
import "github.com/google/shlex"
shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"}
英文:
Looks similar to shlex:
import "github.com/google/shlex"
shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"}
答案2
得分: 11
以下是我翻译好的内容:
这是我最终创建的函数。
它将一个命令分割成参数。例如,cat -v "some file.txt"
,将返回["cat", "-v", "some file.txt"]
。
它还正确处理转义字符,特别是空格。因此,cat -v some\ file.txt
也会被正确分割为["cat", "-v", "some file.txt"]
。
func parseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true
for i := 0; i < len(command); i++ {
c := command[i]
if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}
if escapeNext {
current += string(c)
escapeNext = false
continue
}
if c == '\\' {
escapeNext = true
continue
}
if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}
if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}
if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}
if state == "quotes" {
return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command))
}
if current != "" {
args = append(args, current)
}
return args, nil
}
英文:
For information, this is the function I've ended up creating.
It splits a command into its arguments. For example, cat -v "some file.txt"
, will return ["cat", "-v", "some file.txt"]
.
It also correctly handles escaped characters, spaces in particular. So cat -v some\ file.txt
will also correctly be split into ["cat", "-v", "some file.txt"]
func parseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true
for i := 0; i < len(command); i++ {
c := command[i]
if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}
if (escapeNext) {
current += string(c)
escapeNext = false
continue
}
if (c == '\\') {
escapeNext = true
continue
}
if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}
if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}
if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}
if state == "quotes" {
return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command))
}
if current != "" {
args = append(args, current)
}
return args, nil
}
答案3
得分: 4
如果参数是通过命令行传递给您的程序的,那么shell应该会处理这个,并且os.Args
会被正确填充。例如,在您的情况下,os.Args[1:]
将等于
[]string{"-v", "--format", "some example", "-i", "test"}
但是,如果您只有一个字符串,并且出于某种原因,您想模拟shell对它的处理方式,那么我建议使用一个类似于https://github.com/kballard/go-shellquote的包。
英文:
If the args were passed to your program on the command line then the shell should handle this and os.Args
will be populated correctly. For example, in your case os.Args[1:]
will equal
[]string{"-v", "--format", "some example", "-i", "test"}
If you just have the string though, for some reason, and you'd like to mimic what the shell would do with it, then I recommend a package like https://github.com/kballard/go-shellquote
答案4
得分: 2
@laurent的答案很好,但是当command
包含UTF-8字符时,它不起作用。
它在第三个测试中失败:
func TestParseCommandLine(t *testing.T){
tests := []struct{
name string
input string
want []string
}{
{
"normal",
"hello world",
[]string{"hello", "world"},
},
{
"quote",
"hello \"world hello\"",
[]string{"hello", "world hello"},
},
{
"utf-8",
"hello 世界",
[]string{"hello", "世界"},
},
{
"space",
"hello\\ world",
[]string{"hello world"},
},
}
for _, tt := range tests{
t.Run(tt.name, func(t *testing.T) {
got, _ := parseCommandLine(tt.input)
if !reflect.DeepEqual(got, tt.want){
t.Errorf("expect %v, got %v", tt.want, got)
}
})
}
}
根据他/她的答案,我编写了这个函数,对于UTF-8工作得很好,只需将for i := 0; i < len(command); i++ {c := command[i]
替换为for _, c := range command
。
这是我的答案:
func parseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true
for _, c := range command {
if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}
if escapeNext {
current += string(c)
escapeNext = false
continue
}
if c == '\\' {
escapeNext = true
continue
}
if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}
if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}
if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}
if state == "quotes" {
return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command))
}
if current != "" {
args = append(args, current)
}
return args, nil
}
英文:
@laurent 's answer is wonderful, but it doesn't work when command
includes utf-8 char.
It fail the third test:
func TestParseCommandLine(t *testing.T){
tests := []struct{
name string
input string
want []string
}{
{
"normal",
"hello world",
[]string{"hello", "world"},
},
{
"quote",
"hello \"world hello\"",
[]string{"hello", "world hello"},
},
{
"utf-8",
"hello 世界",
[]string{"hello", "世界"},
},
{
"space",
"hello\\ world",
[]string{"hello world"},
},
}
for _, tt := range tests{
t.Run(tt.name, func(t *testing.T) {
got, _ := parseCommandLine(tt.input)
if !reflect.DeepEqual(got, tt.want){
t.Errorf("expect %v, got %v", tt.want, got)
}
})
}
}
Based on his/her answer, i wrote this func that works good for utf-8, just by replacing for i := 0; i < len(command); i++ {c := command[i]
to for _, c := range command
Here's the my answer:
func parseCommandLine(command string) ([]string, error) {
var args []string
state := "start"
current := ""
quote := "\""
escapeNext := true
for _, c := range command {
if state == "quotes" {
if string(c) != quote {
current += string(c)
} else {
args = append(args, current)
current = ""
state = "start"
}
continue
}
if escapeNext {
current += string(c)
escapeNext = false
continue
}
if c == '\\' {
escapeNext = true
continue
}
if c == '"' || c == '\'' {
state = "quotes"
quote = string(c)
continue
}
if state == "arg" {
if c == ' ' || c == '\t' {
args = append(args, current)
current = ""
state = "start"
} else {
current += string(c)
}
continue
}
if c != ' ' && c != '\t' {
state = "arg"
current += string(c)
}
}
if state == "quotes" {
return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command))
}
if current != "" {
args = append(args, current)
}
return args, nil
}
答案5
得分: 0
hedzr/cmdr可能是一个不错的选择。它是一个类似于getopt的命令行解析器,轻量级,具有流畅的API或经典风格。
英文:
hedzr/cmdr might be good. it's a getopt-like command-line parser, light weight, fluent api or classical style.
答案6
得分: 0
我知道这是一个旧问题,但可能仍然相关。使用正则表达式怎么样?它非常简单,对于大多数情况可能已经足够了:
r := regexp.MustCompile(`\"[^\"]+\"|\S+`)
m := r.FindAllString(`-v --format "some example" -i test`, -1)
fmt.Printf("%q", m)
// 输出 ["-v" "--format" "\"some example\"" "-i" "test"]
你可以在 https://go.dev/play/p/1K0MlsOUzQI 上尝试。
编辑:
为了处理 test\ abc
作为一个条目,可以使用以下正则表达式:\"[^\"]+\"|\S+\\\s\S+|\S+
英文:
I know this is an old question, but might be still relevant. What about using regex? It is quite simple and might be enough for most of cases:
r := regexp.MustCompile(`\"[^\"]+\"|\S+`)
m := r.FindAllString(`-v --format "some example" -i test`, -1)
fmt.Printf("%q", m)
// Prints out ["-v" "--format" "\"some example\"" "-i" "test"]
You can try https://go.dev/play/p/1K0MlsOUzQI
Edit:
To handle also test\ abc
to be a 1 entry, use this regex: \"[^\"]+\"|\S+\\\s\S+|\S+
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论