在 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 设置需要提前执行并恢复到原始值;因此,如上所示使用
readarray
或 read
是更简单的选择。
即使您设法在 $vari
中正确收集文件路径作为数组,引用 该数组作为 ${vari[@]}
- 没有双引号 - 会中断,因为生成的字符串再次受到分词以及路径名扩展(通配)的影响。
- 要安全地将数组扩展到其元素而不对其元素进行任何解释,双引号它:
"${vari[@]}"
[1]
使用进程替换而不是管道,以确保 readarray
/ read
在 current 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 不可用.
基本上我正在使用 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 设置需要提前执行并恢复到原始值;因此,如上所示使用
readarray
或read
是更简单的选择。
- 通过在
即使您设法在
$vari
中正确收集文件路径作为数组,引用 该数组作为${vari[@]}
- 没有双引号 - 会中断,因为生成的字符串再次受到分词以及路径名扩展(通配)的影响。- 要安全地将数组扩展到其元素而不对其元素进行任何解释,双引号它:
"${vari[@]}"
- 要安全地将数组扩展到其元素而不对其元素进行任何解释,双引号它:
[1]
使用进程替换而不是管道,以确保 readarray
/ read
在 current 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 不可用.