Czy istnieje bardziej zwięzła alternatywa dla potokowania do wc w celu zliczania plików w katalogu


12

Jeśli to zrobię ls -1 target_dir | wc -l, dostanę liczbę plików w katalogu. Uważam to za nieco kłopotliwe. Czy istnieje bardziej elegancki lub zwięzły sposób?


2
Nie potrzebujesz „-1” podczas instalacji na wc.
Steve

lsjuż podaje całkowitą liczbę, więc co powiesz na ls -l | head -1? Ustaw go jako alias, jeśli chcesz czegoś krótszego.
Daniel Wagner

2
@DanielWagner Dane wyjściowe „total: nnn” przez ls -loznaczają całkowity rozmiar plików, a nie liczbę plików.
David Richerby

2
Pamiętaj, że podana liczba ls | wc -lbędzie niepoprawna, jeśli nazwy plików zawierają znaki nowej linii.
chepner

Zależy to od systemu plików i liczy katalogi + 2 w katalogu. Odpowiedź ma 2 dodatkowe (jak się liczy, i jej rodzic). stat -c %h .podaje te same informacje, cols -ld . | cut -d" " -f 2
ctrl-alt-delor

Odpowiedzi:


12

Zakładając, że bash 4+ (który ma każda obsługiwana wersja Ubuntu):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Nazwij to jak num_files [dir]. dirjest opcjonalny, w przeciwnym razie korzysta z bieżącego katalogu. Twoja oryginalna wersja nie uwzględnia ukrytych plików, więc też nie. Jeśli tego chcesz, shopt -s dotglobwcześniej set -- *.

Twój oryginalny przykład obejmuje nie tylko zwykłe pliki, ale także katalogi i inne urządzenia - jeśli naprawdę chcesz tylko zwykłe pliki (w tym dowiązania symboliczne do zwykłych plików), musisz je sprawdzić:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

Jeśli masz GNU find, coś takiego jest również opcją (zwróć uwagę, że dotyczy to ukrytych plików, czego nie zrobiło twoje pierwotne polecenie):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(zmień -typena, -xtypejeśli chcesz liczyć również dowiązania symboliczne do zwykłych plików).


Czy nie setzawiedzie, jeśli jest bardzo wiele plików? Myślę, że być może będziesz musiał użyć xargskodu sumującego, aby to zadziałało w ogólnym przypadku.
l0b0

1
Również shopt -s dotglobjeśli chcesz, aby pliki zaczynające się .od zliczania
Digital Trauma

1
@ l0b0 Nie sądzę, setże w tych okolicznościach zawiedzie, ponieważ tak naprawdę nie robimy exec. To znaczy, w moim systemie, getconf ARG_MAXdaje 262144, ale jeśli to zrobię test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, z radością odpowiada 262145.
kojiro

@DavidRicherby -maxdepthnie jest POSIX.
Chris Down

4
@MichaelMartinez Pisanie oczywistego kodu nie zastępuje pisania poprawnego kodu.
Chris Down

3

f=(target_dir/*);echo ${#f[*]}

działa poprawnie dla pliku ze spacjami, znakami nowej linii itp. w nazwie.


czy możesz podać kontekst? Czy powinno to przebiegać w skrypcie bash?
codecowboy

to mogło. możesz również umieścić go bezpośrednio w powłoce. ta wersja zakładała, że ​​chcesz mieć bieżący katalog; Zredagowałem to, więc jest bliżej twojego pytania. w zasadzie tworzy zmienną tablicową powłoki zawierającą wszystkie pliki w katalogu, a następnie wypisuje liczbę tej tablicy. powinien działać w dowolnej powłoce z tablicami - bash, ksh, zsh itp. - ale prawdopodobnie nie zwykły sh / ash / dash.
Aaron Davies

2

lsjest wielokolumnowy tylko wtedy, gdy wysyła bezpośrednio do terminala, możesz usunąć opcję „-1”, możesz usunąć wcopcję „-l”, odczytaj tylko pierwszą wartość (leniwe rozwiązanie, nie może być stosowane do dowodów prawnych, dochodzenia kryminalne, misje krytyczne, operacje taktyczne ...).

ls target | wc 

5
Nie udaje się to w przypadku nazw plików zawierających nowe linie.
l0b0

@Emmanuel Musisz przeanalizować wynik, wcaby uzyskać liczbę plików nawet w trywialnym przypadku, więc jak to w ogóle rozwiązanie?
l0b0

@Emmanuel Może się to nie powieść, jeśli targetjest globem , który po rozwinięciu zawiera pewne rzeczy, które zaczynają się od łączników. Na przykład stwórz nowy katalog, wejdź do niego i zrób touch -- {1,2,3,-a}.txt && ls *|wc(NB: użyj, rm -- *.txtaby usunąć te pliki.)
David Richerby

Miałeś na myśli wc -l? W przeciwnym razie otrzymasz wynik nowej linii, słowa i bajtów ls. Tak powiedział David Richerby: Musisz to jeszcze raz przeanalizować.
erik

@erik Mówię wcbez argumentów, że nie musisz analizować, jeśli twój mózg wie, że pierwszym argumentem jest nowa linia.
Emmanuel

2

Jeśli szukasz zwięzłości (zamiast dokładnej poprawności w przypadku plików z nowymi liniami w nazwach itp.), Zalecam po prostu aliasing wc -ldo lc(„liczba wierszy”):

$ alias lc='wc -l'
$ ls target_dir|lc

Jak zauważyli inni, nie potrzebujesz takiej -1opcji ls, ponieważ jest to automatyczne, gdy lspiszesz do potoku. (Chyba że masz lsalias, aby zawsze używać trybu kolumnowego. Widziałem to wcześniej, ale niezbyt często).

lcAlias jest bardzo przydatny w ogóle, a na to pytanie, jeśli spojrzeć na „Count bieżący katalog” sprawy, ls|lcjest tak zwięzłe, jak można dostać.


2

Jak dotąd podejście Aarona jest bardziej zwięzłe niż twoje. Bardziej poprawna wersja twojego podejścia może wyglądać następująco:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Rekurencyjnie wyświetla listę wszystkich plików - nie katalogów - po jednym w wierszu, w tym .dotfiles pod bieżącym katalogiem, używając globusów powłoki w razie potrzeby do zastąpienia znaków niedrukowalnych. grep odfiltrowuje wszystkie katalogi nadrzędne lub ... lub * / lub puste wiersze - więc powinien być tylko jeden wiersz na plik - całkowita liczba grep do ciebie wraca. Jeśli chcesz również uwzględnić katalogi potomne:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Usuń -Rw obu przypadkach, jeśli nie chcesz wyników rekurencyjnych.


1
Wolę robić takie rzeczy find. Jeśli chcesz tylko zliczać, powinno to działać: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(usuń elementy sterujące głębokością, aby uzyskać rekurencyjne wyniki).
Aaron Davies,

@AaronDavies - to nie działa. Umieść nowy wiersz w dowolnym z tych nazw plików i przekonaj się sam. Ponadto, aby zrobić to samo przenośnie, robisz: find . \! -name . -prune | wc -l- co oczywiście nadal nie działa.
mikeserv

1
Nie podążam - printfinstrukcja wypisuje stały ciąg (nowy wiersz), który w ogóle nie zawiera nazwy pliku, więc wyniki są niezależne od dziwnych nazw plików. Oczywiście tej sztuczki nie da się zrobić za pomocą, findktóra nie obsługuje printf.
Aaron Davies,

@AaronDavies - och, prawda. Zakładam, że nazwa pliku została uwzględniona. Można to oczywiście zrobić przenośnie:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv

znakomity! /ponieważ jest to jedyny inny znak, który nie może pojawić się w nazwach plików, .//.sekwencja gwarantuje pojawienie się dokładnie raz dla każdego pliku, prawda? kilka pytań - dlaczego .//.i dlaczego -prune? kiedy to by się różniło find . \! -name . | grep -c '^\.'? (zakładam, że .w twoim \!.jest literówka.)
Aaron Davies
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.