Como faço para capturar uma expressão com o terminal bash?

Como faço para capturar uma expressão com o terminal bash?

Tenho arquivos que compartilham a mesma nomenclatura log_<date>.txtespalhados 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 .txtbit é 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_.txtao 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 mvcomando real para segurança. Você deve testar uma vez para ver o que acontece primeiro.

A nullglobopção shell torna o bashshellremoverpadrõ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_*.txtarquivos 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 globstaropçã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 findpara 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_*.txtao diretório atual ou abaixo e os passaria em lotes para um sh -cscript 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 zsha do zmv, mmvou 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

informação relacionada