Renomeie arquivos em diferentes subpastas

Renomeie arquivos em diferentes subpastas

Gostaria de renomear vários arquivos localizados em pastas diferentes. Mais especificamente, gostaria de adicionar zeros no meio do nome do arquivo, para que todos os nomes de arquivos tenham três dígitos (e, portanto, apareçam em uma ordem lógica).

Para ser mais específico, tenho mais de 130 pastas com mais de 400 arquivos .nii em cada pasta. Cada arquivo .nii possui o seguinte padrão:

swu_run1_P_139_Vol_1.nii

  • P_### varia de P76 a P277 (número do participante)
  • Vol_### varia de 1 a 405 (número do volume)

Como o volume varia de 1 a 405, significa que qualquer lista começa com 100, em vez de ir de 1 a 405 (por exemplo, começa: 100 - 101 - 102 [..] - 109 -10- 110 - 111 etc.). Uma maneira de resolver isso é adicionar zeros ao nome do arquivo e deixar tudo com três dígitos, por exemplo:

swu_run1_P_277_Vol_1.nii -> swu_run1_P_277_Vol_001.nii
swu_run1_P_277_Vol_2.nii -> swu_run1_P_277_Vol_002.nii
swu_run1_P_277_Vol_10.nii -> swu_run1_P_277_Vol_010.nii
swu_run1_P_277_Vol_120.nii -> swu_run1_P_277_Vol_120.nii

Tenho pouca experiência com sistemas Unix/Linux, mas usando threads anteriores, consegui criar o seguinte código. Tem duas partes:

1. Renomeie vários nomes de arquivos, adicionando zeros no meio do nome do arquivo:

rename Vol_ Vol_0 *Vol_[0-9].nii

Se eu executar isso em uma subpasta, recebo a seguinte mensagem de erro:

Error: rename: swu_run1_P_275_Vol_1.nii: rename to swu_run1_P_275_Vol_01.nii failed: No such file or directory

Estranhamente, está adicionando zero ao Vol_1-9. No entanto, não adiciona zero a nenhum número que já tenha dois ou três dígitos:

swu_run1_P_277_Vol_1.txt -> swu_run1_P_277_Vol_01.nii
swu_run1_P_277_Vol_10.txt -> swu_run1_P_277_Vol_10.nii
swu_run1_P_277_Vol_100.txt -> swu_run1_P_277_Vol_100.nii

Parece que há algum tipo de loop estranho acontecendo com a expressão (ela tenta alterar o novo Vol_01, dando a mensagem de erro)? E por que não está adicionando um zero aos dois/três dígitos?

2. Encontre todos .niios arquivos nas subpastas relevantes e renomeie em lote:

find . -iname "*.nii" -execdir rename Vol_ Vol_0 *Vol_[0-9].nii '{}' \;

Meu entendimento deste código é o seguinte:

  • find . -iname "*.nii"procura todos os arquivos .nii, tanto na pasta atual quanto nas subpastas

  • -execdirdiz para aplicar a seguinte expressão à pasta atual e às subpastas, ou seja, adicionando um zero

  • rename Vol_ Vol_0 *Vol_[0-9].niiadiciona esse zero (usando o formato dedotexto para texto lista de arquivos)

  • '{}'existe para o nome do caminho do arquivo

  • \;existe para encerrar a expressão -execdir

Se eu tentar executar o código em mais pastas, recebo a seguinte mensagem de erro:

Error: rename: *Vol_[0-9].nii: rename to *Vol_0[0-9].nii failed: No such file or directory

Acho que estou recebendo a mensagem de erro porque -execdir não está sendo executado nas subpastas, mas não consigo descobrir como resolver isso.

Prefiro não entrar em cada subpasta manualmente para executar o shell, então você tem alguma sugestão de como melhorar esse código (e fazê-lo funcionar)? E por que estou recebendo a mensagem de erro "Esse arquivo ou diretório não existe"?

Responder1

Se tivéssemos o nome swu_run1_P_277_Vol_1.niina variável name, então ${name##*_}seria 1.nii(a correspondência de string de prefixo mais longa *_é removida).

Pegar isso e remover .niinos deixa com o número que queremos preencher com zero até a largura de três caracteres:

name=swu_run1_P_277_Vol_1.nii

number=${name##*_}
number=${number%.nii}

No bashshell, a maneira mais fácil (possivelmente) de preencher o número com zero é com printf:

printf '%.3d' "$number"    # would print 001 (with no newline)

Podemos construir o novo nome ao mesmo tempo:

printf '%s_%.3d.nii' "${name%_*.nii}" "$number"

Esse printfcomando seria impresso swu_run1_P_277_Vol_001.niiremovendo a sequência de sufixo que corresponde _*.niiao nome original, adicionando _o número preenchido com zero e, em seguida, a .niisequência de sufixo.

Para completar, podemos imprimir a string resultantediretamenteem uma nova variável com printf -v newname ....

Juntando isso para um único nome:

name=swu_run1_P_277_Vol_1.nii

number=${name##*_}
number=${number%.nii}

printf -v newname '%s_%.3d.nii' "${name%_*.nii}" "$number"

Então é só uma questão de mv "$name" "$newname".

Ok, então como fazer isso para todos os arquivos relevantes?

Vamos supor que todos os arquivos relevantes correspondam ao padrão global *Vol_*.nii, então, com find,

find . -type f -name '*Vol_*.nii' -exec bash -c '
    for pathname do
        dirpath=${pathname%/*}  # or: dirpath=$(dirname "$pathname")
        name=${pathname##*/}    # or: name=$(basename "$pathname")

        number=${name##*_}
        number=${number%.nii}

        printf -v newname "%s_%.3d.nii" "${name%_*.nii}" "$number"

        printf "Would rename %s --> %s\n" "$pathname" "$dirpath/$newname"
        # mv "$pathname" "$dirpath/$newname"
    done' bash {} +

O script embutido bash -caqui é chamado findcom lotes de nomes de caminhos encontrados que atendem aos critérios -type f(é um arquivo regular) e -name(tem um nome específico). O script percorre esses nomes de caminho e extrai o nome do arquivo namee o nome do caminho do diretório para dirpath.

Em seguida, ele faz as mesmas operações que fizemos antes para chegar a um novo nome, que é armazenado em newnamee, em seguida, renomeia o arquivo.

Bem, comentei o mvcomando real de segurança. Você deve executar isso uma vez para ver se a saída está correta primeiro. Se você usa ferramentas GNU, você também pode usar mv -bpara fazer backups de arquivos se houver conflitos de nomes.


Como observação lateral, no zshshell, o padrão globbing

./**/*Vol_*.nii(n)

se expandiria para todos esses nomesem ordem numérica(e recursivamente nos subdiretórios int):

$ print -rC1 ./**/*Vol_*.nii(n)
./swu_run1_P_277_Vol_1.nii
./swu_run1_P_277_Vol_2.nii
./swu_run1_P_277_Vol_10.nii
./swu_run1_P_277_Vol_120.nii

informação relacionada