bash:移动带空格的文件

bash:移动带空格的文件

当我移动文件名中带有空格的单个文件时,它的工作原理如下:

$ mv "file with spaces.txt" "new_place/file with spaces.txt"

现在我有一个可能包含空格的文件列表,我想移动它们。例如:

$ echo "file with spaces.txt" > file_list.txt
$ for file in $(cat file_list.txt); do mv "$file" "new_place/$file"; done;

mv: cannot stat 'file': No such file or directory
mv: cannot stat 'with': No such file or directory
mv: cannot stat 'spaces.txt': No such file or directory

为什么第一个示例有效,但第二个示例无效?我怎样才能让它发挥作用?

答案1

永远、永远使用for foo in $(cat bar)。这是一个典型的错误,俗称bash 陷阱第 1 号。你应该使用:

while IFS= read -r file; do mv -- "$file" "new_place/$file"; done < file_list.txt

当您运行for循环时,bash 将对其读取的内容应用分词,这意味着a strange blue cloud将被读取为astrange和:bluecloud

$ cat files 
a strange blue cloud.txt
$ for file in $(cat files); do echo "$file"; done
a
strange
blue
cloud.txt

相比于:

$ while IFS= read -r file; do echo "$file"; done < files 
a strange blue cloud.txt

或者甚至,如果你坚持乌鲁克

$ cat files | while IFS= read -r file; do echo "$file"; done
a strange blue cloud.txt

因此,while循环将读取其输入并使用read将每一行分配给一个变量。将IFS=输入字段分隔符设置为 NULL *,并且-r选项read阻止它解释反斜杠转义(以便将其\t视为斜杠 +t而不是制表符)。 after--mv意思是“将 -- 之后的所有内容视为参数而不是选项”,它可以让您正确处理以-正确开头的文件名。


* 严格来说,这里没有必要,这种情况下的唯一好处是避免read删除任何前导或尾随空格,但当您需要处理包含换行符的文件名时,这是一个好习惯,或者一般来说,当您需要能够处理任意文件名时。

答案2

$(cat file_list.txt)在 POSIX shell 中,如列表上下文中,未加引号的bash是 split+glob 运算zsh符(仅分裂正如您所期望的部分)。

它按$IFS(默认情况下,程控、TAB 和 NL) 并且会进行通配,除非您完全关闭通配。

在这里,您只想在换行符上分割,并且不需要全局部分,所以它应该是:

IFS='
' # split on newline only
set -o noglob # disable globbing

for file in $(cat file_list.txt); do # split+glob
  mv -- "$file" "new_place/$file"
done

while read这还具有(相对于循环)丢弃空行、保留尾部未终止行以及保留mv的 stdin (例如在出现提示时需要)的优点。

它确实有一个缺点,即文件的全部内容必须存储在内存中(使用像bash和 之类的 shell 多次zsh)。

对于某些 shell(ksh以及zsh在较小程度上bash),您可以使用$(<file_list.txt)而不是 来优化它$(cat file_list.txt)

要使用循环执行等效操作while read,您需要:

while IFS= read <&3 -r file || [ -n "$file" ]; do
  {
    [ -n "$file" ] || mv -- "$file" "new_place/$file"
  } 3<&-
done 3< file_list.txt

或者与bash

readarray -t files < file_list.txt &&
for file in "${files[@]}"
  [ -n "$file" ] || mv -- "$file" "new_place/$file"
done

或者与zsh

for file in ${(f)"$(<file_list.txt)"}
  mv -- "$file" "new_place/$file"
done

或者使用 GNUmvzsh

mv -t -- new_place ${(f)"$(<file_list.txt)"}

或者使用 GNUmv和 GNUxargs以及 ksh/zsh/bash:

xargs -rd '\n' -a <(grep . file_list.txt) mv -t -- new_place

更多关于不加引号的扩展意味着什么的阅读:忘记在 bash/POSIX shell 中引用变量的安全隐患

答案3

您可以使用 find.. 来代替编写脚本。

 find -type f -iname \*.txt -print0 | xargs -IF -0 mv F /folder/to/destination/

对于文件位于 file 中的情况,您可以执行以下操作:

cat file_with_spaces.txt | xargs -IF -0 mv F /folder/to/destination

但第二个不确定..

祝你好运

相关内容