与“find”结合使用时如何正确提取“dirname”的各个部分?

与“find”结合使用时如何正确提取“dirname”的各个部分?

这个问题源于我的另一个问题这里(“如何在 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的数组方法类似的行为。abd.zip$1$2$3$components

其中[^/]#的部分#类似于正则*表达式运算符,匹配任何非/的序列。对于 glob,其工作方式与无法跨越 a*相同,但在扩展 glob 后,对结果文件使用模式匹配来提取要替换的部分,并且在那里,将跨越 a so 来代替会匹配太多。*/zmv*/(*)([^/]#)

答案2

仅使用findexec一个严格的要求吗?我宁愿循环结果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

相关内容