Как мне перебрать только каталоги в bash?

251

У меня есть папка с некоторыми каталогами и некоторыми файлами (некоторые скрыты, начиная с точки).

for d in *; do
 echo $d
done

будет перебирать все файлы, но я хочу зацикливать только через каталоги. Как мне это сделать?

rubo77
источник

Ответы:

334

Вы можете указать косую черту в конце, чтобы соответствовать только каталогам:

for d in */ ; do
    echo "$d"
done
choroba
источник
7
Обратите внимание, что он также содержит символические ссылки на каталоги.
Стефан Шазелас
1
Так как же тогда исключить символические ссылки?
rubo77
3
@ rubo77: Вы можете проверить [[ -L $d ]], является ли $ d символической ссылкой.
Чороба
1
@AsymLabs Это неверно, set -Pвлияет только на команды, которые меняют каталог. sprunge.us/TNac
Крис Даун
4
@choroba: [[ -L "$f" ]]не исключать символические ссылки в этом случае с */, вы должны удалить косую черту с [[ -L "${f%/}" ]](см. Тест для связи с косой чертой )
rubo77
78

Вы можете проверить с -d:

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

Это один из операторов проверки файлов .

лютик золотистый
источник
8
Обратите внимание, что он будет содержать символические ссылки на каталоги.
Стефан Шазелас
21
if [[ -d "$f" && ! -L "$f" ]]исключит символические
ссылки
Сломается на пустой каталог. if [[ "$f" = "*" ]]; then continue; fi
Писквор
30

Помните, что решение choroba, хотя и элегантное, может вызвать неожиданное поведение, если в текущем каталоге нет доступных каталогов. В этом состоянии, вместо пропуска forцикла, bash запустит цикл ровно один раз, где dравно */:

#!/usr/bin/env bash

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

Я рекомендую использовать следующее для защиты от этого случая:

#!/usr/bin/env bash

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

Этот код будет перебирать все файлы в текущем каталоге, проверять, fявляется ли каталог каталогом, а затем выводить эхо, fесли условие возвращает true. Если fравно */, echo $fне будет выполняться.

emagdne
источник
3
Намного проще shopt -s nullglob.
Чороба
21

Если вам нужно выбрать более конкретные файлы, чем использовать только каталоги findи передать их while read:

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

Используется shopt -u dotglobдля исключения скрытых каталогов (или setopt dotglob/ unsetopt dotglobв zsh).

IFS=чтобы избежать разделения имен файлов, содержащих одно из $IFS, например:'a b'

см. ответ AsymLabs ниже для большего количества findвариантов


edit:
Если вам нужно создать выходное значение из цикла while, вы можете обойти дополнительный подоболочек с помощью этого трюка:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)
rubo77
источник
2
Я нашел это решение на: stackoverflow.com/a/8489394/1069083
rubo77
11

Вы можете использовать чистый bash для этого, но лучше использовать find:

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

(найти дополнительно будет включать скрытые каталоги)

порыв
источник
8
Обратите внимание, что он не включает символические ссылки на каталоги. Вы можете использовать shopt -s dotglobдля bashвключения скрытых каталогов. Ваш также будет включать .. Также обратите внимание, что -maxdepthэто не стандартная опция ( -pruneесть).
Стефан Шазелас
2
dotglobВариант интересен , но dotglobотносится только к использованию *. find .всегда будет включать скрытые каталоги (и текущий каталог)
rubo77
6

Это делается для поиска как видимых, так и скрытых каталогов в текущем рабочем каталоге, исключая корневой каталог:

просто перебрать каталоги:

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

включить символические ссылки в результат:

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

сделать что-то для каждого каталога (исключая символические ссылки):

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

чтобы исключить скрытые каталоги:

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

выполнить несколько команд для возвращаемых значений (очень надуманный пример):

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

вместо sh -c также можно использовать bash -c и т. д.

AsymLabs
источник
Что произойдет, если echo '* /' in 'для d в echo * /' содержит, скажем, 60000 каталогов?
AsymLabs
Вы получите ошибку «Слишком много файлов», но ее можно решить: Как обойти «Слишком много открытых файлов» в debian
rubo77
1
Пример xargs будет работать только для подмножества имен каталогов 9e.g. те, что с пробелами или переводом строки не будут работать). Лучше использовать, ... -print0 | xargs -0 ...если вы не знаете, каковы точные имена.
Энтон
1
@ rubo77 добавлен способ исключения скрытых файлов / каталогов и выполнения нескольких команд - конечно, это можно сделать и с помощью скрипта.
AsymLabs
1
В своем ответе внизу я добавил пример того, как что-то сделать с каждым каталогом, используя функцию сfind * | while read file; do ...
rubo77 22.10.13
3

Вы можете перебрать все каталоги, включая скрытые каталоги (начиная с точки) в одну строку и несколько команд с помощью:

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

Если вы хотите исключить символические ссылки:

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

примечание: использование списка */ .*/работает в bash, но также отображает папки, .а ..в zsh не показывает их , а выдает ошибку, если в папке нет скрытого файла

Более чистая версия, которая будет включать скрытые каталоги и исключать ../, будет с опцией dotglob:

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

(или setopt dotglobв зш)

Вы можете сбросить дотглоб с

shopt -u dotglob
rubo77
источник
2

Это будет включать полный путь в каждом каталоге в списке:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done
rubo77
источник
1
разрывы при использовании папок с пробелами
Алехандро Сазо
0

Используйте findс, -execчтобы перебрать каталоги и вызвать функцию в опции exec:

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

Использовать shopt -s dotglobили shopt -u dotglobвключать / исключать скрытые каталоги

rubo77
источник
0

Это перечисляет все каталоги вместе с количеством подкаталогов в данном пути:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done
pabloa98
источник
-1
ls -d */ | while read d
do
        echo $d
done
DCAT
источник
This is one directory with spaces in the name- но разбирается как несколько.
Писквор
-5
ls -l | grep ^d

или же:

ll | grep ^d

Вы можете установить его как псевдоним

user250676
источник
1
К сожалению, я не думаю, что это отвечает на вопрос, который был «Я хочу циклически проходить только по каталогам» - что немного отличается от этого lsответа, основанного на списках каталогов.
Джефф Шаллер
1
Никогда не стоит анализировать вывод ls: mywiki.wooledge.org/ParsingLs
codeforester