Tenho alguns arquivos em ~home (em muitos subdiretórios) no meu sistema Ubuntu 22.04. Acredito que a maioria dos nomes de arquivo com espaços veio originalmente do Windows.
Estou encontrando os arquivos ofensivos com o seguinte comando find
encontrar . -nome "* *"
Qual é uma maneira de alterar os nomes para incluir um _ ou algum outro caractere em todos os arquivos que atualmente têm um espaço no nome?
Por exemplo, eu quero mudar
PEDIDO DE PÉS DE MÁQUINA MSC.pdf
para
MSC_MÁQUINA_PÉS_PEDIDO.pdf
Usando o comando find, determinei que tenho cerca de 600 arquivos que precisam ter seus nomes alterados, então gostaria de alguma maneira automatizada de alterar os nomes.
Os nomes de arquivos com espaços estão causando problemas quando executo determinados scripts.
Agradecemos antecipadamente pela sua ajuda.
Você pode usar a expansão de parâmetros para substituir espaços por sublinhados:
${1// /_}
significa "substituir cada espaço no primeiro parâmetro por um sublinhado (veja Expansão de Parâmetros emman bash
).bash -c
executa os comandos do primeiro argumento não-opcional. Aqui, o comando é omv
que substitui espaços por sublinhados.bash -c
não é importante agora, então usei--
. Os argumentos restantes são definidos como argumentos posicionais para o comando.find -exec
executa o comando para cada arquivo encontrado. O{}
é substituído pelo nome do arquivo, o final\;
informafind
onde o comando termina. Então, find chamabash -c
e envia o nome do arquivo como o primeiro argumento, para que ele possa ser renomeado.Este encantamento complexo é necessário para preservar os espaços no primeiro argumento para
mv
.Se você quiser renomear subdiretórios também, você precisa usar
-depth
and-execdir
em vez de-exec
(caso contrário, o nome do diretório muda e o find não consegue mais encontrá-lo para trabalhar em seu conteúdo).Observe que alterar os nomes dos arquivos também pode causar problemas: se um programa espera que um arquivo exista com o nome fornecido, alterar o nome pode fazer o programa falhar.
perl-rename aka
rename
akaprename
é excelente para renomeação de arquivos de substituição de padrões. Veja https://francopasut.netlify.app/post/linux-rename-confusion/ re: os vários nomes sob os quais você pode encontrar esse comando, e que o util-linux costumava também enviar umrename
comando diferente que não usava regexes. De uma rápida olhada, acho que o Ubuntu agora o envia comorename
. De uma antiga página de manual (do Trusty) , naquela época ele fazia parte doperl
pacote; se ainda for o caso, você já o instalou.Ele pega uma declaração perl para ser aplicada a cada nome de arquivo em sua linha de comando. O
s/regex/replacement/
operador opera implicitamente em$_
(linha atual, neste caso nome de arquivo atual), então é altamente útil. (Você pode usars{pat}{rep}
separadores de padrões se quiser corresponder e substituir/
em caminhos.) Veja https://linux.die.net/man/1/perlop para detalhes sobre o operador perls//
.Para o seu caso, basta executar
prename 's/ /_/g'
em quantos arquivos quiser ao mesmo tempo. Como no sed, o/g
modificador substitui cada ocorrência de uma correspondência. Usarfind -execdir
Ensuresprename
só vê o nome do arquivo, não os nomes dos diretórios, já que tentar moverfoo bar/x y.txt
parafoo_bar/x_y.txt
falharia, independentemente defoo bar/
ter sido renomeado ainda (o diretório de origem ou de destino não existirá).-i
prompts antes de sobrescrever um arquivo. Geralmente uma boa ideia se você não pretendia sobrescrever nada. Ele também tem uma opção-n
aka .--dry-run
Não verifiquei a
find
página do manual sobre se-depth
é necessário para correção se você for renomear diretórios conforme ele se repete. Não faz mal neste caso, então incluí.a resposta de choroba seria fork+exec
bash
emv
para cada arquivo ser renomeado.prename
faria apenas um pouco de processamento de texto e umarename(2)
chamada de sistema. E mais importante para uso interativo único, é uma linha de comando mais simples.Para ser justo, eu tive que usar
-execdir
no caso de diretórios que contêm espaços, o que eu só pensei na metade da escrita desta resposta. Não tenho certeza se-depth
é necessário ou não para ambas as nossas respostas; isso se aplicaria de qualquer forma, eu acho.Em um caso mais simples, onde todos os arquivos estão em um diretório, você poderia simplesmente
prename -i 's/ /_/g' *\ *
usar uma expressão glob que corresponde aos nomes de arquivos com espaços.Ou com Bash
set -o extglob
habilitado,prename ... **/*\ *
para corresponder recursivamente. Mas isso permitirá que prename veja nomes de diretórios; se eles contiverem espaços, não funcionará a menos que você torne o código perl mais complicado para, por exemplo, separar o basename, modificá-lo e, em seguida, anexá-lo novamente. Ou você pode apenas executá-lo algumas vezes até parar de receber erros, pois os diretórios são renomeados para que o próximo nível de profundidade seja bem-sucedido.Se você mudar para o zsh, poderá usar o módulo zmv contribuído:
(
-n
é apenas para teste - remova-o quando estiver satisfeito que as operações de renomeação propostas estão corretas).O
**
operador ("globstar") faz a correspondência de padrões descer recursivamente em subdiretórios como ofind
comando. Os dois primeiros conjuntos de parênteses capturam o diretório e o nome do arquivo em variáveis numeradas separadas que podem ser usadas na sequência de substituição, enquanto o terceiro(#q.D)
é um qualificador glob no qual.
restringe a correspondência a arquivos simples (como ofind
do comando-type f
)D
inclui "dotfiles" (ocultos) (para consistência com suafind
expressão original - você pode omitir isso se não precisar corresponder a esses arquivos).A substituição
$1${2// /_}
concatena a parte do diretório não modificada com o nome do arquivo modificado. Você também pode escrever isso usando a expansão no estilo histórico,'$1${2:gs/ /_}'
se preferir.Veja man zshcontrib e man zshexpn .