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?

codecowboy
źródło
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).

Chris Down
źródło
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.

Aaron Davies
źródło
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 
Emmanuel
źródło
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ć.

Aaron Davies
źródło
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.

mikeserv
źródło
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