Como percorrer apenas diretórios no bash?

251

Eu tenho uma pasta com alguns diretórios e alguns arquivos (alguns estão ocultos, começando com ponto).

for d in *; do
 echo $d
done

irá percorrer todos os arquivos, mas eu quero percorrer apenas os diretórios. Como faço isso?

rubo77
fonte

Respostas:

334

Você pode especificar uma barra no final para corresponder apenas aos diretórios:

for d in */ ; do
    echo "$d"
done
choroba
fonte
7
Observe que ele também inclui links simbólicos para diretórios.
Stéphane Chazelas
1
então como você excluiria links simbólicos?
rubo77
3
@ rubo77: Você pode testar [[ -L $d ]]se $ d é um link simbólico.
choroba 14/08
1
@AsymLabs Isso está incorreto, set -Pafeta apenas comandos que alteram o diretório. sprunge.us/TNac
Chris Down
4
@choroba: [[ -L "$f" ]]não excluir links simbólicos neste caso com */, você tem que tirar a barra final com [[ -L "${f%/}" ]](veja Testar link com barra invertida )
rubo77
78

Você pode testar com -d:

for f in *; do
    if [ -d "$f" ]; then
        # $f is a directory
    fi
done

Este é um dos operadores de teste de arquivo .

Cachinhos Dourados
fonte
8
Observe que ele incluirá links simbólicos para diretórios.
Stéphane Chazelas
21
if [[ -d "$f" && ! -L "$f" ]]excluirá links simbólicos
rubo77 22/10
Irá quebrar no diretório vazio. if [[ "$f" = "*" ]]; then continue; fi
Piskvor
30

Cuidado que a solução do choroba, embora elegante, pode provocar um comportamento inesperado se nenhum diretório estiver disponível no diretório atual. Nesse estado, em vez de pular o forloop, o bash executará o loop exatamente uma vez onde dfor igual a */:

#!/usr/bin/env bash

for d in */; do
    # Will print */ if no directories are available
    echo $d
done

Eu recomendo usar o seguinte para proteger contra este caso:

#!/usr/bin/env bash

for f in *; do
    if [ -d ${f} ]; then
        # Will not run if no directories are available
        echo $f
    fi
done

Esse código percorrerá todos os arquivos no diretório atual, verificará se fé um diretório e ecoará fse a condição retornar verdadeira. Se ffor igual a */, echo $fnão será executado.

emagdne
fonte
3
Muito mais fácil shopt -s nullglob.
choroba
21

Se você precisar selecionar arquivos mais específicos do que apenas os diretórios usam finde passá-los para while read:

shopt -s dotglob
find * -prune -type d | while IFS= read -r d; do 
    echo "$d"
done

Use shopt -u dotglobpara excluir diretórios ocultos (ou setopt dotglob/ unsetopt dotglobno zsh).

IFS=para evitar a divisão de nomes de arquivos que contenham um dos $IFS, por exemplo:'a b'

veja a resposta do AsymLabs abaixo para mais findopções


edit:
Caso você precise criar um valor de saída a partir do loop while, é possível contornar o subshell extra com este truque:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)
rubo77
fonte
2
Eu encontrei esta solução em: stackoverflow.com/a/8489394/1069083
rubo77
11

Você pode usar o bash puro para isso, mas é melhor usar o find:

find . -maxdepth 1 -type d -exec echo {} \;

(encontrar adicionalmente incluirá diretórios ocultos)

pressa
fonte
8
Observe que ele não inclui links simbólicos para diretórios. Você pode usar shopt -s dotglobpara bashincluir diretórios ocultos. O seu também incluirá .. Observe também que -maxdepthnão é uma opção padrão ( -pruneé).
Stéphane Chazelas
2
A dotglobopção é interessante, mas dotglobsó se aplica ao uso de *. find .incluirá sempre diretórios ocultos (eo dir atual, bem)
rubo77
6

Isso é feito para localizar os diretórios visíveis e ocultos no atual diretório de trabalho, excluindo o diretório raiz:

apenas percorrer os diretórios:

 find -path './*' -prune -type d

para incluir links simbólicos no resultado:

find -L -path './*' -prune -type d

para fazer algo em cada diretório (excluindo links simbólicos):

find -path './*' -prune -type d -print0 | xargs -0 <cmds>

excluir diretórios ocultos:

find -path './[^.]*' -prune -type d

para executar vários comandos nos valores retornados (um exemplo muito artificial):

find -path './[^.]*' -prune -type d -print0 | xargs -0 -I '{}' sh -c \
"printf 'first: %-40s' '{}'; printf 'second: %s\n' '{}'"

em vez de 'sh -c' também pode usar 'bash -c', etc.

AsymLabs
fonte
O que acontece se o eco '* /' in 'para d in echo * /' contiver, digamos, 60.000 diretórios?
AsymLabs
Você vai ter "Há muitos arquivos" erro, mas que pode ser resolvido: Como contornar “Muitos arquivos abertos” no debian
rubo77
1
O exemplo xargs funcionaria apenas para um subconjunto de nomes de diretório 9e.g. aqueles com espaços ou novas linhas não funcionariam). Melhor usar ... -print0 | xargs -0 ...se você não souber quais são os nomes exatos.
Anthon
1
O @ rubo77 adicionou uma maneira de excluir arquivos / diretórios ocultos e a execução de vários comandos - é claro que isso também pode ser feito com um script.
AsymLabs
1
Eu adicionei um exemplo na minha resposta na parte inferior, como fazer algo para cada diretório usando uma função comfind * | while read file; do ...
rubo77
3

Você pode percorrer todos os diretórios, incluindo os diretórios ocultos (começando com um ponto) em uma linha e vários comandos com:

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

note: o uso da lista */ .*/funciona no bash, mas também exibe as pastas .e, ..enquanto estiver no zsh, não as mostrará, mas gerará um erro se não houver um arquivo oculto na pasta

Uma versão mais limpa que incluirá diretórios ocultos e excluirá ../ estará com a opção dotglob:

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

(ou setopt dotglobem zsh)

você pode desabilitar o dotglob com

shopt -u dotglob
rubo77
fonte
2

Isso incluirá o caminho completo em cada diretório da lista:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done
rubo77
fonte
1
quebra ao usar pastas com espaços
Alejandro Sazo 24/10
0

Use findwith -execpara percorrer os diretórios e chamar uma função na opção exec:

dosomething () {
  echo "doing something with $1"
}
export -f dosomething
find -path './*' -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Use shopt -s dotglobou shopt -u dotglobpara incluir / excluir diretórios ocultos

rubo77
fonte
0

Isso lista todos os diretórios, juntamente com o número de subdiretórios em um determinado caminho:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done
pabloa98
fonte
-1
ls -d */ | while read d
do
        echo $d
done
dcat
fonte
This is one directory with spaces in the name- mas é analisado como múltiplo.
Piskvor 28/03
-5
ls -l | grep ^d

ou:

ll | grep ^d

Você pode configurá-lo como um alias

user250676
fonte
1
Infelizmente, não acho que isso responda à pergunta, que era "Quero fazer um loop apenas por meio de diretórios" - que é um pouco diferente lsda resposta com base nessa lista , que lista os diretórios.
Jeff Schaller
1
Nunca é uma boa idéia analisar a saída de ls: mywiki.wooledge.org/ParsingLs
codeforester