Bash:子shell中的转义引号

Bash:子shell中的转义引号

当我执行以下命令时:

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

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

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

如何修复第二个场景使其与第一个场景等效?

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

find -type f -name ''\''*.c'\'' -print0

答案1

布莱尔的回答是正确的,但要解构这里真正发生的事情(忽略缺少主数据库的拼写错误-name):

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

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

find -type f -name '*.c' -print0

因为 glob*.c被引用,所以 bash 会这样做不是扩展它。但是,单引号被去掉。因此,当find进程启动时,它看到的参数列表是:

-type
f
-name
*.c
-print0

请注意,这些参数用分隔符分隔空字节,不带空格或换行符。这是在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[@]}"

变量的值find_args设置为:

-type f -name '*.c' -print0

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

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

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

分词后,您将得到以下单独的单词:

-type
f
-name
'*.c'
-print0

然后路径名扩展。当然,'*.c'不太可能匹配任何内容,因为您通常不会在文件名中放置单引号,因此结果将可能将会'*.c'作为文字模式传递给find,因此主-name数据库将在所有文件上失败。 (只有存在名称以单引号开头并以三个字符结尾的文件时才会成功.c'


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

例子:

如果你跑步touch "'something.c'",那么全局 '*.c'将扩展为'something.c',然后find主文件-name 'something.c'也将匹配该文件并将其打印。

如果您运行,则touch "'namewithcharset[a].c'"glob'*.c'将被 shell 扩展为 glob,但find主将-name 'namewithcharset[a].c'不是匹配本身——它只会匹配'namewithcharseta.c',而它不存在——所以-print0不会被到达。

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


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

也可以看看:

答案2

-name(请注意,您有一个拼写错误。您在第二个示例中遗漏了该标志。)

一种方法是将参数放入数组中并将该数组适当地传递给find...

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

该格式${foo[@]}扩展到数组的所有元素,每个元素都是一个单独的单词(而不是扩展到单个字符串)。这更接近原始脚本的意图。

答案3

除了已经说过的内容之外,您还需要:

  • 将变量声明$files为数组,因为默认情况下它将是标量,并且var+=something在标量上执行字符串连接(或算术加法,如果标量已被赋予整数属性)。或者使用var+=(something)语法(它会自动将变量转换为数组)。
  • 初始化变量(未设置或为空列表),否则您可能会从环境继承初始值。

正在做:

files=()
while ...
  files+=$file # or files+=("$file")
done

files除非该变量先前已在脚本中被声明为关联数组(在这种情况下files+=something将会files["0"]+=somethingfiles+=("$files")错误的),否则就足够了。

如果您不能保证files脚本之前没有被定义为关联数组,您可能需要:

typeset -a files=()

相反,尽管这会产生将变量的范围限制为封闭函数的副作用。typeset -ga files=()不能正常工作作为解决该问题的方法,因为bash它会在全局范围内声明变量。unset files; files=()可能也不起作用,因为unset files在某些情况下可能会files从外部范围(可能是关联数组)显示变量而不是取消设置它。

相关内容