Esiste un comando bash che conta i file?


182

Esiste un comando bash che conta il numero di file che corrispondono a un modello?

Ad esempio, voglio ottenere il conteggio di tutti i file in una directory che corrisponde a questo modello: log*

Risposte:


243

Questo semplice one-liner dovrebbe funzionare in qualsiasi shell, non solo bash:

ls -1q log* | wc -l

-1q ti darà una riga per file, anche se contengono spazi bianchi o caratteri speciali come newline.

L'output viene reindirizzato a wc -l, che conta il numero di righe.


10
Non userei -l, poiché ciò richiede stat(2)ogni file e ai fini del conteggio non aggiunge nulla.
Camh,

12
Non userei ls, poiché crea un processo figlio. log*è espanso dalla shell, no ls, quindi un semplice echofarebbe.
cdarke,

2
Tranne un eco non funzionerà se si hanno nomi di file con spazi o caratteri speciali.
Daniel,

4
@WalterTross È vero (non che l'efficienza era un requisito della domanda originale). Ho anche scoperto che -q si occupa dei file con newline, anche quando l'output non è il terminale. E questi flag sono supportati da tutte le piattaforme e le shell su cui ho provato. Aggiornamento della risposta, grazie a te e camh per l'input!
Daniel,

3
Se esiste una directory chiamata logsnella directory in questione, verrà conteggiato anche il contenuto di quella directory dei registri. Questo probabilmente non è intenzionale.
mogsie,

54

Puoi farlo in modo sicuro (cioè non sarà disturbato da file con spazi o \nnel loro nome) con bash:

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

È necessario attivare nullglobin modo che non si ottiene il valore letterale *.lognella $logfiles serie , se nessun file corrispondono. (Vedi Come "annullare" un 'set -x'? Per esempi su come ripristinarlo in sicurezza.)


2
Forse fai notare esplicitamente che questa è una risposta solo Bash , specialmente per i nuovi visitatori che non sono ancora del tutto aggiornati sulla differenza tra sh e bash
tripleee

Inoltre, la finale shopt -u nullglobdeve essere ignorata se nullglobnon è stata disattivata, quindi hai iniziato.
Tripleee

Nota: la sostituzione *.logcon just *conterà le directory. Se i file che si desidera enumerare hanno la tradizionale convenzione di denominazione name.extension, utilizzare *.*.
Annunciato il

52

Molte risposte qui, ma alcune non tengono conto

  • nomi di file con spazi, newline o caratteri di controllo al loro interno
  • nomi di file che iniziano con trattini (immagina un file chiamato -l )
  • file nascosti, che iniziano con un punto (se il glob era *.loginvece dilog*
  • directory che corrispondono al glob (ad esempio una directory chiamata logsche corrispondelog* )
  • directory vuote (ovvero il risultato è 0)
  • directory estremamente grandi (elencandole tutte si potrebbe esaurire la memoria)

Ecco una soluzione che li gestisce tutti:

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

Spiegazione:

  • -Ufa sì lsche le voci non vengano ordinate, il che significa che non è necessario caricare l'intero elenco di directory in memoria
  • -bstampa le fughe in stile C per i caratteri non grafici, determinando in modo cruciale la stampa di nuove righe come \n.
  • -astampa tutti i file, anche i file nascosti (non strettamente necessari quando il glob log*non implica file nascosti)
  • -dstampa le directory senza tentare di elencare il contenuto della directory, cosa che lsnormalmente farebbe
  • -1 si assicura che si trovi su una colonna (lo fa automaticamente quando si scrive su una pipe, quindi non è strettamente necessario)
  • 2>/dev/nullreindirizza stderr in modo che se ci sono 0 file di registro, ignora il messaggio di errore. (Nota che shopt -s nullglobpotrebbe causarels elencare l'intera directory di lavoro.)
  • wc -lconsuma l'elenco delle directory mentre viene generato, quindi l'output di lsnon è mai in memoria in nessun momento.
  • --I nomi dei file vengono separati dal comando usando in --modo da non essere compresi come argomenti ls(nel caso in cui log*venga rimosso)

La shell si espanderà log*all'elenco completo dei file, il che potrebbe esaurire la memoria se contiene molti file, quindi è meglio eseguirlo attraverso grep:

ls -Uba1 | grep ^log | wc -l

Quest'ultimo gestisce directory di file estremamente grandi senza usare molta memoria (sebbene utilizzi una sottoshell). Non -dè più necessario, poiché elenca solo i contenuti della directory corrente.


48

Per una ricerca ricorsiva:

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

wc -cconterà il numero di caratteri nell'output di find, mentre -printf xindica finddi stampare un singolo xper ogni risultato.

Per una ricerca non ricorsiva, procedere come segue:

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

6
Anche se si non si dispone di file con gli spazi, qualche altro utente del vostro script potrebbe incontrare un file chiamato maliziosamente, causando gli script per sicuro. Inoltre, altre persone che incontrano questo su StackOverflow potrebbero avere file con nuove righe e devono conoscere le insidie.
mogsie,

Cordiali saluti, se si lascia semplicemente fuori -name '*.log', conterà tutti i file, che è quello che mi serviva per il mio caso d'uso. Anche il flag -maxdepth è estremamente utile, grazie!
starmandeluxe,

2
Questo produce ancora risultati errati se ci sono nomi di file con newline al loro interno. La soluzione è semplice find; basta stampare qualcosa di diverso dal nome del file alla lettera.
Tripleee

8

La risposta accettata per questa domanda è sbagliata, ma ho un rappresentante basso, quindi non posso aggiungere un commento.

La risposta corretta a questa domanda è data da Mat:

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

Il problema con la risposta accettata è che wc -l conta il numero di caratteri di nuova riga e li conta anche se stampano sul terminale come "?" nell'output di 'ls -l'. Ciò significa che la risposta accettata NON RIUSCE quando un nome file contiene un carattere di nuova riga. Ho testato il comando suggerito:

ls -l log* | wc -l

e riporta erroneamente un valore di 2 anche se c'è solo 1 file corrispondente allo schema il cui nome sembra contenere un carattere di nuova riga. Per esempio:

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

6

Se hai molti file e non vuoi usare la shopt -s nullglobsoluzione array elegante e bash, puoi usare find e così via finché non stampi il nome del file (che potrebbe contenere newline).

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

Questo troverà tutti i file che corrispondono al registro * e che non iniziano con .* - Il "non nome. *" È ridondante, ma è importante notare che il valore predefinito per "ls" non è mostrare i file punto, ma il valore predefinito perché trovare è includerli.

Questa è una risposta corretta e gestisce qualsiasi tipo di nome file che puoi lanciarvi, perché il nome file non viene mai passato tra i comandi.

Ma la shopt nullglobrisposta è la migliore risposta!


Probabilmente dovresti aggiornare la risposta originale invece di rispondere di nuovo.
qodeninja,

Penso che usare findvs usare lssiano due modi diversi di risolvere il problema. findnon è sempre presente su una macchina, ma di lssolito lo è,
mogsie il

2
Ma poi una scatola di strutto che non ha findprobabilmente non ha tutte quelle opzioni fantasiose per lsentrambi.
Tripleee

1
Nota anche come questo si estende a un intero albero di directory se -maxdepth 1
estrai

1
Nota che questa soluzione conterà i file all'interno delle directory nascoste nel suo conteggio. findfa questo di default. Questo può creare confusione se non ci si rende conto che esiste una cartella figlio nascosta e può essere vantaggioso da utilizzare lsin alcune circostanze, che non riporta i file nascosti per impostazione predefinita.
MrPotatoHead,

6

Ecco la mia unica fodera per questo.

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

Mi ci è voluto un po 'di google per capire, ma è bello! Quindi set -- non sta facendo altro che prepararci $#, che memorizza il numero di argomenti della riga di comando che sono stati passati al programma della shell
xverges

@xverges Sì, "shopt -s nullglob" serve per non contare i file nascosti (.file). set - serve per memorizzare / impostare il numero di parametri posizionali (numero di file, in questo caso). e # $ per visualizzare il numero di parametri posizionali (conteggio dei file).
zee,

3

È possibile utilizzare l'opzione -R per trovare i file insieme a quelli all'interno delle directory ricorsive

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

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

puoi usare modelli al grep


3

Un commento importante

(reputazione insufficiente per commentare)

Questo è BUGGY :

ls -1q some_pattern | wc -l

Se shopt -s nullglobsembra impostato, stampa il numero di TUTTI i file regolari, non solo quelli con il modello (testato su CentOS-8 e Cygwin). Chissà cosa fanno altri bug insignificantils ha?

Questo è CORRETTO e molto più veloce:

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

Fa il lavoro previsto.


E i tempi di esecuzione differiscono.
Il 1 °: 0.006su CentOS e 0.083su Cygwin (nel caso sia usato con cura).
Il secondo: 0.000su CentOS e 0.003su Cygwin.


2

È possibile definire facilmente un tale comando, usando una funzione shell. Questo metodo non richiede alcun programma esterno e non genera alcun processo figlio. Non tenta di lsanalizzare in modo pericoloso e gestisce i caratteri "speciali" (spazi bianchi, nuove righe, barre rovesciate e così via) bene. Si basa solo sul meccanismo di espansione del nome file fornito dalla shell. È compatibile con almeno sh, bash e zsh.

La riga seguente definisce una funzione chiamata countche stampa il numero di argomenti con cui è stata chiamata.

count() { echo $#; }

Chiamalo semplicemente con il modello desiderato:

count log*

Affinché il risultato sia corretto quando il modello globbing non ha corrispondenza, l'opzione shell nullglob(o failglob- che è il comportamento predefinito su zsh) deve essere impostata al momento dell'espansione. Può essere impostato in questo modo:

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

A seconda di cosa vuoi contare, potresti anche essere interessato all'opzione shell dotglob.

Sfortunatamente, almeno con bash, non è facile impostare queste opzioni localmente. Se non si desidera impostarli a livello globale, la soluzione più semplice è utilizzare la funzione in questo modo più contorto:

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

Se vuoi recuperare la sintassi leggera count log*, o se vuoi davvero evitare di generare una subshell, puoi hackerare qualcosa sulla falsariga di:

# 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'

Come bonus, questa funzione è di uso più generale. Per esempio:

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

Trasformando la funzione in un file di script (o un programma C equivalente), richiamabile dal PATH, può anche essere composta con programmi come finde xargs:

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

2

Ho pensato molto a questa risposta, soprattutto considerando le cose da non perdere . All'inizio ci ho provato

<WARNING! NON FUNZIONA>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</ ATTENZIONE! NON FUNZIONA>

che ha funzionato se esistesse solo un nome file come

touch $'w\nlf.aa'

ma fallisco se ho creato un nome file come questo

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

Alla fine ho pensato a quello che sto mettendo di seguito. Nota Stavo cercando di ottenere un conteggio di tutti i file nella directory (non includere alcuna sottodirectory). Penso che, insieme alle risposte di @Mat e @Dan_Yard, oltre ad avere almeno la maggior parte dei requisiti stabiliti da @mogsie (non sono sicuro della memoria.) Penso che la risposta di @mogsie sia corretta, ma cerco sempre di stare lontano dall'analisi a lsmeno che non sia una situazione estremamente specifica.

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

Più facilmente:

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

Questo sta facendo una ricerca specifica per i file, delimitando l'output con un carattere null (per evitare problemi con spazi e avanzamenti di riga), quindi contando il numero di caratteri null. Il numero di file sarà uno in meno rispetto al numero di caratteri null, poiché alla fine ci sarà un carattere null.

Per rispondere alla domanda del PO, ci sono due casi da considerare

1) Ricerca non ricorsiva:

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

2) Ricerca ricorsiva. Nota che -namepotrebbe essere necessario modificare il contenuto del parametro per comportamenti leggermente diversi (file nascosti, ecc.).

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

Se qualcuno desidera commentare come queste risposte sono paragonabili a quelle che ho menzionato in questa risposta, per favore fallo.


Nota, ho ottenuto questo processo di pensiero mentre ottenevo questa risposta .


1

Ecco cosa faccio sempre:

ls log * | awk 'END {print NR}'


awk 'END{print NR}'dovrebbe essere equivalente a wc -l.
musiphil,

0
ls -1 log* | wc -l

Il che significa elencare un file per riga e quindi reindirizzarlo al comando conteggio parole con il parametro che passa alle righe di conteggio.


L'opzione "-1" non è necessaria per il piping dell'output ls. Ma potresti voler nascondere il messaggio di errore se nessun file corrisponde allo schema. Suggerisco "ls log * 2> / dev / null | wc -l".
JohnMudd,

La discussione sotto la risposta di Daniel è rilevante anche qui. Funziona bene quando non si hanno directory corrispondenti o nomi di file con le nuove righe, ma una buona risposta dovrebbe almeno indicare queste condizioni al contorno e una grande risposta non dovrebbe averle. Molti bug sono dovuti al fatto che qualcuno ha copiato / incollato il codice che non ha capito; quindi evidenziare i difetti almeno li aiuta a capire a cosa prestare attenzione. (Certo, molti più bug si verificano perché hanno ignorato le avvertenze e poi le cose sono cambiate dopo aver pensato che il codice fosse probabilmente abbastanza buono per il loro scopo.)
tripleee

-1

Per contare tutto basta reindirizzare ls alla riga di conteggio parole:

ls | wc -l

Per contare con il motivo, pipe prima di grep:

ls | grep log | wc -l
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.