当我执行以下命令时:
#!/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"]+=something
是files+=("$files")
错误的),否则就足够了。
如果您不能保证files
脚本之前没有被定义为关联数组,您可能需要:
typeset -a files=()
相反,尽管这会产生将变量的范围限制为封闭函数的副作用。typeset -ga files=()
不能正常工作作为解决该问题的方法,因为bash
它会在全局范围内声明变量。unset files; files=()
可能也不起作用,因为unset files
在某些情况下可能会files
从外部范围(可能是关联数组)显示变量而不是取消设置它。