Como loop sobre diretórios no Linux?

168

Estou escrevendo um script no bash no Linux e preciso passar por todos os nomes de subdiretórios em um determinado diretório. Como posso percorrer esses diretórios (e pular arquivos regulares)?

Por exemplo:
o diretório fornecido /tmp/
possui os seguintes subdiretórios:/tmp/A, /tmp/B, /tmp/C

Eu quero recuperar A, B, C.

Erik Sapir
fonte

Respostas:

129
cd /tmp
find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n'

Uma breve explicação:

  • find encontra arquivos (obviamente)

  • .é o diretório atual, que após o cdé /tmp(IMHO é mais flexível do que ter /tmpdiretamente no findcomando. Você tem apenas um lugar, o cd, para alterar, se desejar que mais ações ocorram nesta pasta)

  • -maxdepth 1e -mindepth 1certifique-se de que findapenas olhe no diretório atual e não se inclua .no resultado

  • -type d procura apenas diretórios

  • -printf '%f\n imprime apenas o nome da pasta encontrada (mais uma nova linha) para cada ocorrência.

Et voilà!

Boldewyn
fonte
A propósito: Observe que todas as respostas também encontrarão pastas ocultas (ou seja, pastas começando com um ponto)! Se você não quiser isso, adicione `-regex '\ ./ [^ \.]. *' Antes das opções printf ou exec.
Boldewyn
1
Útil - se você precisar executar apenas um comando para cada ocorrência ;-) - Somente eu tenho vários comandos para executar :-(.
Martin
Não tem problema: adapte esta solução: transnum.blogspot.de/2008/11/… Dentro do while..doneloop, você pode enlouquecer.
Boldewyn
1
Este é um método muito bom para alguns casos de uso; findA -execopção de permite executar qualquer comando para cada arquivo / diretório.
Jtpereyda # 14/17
451

Todas as respostas usadas até agora find, então aqui está uma com apenas o shell. Não há necessidade de ferramentas externas no seu caso:

for dir in /tmp/*/     # list directories in the form "/tmp/dirname/"
do
    dir=${dir%*/}      # remove the trailing "/"
    echo ${dir##*/}    # print everything after the final "/"
done
ghostdog74
fonte
18
1 - Por que se preocupar com findquando você pode anexar uma barra para um curinga
Tobias KIENZLER
19
assim for dir in */; do echo $dir; doneé para diretórios no diretório atual.
Ehtesh Choudhury
41
Seria bom se ele poderia conter uma explicação sobre o que dir=${dir%*/}e echo ${dir##*/}está fazendo.
21415 Jeremy
11
Isso ocorre porque a variável dir incluirá a barra invertida no final. Ele está apenas removendo-o para ter um nome limpo. % removerá o / no final. ## removerá o / no início. Esses são operadores para lidar com substrings.
Sfratini 23/10/2015
8
Se não houver correspondência, o loop será acionado /tmp/*/; seria recomendável incluir uma verificação para ver se o diretório realmente existe.
Jrs42
41

Você pode percorrer todos os diretórios, incluindo diretórios ocultos (começando com um ponto) com:

for file in */ .*/ ; do echo "$file is a directory"; done

nota: o uso da lista */ .*/funciona no zsh apenas se existir pelo menos um diretório oculto na pasta. No bash, mostrará também .e..


Outra possibilidade do bash incluir diretórios ocultos seria usar:

shopt -s dotglob;
for file in */ ; do echo "$file is a directory"; done

Se você deseja excluir links simbólicos:

for file in */ ; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    echo "$file is a directory"; 
  fi; 
done

Para gerar apenas o nome do diretório à direita (A, B, C, conforme questionado) em cada solução, use-o nos loops:

file="${file%/}"     # strip trailing slash
file="${file##*/}"   # strip path and leading slash
echo "$file is the directoryname without slashes"

Exemplo (isso também funciona com diretórios que contêm espaços):

mkdir /tmp/A /tmp/B /tmp/C "/tmp/ dir with spaces"
for file in /tmp/*/ ; do file="${file%/}"; echo "${file##*/}"; done
rubo77
fonte
16

Funciona com diretórios que contêm espaços

Inspirado por Sorpigal

while IFS= read -d $'\0' -r file ; do 
    echo $file; ls $file ; 
done < <(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d -print0)

Postagem original (não funciona com espaços)

Inspirado por Boldewyn : Exemplo de loop com findcomando.

for D in $(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d) ; do
    echo $D ;
done
zpon
fonte
9
find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n"
Ignacio Vazquez-Abrams
fonte
Isso também é legal e elimina a necessidade basename. Eu preferiria isso à minha resposta.
precisa saber é o seguinte
6

A técnica que eu uso com mais frequência é find | xargs. Por exemplo, se você deseja tornar todos os arquivos neste diretório e todos os seus subdiretórios legíveis pelo mundo, você pode:

find . -type f -print0 | xargs -0 chmod go+r
find . -type d -print0 | xargs -0 chmod go+rx

A -print0opção termina com um caractere NULL em vez de um espaço. A -0opção divide sua entrada da mesma maneira. Portanto, essa é a combinação a ser usada em arquivos com espaços.

Você pode imaginar essa cadeia de comandos como pegando cada saída de linha finde colando-a no final de um chmodcomando.

Se o comando que você deseja executar como argumento no meio, e não no final, precisa ser um pouco criativo. Por exemplo, eu precisava mudar para todos os subdiretórios e executar o comando latemk -c. Então eu usei (da Wikipedia ):

find . -type d -depth 1 -print0 | \
    xargs -0 sh -c 'for dir; do pushd "$dir" && latexmk -c && popd; done' fnord

Isso tem o efeito de for dir $(subdirs); do stuff; done, mas é seguro para diretórios com espaços em seus nomes. Além disso, as chamadas separadas para stuffsão feitas no mesmo shell, e é por isso que no meu comando temos que retornar ao diretório atual com popd.

Matthew Leingang
fonte
3

um loop bash mínimo do qual você pode criar (com base na resposta ghostdog74)

for dir in directory/*                                                     
do                                                                                 
  echo ${dir}                                                                  
done

compactar um monte de arquivos por diretório

for dir in directory/*
do
  zip -r ${dir##*/} ${dir}
done                                               
Harry Moreno
fonte
Isso lista todos os arquivos directory, não apenas os subdiretórios.
dexteritas
2

find . -type d -maxdepth 1

Anônimo
fonte
2
esta resposta é boa, exceto que eu recebo os caminhos completos, enquanto eu quero apenas os nomes dos diretórios.
Erik Sapir
2

Se você deseja executar vários comandos em um loop for, é possível salvar o resultado de findwith mapfile(bash> = 4) como uma variável e percorrer a matriz com ${dirlist[@]}. Também funciona com diretórios contendo espaços.

O findcomando é baseado na resposta de Boldewyn. Mais informações sobre o findcomando podem ser encontradas lá.

IFS=""
mapfile -t dirlist < <( find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n' )
for dir in ${dirlist[@]}; do
    echo ">${dir}<"
    # more commands can go here ...
done
dexteritas
fonte