如何以以下格式展平目录?
之前: ./aaa/bbb/ccc.png
后: ./aaa-bbb-ccc.png
./foo/bar/baz.png
,并./foo-bar-baz.png
同时存在。我假设您不想用前者代替后者?
如何以以下格式展平目录?
之前: ./aaa/bbb/ccc.png
后: ./aaa-bbb-ccc.png
./foo/bar/baz.png
,并./foo-bar-baz.png
同时存在。我假设您不想用前者代替后者?
Answers:
警告:我直接在浏览器中键入了大多数这些命令。注意事项。
使用zsh和zmv:
zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'
说明:该模式**/*
以递归方式匹配当前目录子目录中的所有文件(它与当前目录中的文件不匹配,但是不需要重命名)。前两对括号是可以refered为团体$1
和$2
置换文本中。最后一对括号添加了D
glob限定符,因此不会省略点文件。-o -i
表示将-i
选项传递给,mv
以便提示您是否将覆盖现有文件。
仅使用POSIX工具:
find . -depth -exec sh -c '
for source; do
case $source in ./*/*)
target="$(printf %sz "${source#./}" | tr / -)";
mv -i -- "$source" "${target%z}";;
esac
done
' _ {} +
说明:该case
语句省略了当前目录和当前目录的顶级子目录。target
包含源文件名($0
),其中前导./
句被去掉,所有斜杠均由破折号代替,最后还有z
。z
如果文件名以换行符结尾,则存在最终名称:否则,命令替换将删除它。
如果您find
不支持-exec … +
(OpenBSD,我在找您):
find . -depth -exec sh -c '
case $0 in ./*/*)
target="$(printf %sz "${0#./}" | tr / -)";
mv -i -- "$0" "${target%z}";;
esac
' {} \;
使用bash(或ksh93),您无需调用外部命令即可将破折号替换为破折号,您可以将ksh93参数扩展与字符串替换构造一起使用${VAR//STRING/REPLACEMENT}
:
find . -depth -exec bash -c '
for source; do
case $source in ./*/*)
source=${source#./}
target="${source//\//-}";
mv -i -- "$source" "$target";;
esac
done
' _ {} +
for source
实例错过了第一次提出的文件/目录...我终于找到了为什么你(通常)已经使用_
的$0
:) ...和伟大的答案感谢。我从他们那里学到了很多。
尽管这里已经有很好的答案,但我发现在以下方面更直观bash
:
find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;
aaa
现有目录在哪里。它包含的文件将被移动到当前目录(aaa中仅留空目录)。这两个调用可同时tr
处理目录和空格(对于转义斜线没有逻辑,这是读者的一项练习)。
我认为这更直观且相对容易调整。即,find
如果说我只想查找.png文件,则可以更改参数。我可以更改tr
通话,或根据需要添加更多通话。如果我想直观地检查它是否可以做对,我可以在echo
前面添加mv
(然后echo
仅在看起来正确的情况下重复执行,而不做其他事情:
find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;
另请注意,我使用的是find aaa
...而不是find .
...或find aaa/
...,因为后两者会在最终文件名中留下有趣的瑕疵。
find . -mindepth 2 -type f -name '*' |
perl -l000ne 'print $_; s/\//-/g; s/^\.-/.\// and print' |
xargs -0n2 mv
注意:这不适用于包含的文件名\n
。
当然,这只会移动类型f
文件...
唯一的名称冲突将来自pwd中预先存在的文件
用这个基本子集测试
rm -fr junk
rm -f junk*hello*
mkdir -p junk/junkier/junkiest
touch 'hello hello'
touch 'junk/hello hello'
touch 'junk/junkier/hello hello'
touch 'junk/junkier/junkiest/hello hello'
导致
./hello hello
./junk-hello hello
./junk-junkier-hello hello
./junk-junkier-junkiest-hello hello
这是一个ls
版本..评论欢迎。它适用于像这样的怪异文件名
"j1ळ\n\001\n/j2/j3/j4/\nh h\n"
,但是我真的很想知道任何陷阱。它更像是一种练习,“恶人ls
实际上可以(健壮地)做到吗?”
ls -AbQR1p * | # paths in "quoted" \escaped format
sed -n '/":$/,${/.[^/]$/p}' | # skip files in pwd, blank and dir/ lines
{ bwd="$PWD"; while read -n1; # save base dir strip leading "quote
read -r p; do # read path
printf -vs "${p%\"*}" # strip trailing quote"(:)
[[ $p == *: ]] && { # this is a directory
cd "$bwd/$s" # change into new directory
rnp="${s//\//-}"- # relative name prefix
} || mv "$s" "$bwd/$rnp$s"
done; }
只需将文件名+路径传递给tr
命令即可。tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cp
线会cp -v "$FILE" "$NEWFILE"
有所帮助吗?(此外,您不应仅假设png文件。)
cp
是不够的。find
用for
循环解析输出的整个方法都是有缺陷的。唯一安全的使用方式find
是使用-exec
,或者(-print0
如果有的话)(便携性低于-exec
)。我建议使用一个更可靠的替代方法,但您的问题目前未明确说明。
安全的文件名中的空格:
#!/bin/bash
/bin/find $PWD -type f | while read FILE
do
_new="${FILE//\//-}"
_new="${_new:1}"
#echo $FILE
#echo $_new
/bin/mv "$FILE" "$_new"
done
用cp
代替我的mv
原因不是很明显,而是应该产生相同的结果。
type find
-> find is /usr/bin/find
在我的机器上。使用PATH
作为脚本变量名是一个可怕的想法。另外,脚本会使用空格或反斜杠来修饰文件名,因为您滥用了该read
命令(在此站点上搜索IFS read -r
)。
find is hashed (/bin/find)
,相关如何?不同的发行版是不同的吗?
PATH
环境变量的要点,这是每个 Unix系统使用的东西。如果您编写了/bin/find
脚本,那么您的脚本实际上在没有的所有系统(Gilles,我的以及可能的OP)上都被破坏了/bin/find
(例如b / c在其中/usr/bin/find
)。在整点PATH
的环境变量,使之成为非问题。
$(pwd)
已经在变量中可用了:$PWD
。但是您不能在此处使用绝对路径:例如,如果您的脚本是从调用的/home/tim
,它将尝试重命名/home/tim/some/path
为/home-tim-some-path
。