Docker Compose 如何解析 `command` 字符串?

huangapple go评论136阅读模式
英文:

How docker-compose parse the `command` string?

问题

I ran the command:

docker-compose -p foo -f - up <<YAML
version: "3.8"
services:
  main:
    image: alpine
    command: echo x && y
YAML

Expected

[+] Running 1/0
 ⠿ Container foo-main-1  Created                                                                                                                                    0.0s
Attaching to foo-main-1
foo-main-1  | x && y
foo-main-1 exited with code 0

Actual

[+] Running 1/0
 ⠿ Container foo-main-1  Created                                                                                                                                    0.0s
Attaching to foo-main-1
foo-main-1  | x
foo-main-1 exited with code 0

I know CMD of Dockerfile supports exec form and shell form and expect docker-compose.yml would behave like Dockerfile, however, looks like docker-compose uses some hacky way to pass the command string.

command output
echo x y z x y z
echo x && y x && y
echo x | y x
echo x; y x; y

Question

  1. Does docker-compose simply use a regular expression to remove everything after ;, &&, and |?
  2. If not, how does docker-compose process the command string?

I would appreciate if you can also provide the source code reference of which line parsing command string.

https://github.com/docker/compose

My Environment

λ docker -v
Docker version 20.10.22, build 3a2c30b
λ docker-compose -v
Docker Compose version v2.15.1
英文:

I ran the command:

docker-compose -p foo -f - up &lt;&lt;YAML
version: &quot;3.8&quot;
services:
  main:
    image: alpine
    command: echo x &amp;&amp; y
YAML

Expected

[+] Running 1/0
 ⠿ Container foo-main-1  Created                                                                                                                                    0.0s
Attaching to foo-main-1
foo-main-1  | x &amp;&amp; y
foo-main-1 exited with code 0

Actual

[+] Running 1/0
 ⠿ Container foo-main-1  Created                                                                                                                                    0.0s
Attaching to foo-main-1
foo-main-1  | x
foo-main-1 exited with code 0

I know CMD of Dockerfile supports exec form and shell form and expect docker-compose.yml would behave like Dockerfile, however, looks like docker-compose uses some hacky way to pass the command string.

command output
echo x y z x y z
echo x && y x
echo x | y x
echo x; y x

Question

  1. Does docker-compose simply use a regular expression to remove everything after ;, &amp;&amp; and |?
  2. If not, how does docker-compose process the command string?

I would appreciate if you can also provide the source code reference of which line parsing command string.

https://github.com/docker/compose

My Environment

λ docker -v
Docker version 20.10.22, build 3a2c30b
λ docker-compose -v
Docker Compose version v2.15.1

答案1

得分: 2

Docker Compose V2已经用Go重写,它解析'command'的方式与V1不同。Docker Compose V2使用Go包'go-shellwords'来解析命令行,特定的特殊字符会被处理,比如:

\`)(&quot;;&amp;|&lt;&gt;

对于字符串'echo x && y','&'字符会中断Parser()循环,导致字符串解析结束,输出结果为'echo x'。

此外,shell形式'echo x && y'表示'echo x',然后执行'y'。这可能不是您想要的,所以您可以明确声明命令字符串为'echo "x && y"'或'[ "sh", "-c", "echo x && y" ]',这取决于您的具体需求。

如果您想了解Docker Compose如何解析命令字符串的更多信息,可以参考源代码,特别是:

  1. https://github.com/docker/compose/blob/v2/cmd/compose/compose.go#L187
func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
...
	project, err := cli.ProjectFromOptions(options)
	if err != nil {
...
  1. https://github.com/compose-spec/compose-go/blob/master/cli/options.go#L381
func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
...
	project, err := loader.Load(types.ConfigDetails{
		ConfigFiles: configs,
		WorkingDir:  workingDir,
		Environment: options.Environment,
	}, options.loadOptions...)
	if err is not nil {
		return nil, err
	}
  1. https://github.com/compose-spec/compose-go/blob/master/loader/loader.go#L1156
var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
	if str, ok := value.(string); ok {
		return shellwords.Parse(str)
	}
	return value, nil
}
  1. https://github.com/mattn/go-shellwords/blob/master/shellwords.go#L125
func (p *Parser) Parse(line string) ([]string, error) {
	args := []
	buf := ""
	var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
	backtick := ""
...

您可以查看shellswords.go以了解Parse()函数如何解析命令字符串。

英文:

Docker Compose V2 has been rewritten in Go, and the way it parses 'command' is different from V1. Docker Compose V2 uses Go package 'go-shellwords' to parse the command line, with certain special characters being handled specifically, such as:

\`)(&quot;;&amp;|&lt;&gt;

In the case of the string 'echo x && y', the '&' character breaks the Parser() loop and leads to the end of string parsing, resulting in the output terminating in 'echo x'.

Additionally, the shell form 'echo x && y' means 'echo x' and then execute 'y'. This may not be what you intended, so you can explicitly declare the command string as 'echo "x && y"' or '["sh", "-c", "echo x && y"]', depends on your specific needs.

If you're interested in learning more about how command strings are parsed by Docker Compose, you can refer to the source code, specifically:

  1. https://github.com/docker/compose/blob/v2/cmd/compose/compose.go#L187
func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
...
	project, err := cli.ProjectFromOptions(options)
	if err != nil {
...
  1. https://github.com/compose-spec/compose-go/blob/master/cli/options.go#L381
func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
...
	project, err := loader.Load(types.ConfigDetails{
		ConfigFiles: configs,
		WorkingDir:  workingDir,
		Environment: options.Environment,
	}, options.loadOptions...)
	if err != nil {
		return nil, err
	}
  1. https://github.com/compose-spec/compose-go/blob/master/loader/loader.go#L1156
var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
	if str, ok := value.(string); ok {
		return shellwords.Parse(str)
	}
	return value, nil
}
  1. https://github.com/mattn/go-shellwords/blob/master/shellwords.go#L125
func (p *Parser) Parse(line string) ([]string, error) {
	args := []string{}
	buf := &quot;&quot;
	var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
	backtick := &quot;&quot;
...

You can take a look at shellswords.go to see how command string is parsed by Parse() function.

huangapple
  • 本文由 发表于 2023年3月9日 17:16:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75682540.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定