Como extrair corretamente várias partes de 'dirname' quando combinado com 'find'?

1

Esta questão surgiu de outra questão que eu tinha aqui (" Como extrair o nome base do diretório pai no shell "), o que parece ter aberto o" buraco do coelho "nas manipulações da cadeia do Unix para mim Então, aqui vai a pergunta suplementar:

Qual é a maneira correta de extrair várias partes ("níveis") de dirname resultados combinados com find ?

Vamos supor que eu tenha a seguinte hierarquia:

DE_AT/adventure/motovun/300x250/A2_300x250.zip

Eu "encontro" o arquivo da seguinte forma:

find . -name "*.zip" 

execute o shell nos resultados find :

-exec sh -c '' {} \;

Como eu extrairia cada parte do caminho completo? Como eu obtenho:

  • DE_AT
  • aventura
  • motovun
  • 300 x 250
  • A2_300x250.zip

Isso é o que eu sei até agora:

basename "$1" # gets me: A2_300x250.zip
dirname "$1"  # gets me: ./DE_AT/adventure/motovun/300x250

Estou perguntando isso porque preciso renomear esses arquivos .zip para someString_DE_AT_motovun + A2_300x250.zip .

Eu inventei uma solução frank horrível assim:

find . -name "*.zip" -exec sh -c '
    mv "$0" "myString_$(basename $(dirname $(dirname \
    $(dirname "$0")_...+$(basename "$0")"
' {} \;

Eu nem quero tentar isso porque isso simplesmente não pode estar correto.

    
por Alex Starbuck 30.08.2016 / 16:59

2 respostas

4

Você pode usar o operador 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 teria . , $2 DE_AT e assim por diante. Para obter o último argumento, torna-se complicado, pois você precisa de algo como:

eval "last=\${$#}"

Pode ser mais fácil usar um shell diferente, como zsh , que possui operadores e matrizes apropriados para isso:

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 {} +

Com zsh , você também pode usar o zmv ferramenta de renomeação de lotes:

autoload zmv # best in ~/.zshrc
zmv -n '([^/]#)/**/(*)/*/(*.zip)' 'someString_${1}_${2}+$3'

A parte **/ corresponde a qualquer nível (incluindo 0) dos subdiretórios, portanto corresponderá a (a)/b/c/(d)/e/(f.zip) ou (a)/(b)/c/(d.zip) com as sequências capturadas ( a / d / f.zip , a / b / d.zip ) indo em $1 / $2 / $3 para a substituição, de modo a obter um comportamento semelhante ao da abordagem da matriz $components acima.

A parte [^/]# em que # é como o operador regexp * , corresponde a qualquer sequência de não /. Para globs, * funciona da mesma forma que * não pode passar um / , mas depois de expandir o glob, zmv usa a correspondência de padrões nos arquivos resultantes para extrair as partes para a substituição, e lá, * iria para um / , então (*) no lugar de ([^/]#) corresponderia muito.

    
por 30.08.2016 / 17:05
0

Está usando apenas find exec um requisito estrito? Eu prefiro colar em find resultados e combiná-lo com uma ferramenta amigável de manipulação de strings como awk :

for ii in $(find . -name "*.zip")
do
    mv $ii $(echo $ii|awk -F/ '{print "someString_" $2 "_" $4 "+" $6}')
done

(Substitua mv por echo mv para fins de teste).

NB: -F/ opção de awk conjuntos / como o separador em vez de espaços em branco e tabulações.

Atualizar

Como sugerido nos comentários de Stéphane, seria provavelmente mais inteligente e mais robusto ajustar o operador split+glob (mais informações sobre ele aqui ) de antemão:

IFS=$'\n'
set -f

A linha anterior é obrigatória, de qualquer forma, se os nomes dos seus arquivos contiverem espaços, e a segunda linha, se os nomes dos seus arquivos contiverem caracteres curinga.

Não se esqueça de alterá-los para configurações anteriores depois, se não quiser arrancar o cabelo por causa de um comportamento "estranho" depois ... Supondo que você não tenha personalizado essas configurações:

unset IFS
set +f
    
por 30.08.2016 / 17:12