2009-02-12 8 views
21

Pierwsza lista jest ważna tutaj. Ograniczenie szukanej głębokości byłoby miłe.Jak rekurencyjnie wyliczyć wszystkie katalogi w miejscu, szerokość pierwsza?

$ 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 

Chciałbym zrobić to za pomocą bashowej jednej liniówki, jeśli to możliwe. Gdyby nie było javascript-shell, to bym sobie wyobrazić coś takiego

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

mógłbyś rozwinąć ten obejmuje język z wyboru, a OS –

+0

Arg (Linux?)! To jest pytanie "wiki społeczności". Denerwujący. –

+0

co sprawia, że ​​jest to pytanie typu wiki społeczności? –

Odpowiedz

15

Komenda find obsługuje -printf opcję, która rozpoznaje wiele zastępczych.

Jednym z takich symboli zastępczych jest %d, który renderuje głębokość danej ścieżki względem miejsca, w którym rozpoczęło się find.

Dlatego można użyć poniższy prosty-liner:

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

Jest to dość proste, a nie zależy od ciężkiego oprzyrządowania jak perl.

Jak to działa:

  • to wewnętrznie generuje listę plików, każdy renderowane jako linia dwa pola
  • pierwsze pole zawiera głębokość, która jest używana do (reverse) liczbową sortowaniem i następnie odciąć
  • Otrzymana jest prosta listę plików, jeden plik na linię, w najgłębszym-pierwszej kolejności
+0

Prosta i elegancka dla jednego liniowca. Dzięki! – gnzg

2

Bez zasłużony zamawiającego: znaleźć -maxdepth -type d

Aby otrzymać zasłużoną kolejność, trzeba zrobić samemu rekursji , z tym małym skoroszytem:

#!/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" 

Następnie można wywołać ten skrypt z katalogu podstawowego i głębokości parametrów.

+0

Dokładnie tego chcę, ale przy złej kolejności. Zmieniłem pytanie, by wyjaśnić, dzięki! –

+0

zobacz mój dodatek! Nie skończyłem :) – ypnos

4

Nie sądzę, że można to zrobić za pomocą wbudowanych narzędzi, ponieważ podczas przechodzenia przez hierarchię katalogów prawie zawsze potrzebujesz pierwszego wyszukiwania z góry, od góry do dołu lub od dołu do góry. Oto skrypt Pythona, który daje przeszukiwanie wszerz:

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. Korzystanie os.path -module zamiast os.stat -function i stat -module.
  2. Używanie operatorów list.pop i list.extend zamiast operatorów del i +=.
3

Próbowałem znaleźć sposób, aby to zrobić z find, ale wydaje się, że nie ma czegoś takiego jak opcja -breadth. Krótki pisać poprawkę na to, wypróbuj następujące powłoki zaklęcie (dla 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 

ja rodzaj natknął się to przypadkowo, więc nie wiem, czy to działa w ogóle (ja testowałem go tylko na specyficzna struktura katalogów pytałeś o)

Jeśli chcesz ograniczyć głębokość, umieścić zmienną licznika w zewnętrznej pętli, tak jak (mam także dodawanie komentarzy do tego):

# 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 

(zamień 2 w -lt 2 na głębokość)

Zasadniczo implementuje standardowy algorytm przeszukiwania według szerokości, używając $LIST i jako kolejki nazw katalogów.Oto ostatnie podejście jako jedną wkładką do łatwego kopiowania i wklejania:

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 
+1

Patrząc na to jeszcze raz, to zdecydowanie tworzy moją listę "rzeczy, których nigdy nie powinno się robić w Bash" ;-) –

+0

Czy możesz również sformatować to nie jako jedno-liniowe, aby ułatwić zrozumienie kodu? ? (Ale tak, nie rób tego w bashu :-) –

+0

Dobry pomysł, teraz sformatowany i skomentowany ;-) –

19

Jeśli chcesz zrobić to za pomocą standardowych narzędzi, następujące rurociąg powinno działać:

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

Oznacza to,

  1. znaleźć i wydrukować wszystkie katalogi tutaj w pierwszej kolejności głębokości
  2. policzyć liczbę ukośniki w każdym katalogu i poprzedzić go do ścieżki
  3. sortuj według głębokości (tj. Liczba ukośników)
  4. Wyciągnij ścieżkę.

Aby ograniczyć znalezioną głębokość, dodaj argument -maxdepth do polecenia find.

Jeśli chcesz katalogi wymienione w tej samej kolejności, które znajdują się w wynikach, użyj "sort -n -s" zamiast "sort -n"; flaga "-s" stabilizuje sortowanie (tj. zachowuje kolejność wprowadzania wśród elementów, które porównywane są jednakowo).

+0

Dodaj "2>/dev/null" do polecenia find, tj. find. -typ d 2>/dev/null Zapewni, że błąd znalezienia nie zepsuje wyników. –

+0

Jak sortować alfabetycznie? – mr5

+0

Nie działa, jeśli w dirnames są spacje. Na przykład, jeśli katalog to "/ data/Mundial/Trinidad \ y \ Tobago /", będziesz miał tylko "/ data/arbol/Mundial/Trinidad". – alemol

1

Oto możliwy sposób, używając find. I nie zostały gruntownie przetestowane, więc użytkownik uważaj ...

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

coś takiego:

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

Mam wrażenie, że jest to lepsze rozwiązanie niż te wspomniane wcześniej. Obejmuje grep i takie i pętlę, ale uważam, że działa bardzo dobrze, szczególnie w przypadkach, gdy chcesz, aby rzeczy były buforowane, a nie pełne, buforowane.

Jest bardziej zasób intensywne z powodu:

  • Partie rozwidlone
  • Wiele znalezisk
  • Każdy katalog przed aktualnej głębokości jest dotkniętych znaleźć tyle razy, ile jest całkowita głębokość do struktura pliku (nie powinno to być problemem, jeśli masz praktycznie każdą ilość pamięci RAM ...)

to jest dobre, ponieważ:

  • Wykorzystuje bash i podstawowe narzędzia GNU
  • Może być uszkodzony kiedy chcesz (jak widać to, czego szukaliśmy locie przez)
  • Działa na linię, a nie za znalezisko, więc kolejne polecenia don” Trzeba poczekać na wyszukanie i sortowanie. Działa ono w oparciu o rzeczywistą separację systemu plików, więc jeśli masz katalog z ukośnikiem, nie będzie on na liście głębiej niż jest; jeśli masz skonfigurowany inny separator ścieżek, nadal wszystko jest w porządku.
#!/bin/bash 
depth=0 

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

Można także dopasować je na jednej linii dość (?) Łatwo:

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

Ale wolę małe skrypty ponad typowania ...

3

można użyć polecenia find/ścieżka/do/dir -type znaleźć d Więc poniżej przykład listy katalogów w bieżącym katalogu:

find . -type d