Bash:子shell中的转义引号

Rog*_*hin 3 bash quoting escape-characters subshell

当我执行以下命令时:

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"
Run Code Online (Sandbox Code Playgroud)

我没有得到与此相同的结果:

#!/bin/bash
find_args="-type f '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"
Run Code Online (Sandbox Code Playgroud)

如何将第二个场景修复为与第一个相同?

我的理解是,因为双引号中有单引号,单引号会被转义,这会产生一个糟糕的扩展,看起来像这样:

find -type f -name ''\''*.c'\'' -print0
Run Code Online (Sandbox Code Playgroud)

Wil*_*ard 5

BLayer 的答案是正确的,但要解构这里真正发生的事情(忽略缺少的-name主要的错字):

#!/bin/bash
while IFS= read -r -d '' file; do
    files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"
Run Code Online (Sandbox Code Playgroud)

在进程替换( <(...))启动的shell中,bash解析如下命令:

find -type f -name '*.c' -print0
Run Code Online (Sandbox Code Playgroud)

因为水珠*.c被引用,bash所做的没有展开。但是,单引号被删除了。所以当find进程开始时,它看到的参数列表是:

-type
f
-name
*.c
-print0
Run Code Online (Sandbox Code Playgroud)

请注意,这些参数用空字节分隔,而不是用空格或换行符分隔。这是在 C 级别,而不是在 shell 级别。这与如何使用execve()C执行程序有关。

现在对比一下,在以下代码段中:

#!/bin/bash
find_args="-type f -name '*.c' -print0"
while IFS= read -r -d '' file; do
    files+=$file
done < <(find $find_args)
echo "${files[@]}"
Run Code Online (Sandbox Code Playgroud)

变量的值find_args设置为:

-type f -name '*.c' -print0
Run Code Online (Sandbox Code Playgroud)

(双引号不是值的一部分,但单引号字符是。

当命令find $find_args运行时,根据man bash,令牌$find_args受到参数扩展,然后是分词,然后是路径名扩展(又名全局扩展)。

参数扩展后,您有-type f -name '*.c' -print0. 请注意,这是引用删除之后。所以单引号不会被删除。

分词后,您将以下内容作为单独的词:

-type
f
-name
'*.c'
-print0
Run Code Online (Sandbox Code Playgroud)

然后是路径名扩展。当然'*.c'不太可能匹配任何你不一般把单引号中的文件名,所以其结果将可能'*.c'将作为文本模式传递find,因此-name主要将失败的所有文件。(仅当存在名称以单引号开头并以三个字符结尾的文件时才会成功.c'


编辑:实际上,如果有这样的文件,glob'*.c'将扩展以匹配该文件和任何其他此类文件,然后扩展[实际文件名] 将find作为模式传递给 因此,是否-print0会到达主要文件取决于 (a) 是否只有一个这样的文件名,以及 (b) 解释为 glob 的文件名是否与自身匹配。

例子:

如果您运行touch "'something.c'",则glob '*.c'将扩展为'something.c',然后find-name 'something.c'文件也将匹配该文件并打印出来。

如果你运行touch "'namewithcharset[a].c'",水珠'*.c'会在外壳扩展至如此,但find主要-name 'namewithcharset[a].c'匹配自身,它只会匹配'namewithcharseta.c',不存在,那么-print0将无法达成。

如果您运行touch "'x.c'" "'y.c'",glob'*.c'将扩展为两个文件名,这将导致输出错误,find因为'y.c'它不是一个有效的主(并且它不能是因为它不以连字符开头)。


如果nullglob设置了该选项,您将获得不同的行为。

也可以看看: