Czy istnieje polecenie bash, które zlicza pliki?


182

Czy istnieje polecenie bash, które zlicza pliki pasujące do wzorca?

Na przykład chcę uzyskać liczbę wszystkich plików w katalogu, które pasują do tego wzorca: log*

Odpowiedzi:


243

Ten prosty jednolinijkowy powinien działać w każdej powłoce, nie tylko w bashu:

ls -1q log* | wc -l

ls -1q da ci jedną linię na plik, nawet jeśli zawierają spacje lub znaki specjalne, takie jak nowe linie.

Dane wyjściowe są przesyłane potokiem do wc -l, które zlicza liczbę wierszy.


10
Nie używałbym -l, ponieważ wymaga to stat(2)na każdym pliku i na potrzeby liczenia nic nie dodaje.
camh

12
Nie użyłbym ls, ponieważ tworzy proces potomny. log*jest rozszerzany przez powłokę, a nie ls, więc wystarczyłoby proste echo.
cdarke

2
Z wyjątkiem tego, że echo nie zadziała, jeśli masz nazwy plików ze spacjami lub znakami specjalnymi.
Daniel,

4
@WalterTross To prawda (nie to, że wydajność była wymogiem pierwotnego pytania). Właśnie odkryłem również, że -q zajmuje się plikami z nowymi liniami, nawet jeśli wyjście nie jest terminalem. Te flagi są obsługiwane przez wszystkie platformy i powłoki, na których testowałem. Aktualizuję odpowiedź, dzięki Tobie i Camh za wejście!
Daniel

3
Jeśli istnieje katalog o nazwie logsw tym katalogu, zawartość tego katalogu dzienników również zostanie policzona. To prawdopodobnie nie jest zamierzone.
mogsie

54

Możesz to zrobić bezpiecznie (tj. Nie będziesz mieć problemów z plikami ze spacjami lub \nw ich nazwie) za pomocą basha:

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

Musisz włączyć nullglob, aby nie uzyskać literału *.logw $logfiles tablicy, jeśli żaden plik nie pasuje. (Zobacz Jak „cofnąć” polecenie „set -x” ?, aby zapoznać się z przykładami bezpiecznego resetowania).


2
Być może wyraźnie zaznacz, że jest to odpowiedź tylko na Bash , szczególnie dla nowych gości, którzy nie są jeszcze w pełni na
bieżąco

Ponadto finał shopt -u nullglobpowinien zostać pominięty, jeśli nullglobnie był ustawiony, gdy zacząłeś.
tripleee

Uwaga: Zastąpienie *.logpo prostu *spowoduje policzenie katalogów. Jeśli pliki, które chcesz wyliczyć, mają tradycyjną konwencję nazewnictwa name.extension, użyj *.*.
AlainD

52

Wiele odpowiedzi tutaj, ale niektóre nie biorą pod uwagę

  • nazwy plików zawierające spacje, znaki nowej linii lub znaki sterujące
  • nazwy plików zaczynające się od myślników (wyobraź sobie plik o nazwie -l)
  • ukryte pliki, które zaczynają się kropką (jeśli glob był *.logzamiastlog*
  • katalogi, które pasują do globu (np. katalog o nazwie, logsktóry pasuje log*)
  • puste katalogi (czyli wynik to 0)
  • bardzo duże katalogi (wymienienie ich wszystkich może wyczerpać pamięć)

Oto rozwiązanie, które obsługuje je wszystkie:

ls 2>/dev/null -Ubad1 -- log* | wc -l

Wyjaśnienie:

  • -Upowoduje, lsże wpisy nie są sortowane, co oznacza, że ​​nie musi ładować całej listy katalogów do pamięci
  • -bwypisuje znaki specjalne w stylu C dla znaków niegraficznych, powodując, że znaki nowej linii są drukowane jako \n.
  • -adrukuje wszystkie pliki, nawet pliki ukryte (niepotrzebne, gdy glob log*nie sugeruje żadnych ukrytych plików)
  • -dwypisuje katalogi bez próby wypisania zawartości katalogu, co lsnormalnie by zrobił
  • -1 upewnia się, że jest w jednej kolumnie (ls robi to automatycznie podczas pisania do potoku, więc nie jest to bezwzględnie konieczne)
  • 2>/dev/nullprzekierowuje stderr, więc jeśli jest 0 plików dziennika, zignoruj ​​komunikat o błędzie. (Zauważ, shopt -s nullglobże spowodowałoby tols to wyświetlenie zamiast tego całego katalogu roboczego).
  • wc -lzużywa listę katalogów podczas generowania, więc dane wyjściowe lsnigdy nie są w pamięci w żadnym momencie.
  • --Nazwy plików są oddzielone od polecenia za pomocą, --aby nie były rozumiane jako argumenty do ls(w przypadku log*usunięcia)

Powłoka będzie rozszerzyć log*do pełnej listy plików, które mogą wyczerpać pamięć, jeśli jest dużo plików, więc następnie uruchomienie go przez grep ma być lepiej:

ls -Uba1 | grep ^log | wc -l

Ten ostatni obsługuje bardzo duże katalogi plików bez zajmowania dużej ilości pamięci (chociaż używa podpowłoki). Nie -djest już potrzebny, ponieważ wyświetla tylko zawartość bieżącego katalogu.


48

W przypadku wyszukiwania rekurencyjnego:

find . -type f -name '*.log' -printf x | wc -c

wc -cpoliczy liczbę znaków na wyjściu find, a -printf xmówi, findże wypisuje po jednym xdla każdego wyniku.

W przypadku wyszukiwania nierekurencyjnego wykonaj następujące czynności:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c

6
Nawet jeśli ty nie masz plików ze spacjami, jakiś inny użytkownik skryptu mogą napotkać złośliwie nazwie pliku, powodując skrypty na niepowodzenie. Ponadto inne osoby, które napotykają ten problem na StackOverflow, mogą mieć pliki z nowymi wierszami i muszą znać pułapki.
mogsie 22.08.15

Do Twojej wiadomości, jeśli po prostu pominiesz, -name '*.log'policzy wszystkie pliki, czego potrzebowałem w moim przypadku użycia. Również flaga -maxdepth jest niezwykle przydatna, dzięki!
starmandeluxe

2
Nadal daje to nieprawidłowe wyniki, jeśli istnieją nazwy plików z nowymi wierszami. Obejście jest łatwe dzięki find; po prostu wypisz coś innego niż dosłowna nazwa pliku.
tripleee

8

Przyjęta odpowiedź na to pytanie jest nieprawidłowa, ale mam niską reprezentację, więc nie mogę dodać do niej komentarza.

Prawidłowej odpowiedzi na to pytanie udziela Mat:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

Problem z akceptowaną odpowiedzią polega na tym, że wc -l zlicza liczbę znaków nowej linii i liczy je, nawet jeśli wypisują na terminal jako „?” na wyjściu 'ls -l'. Oznacza to, że zaakceptowana odpowiedź NIE powiedzie się, gdy nazwa pliku zawiera znak nowej linii. Przetestowałem sugerowane polecenie:

ls -l log* | wc -l

i błędnie zgłasza wartość 2, nawet jeśli istnieje tylko 1 plik pasujący do wzorca, którego nazwa zawiera znak nowej linii. Na przykład:

touch log$'\n'def
ls log* -l | wc -l

6

Jeśli masz dużo plików i nie chcesz używać eleganckiego shopt -s nullglobrozwiązania tablicowego i bash, możesz użyć funkcji find i tak dalej, o ile nie drukujesz nazwy pliku (która może zawierać znaki nowej linii).

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

Spowoduje to znalezienie wszystkich plików, które pasują do dziennika * i które nie zaczynają się od .*- „Nie nazwa. *” Jest zbędne, ale ważne jest, aby pamiętać, że domyślnym ustawieniem „ls” jest nie pokazywanie plików z kropkami, ale domyślne ponieważ find to ich uwzględnienie.

To jest prawidłowa odpowiedź i obsługuje dowolne nazwy plików, które możesz do niego rzucić, ponieważ nazwa pliku nigdy nie jest przekazywana między poleceniami.

Ale shopt nullglobodpowiedź jest najlepszą odpowiedzią!


Prawdopodobnie powinieneś zaktualizować swoją pierwotną odpowiedź zamiast odpowiadać ponownie.
qodeninja

Myślę, że używanie findvs używanie lsto dwa różne sposoby rozwiązania problemu. findnie zawsze jest obecny na maszynie, ale lszwykle jest,
mogsie

2
Ale z drugiej strony pudełko smalcu, którego nie ma, findprawdopodobnie nie ma wszystkich tych wymyślnych opcji ls.
tripleee

1
Zwróć również uwagę, jak to rozciąga się na całe drzewo katalogów, jeśli -maxdepth 1
wyjmiesz

1
Zwróć uwagę, że to rozwiązanie policzy pliki w ukrytych katalogach w swojej liczbie. findrobi to domyślnie. Może to spowodować zamieszanie, jeśli ktoś nie zdaje sobie sprawy, że istnieje ukryty folder podrzędny, i może sprawić, że korzystanie z niego będzie korzystne lsw niektórych okolicznościach, które domyślnie nie zgłaszają ukrytych plików.
MrPotatoHead

6

Oto moja jedyna wkładka do tego.

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

Zajęło mi trochę googlowania, aby zrozumieć, ale to jest miłe! Więc set -- nie robi nic poza przygotowaniem nas do $#tego, że przechowuje liczbę argumentów wiersza poleceń, które zostały przekazane do programu powłoki
xverges

@xverges Tak, "shopt -s nullglob" służy do nie liczenia ukrytych plików (.files). set - służy do przechowywania / ustawiania liczby parametrów pozycyjnych (w tym przypadku liczby plików). i # $ do wyświetlania liczby parametrów pozycyjnych (liczby plików).
zee

3

Możesz użyć opcji -R, aby znaleźć pliki wraz z plikami wewnątrz katalogów rekurencyjnych

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log

możesz użyć wzorów na grep


3

Ważna uwaga

(niewystarczająca reputacja do komentowania)

To jest BUGGY :

ls -1q some_pattern | wc -l

Jeśli shopt -s nullglobzdarzy się, że zostanie ustawiony, wypisuje liczbę WSZYSTKICH zwykłych plików, a nie tylko tych ze wzorcem (testowane na CentOS-8 i Cygwin). Kto wie, jakie inne bezsensowne błędy lsmają?

To jest PRAWIDŁOWE i znacznie szybsze:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

Wykonuje oczekiwaną pracę.


A czasy pracy są różne.
Pierwszy: 0.006na CentOS i 0.083na Cygwin (na wypadek, gdyby był używany ostrożnie).
Drugi: 0.000na CentOS i 0.003na Cygwin.


2

Możesz łatwo zdefiniować takie polecenie, używając funkcji powłoki. Ta metoda nie wymaga żadnego zewnętrznego programu i nie generuje żadnego procesu potomnego. Nie próbuje niebezpiecznego lsparsowania i dobrze radzi sobie ze znakami „specjalnymi” (spacje, znaki nowej linii, ukośniki odwrotne itd.). Opiera się tylko na mechanizmie rozszerzania nazw plików udostępnianym przez powłokę. Jest kompatybilny przynajmniej z sh, bash i zsh.

Poniższy wiersz definiuje funkcję o nazwie count która wypisuje liczbę argumentów, z którymi została wywołana.

count() { echo $#; }

Po prostu nazwij to pożądanym wzorem:

count log*

Aby wynik był poprawny, gdy wzorzec globowania nie pasuje, opcja powłoki nullglob(lub failglob- co jest domyślnym zachowaniem w zsh) musi być ustawiona w momencie rozwinięcia. Można to ustawić w następujący sposób:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

W zależności od tego, co chcesz policzyć, możesz być również zainteresowany opcją powłoki dotglob .

Niestety, przynajmniej w przypadku basha nie jest łatwo ustawić te opcje lokalnie. Jeśli nie chcesz ustawiać ich globalnie, najprostszym rozwiązaniem jest użycie funkcji w bardziej zawiły sposób:

( shopt -s nullglob ; shopt -u failglob ; count log* )

Jeśli chcesz odzyskać lekką składnię count log*lub naprawdę chcesz uniknąć tworzenia podpowłoki, możesz zhakować coś w następujący sposób:

# sh / bash:
# the alias is expanded before the globbing pattern, so we
# can set required options before the globbing gets expanded,
# and restore them afterwards.
count() {
    eval "$_count_saved_shopts"
    unset _count_saved_shopts
    echo $#
}
alias count='
    _count_saved_shopts="$(shopt -p nullglob failglob)"
    shopt -s nullglob
    shopt -u failglob
    count'

Jako bonus, ta funkcja ma bardziej ogólne zastosowanie. Na przykład:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

Przekształcając funkcję w plik skryptu (lub równoważny program w C), wywoływany z PATH, można ją również komponować z programami takimi jak findi xargs:

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search

2

Dużo się zastanawiałem nad tą odpowiedzią, zwłaszcza biorąc pod uwagę rzeczy, które nie są analizowane . Na początku próbowałem

<OSTRZEŻENIE! NIE DZIAŁAŁO>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ OSTRZEŻENIE! NIE DZIAŁAŁO>

co działało, gdyby istniała tylko nazwa pliku, taka jak

touch $'w\nlf.aa'

ale nie udało się, jeśli utworzyłem taką nazwę pliku

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

W końcu wymyśliłem to, co poniżej. Uwaga: Próbowałem uzyskać liczbę wszystkich plików w katalogu (bez żadnych podkatalogów). Myślę, że wraz z odpowiedziami @Mat i @Dan_Yard, a także mając co najmniej większość wymagań stawianych przez @mogsie (nie jestem pewien co do pamięci). Myślę, że odpowiedź @mogsie jest poprawna, ale zawsze staram się trzymać z daleka od analizowania, lschyba że jest to wyjątkowo specyficzna sytuacja.

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

Bardziej czytelnie:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

Robi to wyszukiwanie specjalnie dla plików, ograniczając wyjście znakiem null (aby uniknąć problemów ze spacjami i wysunięciami), a następnie zliczając liczbę znaków null. Liczba plików będzie o jeden mniejsza niż liczba znaków null, ponieważ na końcu będzie znak pusty.

Aby odpowiedzieć na pytanie PO, należy rozważyć dwa przypadki

1) Wyszukiwanie nierekurencyjne:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2) Wyszukiwanie rekurencyjne. Zwróć uwagę, że to, co znajduje się w -nameparametrze, może wymagać zmiany w celu uzyskania nieco innego zachowania (ukryte pliki itp.).

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

Jeśli ktoś chciałby skomentować porównanie tych odpowiedzi z tymi, o których wspomniałem w tej odpowiedzi, zrób to.


Uwaga, dotarłem do tego procesu myślowego, otrzymując tę odpowiedź .



0
ls -1 log* | wc -l

Oznacza to wyświetlenie jednego pliku w wierszu, a następnie przekazanie go do polecenia zliczania słów z przełączaniem parametrów na liczbę wierszy.


Opcja „-1” nie jest potrzebna podczas podłączania wyjścia ls. Ale możesz chcieć ukryć komunikat o błędzie ls, jeśli żaden plik nie pasuje do wzorca. Proponuję "ls log * 2> / dev / null | wc -l".
JohnMudd,

Również tutaj ważna jest dyskusja pod odpowiedzią Daniela . Działa to dobrze, gdy nie masz pasujących katalogów lub nazw plików z nowymi wierszami, ale dobra odpowiedź powinna przynajmniej wskazywać te warunki brzegowe, a świetna odpowiedź nie powinna ich mieć. Wiele błędów wynika z tego, że ktoś skopiował / wkleił kod, którego nie zrozumiał; więc wskazanie wad pomaga im przynajmniej zrozumieć, na co mają uważać. (To prawda, wiele więcej błędów się zdarza, ponieważ zignorowali zastrzeżenia, a potem sytuacja uległa zmianie, gdy myśleli, że kod jest prawdopodobnie wystarczająco dobry do ich celów).
tripleee

-1

Aby policzyć wszystko, po prostu potokiem ls do linii liczącej słowa:

ls | wc -l

Aby liczyć ze wzorem, najpierw potokuj do grep:

ls | grep log | wc -l
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.