在 shell 脚本中将带有嵌入空格的文件名读入数组

Read filenames with embedded whitespace into an array in a shell script

基本上我正在使用 find 命令搜索存在于许多目录中的多词文件,并将输出存储到变量 vari

    vari = `find -name "multi word file.xml"

当我尝试使用 for 循环遍历删除文件时,

    for file in ${vari[@]}

执行失败说.,

    rm: cannot remove `/abc/xyz/multi':: No such file or directory

你们能帮我解决这个问题吗??

解法均为线解法。底部有一个测试环境,目前还没有已知的解决方案。

如前所述,可以使用以下经过测试的命令删除文件:

$ find . -name "multi word file".xml -exec rm {} +

当路径或文件名包含 \n.

时,我无法使用带变量的 rm 命令

测试环境:

$ mkdir "$(printf "testdir" "")"
$ touch "multi word file".xml
$ mv *xml *testdir/
$ touch "2nd multi word file".xml ; mv *xml *testdir
$ ls -b
[=11=]1[=11=]2[=11=]3[=11=]4[=11=]5[=11=]6\a\b\t\n\v\f\r670123456701234567\ !"#$%&'testdir
$ ls -b *testdir
2nd\ multi\ word\ file.xml  multi\ word\ file.xml

这里有一些方法:

# change the input field separator to a newline to ignore spaces
IFS=$'\n'
for file in $(find . -name '* *.xml'); do
    ls "$file"
done

# pipe find result lines to a while loop
IFS=
find . -name '* *.xml' | while read -r file; do
    ls "$file"
done

# feed the while loop with process substitution
IFS=
while read -r file; do
    ls "$file"
done < <(find . -name '* *.xml')

如果您对结果满意,请将 ls 替换为 rm

  • 如果您确实需要预先捕获数组中的所有文件路径(假设 bash,主要是由于使用数组和 process substitution (<(...))[1];符合POSIX的解决方案会更麻烦[2];另请注意,这是基于的解决方案, 因此它不会正确处理带有 嵌入 换行符的文件名,但这在实践中非常罕见):
# Read matches into array `vari` - safely: no word splitting, no
# globbing. The only caveat is that filenames with *embedded* newlines
# won't be handled correctly, but that's rarely a concern.
# bash 4+:
readarray -t vari < <(find . -name "multi word file.xml")
# bash 3:
IFS=$'\n' read -r -d '' -a vari < <(find . -name "multi word file.xml")

# Invoke `rm` with all array elements:
rm "${vari[@]}"  # !! The double quotes are crucial.
  • 否则,让find直接执行删除(这些解决方案也能正确处理带有嵌入换行符的文件名):
find . -name "multi word file.xml" -delete

# If your `find` implementation doesn't support `-delete`:
find . -name "multi word file.xml" -exec rm {} +

至于你尝试了什么:

  • vari=`find -name "multi word file.xml"`(我已经删除了 = 周围的空格,这会导致语法错误) not create数组;这样一个 command substitution returns 所附命令的标准输出输出为 单个 字符串(删除尾随换行符)。

    • 通过在 ( ... ) 中包含命令替换,您 可以 创建一个数组:
      vari=( `find -name "multi word file.xml"` ),
      但这会在 find 的输出上执行 word splitting,并且不能正确地保留带空格的文件名。
    • 虽然这可以通过 IFS=$'\n' 解决以便仅在行边界处拆分,但生成的标记仍然受制于 pathname expansion (globbing),这可能会无意中改变文件路径。
    • 虽然这也可以通过 shell 选项解决,但您现在有 2 设置需要提前执行并恢复到原始值;因此,如上所示使用 readarrayread 是更简单的选择。
  • 即使您设法在 $vari 中正确收集文件路径作为数组,引用 该数组作为 ${vari[@]} - 没有双引号 - 会中断,因为生成的字符串再次受到分词以及路径名扩展(通配)的影响。

    • 要安全地将数组扩展到其元素而不对其元素进行任何解释,双引号它:"${vari[@]}"

[1]

使用进程替换而不是管道,以确保 readarray / readcurrent shell 而不是比 subshell.

正如 eckes 在评论中指出的那样,如果您要尝试 find ... | IFS=$'\n' read ...read 会 运行 在 subshell,这意味着它创建的变量将在命令returns时消失(超出范围)并且以后无法使用。

[2]

POSIX shell 规范。既不支持数组也不支持进程替换(也不支持 readarray,也不支持除 -r 之外的任何 read 选项);您必须按如下方式实现逐行处理:

while IFS='
' read -r vari; do
  pv vari
done <<EOF
$(find . -name "multi word file.xml")
EOF

请注意 IFS='' 之间需要 actual 换行符以便分配换行符,因为 $'\n' syntax 不可用.