通过将文件列表与目标索引匹配来移动文件列表

通过将文件列表与目标索引匹配来移动文件列表

假设我有两个文本文件src.txtdest.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命令,但我不确定这里是否需要grepsed需要。我不断遇到cannot stat...空格字符解析错误。

答案1

zsh

src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f

这会读取数组中的每个文件,然后对于数组中的每个元素“目的地”数组,如果基本名称(:tzsh删除所有前导路径名组件的修饰符)也在“源”array 然后它移动文件。要执行空运行,请替换mvprintf '"%s" -> "%s"\n'.


现在,您还可以运行(仍在zsh):

for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f

只要src.txtin 中的任何文件名都不与路径列表中的任何目录名(或该名称的一部分)匹配dest.txt(例如,文件名data1in和类似in 的src.txt路径会给出误报),则该方法就可以正常工作。为了避免这种情况,您可以将文件名作为模式传递(即使用正则表达式,如)而不是固定字符串,以便仅匹配 中路径的最后一个组成部分。尽管这需要转义 中文件名中的所有特殊字符(如果有),例如这次使用( ):/path/data1_dir/some_filedest.txtgrep/filename$Fdest.txtsrc.txtbash4

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

一行脚本生成一个脚本,该脚本生成一个脚本。

在此示例中,我们使用第一次调用sedonsrc.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

相关内容