Odpowiedzi:
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.
ls
, ponieważ tworzy proces potomny. log*
jest rozszerzany przez powłokę, a nie ls
, więc wystarczyłoby proste echo
.
logs
w tym katalogu, zawartość tego katalogu dzienników również zostanie policzona. To prawdopodobnie nie jest zamierzone.
Możesz to zrobić bezpiecznie (tj. Nie będziesz mieć problemów z plikami ze spacjami lub \n
w ich nazwie) za pomocą basha:
$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
Musisz włączyć nullglob
, aby nie uzyskać literału *.log
w $logfiles
tablicy, jeśli żaden plik nie pasuje. (Zobacz Jak „cofnąć” polecenie „set -x” ?, aby zapoznać się z przykładami bezpiecznego resetowania).
shopt -u nullglob
powinien zostać pominięty, jeśli nullglob
nie był ustawiony, gdy zacząłeś.
*.log
po prostu *
spowoduje policzenie katalogów. Jeśli pliki, które chcesz wyliczyć, mają tradycyjną konwencję nazewnictwa name.extension
, użyj *.*
.
Wiele odpowiedzi tutaj, ale niektóre nie biorą pod uwagę
-l
)*.log
zamiastlog*
logs
który pasuje log*
)Oto rozwiązanie, które obsługuje je wszystkie:
ls 2>/dev/null -Ubad1 -- log* | wc -l
Wyjaśnienie:
-U
powoduje, ls
że wpisy nie są sortowane, co oznacza, że nie musi ładować całej listy katalogów do pamięci-b
wypisuje znaki specjalne w stylu C dla znaków niegraficznych, powodując, że znaki nowej linii są drukowane jako \n
.-a
drukuje wszystkie pliki, nawet pliki ukryte (niepotrzebne, gdy glob log*
nie sugeruje żadnych ukrytych plików)-d
wypisuje katalogi bez próby wypisania zawartości katalogu, co ls
normalnie 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/null
przekierowuje 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 -l
zużywa listę katalogów podczas generowania, więc dane wyjściowe ls
nigdy 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 -d
jest już potrzebny, ponieważ wyświetla tylko zawartość bieżącego katalogu.
W przypadku wyszukiwania rekurencyjnego:
find . -type f -name '*.log' -printf x | wc -c
wc -c
policzy liczbę znaków na wyjściu find
, a -printf x
mówi, find
że wypisuje po jednym x
dla każdego wyniku.
W przypadku wyszukiwania nierekurencyjnego wykonaj następujące czynności:
find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
-name '*.log'
policzy wszystkie pliki, czego potrzebowałem w moim przypadku użycia. Również flaga -maxdepth jest niezwykle przydatna, dzięki!
find
; po prostu wypisz coś innego niż dosłowna nazwa pliku.
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
Jeśli masz dużo plików i nie chcesz używać eleganckiego shopt -s nullglob
rozwią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 nullglob
odpowiedź jest najlepszą odpowiedzią!
find
vs używanie ls
to dwa różne sposoby rozwiązania problemu. find
nie zawsze jest obecny na maszynie, ale ls
zwykle jest,
find
prawdopodobnie nie ma wszystkich tych wymyślnych opcji ls
.
-maxdepth 1
find
robi 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 ls
w niektórych okolicznościach, które domyślnie nie zgłaszają ukrytych plików.
Oto moja jedyna wkładka do tego.
file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
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
(niewystarczająca reputacja do komentowania)
To jest BUGGY :
ls -1q some_pattern | wc -l
Jeśli shopt -s nullglob
zdarzy 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 ls
mają?
To jest PRAWIDŁOWE i znacznie szybsze:
shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
Wykonuje oczekiwaną pracę.
0.006
na CentOS i 0.083
na Cygwin (na wypadek, gdyby był używany ostrożnie).
0.000
na CentOS i 0.003
na Cygwin.
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 ls
parsowania 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 find
i xargs
:
find "$FIND_OPTIONS" -exec count {} \+ # count results of a search
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, ls
chyba ż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 -name
parametrze, 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ź .
Oto, co zawsze robię:
ls log * | awk „END {print NR}”
awk 'END{print NR}'
powinien być równoważny z wc -l
.
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.
-l
, ponieważ wymaga tostat(2)
na każdym pliku i na potrzeby liczenia nic nie dodaje.