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

1

Eu tenho a seguinte estrutura de arquivos:

  • Algum diretório
    • Alguns arquivos.txt
    • Outro arquivo aqui.log
    • Ainda outro 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 padrão / pares de substituição para renomear esses arquivos recursivamente de acordo com eles. De modo que cada "outro" (insensível a maiúsculas e minúsculas) seja substituído por "foo" ou cada "algum" com "barra".

Já experimentei muitas coisas com iteração de arquivos e leitura do arquivo de entrada, mas nada funcionou como deveria e finalmente consegui substituir acidentalmente o script de teste. Mas havia muito ls , while , sed ou mv em uso.

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 apontar na direção certa?

    
por derbenni 13.04.2017 / 22:58

4 respostas

1
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 find apenas para diretórios da rede e use sh para baixo em um gole. Isso minimiza o número de invocações de sh .
  • Configure find em cada um desses diretórios para net regular files, em um nível de profundidade de apenas 1 e alimente-os para sh em um gulp. Isso minimiza o número de vezes que o utilitário rename pode ser chamado.
  • Configure um loop while para ler os vários pares pattern <-> replacement e aplique-os em todos os arquivos regular .
  • No processo de rename - ing , mantemos uma nota sobre se um arquivo ainda estava de pé após o processo rename . Se descobrirmos que um arquivo ainda existe, significa que, por algum motivo, ele não pode ser renomeado e, portanto, será tentado na próxima iteração pat/repl . OTOH, se o arquivo foi renomeado com sucesso, então NÃO aplicaremos a próxima iteração pat/repl neste arquivo, retirando-o da lista de argumentos da linha de comando.
por 15.04.2017 / 09:26
1
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 no seu arquivo de renomear pares e também este arquivo é colocado longe do caminho de busca.

    
por 13.04.2017 / 23:15
1

Depois da resposta de Rakesh Sharma, cheguei na direção certa depois de experimentar um pouco mais e dormir um pouco.

Por fim, 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.csv e percorre suas linhas preenchendo as variáveis $pattern e $replacement Na segunda etapa, todos os arquivos dentro de um diretório ./files são encontrados, que correspondem ao padrão atual. Isso tem que ser feito para evitar a tentativa de renomear os 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 ocorrências que não diferenciam maiúsculas de minúsculas, mas eu posso viver com isso.

    
por 14.04.2017 / 11:31
0

O ponto importante a ter em mente é que a varredura através da árvore de diretórios é um processo lento, portanto, isso é feito apenas uma vez. O que fazemos primeiro é fazer com que find veja apenas os diretórios na árvore. E para o diretório foreach, vamos procurar todos os regular files abaixo deles (sem recursão aqui). Em seguida, aplicamos a transformação de renomeação nesses nomes de arquivos e, ao mesmo tempo, mantemos uma nota sobre se ela foi bem-sucedida ou não. Se for bem-sucedido, sairemos do loop while evitando que o próximo patt / repl seja aplicado nesse 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
' {} {} \;
    
por 15.04.2017 / 00:10