Как мне рекурсивно перечислить все каталоги в месте, в ширину? - PullRequest
28 голосов
/ 12 февраля 2009

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

$ 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-shell, я бы вообразил что-то вроде

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

Ответы [ 9 ]

28 голосов
/ 07 декабря 2014

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

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

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

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

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

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

  • внутренне генерирует список файлов, каждый из которых отображается в виде строки из двух полей
  • первое поле содержит глубину, которая используется для (обратной) числовой сортировки, а затем обрезается
  • в результате получается простой список файлов, по одному файлу на строку, в самом глубоком порядке
21 голосов
/ 12 февраля 2009

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

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

То есть

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

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

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

5 голосов
/ 03 апреля 2015

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

find . -type d
5 голосов
/ 07 декабря 2012

Мне кажется, что это лучшее решение, чем упомянутое ранее. Он включает в себя 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

Но я предпочитаю небольшие сценарии, чем набирать текст ...

5 голосов
/ 12 февраля 2009

Я не думаю, что вы могли бы сделать это, используя встроенные утилиты, поскольку при обходе иерархии каталогов почти всегда требуется поиск в глубину, либо сверху вниз, либо снизу вверх. Вот скрипт 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))

Edit:

  1. Использование os.path -модуля вместо os.stat -функции и stat -модуля.
  2. Использование list.pop и list.extend вместо del и += операторов.
3 голосов
/ 12 февраля 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

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

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

# 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
2 голосов
/ 12 февраля 2009

без заслуженного заказа: найти -maxdepth -тип 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"

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

1 голос
/ 12 февраля 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
0 голосов
/ 22 февраля 2009

Примерно так:

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