para loop em pastas com \ n caracteres nos nomes

9

Eu tenho algumas pastas com \ncaracteres, seus nomes.

por exemplo:

$ ls
''$'\n''Test'

Isso se refere a uma pasta com o nome do teste e uma linha vazia antes do nome.

Então, quando eu executo alguns scripts como este, em seu diretório pai:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

Isto mostra:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

Porque é executado duas vezes, uma vez ativada \ne outra ativada Test, porque o nome da pasta possui duas linhas.

Então, como posso resolver esse problema de maneira que, o script sabe que \nTesté apenas uma pasta?

Tara S Volpe
fonte
3
Você precisa usar a -print0diretiva find e a -dopção de leitura. Veja stackoverflow.com/a/40189667/7552
glenn jackman
11
@glennjackman Por favor, responda!
Dessert
@glennjackman Obrigado pela sua resposta, mas o find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; donecomando tem esta saída rmdir: failed to remove 'Test': No such file or directory.
Tara S Volpe
5
Você deve citar a variável:rmdir "$file"
glenn jackman 4/18
11
@glennjackman Basta postar isso como resposta. É uma solução adequada e, além dos comentários, não é o melhor lugar para isso. Upvote já implicava :)
Sergiy Kolodyazhnyy

Respostas:

12

Você tem apenas um comando lá, portanto, é suficiente chamar findcom -execsinalização rmdir:

find -depth -type d -exec rmdir {} \;

Ou use a -deleteopção como em find -type d -delete, mas ela não funcionará com diretórios não vazios. Para isso você também precisará de uma -emptybandeira. Observe também, -deleteimplica -depthque pode ser pulado. Assim, outra alternativa viável que mantém tudo como um processo:

find -type d -empty -delete

Se o diretório não estiver vazio, use rm -rf {} \;. Para isolar apenas diretórios com \nin filename, podemos combinar a cotação ANSI-C do bash $'...'com a -nameopção:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly, nós poderíamos lidar com isso desta maneira:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

Vale ressaltar que, se seu objetivo é a remoção de diretórios, -deleteé suficiente; no entanto, se você deseja executar um comando no diretório, -execé o mais apropriado.

Veja também

Sergiy Kolodyazhnyy
fonte
2
... use rm -rf com cuidado . ; P Não findtem uma -deleteopção entre? Exclui diretórios vazios e gera erros para diretórios não vazios, como rmdir- pode ser mais barato.
Sobremesa
3
@dessert Faz isso e adicionei isso, mas -deletenão removemos diretórios não vazios. Portanto, o uso cuidadoso de #rm -rf
Sergiy Kolodyazhnyy
6
Sempre dry-executar tais findcomandos sem qualquer carga destrutiva (ou seja, remover o -deleteou -exec whatever \;) para verificar se a lista de arquivos afetados é realmente correto e não contém material que não deve ficar excluído ...
Byte Comandante
2
Este é o askubuntu.com para que possamos assumir o GNU find. Use -exec rmdir {} +para agrupar vários argumentos em uma rmdirlinha de comando. E BTW ", mas não funcionará com diretórios não vazios " .rmdir Também se aplica , portanto, isso não é realmente uma diferença -delete. É uma diferença de rm -r.
Peter Cordes
2
find -type d -empty -delete( -deleteimplica -depth).
Kevin
10

Você pode usar globs de shell em vez de find:

for d in */ ; do 
    rmdir "$d"
done

O shell glob */corresponde a todas as pastas no diretório atual. Essa construção de loop for cuida da divisão correta da palavra automaticamente.

Observe que, dependendo das opções do shell, isso pode ignorar pastas ocultas (nome começando com a.). Esse comportamento pode ser alterado para coincidir com todos os arquivos para a sessão atual usando o comando shopt -s dotglob.

Também não se esqueça de sempre citar suas variáveis.

Byte Commander
fonte
o glob só vai encontrar arquivos / diretórios no diretório atual, não recursivamente como findfaz
ilkkachu
11
Você está certo @ilkkachu. Isso pode ser alterado ativando a opção de shell globstar shopt -s globstare usando um **/*/glob.
Byte Commander
6

Ambas as respostas escritas até o momento chamam rmdiruma vez por diretório, mas, como rmdirpode levar vários argumentos, eu me pergunto: Não há uma maneira mais eficiente?

Alguém poderia simplesmente fazer

rmdir */

e essa é definitivamente a maneira mais fácil e eficiente, mas pode gerar um erro no caso de muitos diretórios (consulte Qual é o tamanho máximo dos argumentos da linha de comando no gnome-terminal? ). Se você deseja que essa abordagem funcione recursivamente, ative a globstaropção de shell com shopt -s globstare use em **/*/vez de */.

Com o GNU find(e se não queremos apenas usar -delete), poderíamos fazer

find -depth -type d -exec rmdir {} +

que constrói a linha de comando "da mesma maneira que xargsconstrói suas linhas de comando" ( man find). Substituto -depthpara -maxdepth 1se você não quer que ele funcione de forma recursiva.

Uma terceira maneira brilhante e IMO é explicada por steeldriver nesta resposta :

printf '%s\0' */ | xargs -0 rmdir

Isso usa o shell interno printfpara criar uma lista de argumentos delimitada a zero; essa lista é então canalizada para a xargsqual chama rmdirexatamente quantas vezes for necessário. Você pode fazê-lo funcionar recursivamente com shopt -s globstare em **/*/vez de */como acima.

sobremesa
fonte
2
findé recursivo, mas o loop do OP não. Para replicar esse comportamento, use find -maxdepth 1 -type d -exec rmdir {} +. ( -depthé redundante nesse caso.) Um truque legal printfpara imitar find -print0, eu nunca tinha visto isso antes.
5608 Peter Cordes #
11
Oh! Eu vi o lse o whileloop, eu perdi que eles estavam redirecionando de uma substituição processo com findem vez de tubulação lspara o loop e simplesmente não mostrá-lo por algum motivo. Isso find *está realmente enterrado. Sim, é recursivo e falhará se houver muitas entradas de diretório no diretório, incluindo os não-diretórios, porque ele não é usado */. find .seria muito melhor, porque findé mais rápido statque a expansão glob do bash.
Peter Cordes