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.
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.
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
¹ 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).
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.
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
-1
for i in `ls -1`; do echo $i : `ls -1 $i|wc -l`; done
find
kiedy 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..
)Odpowiedzi:
Robi to w bezpieczny i przenośny sposób. Nie pomylą go dziwne nazwy plików.
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
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$f
zawierała nazwę pliku-test
,find
narzekał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
$f
zawiera nazwę pliku „witaj, kolego”find ./$f
, to jestfind ./hello, buddy
. Mówisz,find
żeby spojrzeć na./hello,
ibuddy
. 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
find
polecenie ma po prostu-exec echo \;
i nie-exec echo {} \;
. Chcę wydrukować tylko jedną nową linię w celu liczenia plików.źródło
-mindepth 1
-printf '\n'
zamiast-exec echo
.-printf
, ale nie, jeśli chcesz, aby na przykład działało na FreeBSD.Zakładając, że szukasz standardowego rozwiązania dla systemu Linux, stosunkowo prostym sposobem na osiągnięcie tego jest
find
:Gdzie
find
przemierza dwa określone podkatalogi, do jednego-maxdepth
z 1, co zapobiega dalszej rekurencji i zgłasza tylko pliki (-type f
) oddzielone znakami nowej linii. Wynik jest następnie przesyłany potokowo wwc
celu zliczenia liczby tych linii.źródło
find . -maxdepth 1 -type d
wyjściem?find $dirs ...
(b), jeśli znajdują się wyłącznie w jednym katalogu wyższego poziomu, glob z tego katalogu,find */ ...
-exec echo
do swojej komendy find - w ten sposób nie odbija echa nazwy pliku, tylko nowy wiersz.Czy przez „bez rekurencji” rozumiesz, że jeśli
directoryName1
ma 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:Zauważ, że
-f
test 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ć-f
się-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:
lub wszystkie pliki po prostu tak:
Bash ma różne sposoby na uproszczenie tego. Aby policzyć zwykłe pliki:
Aby policzyć wszystkie pliki:
Jak zwykle w Zsh jest jeszcze prostszy. Aby policzyć zwykłe pliki:
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
).źródło
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:
printf %q
polega 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, patrzecho -n $'\tfoo\nbar'
vsprintf %q $'\tfoo\nbar'
.find
Polecenie działa po prostu drukuje pojedynczy znak dla każdego pliku, a następnie licząc tych liniach zamiast liczenia.źródło
Oto „brute-force” -ish sposób, aby uzyskać wynik, używając
find
,echo
,ls
,wc
,xargs
iawk
.źródło
źródło
źródło