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

时间:2016-04-02 12:45:10

标签: arrays shell whitespace

基本上我正在使用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

你们可以帮助我解决这个问题吗?

3 个答案:

答案 0 :(得分:4)

  • 如果您确实需要预先捕获阵列中的所有文件路径(假定为bash,主要是由于使用了数组而process substitution (<(...)) [1] ;一个POSIX符合条件的解决方案会更麻烦 [2] ;另请注意,这是一个基于 line 的解决方案,因此它不会处理 embedded newlines正确,但这在实践中非常罕见):
# 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"`(我删除了=周围的空格,这会导致语法错误)创建数组;这样的command substitution将封闭命令的stdout输出作为字符串返回(删除尾随换行符)。

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

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

[1]

使用进程替换而不是管道,以确保在当前 shell而不是子shell中执行readarray / read

正如eckes在评论中指出的那样,如果您尝试使用find ... | IFS=$'\n' read ...read将在子shell 中运行,这意味着变量当命令返回时,它创建将消失(超出范围),以后不能使用。

[2]

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

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

请注意IFS=''之间的实际换行符,以便在$'\n' syntax不可用的情况下指定换行符。

答案 1 :(得分:2)

以下是一些方法:

# 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

答案 2 :(得分:0)

解决方案都是基于行的解决方案。底部有一个测试环境,没有已知的解决方案。

如前所述,可以使用此测试命令删除该文件:

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

当路径或文件名包含 \ n 时,我无法使用带有变量的 rm 命令。

测试环境:

$ mkdir "$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47testdir" "")"
$ touch "multi word file".xml
$ mv *xml *testdir/
$ touch "2nd multi word file".xml ; mv *xml *testdir
$ ls -b
\001\002\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\ !"#$%&'testdir
$ ls -b *testdir
2nd\ multi\ word\ file.xml  multi\ word\ file.xml