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
, while
ou sed
em mv
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 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
find
apenas para diretórios da rede esh
baixe-os de uma só vez. Isso minimiza o número de invocações dosh
. - Configure
find
em cada um desses diretórios osregular
arquivos líquidos, em um nível de profundidade de apenas 1, e alimente-ossh
em um gole. Isso minimiza o número de vezes que orename
utilitário pode ser chamado. - Configure um
while
loop para ler os váriospattern <-> replacement
pares e aplicá-los em todos osregular
arquivos. - No processo de
rename
-ingmantemos uma nota sobre se um arquivo ainda estava de pé após orename
processo. 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óximapat/repl
iteração. OTOH, se o arquivo foi renomeado com sucesso, NÃO aplicamos a próximapat/repl
iteraçã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.csv
e percorre suas linhas preenchendo as variáveis $pattern
e $replacement
. Na segunda etapa, ./files
sã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 find
olhar apenas os diretórios da árvore. E em cada diretório procuramos todos 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, 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
' {} {} \;