Tenho arquivos que compartilham a mesma nomenclatura log_<date>.txt
espalhados em um diretório aninhado root
. Onde <date>
está a data em que os arquivos foram criados no formato AAAAmmddHHMM. Por problemas de compatibilidade de código, gostaria de renomear todos os arquivos para<date>_log.txt
Eu sei que teria que capturar a <date>
expressão, mas como faço isso no bash? Além disso, como faço isso em um diretório aninhado?
Responder1
Digamos que você tenha o nome de um desses arquivos na variável shell name
. A remoção do log_
bit do valor da variável é feita com ${name#log_}
, e a remoção do .txt
bit é feita com ${name%.txt}
. O que resta é a data, e o formato em que ela está essencialmente não é interessante. Seria fácil pegar o valor eliminado e adicioná-lo _log_.txt
ao final para criar o novo nome.
Para fazer isso para os arquivos em um único diretório, use um loop. Observe que agora também temos que remover o nome do diretório do início do valor da variável.
shopt -s nullglob
for name in root/log_*.txt; do
newname=root/${name#root/log_} # remove prefix, add root/ back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf 'Would move "%s" to "%s"\n' "$name" "$newname"
# mv -i "$name" "$newname"
done
Comentei o mv
comando real para segurança. Você deve testar uma vez para ver o que acontece primeiro.
A nullglob
opção shell torna o bash
shellremoverpadrões incomparáveis, em vez de deixá-los sem expansão. Isso significa que o loop não seria executado se não houvesse log_*.txt
arquivos no diretório especificado.
Isso pode ser aplicado a qualquer arquivo no diretório atual (incluindo o diretório atual) recursivamente pormais ou menosapenas modificando o padrão que percorremos:
shopt -s globstar nullglob
for name in ./**/log_*.txt; do
dirpath=${name%/*} # get path of directory
newname=$dirpath/${name#$dirpath/log_} # remove prefix, add directory path back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf 'Would move "%s" to "%s"\n' "$name" "$newname"
# mv -i "$name" "$newname"
done
As outras mudanças incluem a ativação da globstar
opção shell, que nos permite **
combinar subdiretórios. Além disso, escolho o caminho do diretório para poder anexá-lo ao novo nome.
Você também pode usar find
para gerar a entrada para o loop:
find . -type f -name 'log_*.txt' -exec sh -c '
for name do
dirpath=${name%/*} # get path of directory
newname=$dirpath/${name#$dirpath/log_} # remove prefix, add directory path back
newname=${newname%.txt}_log.txt # remove suffix, add _log.txt back
printf "Would move \"%s\" to \"%s\"\n" "$name" "$newname"
# mv -i "$name" "$newname"
done' sh {} +
Isso encontraria todos os arquivos regulares cujos nomes correspondem log_*.txt
ao diretório atual ou abaixo e os passaria em lotes para um sh -c
script em linha que faria essencialmente o mesmo trabalho que o loop na segunda variação acima.
Responder2
O melhor é usar uma ferramenta de renomeação de arquivos em lote como zsh
a do zmv
, mmv
ou uma das variantes baseadas em perl do rename
:
zsh << 'EOF'
autoload zmv
zmv 'log_(*).txt' '${1}_log.txt'
EOF
Ou
prename 's/log_(.*)\.txt/${1}_log.txt/' log*.txt