Jak zgłosić liczbę plików we wszystkich podkatalogach?

24

Muszę sprawdzić wszystkie podkatalogi i zgłosić, ile plików (bez dalszej rekurencji) zawierają:

directoryName1 numberOfFiles
directoryName2 numberOfFiles
Nieśmiały chłopiec
źródło
Dlaczego chcesz używać, findkiedy zrobi to Bash? (shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done): dla wszystkich katalogów policz liczbę wpisów w tym katalogu (w tym ukryte pliki kropek, z wyłączeniem .i ..)
janmoesen
@janmoesen Dlaczego nie udzieliłeś odpowiedzi? Jestem nowy w skryptowaniu powłoki, ale nie widzę żadnych problemów z twoją metodą. Dla mnie wygląda to na najlepszy sposób. Nikt nie ocenił twojego komentarza, ale nikt nie skomentował, dlaczego może być zły. Przegłosowane odpowiedzi mają znacznie więcej przedstawicieli niż ty, więc zastanawiam się, czy coś mi umknęło.
toksalot
@toxalot: Nie zawracałem sobie głowy dodaniem go jako odpowiedzi, ponieważ był tak krótki (i być może nieco protekcjonalny w tonie). Zachęcamy do głosowania na komentarz. :-) Ponadto pytanie jest nieco niejasne w odniesieniu do tego, co oznacza „ile plików”. Moje rozwiązanie obejmuje „zwykłe” pliki i katalogi; może plakat naprawdę oznaczał „pliki, a nie katalogi”. Inną rzeczą, o której należy pamiętać, jest to, że globowanie nie bierze pod uwagę „ukrytych” plików kropek. Istnieją jednak sposoby na obejście obu tych błędów. Ale znowu: nie jestem pewien dokładnych wymagań oryginalnego plakatu.
janmoesen

Odpowiedzi:

30

Robi to w bezpieczny i przenośny sposób. Nie pomylą go dziwne nazwy plików.

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

Zauważ, że najpierw wypisze liczbę plików, a następnie nazwę katalogu w osobnym wierszu. Jeśli chcesz zachować format OP, będziesz potrzebował dalszego formatowania, np

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

Jeśli masz określony zestaw podkatalogów, którymi jesteś zainteresowany, możesz go zastąpić *.

Dlaczego to jest bezpieczne? (a zatem godny skryptu)

Nazwy plików mogą zawierać dowolne znaki oprócz /. Istnieje kilka znaków, które są traktowane specjalnie przez powłokę lub polecenia. Należą do nich spacje, znaki nowej linii i myślniki.

Korzystanie z for f in *konstrukcji jest bezpiecznym sposobem uzyskania każdej nazwy pliku, bez względu na to, co zawiera.

Gdy masz nazwę pliku w zmiennej, nadal musisz unikać takich rzeczy find $f. Gdyby $fzawierała nazwę pliku -test, findnarzekałaby na opcję, którą właśnie mu podałeś. Sposobem na uniknięcie tego jest użycie ./przed nazwą; w ten sposób ma to samo znaczenie, ale nie zaczyna się już od myślnika.

Problemem są także nowe linie i spacje. Jeśli $fzawiera nazwę pliku „witaj, kolego” find ./$f, to jest find ./hello, buddy. Mówisz, findżeby spojrzeć na ./hello,i buddy. Jeśli te nie istnieją, narzeka i nigdy nie zajrzy ./hello, buddy. Łatwo tego uniknąć - użyj cudzysłowów wokół zmiennych.

Wreszcie nazwy plików mogą zawierać znaki nowej linii, więc zliczanie nowych linii na liście nazw plików nie będzie działać; otrzymasz dodatkową liczbę dla każdej nazwy pliku z nową linią. Aby tego uniknąć, nie licz nowych linii na liście plików; zamiast tego policz znaki nowej linii (lub dowolny inny znak), które reprezentują pojedynczy plik. Dlatego findpolecenie ma po prostu -exec echo \;i nie -exec echo {} \;. Chcę wydrukować tylko jedną nową linię w celu liczenia plików.

Shawn J. Goff
źródło
1
Dlaczego na świecie jest osoba, która używa nowego wiersza w nazwie pliku? Dziękuję za odpowiedź.
ShyBoy,
1
Nazwy plików mogą zawierać dowolne znaki oprócz / i znak zerowy, jak sądzę. dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm
2
Liczba obejmie sam katalog. Jeśli chcesz wykluczyć to z -mindepth 1
obliczeń
Możesz także użyć -printf '\n'zamiast -exec echo.
toxalot
1
@toxalot możesz, jeśli masz znalezisko, które obsługuje -printf, ale nie, jeśli chcesz, aby na przykład działało na FreeBSD.
Shawn J. Goff,
6

Zakładając, że szukasz standardowego rozwiązania dla systemu Linux, stosunkowo prostym sposobem na osiągnięcie tego jest find:

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

Gdzie findprzemierza dwa określone podkatalogi, do jednego -maxdepthz 1, co zapobiega dalszej rekurencji i zgłasza tylko pliki ( -type f) oddzielone znakami nowej linii. Wynik jest następnie przesyłany potokowo w wccelu zliczenia liczby tych linii.

jasonwryan
źródło
Mam więcej niż 2 katalogi ... Jak mogę połączyć twoje polecenie z find . -maxdepth 1 -type dwyjściem?
ShyBoy,
Możesz albo (a) zawrzeć wymagane katalogi w zmiennej i find $dirs ...(b), jeśli znajdują się wyłącznie w jednym katalogu wyższego poziomu, glob z tego katalogu,find */ ...
jasonwryan
1
Spowoduje to zgłoszenie niepoprawnych wyników, jeśli nazwa pliku zawiera znak nowej linii.
Shawn J. Goff,
@Shawn: dzięki. Myślałem, że mam nazwy plików z zakrytymi spacjami, ale nie zastanawiałem się nad nowymi wierszami: jakieś sugestie dotyczące poprawki?
jasonwryan
Dodaj -exec echodo swojej komendy find - w ten sposób nie odbija echa nazwy pliku, tylko nowy wiersz.
Shawn J. Goff
5

Czy przez „bez rekurencji” rozumiesz, że jeśli directoryName1ma podkatalogi, to nie chcesz liczyć plików w podkatalogach? Jeśli tak, oto sposób na policzenie wszystkich zwykłych plików we wskazanych katalogach:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

Zauważ, że -ftest wykonuje dwie funkcje: sprawdza, czy pozycja pasująca do jednej z powyższych globów jest zwykłym plikiem, i sprawdza, czy pozycja była zgodna (jeśli jedna z globów nic nie pasuje, wzór pozostaje niezmieniony). Jeśli chcesz liczyć wszystkie wpisy w podanych katalogach, niezależnie od ich rodzaju, wymienić -fsię -e.

Ksh ma sposób na dopasowanie wzorców do plików kropek i utworzenie pustej listy na wypadek, gdyby żaden plik nie pasował do wzorca. Więc w ksh możesz policzyć zwykłe pliki takie jak to:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

lub wszystkie pliki po prostu tak:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

Bash ma różne sposoby na uproszczenie tego. Aby policzyć zwykłe pliki:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

Aby policzyć wszystkie pliki:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

Jak zwykle w Zsh jest jeszcze prostszy. Aby policzyć zwykłe pliki:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

Zmień, (DN.)aby (DN)policzyć wszystkie pliki.

¹ Pamiętaj, że każdy wzorzec pasuje do siebie, w przeciwnym razie wyniki mogą być wyłączone (np. Jeśli liczysz pliki zaczynające się od cyfry, nie możesz tego zrobić, for x in [0-9]*; do if [ -f "$x" ]; then …ponieważ może istnieć plik o nazwie [0-9]foo).

Gilles „SO- przestań być zły”
źródło
2

Na podstawie skryptu zliczania , odpowiedzi Shawna i sztuczki Bash, aby upewnić się, że nawet nazwy plików z nowymi liniami są drukowane w użytecznej formie w jednym wierszu:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %qpolega na wydrukowaniu cytowanej wersji łańcucha, czyli ciągu jednowierszowego, który można umieścić w skrypcie Bash, aby interpretować go jako ciąg literalny zawierający (potencjalnie) znaki nowej linii i inne znaki specjalne. Na przykład, patrz echo -n $'\tfoo\nbar'vs printf %q $'\tfoo\nbar'.

findPolecenie działa po prostu drukuje pojedynczy znak dla każdego pliku, a następnie licząc tych liniach zamiast liczenia.

l0b0
źródło
1

Oto „brute-force” -ish sposób, aby uzyskać wynik, używając find, echo, ls, wc, xargsi awk.

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'
Generał
źródło
Ta praca. Ale wyjście zawiodło, jeśli masz katalogi, które mają `` spację w nazwie.
ShyBoy,
Spowoduje to zgłoszenie niepoprawnych wyników, jeśli nazwa pliku zawiera znak nowej linii.
Shawn J. Goff,
-1
for i in *; do echo $i; ls $i | wc -l; done
Dinesh
źródło
4
Witamy w U&L. Odpowiedzi powinny mieć długą formę z objaśnieniami, a nie tylko krople kodu. Rozwiń to i wyjaśnij, co się dzieje. Jest to również bardzo nieefektywny sposób i nie obsługuje na przykład plików ze spacjami.
slm