
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 .nii
os 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-execdir
diz para aplicar a seguinte expressão à pasta atual e às subpastas, ou seja, adicionando um zerorename Vol_ Vol_0 *Vol_[0-9].nii
adiciona 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.nii
na variável name
, então ${name##*_}
seria 1.nii
(a correspondência de string de prefixo mais longa *_
é removida).
Pegar isso e remover .nii
nos 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 bash
shell, 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 printf
comando seria impresso swu_run1_P_277_Vol_001.nii
removendo a sequência de sufixo que corresponde _*.nii
ao nome original, adicionando _
o número preenchido com zero e, em seguida, a .nii
sequê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 -c
aqui é chamado find
com 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 name
e 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 newname
e, em seguida, renomeia o arquivo.
Bem, comentei o mv
comando 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 -b
para fazer backups de arquivos se houver conflitos de nomes.
Como observação lateral, no zsh
shell, 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