Come posso ottenere un singolo totale di righe con `wc -l`?


12

Ho aggiunto un alias git per darmi il conteggio delle righe di file specifici nella mia cronologia:

[alias]
lines = !lc() { git ls-files -z ${1} | xargs -0 wc -l; }; lc

Tuttavia, wc -lsta segnalando più totali, in modo tale che se ho più di ~ 100k righe, segnala il totale per loro, quindi continua. Ecco un esempio:

<100k linee (output desiderato)

$ git lines \*.xslt
  46 packages/NUnit-2.5.10.11092/doc/files/Summary.xslt
 232 packages/NUnit-2.5.10.11092/samples/csharp/_UpgradeReport_Files/UpgradeReport.xslt
 278 total

> 100k linee (ha dovuto eseguire il pipe grep "total")

$ git lines \*.cs | grep "total"
 123569 total
 107700 total
 134796 total
 111411 total
  44600 total

Come posso ottenere un totale reale da wc -l, non una serie di subtotali?


Secondo stackoverflow.com/questions/2501402/… il problema è xargs, non wc. Sono ancora interessato a come risolverlo e non vedo una buona soluzione nelle risposte.
Ehryk,

3
La tua versione di wcsupporta l' --files0-fromopzione? Quindi puoi fare{ git ls-files -z ${1} | wc -l --files0-from=- ; }
Mark Plotnick il

@MarkPlotnick Penso che meriti di essere una risposta.
terdon

No. wc: unrecognized option '--files0-from=-'
Ehryk,

Risposte:


12

Prova questo e ci scusiamo per essere ovvio:

cat *.cs | wc -l

o, con git:

git ls-files -z ${1} | xargs -0 cat | wc -l

Se vuoi davvero che l'output assomigli wcall'output, con entrambi i conteggi individuali e una somma, puoi usare awkper sommare le singole righe:

git ls-files -z ${1} | xargs -0 wc -l |
awk '/^[[:space:]]*[[:digit:]]+[[:space:]]+total$/{next}
     {total+=$1;print}
     END {print total,"total"}'

Questo non sarà allineato così bene wc, nel caso in cui ciò che conta per te. Per fare ciò, dovresti leggere l'intero input e salvarlo, calcolando il totale, quindi utilizzare il totale per calcolare la larghezza del campo prima di utilizzare quella larghezza del campo per stampare un output formattato delle righe memorizzate. Come i progetti di ristrutturazione domestica, gli awkscript non sono mai veramente finiti.

(Nota per i redattori entusiasti: l'espressione regolare nella prima awkcondizione è nel caso in cui ci sia un file il cui nome inizia con "totale" e uno spazio; altrimenti, la condizione avrebbe potuto essere molto più semplice $2 == "total".)


Funziona, ma genera solo il totale ( git ls-files -z ${1} | xargs -0 cat | wc -l). Tuttavia, mi manca il conteggio delle righe per file che wc -l fornisce come nel mio primo esempio sopra. Un modo per ottenere il meglio da entrambi i mondi qui?
Ehryk,

Oppure, se è troppo difficile, che ne dici di uno switch tale che se lo spezzasse: basta dare il totale, in caso contrario, dare il normale wc per file con un output totale?
Ehryk,

@Ehryk: potresti farlo due volte, una volta nel modo in cui lo facevi grep -vper eliminare le linee totali, e una volta nel modo che suggerisco di ottenere il totale. Oppure potresti provare la soluzione awk nella risposta modificata,
rici

+1: "Come i progetti di ristrutturazione di case, gli script di awk non sono mai veramente finiti."
Ehryk,

Funzionava come un fascino. Il mio risultato finale:git ls-files -z ${1} | xargs -0 wc -l | awk '/^[[:space:]]*[[:digit:]]+[[:space:]]+total$/{next} {total+=$1;print} END {print "\n Total:",total,"lines"}'
Ehryk,

7

Se stai wcusando Linux, probabilmente viene da GNU Coreutils e ha --files0-fromun'opzione per leggere un file (o stdin) contenente un elenco arbitrariamente lungo di nomi di file terminati con NUL da contare. La documentazione di GNU Coreutils wc dice "Questo è utile quando l'elenco dei nomi dei file è così lungo che potrebbe superare un limite di lunghezza della riga di comando. In questi casi, l'esecuzione di wc tramite xargs è indesiderabile perché divide l'elenco in pezzi e fa stampare wc un totale per ciascun elenco secondario anziché per l'intero elenco. "

Quindi prova questo:

lc() { git ls-files -z ${1} | wc -l --files0-from=- ; } 

Modifica: poiché il tuo wcè dell'ultimo millennio e non ha questa opzione, ecco una soluzione più portatile, supponendo che tu abbia awke non abbia alcun file chiamato "totale". Filtrerà l'output di wc, omettendo qualsiasi totalriga e invece riassumendole e stampando il totale generale alla fine.

Una cosa che non so è se l' gitimplementazione dell'alias avrà problemi con $1e $2all'interno delle virgolette singole, che devono essere passate invariate awk.

lc() {
  git ls-files -z ${1} |
  xargs -0 wc -l |
  awk 'BEGIN { total=0; } { if (NF==2 && $2 == "total") total += $1; else print; } END { print total, "total"; }' ;
}

Non eseguo Linux, è nel prompt git bash di Git per Windows msysgit.github.io (msysgit).
Ehryk,

OK. Quindi il xargse wcstai correndo sono di Cygwin? Puoi incollare l'output di wc --version?
Mark Plotnick,

Non provengono da un'installazione Cygwin completa:$ wc --version wc (GNU textutils) 2.0 Written by Paul Rubin and David MacKenzie. Copyright (C) 1999 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Ehryk,

È un eseguibile completo su Windows,C:\Program Files (x86)\Git\bin\wc.exe
Ehryk,

@Ehryk Msysgit è una porta degli strumenti di Linux, ma tende ad avere versioni precedenti, quindi potrebbe non esserlo --files0-from.
Gilles 'SO- smetti di essere malvagio' il

4

Il problema è xargsche sta dividendo il comando in più esecuzioni, quindi wcsegnala il totale per ogni volta. Hai alcune opzioni, potresti mantenere le cose come sono e analizzare l' wcoutput:

git ls-files -z ${1} | xargs -0 wc -l | awk '/total/{k+=$1}END{print k,"total"}';

Puoi cat i file:

git ls-files -z ${1} | xargs -0 cat | wc -l

Oppure potresti saltare del xargstutto (adattato da qui ):

unset files i; while IFS= read -r -d $'\0' name; do 
 files[i++]="$name"; 
done < <(git ls-files -z ${1} ) && wc -l "${files[@]}"

Ciò si interromperà tuttavia se l'elenco dei file è più lungo di ARG_MAX .


-1
j=0; for i in *.php *.js *.css; do let j+=`wc -l $i | awk {'print $1'}`; done; echo $j;
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.