假设我有两个文本文件src.txt
和dest.txt
,其中src.txt
包含文件名列表(其中一些包含空格) ,/src/dir/
并且dest.txt
以随机顺序包含它们所属的完整文件路径列表(同样包含空格)。例如:
src.txt:
file 1.jpg
file_2.html
file 3.jpg
目标.txt:
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
如何从 shell 执行批量移动操作?我一直while read
在对源文件进行循环处理,并且我很确定我需要使用该mv
命令,但我不确定这里是否需要grep
或sed
需要。我不断遇到cannot stat...
空格字符解析错误。
答案1
和zsh
:
src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f
这会读取数组中的每个文件,然后对于数组中的每个元素“目的地”数组,如果基本名称(:t
是zsh
删除所有前导路径名组件的修饰符)也在“源”array 然后它移动文件。要执行空运行,请替换mv
为printf '"%s" -> "%s"\n'
.
现在,您还可以运行(仍在zsh
):
for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f
只要src.txt
in 中的任何文件名都不与路径列表中的任何目录名(或该名称的一部分)匹配dest.txt
(例如,文件名data1
in和类似in 的src.txt
路径会给出误报),则该方法就可以正常工作。为了避免这种情况,您可以将文件名作为模式传递(即使用正则表达式,如)而不是固定字符串,以便仅匹配 中路径的最后一个组成部分。尽管这需要转义 中文件名中的所有特殊字符(如果有),例如这次使用( ):/path/data1_dir/some_file
dest.txt
grep
/filename$
F
dest.txt
src.txt
bash
4
readarray -t files < <(sed 's|[[\.*^$/]|\\&|g;s|.*|/&$|' src.txt | grep -f- dest.txt)
for f in "${files[@]}"; do mv /src/dir/"${f##*/}" "$f"; done
答案2
如果换行符是可接受的分隔符,那么以下内容在 POSIX shell 中应该非常可靠:
IFS='
';set -f
for f in $(cat <"$destfile")
do [ -e "./${f##*/}" ] ||
[ -h "./${f##*/}" ] &&
mv "./${f##*/}" "$f"
done
我可以想象该解决方案可能存在两个问题:
输入文件大小太大,无法像这样一次性拆分。
- 在我的系统上,直到输入接近数万行之前,这根本不值得认真考虑。
文件名
$destfile
可能存在于当前目录中,但应该不是无论如何都会被感动。- 因为此解决方案完全放弃比较两个输入文件,而仅检查
$destfile
当前目录中每个最后的路径名组件是否存在,因此如果任何文件名可能无意匹配,则不应考虑。
- 因为此解决方案完全放弃比较两个输入文件,而仅检查
如果只需要处理第一个问题:
sed -ne"s|'|'"'\\&&|g' <"$destfile" \
-e "s|.*/\([^/].*\)|_mv './\1' '&'|p" |
sh -c '_mv(){ [ -e "$1" ]||[ -h "$1" ]&& mv "$@";};. /dev/fd/0'
如果您sh
是,您可以在末尾dash
删除并使用:. /dev/fd/0
sed ... | sh -cs '_mv(){ ...;}'
...因为dash
奇怪的是,它同时处理命令行和标准输入调用选项,而且没有任何抱怨。这不是很便携,但是. /dev/fd/0
——虽然相当便携——也不是严格符合标准的。
如果第二个问题是一个问题:
export LC_ALL=C
sed -ne'\|/$|!s|.*/\(.*\)|\1/&|p' <"$destfile" |
sort -t/ -k1,1 - ./"$srcfile" | cut -d/ -f2- |
sed -e "\|/|!N;\|\n.*/|!d" \
-e "s|'|'"'\\&&|g' \
-e "s|\n|' '|;s|.*|mv './&'|" | sh
...只要所有文件名./"$srcfile"
在"$destfile"
.sort
将始终将两个相同的比较中较短的一个浮动到顶部,因此当只有第一个字段重要时,并且文件名被添加到每个路径名的开头,那么两个文件的"$destfile"
合并操作将输出如下序列:sort
$srcfile: no /
$destfile: match
$destfile: unique
$destfile: unique
...
$srcfile: no /
$destfile: match
$destfile: unique
...因此您只需要关心以不匹配的行开头的行对/
。
答案3
while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt
这将打印本来要做的事情。只需删除echo
即可实际复制文件。
答案4
一行脚本生成一个脚本,该脚本生成一个脚本。
在此示例中,我们使用第一次调用sed
onsrc.txt
来生成第二个sed
脚本,该脚本将运行 ondest.txt
来生成用于复制文件的 shell 脚本。
这是一行:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt #| sh -x
和输出:
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
#| sh
请注意命令末尾的注释。这样,您可以尝试该命令并查看它会执行什么操作,如果效果良好,请取消注释管道sh
并真正复制文件。
内部 sed 命令根据 src.txt 构建 sed 脚本。生成的脚本的第一行如下所示:
/\/file 1.jpg$/ { s/^/cp file 1.jpg /; p; }
它是这样工作的:
输入:
$ cat src.txt
file 1.jpg
file_2.html
file 3.jpg
$ cat dest.txt
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
第一次sed
调用。这显示了生成的脚本,该脚本将由第二次调用进行解释sed
:
$ sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt
/\/file 1.jpg$/ { s/^/cp "file 1.jpg" "/; s/$/";/; p; }
/\/file_2.html$/ { s/^/cp "file_2.html" "/; s/$/";/; p; }
/\/file 3.jpg$/ { s/^/cp "file 3.jpg" "/; s/$/";/; p; }
使用 shell 命令替换将第一个命令的输出用作传递sed
给第二个调用的命令行上的脚本sed
:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
sh -x
现在,使用 xtrace 选项 ( )将 sed 的输出通过管道传输到 shell 。我没有任何文件,因此出现错误:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt | sh -x
+ cp file 3.jpg /dest/dir 1/file 3.jpg
cp: cannot stat ‘file 3.jpg’: No such file or directory
+ cp file 1.jpg /dest/dir 2/file 1.jpg
cp: cannot stat ‘file 1.jpg’: No such file or directory
+ cp file_2.html /dest/file_2.html
cp: cannot stat ‘file_2.html’: No such file or directory