这个问题源于我的另一个问题这里(“如何在 shell 中提取父目录的基本名称”),这似乎打开了“兔子洞”Unix 字符串操作为我。那么,这里有一个补充问题:
dirname
从结果中提取各个部分(“级别”)的正确方法是什么find
?
假设我有以下层次结构:
DE_AT/adventure/motovun/300x250/A2_300x250.zip
我像这样“找到”该文件:
find . -name "*.zip"
执行shell结果find
:
-exec sh -c '' {} \;
我将如何提取完整路径的每个部分?如何得到:
- DE_AT
- 冒险
- 莫托文
- 300x250
- A2_300x250.zip
这是我目前所知道的:
basename "$1" # gets me: A2_300x250.zip
dirname "$1" # gets me: ./DE_AT/adventure/motovun/300x250
我问这个是因为我需要将此 .zip 文件重命名为someString_DE_AT_motovun+A2_300x250.zip。
我想出了一个可怕的弗兰肯解决方案,如下所示:
find . -name "*.zip" -exec sh -c '
mv "$0" "myString_$(basename $(dirname $(dirname \
$(dirname "$0")_...+$(basename "$0")"
' {} \;
我什至不想尝试这个,因为这根本不可能是正确的。
答案1
您可以使用split+glob
运算符:
find . -name '*.zip' -exec sh -c '
IFS=/ # split on /
set -f # disable glob
for file do
set -- $file # invoke split+glob, store in positional parameters
# now the path components are in $1, $2...
mv -i -- "$file" "someString_${2}_${4}+${6}"
done' sh {} +
$1
会有.
,$2
DE_AT
等等。要获得最后一个参数,它变得很棘手,因为您需要类似的东西:
eval "last=\${$#}"
使用不同的 shell 可能会更容易,例如zsh
具有适当的分割运算符和数组:
find . -name '*.zip' -exec zsh -c '
for file do
components=(${(s:/:)file})
printf "Last component: %s\n" $components[-1]
mv -i -- "$file" "someString_$components[2]_$components[-3]+$components[-1]"
done' zsh {} +
有了zsh
,您还可以使用它的zmv
批量重命名工具:
autoload zmv # best in ~/.zshrc
zmv -n '([^/]#)/**/(*)/*/(*.zip)' 'someString_${1}_${2}+$3'
该部分匹配任意级别(包括 0级**/
)的子目录,因此它将与进入 // 的捕获字符串( // 、 // )进行匹配(a)/b/c/(d)/e/(f.zip)
以进行替换,(a)/(b)/c/(d.zip)
从而a
获得d
与上面f.zip
的数组方法类似的行为。a
b
d.zip
$1
$2
$3
$components
其中[^/]#
的部分#
类似于正则*
表达式运算符,匹配任何非/的序列。对于 glob,其工作方式与无法跨越 a*
相同,但在扩展 glob 后,对结果文件使用模式匹配来提取要替换的部分,并且在那里,将跨越 a so 来代替会匹配太多。*
/
zmv
*
/
(*)
([^/]#)
答案2
仅使用find
是exec
一个严格的要求吗?我宁愿循环结果find
并将其与字符串操作友好的工具结合起来,例如awk
:
for ii in $(find . -name "*.zip")
do
mv $ii $(echo $ii|awk -F/ '{print "someString_" $2 "_" $4 "+" $6}')
done
mv
(出于echo mv
测试目的替换为。)
注意:设置作为分隔符而不是空格和制表符-F/
的选项。awk
/
更新
正如 Stéphane 的评论中所建议的,调整运算符可能会更明智、更稳健split+glob
(有关它的更多信息这里)事先:
IFS=$'\n'
set -f
如果您的文件名包含空格,则前一行是强制性的;如果您的文件名包含通配符,则第二行是强制性的。
如果您不想因为以后的“奇怪”行为而抓狂,请不要忘记将它们切换到以前的设置......假设您还没有自定义这些设置:
unset IFS
set +f