Bash move e renomeia analisando a saída do find

4

Aninhei diretórios de arquivos pdf e gostaria de extraí-los para um diretório de nível mais alto renomeando-os da seguinte forma:

Meus arquivos são algo como:

./path1/pathA/fileI.pdf
./path1/pathB/fileII.pdf

Eu quero alcançar:

./path1_pathA_fileI.pdf    
./path1_pathB_fileII.pdf

Eu sei que posso fazer uma lista dos arquivos fazendo

find . -type f -name "*.pdf"

E posso imaginar uma solução usando

find . -type f -name "*.pdf" | mv -t ...

Mas eu não sei como preencher o ... porque eu não entendo a análise e a atribuição de variáveis no bash. Como se divide o caminho no '/' e forma um novo caminho e nome de arquivo como acima?

Muito obrigado antecipadamente!

    
por Canaryyellow 29.06.2016 / 05:13

1 resposta

6

Tente:

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'f=${1#./}; mv "$1" "./${f//\//_}"' None {} \;

Isso é seguro para todos os nomes de arquivos, mesmo aqueles com novas linhas em seus nomes.

Como funciona

  • -mindepth 2

    Isto diz para não processar nenhum arquivo que já esteja no diretório atual.

  • -type f -name '*.pdf'

    Isso restringe a pesquisa a arquivos regulares com a extensão pdf .

  • -exec bash -c '...' None {} \;

    Isso executa o comando na cadeia entre aspas, fornecendo o nome do arquivo como o primeiro argumento, $1 .

    Para nossos objetivos, a string None é simplesmente um espaço reservado. Ele é atribuído a $0 , sob as convenções do bash, é o nome do comando que estamos executando.

  • f=${1#./}; mv "$1" "./${f//\//_}"

    Isso (a) remove o prefixo ./ do nome do arquivo e (b) move o arquivo para o local desejado com o novo nome.

    ${1#./} é um exemplo de remoção de prefixo do bash . Ele retorna o strign $1 com ./ removido do começo. ${f//\//_} é um exemplo de substituição de padrões de bash . Ele retorna a string $f com todos os / substituídos por _ . Para ler mais sobre esses recursos, veja a seção em man bash intitulada Expansão do Parâmetro .

Versão mais eficiente

A versão acima invoca o bash para cada arquivo encontrado. Alternativamente, podemos invocar bash apenas uma vez para vários arquivos encontrados. Para fazer isso, colocamos nosso comando em um for loop:

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'for f in "$@"; do f=${f#./}; echo mv "$f" "./${f//\//_}"; done' None {} +

Problema alternativo

Suponha que todos os arquivos que desejamos estejam em um diretório de segundo nível e queremos que os arquivos movidos tenham a ordem dos nomes de diretórios invertidos, de modo que, em vez de ./path1_pathA_fileI.pdf , acabemos com ./pathA_path1_fileI.pdf . Neste caso:

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

Ou, para aqueles que preferem seus comandos espalhados por várias linhas:

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
    
por 29.06.2016 / 06:49