Adicionar prefixo de caractere sublinhado a todos os diretórios em um diretório

Adicionar prefixo de caractere sublinhado a todos os diretórios em um diretório

Tenho vários diretórios dentro de um diretório que preciso renomear para incluir um caractere de sublinhado "_" no início do nome.

Como teste, criei um diretório pai cujo conteúdo possui três diretórios:

dir1, dir2 and dir3; three files: file1, file2, file3

e três arquivos com prefixo de ponto:

.file1, .file2, .file3 

Até agora, tentei usar gfind, depois de instalar findutilsno Mac:

gfind . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

Isso retorna:

mv: rename ./. to ./_.: Invalid argument

Então, tentei usar renomear com find:

find . -type d -exec rename 's/^/_/' {} \;

Isso retorna:

Can't rename '.' to '_.': Invalid argument
Can't rename './dir1' to '_./dir1': No such file or directory
Can't rename './dir2' to '_./dir2': No such file or directory
Can't rename './dir3' to '_./dir3': No such file or directory

Alguém tem uma solução viável ou sabe onde posso estar?

Responder1

Se entendi direito, você tem arquivos e diretórios como este:

$ mkdir dir{1,2,3} ; touch file{1,2,3} .file{1,2,3}
$ ls -A
.file1  .file2  .file3  dir1  dir2  dir3  file1  file2  file3

e deseja renomear os diretórios para _dir1, _dir2, _dir3?

Isso find/ printf mvparece factível. Vamos ver o que ele imprime:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n"
mv "./." "./_."
mv "./dir2" "./_dir2"
mv "./dir3" "./_dir3"
mv "./dir1" "./_dir1"

Esse primeiro comando gerará um erro, pois o “ponto” é especial e você não pode renomeá-lo. Mas os outros devem estar ok, e devem renomear os diretórios como desejar:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
mv: cannot move ‘./.’ to ‘./_.’: Device or resource busy
$ ls -d _dir*
_dir1  _dir2  _dir3

Mas canalizar comandos para um shell é um pouco feio, e se os nomes dos arquivos forem estranhos o suficiente, os resultados serão surpreendentes (por exemplo, se eles contiverem $sinais que acionarão a substituição de variáveis ​​ou comandos no shell).

Se os arquivos estiverem todos em um nível, isso deverá servir:

for x in * ; do [ -d "$x" ] && mv "$x" "_$x" ; done

(Embora se _$xjá existir, $xserá movido para ele.)

Se você quiser incluir diretórios cujos nomes começam com um ponto, use shopt -s dotglobantecipadamente.

Isso também está próximo:

find . -type d -exec rename 's/^/_/' {} \;

Mas como findfornece renameos caminhos que começam com ./, precisamos levar isso em consideração. Em apenas um nível, isso deve servir (muda o início ./para ./_):

find . -type d -exec rename 's,^./,./_,' {} \;

Para obter os diretórios em todos os níveis, find -execdirpode ser mais fácil de usar. Ele executa o comando no diretório do arquivo. Precisamos -depthlidar com as renomeações na ordem correta.

find . -depth -type d -execdir rename 's,^./,./_,' {} \;

Talvez adicione ! -name .também. por exemplo

$ mkdir foo foo/dir1 foo/dir2
$ find . -depth \! -name . -type d -execdir rename 's,^./,./_,' {} \;
$ ls  _foo
_dir1  _dir2

Responder2

O problema com sua primeira tentativa é que você está tentando renomear .a si mesmo (o diretório atual), além de todos os subdiretórios. Pule isso.

gfind . -mindepth 1 -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

Mas na verdade, não faça isso. nunca coloque coisas em sh.Isso não funcionaa menos que seus nomes de arquivo não contenham nenhum caractere especial do shell. Se você tiver um arquivo chamado `rm -r ~`(um nome de arquivo perfeitamente válido, embora incomum), então pop vai para seu diretório inicial. Invoque um shell -execcomo mostro abaixo.

Sua segunda tentativa não funciona porque você está inserindo _no início do caminho completo, em vez de adicioná-lo no início do nome base, ou seja, após o último /.

find . -mindepth 1 -type d -exec rename 's![^/]+$!_$&!' {} \;

Nenhum deles realmente funciona se você tiver diretórios aninhados, porque eles renomeiam os diretórios enquanto findos percorrem. Ao fazer isso, você precisa agir no conteúdo de um diretório antes de agir no próprio diretório, com a -depthopção.

find . -mindepth 1 -depth -type d -exec rename 's![^/]+$!_$&!' {} \;

De forma mais portável, chame um shell e execute mv. Isso funciona em qualquer sistema POSIX.

find . -depth -type d -name . -o -exec sh -c '
    for d do mv "$d" "${d%/*}/_${d##*/}"; done
' sh {} +

Se não houver subdiretórios aninhados, você poderá usar um loop de shell simples. Se você não quiser renomear diretórios cujo nome comece com um ponto, basta usar o */curinga, que corresponde apenas a diretórios e links simbólicos para diretórios.

for d in */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi   # robustness in case there are no subdirectories
  if [ -L "$d" ]; then continue; fi     # optional: skip symbolic links to directories
  mv -- "$d" "_${d%/}"
done

Se você quiser renomear todos os diretórios, incluindo aqueles cujo nome começa com um ponto, inclua .*também o curinga.

for d in .*/ */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi
  if [ -L "$d" ]; then continue; fi
  mv -- "$d" "_${d%/}"
done

Observe que quando você estiver migrando foopara _foo, se já existir um diretório chamado _foo, isso moverá o antigo foopara _foo/foo. Para se proteger contra isso, adicione um teste explícito.

Ou, alternativamente, use zsh. Está pré-instalado no macOS. Primeiro execute autoload -U zmv(coloque isso no seu .zshrcou no seu script) para carregar ozmvfunção, então a versão não recursiva (incluindo arquivos de ponto, excluindo links simbólicos -  (/D)é um conjunto deeliminatórias globais):

zmv -Q '*(/D)' '_$f'

ou a versão recursiva (usandomodificadorespara extrair o dirname e o basename):

zmv -Q '**/*(/D)' '$f:h/_$f:t'

zmvse recusará a fazer um movimento se o alvo já existir.

informação relacionada