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

1

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

Como teste, criei um diretório pai cujo conteúdo tem 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 ter instalado o findutils no 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 por acaso tem uma solução viável ou sabe onde eu posso estar melhorando?

    
por lmind 28.09.2016 / 18:03

2 respostas

1

Se eu acertei, 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 dirs para _dir1 , _dir2 , _dir3 ?

Esse find / printf mv parece factível. Vamos ver o que é impresso:

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

Esse primeiro comando soltará um erro, já que o "ponto" é especial e você não pode renomeá-lo. Mas os outros devem estar ok, e deve renomear os diretórios como você queria:

$ 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 os comandos de tubulação para um shell são um pouco feios e, se os nomes dos arquivos forem estranhos, os resultados serão surpreendentes (por exemplo, se eles contiverem $ sinais que dispararão variáveis ou substituição de comandos no shell). / p>

Se os arquivos estiverem todos em um nível, isso deve ser feito:

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

(Embora se _$x já exista, $x será movido para ele.)

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

Isso também está próximo:

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

Mas, como find fornece rename aos caminhos que começam com ./ , precisamos levar isso em consideração. Em apenas um nível, isso deve ser feito (altera o primeiro ./ para ./_ ):

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

Para obter os diretórios em todos os níveis, find -execdir pode ser mais fácil de usar. Ele executa o comando no diretório do arquivo. Precisamos de -depth para manipular 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
    
por 28.09.2016 / 19:57
1

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

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

Mas, na verdade, não faça isso. Nunca canalize coisas em sh . Isso não funciona a menos que os nomes dos arquivos não contenham caracteres especiais do shell. Se você tiver um arquivo chamado 'rm -r ~' (um nome de arquivo perfeitamente válido, se for incomum), o pop irá para o seu diretório pessoal. Invoque um shell com -exec como mostrado abaixo.

Sua segunda tentativa não funciona porque você está inserindo o _ 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 renomear os diretórios enquanto find os estiver percorrendo. Quando você faz isso, você precisa agir sobre o conteúdo de um diretório antes de agir no próprio diretório, com a opção -depth .

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

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 você não tiver subdiretórios aninhados, poderá usar um loop de shell simples. Se você não quiser renomear os diretórios cujo nome começa com um ponto, use apenas o curinga */ , que corresponde apenas aos diretórios e links simbólicos para os 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 caractere 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ê está movendo foo para _foo , se já houver um diretório chamado _foo , isso moverá o antigo foo para _foo/foo . Para se proteger disso, adicione um teste explícito.

Ou, como alternativa, use zsh. Está pré-instalado no macOS. Primeira execução autoload -U zmv (coloque isso no seu .zshrc ou no seu script) para carregar o zmv function , então a versão não-recursiva (incluindo arquivos de ponto, excluindo links simbólicos - (/D) é um conjunto de qualificadores glob ):

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

ou a versão recursiva (usando modificadores para extrair o dirname e o nome de base):

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

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

    
por 29.09.2016 / 03:19

Tags