当我移动文件名中带有空格的单个文件时,它的工作原理如下:
$ 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
将被读取为a
、strange
和:blue
cloud
$ 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
或者使用 GNUmv
和zsh
:
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
但第二个不确定..
祝你好运