尝试:
find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'f=${1#./}; mv "$1" "./${f//\//_}"' None {} \;
这对所有文件名都是安全的,即使名称中包含换行符也是如此。
这个怎么运作
-mindepth 2
这告诉find不要处理当前目录中已有的任何文件。
-type f -name '*.pdf'
这会将搜索限制为具有pdf
扩展名的常规文件。
-exec bash -c '...' None {} \;
这将在带引号的字符串中运行命令,并将文件名作为第一个参数提供$1
。
出于我们的目的,字符串None
只是一个占位符。$0
根据bash约定,它被分配给我们正在运行的命令的名称。
f=${1#./}; mv "$1" "./${f//\//_}"
这(a)./
从文件名中删除前缀,(b)使用新名称将文件移动到所需位置。
${1#./}
是bash的前缀删除的一个例子。它返回strign $1
与./
从一开始就删除。${f//\//_}
是bash 模式替换的一个例子。它返回$f
全部/
替换为的字符串_
。要阅读有关这些功能的更多信息,请参阅man bash
标题参数扩展中的部分。
更高效的版本
上面的版本为找到的每个文件调用bash。或者,我们可以为找到的几个文件调用一次bash。为此,我们将命令包装在一个for
循环中:
find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'for f in "$@"; do f=${f#./}; echo mv "$f" "./${f//\//_}"; done' None {} +
替代问题
假设我们想要的所有文件都在二级目录中,并且我们希望移动的文件具有相反的目录名称顺序,以便./path1_pathA_fileI.pdf
我们最终得到./pathA_path1_fileI.pdf
。在这种情况下:
for d1 in */; do d1=${d1%/}; for d2 in "$d1"/*/; do d2=${d2%/}; p="${d2#$d1/}_$d1"; for f in "./$d2"/*.pdf; do echo mv "$f" "./${p}_${f#./$d2/}"; done; done; done
或者,对于那些喜欢将命令分散在多行中的人:
for d1 in */
do
d1=${d1%/}
for d2 in "$d1"/*/
do
d2=${d2%/}
p="${d2#$d1/}_$d1"
for f in "./$d2"/*.pdf
do
echo mv "$f" "./${p}_${f#./$d2/}"
done
done
done