Как мне рекурсивно перечислить все каталоги в одном месте в ширину?

Здесь важен список в ширину. Кроме того, было бы неплохо ограничить глубину поиска.

$ find . -type d
/foo
/foo/subfoo
/foo/subfoo/subsub
/foo/subfoo/subsub/subsubsub
/bar
/bar/subbar

$ find . -type d -depth
/foo/subfoo/subsub/subsubsub
/foo/subfoo/subsub
/foo/subfoo
/foo
/bar/subbar
/bar

$ < what goes here? >
/foo
/bar
/foo/subfoo
/bar/subbar
/foo/subfoo/subsub
/foo/subfoo/subsub/subsubsub

Я хотел бы сделать это, используя однострочный bash, если это возможно. Если бы была javascript-оболочка, я бы представил что-то вроде

bash("find . -type d").sort( function (x) x.findall(/\//g).length; )

person Community    schedule 12.02.2009    source источник
comment
Не могли бы вы расширить это, включив выбранный вами язык и ОС (Linux?)   -  person Don Branson    schedule 12.02.2009
comment
Арг! Это вопрос вики сообщества. Раздражающий.   -  person Jon Ericson    schedule 12.02.2009
comment
что делает это вопросом вики сообщества?   -  person Don Branson    schedule 12.02.2009
comment
Спрашивающий установил флажок вики-сообщества. Я думаю, что это случай: ">stackoverflow.uservoice.com/pages/general/suggestions/   -  person Jon Ericson    schedule 12.02.2009
comment
Это может стать забавным вопросом на собеседовании/телефонном экране.   -  person Emil Sit    schedule 12.02.2009
comment
Эмиль: хорошая идея, теги соответствующие.   -  person Andrey Fedorov    schedule 12.02.2009
comment
Я написал это, которое должно удовлетворить ваши потребности: github.com/tavianator/bfs   -  person Tavian Barnes    schedule 21.03.2019


Ответы (9)


Команда find поддерживает параметр -printf, который распознает множество заполнителей.

Одним из таких заполнителей является %d, который отображает глубину заданного пути относительно того места, где начинается find.

Поэтому вы можете использовать следующий простой однострочный:

find -type d -printf '%d\t%P\n' | sort -r -nk1 | cut -f2-

Это довольно просто и не зависит от тяжелых инструментов, таких как perl.

Как это работает:

  • он внутренне генерирует список файлов, каждый из которых отображается как строка с двумя полями
  • первое поле содержит глубину, которая используется для (обратной) числовой сортировки, а затем отсекается
  • в результате получается простой список файлов, по одному файлу в строке, в самом глубоком порядке
person Community    schedule 06.12.2014

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

find . -type d | perl -lne 'print tr:/::, " $_"' | sort -n | cut -d' ' -f2

То есть,

  1. найти и распечатать все каталоги здесь в глубине первого порядка
  2. подсчитайте количество косых черт в каждом каталоге и добавьте его к пути
  3. сортировать по глубине (т. е. количеству косых черт)
  4. извлеките только путь.

Чтобы ограничить найденную глубину, добавьте в команду find аргумент -maxdepth.

Если вы хотите, чтобы каталоги перечислялись в том же порядке, что и find, используйте «sort -n -s» вместо «sort -n»; флаг «-s» стабилизирует сортировку (т. е. сохраняет порядок ввода среди элементов, которые сравниваются одинаково).

person Community    schedule 12.02.2009
comment
Добавьте 2›/dev/null к команде find, т. е. find . -type d 2›/dev/null Гарантирует, что ошибка поиска не испортит результаты. - person phileas fogg; 01.11.2011
comment
Как насчет сортировки по алфавиту? - person mr5; 24.10.2015
comment
Не работает, если в именах каталогов есть пробелы. Например, если каталог /data/Mundial/Trinidad\ y\ Tobago/, у вас будет просто /data/arbol/Mundial/Trinidad. - person alemol; 16.11.2016
comment
Спасибо, это лучший лайнер для сортировки путей в ширину, который я нашел (игнорируя такие проблемы, как пробелы в именах путей). Если вам нужно отсортировать в глубину, см. это объяснение обратного вызова сортировки с помощью strcmp() для каждого элемента пути, возвращающего length1 - length2, если все компоненты совпадают (эквивалентно обработке несуществующих компонентов как пустых строк) stackoverflow.com/a/4820233/539149 - person Zack Morris; 21.04.2018

Вы можете использовать команду find, find /path/to/dir -type d Итак, ниже приведен пример списка каталогов в текущем каталоге:

find . -type d
person Community    schedule 03.04.2015
comment
Это не в ширину, как буквально объясняется в вопросе. - person Camille Goudeseune; 03.03.2021

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

Он более ресурсоемкий из-за:

  • Много разветвлений
  • Много находок
  • Каждый каталог перед текущей глубиной находит столько раз, сколько общей глубины файловой структуры (это не должно быть проблемой, если у вас практически любой объем оперативной памяти...)

Это хорошо, потому что:

  • Он использует bash и основные инструменты gnu
  • Его можно сломать, когда захочешь (как будто видишь, что ищешь, пролетает мимо)
  • Он работает на строку, а не на поиск, поэтому последующим командам не нужно ждать поиска и сортировки.
  • Он работает на основе фактического разделения файловой системы, поэтому, если у вас есть каталог с косой чертой, он не будет отображаться глубже, чем он есть; если у вас настроен другой разделитель пути, все в порядке.
#!/bin/bash 
depth=0

while find -mindepth $depth -maxdepth $depth | grep '.'
do
    depth=$((depth + 1))
done

Вы также можете легко поместить его в одну строку (?):

depth=0; while find -mindepth $depth -maxdepth $depth | grep --color=never '.'; do depth=$((depth + 1)); done

Но я предпочитаю маленькие скрипты печатать...

person Community    schedule 07.12.2012

Я не думаю, что вы могли бы сделать это с помощью встроенных утилит, поскольку при обходе иерархии каталогов вам почти всегда нужен поиск в глубину, либо сверху вниз, либо снизу вверх. Вот скрипт Python, который даст вам поиск в ширину:

import os, sys

rootdir = sys.argv[1]
queue = [rootdir]

while queue:
    file = queue.pop(0)
    print(file)
    if os.path.isdir(file):
        queue.extend(os.path.join(file,x) for x in os.listdir(file))

Изменить:

  1. Использование os.path-модуля вместо os.stat-функции и stat-модуля.
  2. Использование операторов list.pop и list.extend вместо операторов del и +=.
person Community    schedule 12.02.2009

Я пытался найти способ сделать это с помощью find, но, похоже, у него нет ничего похожего на параметр -breadth. Если не считать написания патча для него, попробуйте следующее заклинание оболочки (для bash):

LIST="$(find . -mindepth 1 -maxdepth 1 -type d)";
while test -n "$LIST"; do
    for F in $LIST; do
        echo $F;
        test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)";
    done;
    LIST=$NLIST;
    NLIST="";
done

Я случайно наткнулся на это, поэтому не знаю, работает ли он вообще (я тестировал его только в конкретной структуре каталогов, о которой вы спрашивали)

Если вы хотите ограничить глубину, поместите переменную-счетчик во внешний цикл, например так (я также добавляю комментарии к этому):

# initialize the list of subdirectories being processed
LIST="$(find . -mindepth 1 -maxdepth 1 -type d)";
# initialize the depth counter to 0
let i=0;
# as long as there are more subdirectories to process and we haven't hit the max depth
while test "$i" -lt 2 -a -n "$LIST"; do
    # increment the depth counter
    let i++;
    # for each subdirectory in the current list
    for F in $LIST; do
        # print it
        echo $F;
        # double-check that it is indeed a directory, and if so
        # append its contents to the list for the next level
        test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)";
    done;
    # set the current list equal to the next level's list
    LIST=$NLIST;
    # clear the next level's list
    NLIST="";
done

(замените 2 в -lt 2 на глубину)

По сути, это реализует стандартный алгоритм поиска в ширину с использованием $LIST и $NLIST в качестве очереди имен каталогов. Вот последний подход как однострочный для простого копирования и вставки:

LIST="$(find . -mindepth 1 -maxdepth 1 -type d)"; let i=0; while test "$i" -lt 2 -a -n "$LIST"; do let i++; for F in $LIST; do echo $F; test -d "$F" && NLIST="$NLIST $(find $F -maxdepth 1 -mindepth 1 -type d)"; done; LIST=$NLIST; NLIST=""; done
person Community    schedule 12.02.2009
comment
Глядя на это снова, это определенно входит в мой список вещей, которые никогда не следует делать в Bash ;-) - person David Z; 12.02.2009
comment
Можете ли вы также отформатировать его не как однострочный, чтобы упростить понимание кода? (Но да, не делайте этого в bash :-) - person Emil Sit; 12.02.2009
comment
Также печатает обычные файлы в базовом каталоге. - person ypnos; 12.02.2009
comment
Параметр -1 для ls не нужен — ls автоматически печатает в один столбец, если stdout не является терминалом (т. е. если это канал). - person Adam Rosenfield; 12.02.2009
comment
k, исправлена ​​обычная проблема с файлами - person David Z; 12.02.2009

Без заслуженного порядка: find -maxdepth -type d

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

#!/bin/bash
r () 
{
    let level=$3+1
    if [ $level -gt $4 ]; then return 0; fi
    cd "$1"
    for d in *; do
        if [ -d "$d" ]; then
            echo $2/$d
        fi;
    done
    for d in *; do
        if [ -d "$d" ]; then
            (r "$d" "$2/$d" $level $4)
        fi;
    done
}
r "$1" "$1" 0 "$2"

Затем вы можете вызвать этот скрипт с параметрами базового каталога и глубины.

person Community    schedule 12.02.2009
comment
Это именно то, что я хочу, но с неправильным порядком. Я изменил вопрос, чтобы уточнить, спасибо! - person Andrey Fedorov; 12.02.2009
comment
смотрите мое дополнение! Я не закончил :) - person ypnos; 12.02.2009

Вот возможный способ, используя find. Я не проверял это полностью, так что будьте осторожны...

depth=0
output=$(find . -mindepth $depth -maxdepth $depth -type d | sort); 
until [[ ${#output} -eq 0 ]]; do 
  echo "$output"
  let depth=$depth+1
  output=$(find . -mindepth $depth -maxdepth $depth -type d | sort)
done
person Community    schedule 12.02.2009

Что-то вроде этого:

find . -type d | 
  perl -lne'push @_, $_;
    print join $/,
      sort { 
        length $a <=> length $b || 
          $a cmp $b 
        } @_ if eof'
person Community    schedule 22.02.2009