Renomeie arquivos recursivamente usando uma lista de padrões e substituições

Renomeie arquivos recursivamente usando uma lista de padrões e substituições

Eu tenho a seguinte estrutura de arquivos:

  • Algum diretório
    • Algum arquivo.txt
    • Outro arquivo aqui.log
    • Mais um arquivo.mp3
  • Outro diretório
    • Com algum outro arquivo.txt
  • Arquivo no nível raiz.txt
  • Outro arquivo no nível raiz.ext

O que eu quero fazer agora é executar um pequeno script que usa outro arquivo como entrada contendo algum tipo de pares de padrão/substituição para renomear esses arquivos recursivamente de acordo com eles. Para que todo "outro" (sem distinção entre maiúsculas e minúsculas) seja substituído por "foo" ou todo "algum" por "bar".

Já tentei muitas coisas iterando arquivos e lendo o arquivo de entrada, mas nada funcionou como deveria e finalmente consegui substituir acidentalmente meu script de teste. Mas havia muitos ls, whileou sedem mvuso.

As duas coisas que não consegui resolver foram como lidar com espaços em branco em nomes de arquivos e como não lidar com arquivos que já foram renomeados em uma correspondência de padrão anterior.

Talvez você possa me indicar a direção certa?

Responder1

TOP="`pwd -P`" \
find . -type d -exec sh -c '
   for d
   do
      cd "$d" && \
         find . ! -name . -prune -type f -exec sh -c '\''
            while IFS=\; read -r pat repl
            do
               rename "s/$pat/$repl/g" "$@"
               N=$#
               for unmoved
               do
                  if [ -f "$unmoved" ]
                  then
                     set X ${1+"$@"} "$unmoved"
                     shift
                  fi
               done
               shift "$N"
               case $# in 0 ) break ;; esac
            done < patterns.csv
         '\'' x \{\} +
      cd "$TOP"
   done
' x {} +
  • Configure findapenas para diretórios da rede e shbaixe-os de uma só vez. Isso minimiza o número de invocações do sh.
  • Configure findem cada um desses diretórios os regulararquivos líquidos, em um nível de profundidade de apenas 1, e alimente-os shem um gole. Isso minimiza o número de vezes que o renameutilitário pode ser chamado.
  • Configure um whileloop para ler os vários pattern <-> replacementpares e aplicá-los em todos os regulararquivos.
  • No processo de rename-ingmantemos uma nota sobre se um arquivo ainda estava de pé após o renameprocesso. Se descobrirmos que um arquivo ainda existe, isso significa que, por algum motivo, ele não pôde ser renomeado e, portanto, seria tentado na próxima pat/repliteração. OTOH, se o arquivo foi renomeado com sucesso, NÃO aplicamos a próxima pat/repliteração neste arquivo, retirando-o da lista de argumentos da linha de comando.

Responder2

rPairs="/tmp/rename_pairs" \
find . -type f -exec sh -c '
   while read -r old new; do
      rename "s/$old/$new/i" "$@"
   done < "$rPairs"
' x {} +

Supondo que não haja caracteres não ASCII em seu arquivo de pares renomeados e que este arquivo seja colocado fora do caminho de pesquisa.

Responder3

Após a resposta de Rakesh Sharma, segui na direção certa depois de experimentar um pouco mais e dormir um pouco.

Finalmente criei o seguinte script:

#!/bin/bash


while IFS=";" read pattern replacement
do
  if [[ ! -z $pattern ]]
  then
    echo "Checking files for pattern '$pattern'."

    find ./files -name "*$pattern*" -type f | while read fpath
    do
      fname=$(basename "$fpath")
      dname=$(dirname "$fpath")

      echo "  Found file '$fname' in directory '$dname'. Renaming to '${fname/$pattern/$replacement}'."
      mv -- "$fpath" "$dname/${fname/$pattern/$replacement}"
    done
  fi
done < patterns.csv

Ele lê o arquivo pattern.csve percorre suas linhas preenchendo as variáveis $pattern​​​​e $replacement. Na segunda etapa, ./filessão encontrados todos os arquivos dentro de um diretório que correspondam ao padrão atual. Isso deve ser feito para evitar tentar renomear arquivos novamente quando um segundo padrão corresponder, pois isso falharia. Finalmente, ele apenas renomeia o arquivo em si, não os diretórios que o contêm, usando a substituição de parâmetros do shell.

O que não está funcionando é substituir as correspondências que não diferenciam maiúsculas de minúsculas, mas posso conviver com isso.

Responder4

O ponto importante a ter em mente é que percorrer a árvore de diretórios é um processo lento, portanto, feito apenas uma vez. O que fazemos é primeiro findolhar apenas os diretórios da árvore. E em cada diretório procuramos todos regular filesabaixo deles (sem recursão aqui). Em seguida, aplicamos a transformação de renomeação nesses nomes de arquivos e, ao mesmo tempo, anotamos se ela foi bem-sucedida ou não. Se for bem-sucedido, saímos do loop while, evitando assim que o próximo patt/repl seja aplicado neste arquivo.

tempd="`mktemp -d`" \
find . -type d -exec sh -c '
   cd "$1" && \
   for f in ./*
   do
      [ -f "$f" ] || continue
      while IFS=\; read -r patt repl
      do
         case $f in
            ./*"$patt"* )
               rename -v "s/$patt/$repl/g" "$f" 2>&1 | tee "$tempd/$f"
               case $(< "$tempf/$f") in "$f renamed "* ) break ;; esac ;;
         esac
      done < /tmp/patterns.csv
   done
' {} {} \;

informação relacionada