¿Cómo obtengo un solo total de líneas con `wc -l`?

12

He agregado un alias git para darme el recuento de líneas de archivos específicos en mi historial:

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

Sin embargo, wc -linforma múltiples totales, de modo que si tengo más de ~ 100k líneas, informa el total para ellos, luego continúa. Aquí hay un ejemplo:

<100k líneas (salida deseada)

$ 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 líneas (tuvieron que canalizar grep "total")

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

¿Cómo obtengo un total verdadero wc -l, no una serie de subtotales?

Ehryk
fuente
De acuerdo con stackoverflow.com/questions/2501402/… el problema es con xargs, no wc. Todavía estoy interesado en cómo solucionarlo, y no veo una buena solución en las respuestas.
Ehryk
3
¿Su versión de wcadmite la --files0-fromopción? Entonces puedes hacerlo{ git ls-files -z ${1} | wc -l --files0-from=- ; }
Mark Plotnick
@ MarkPlotnick Creo que eso merece ser una respuesta.
terdon
No wc: unrecognized option '--files0-from=-'
Ehryk

Respuestas:

12

Prueba esto y disculpas por ser obvio:

cat *.cs | wc -l

o con git:

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

Si realmente desea que la salida se vea como wcsalida, con recuentos individuales y una suma, puede usar awkpara sumar las líneas individuales:

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

Eso no se alineará tan bien como lo wchace, en caso de que le importe. Para hacer eso, necesitaría leer la entrada completa y guardarla, calcular el total y luego usar el total para calcular el ancho del campo antes de usar ese ancho de campo para imprimir una salida formateada de las líneas recordadas. Al igual que los proyectos de renovación de viviendas, los awkguiones nunca se terminan realmente.

(Nota para editores entusiastas: la expresión regular en la primera awkcondición es en caso de que haya un archivo cuyo nombre comience con "total" y un espacio; de lo contrario, la condición podría haber sido mucho más simple $2 == "total").

rici
fuente
Eso funciona, pero solo genera el total ( git ls-files -z ${1} | xargs -0 cat | wc -l). Sin embargo, me falta el recuento de líneas por archivo que wc -l proporciona, como en mi primer ejemplo anterior. ¿Alguna forma de obtener lo mejor de ambos mundos aquí?
Ehryk
O, si eso es demasiado difícil, ¿qué tal un interruptor tal que si lo dividiera: solo dé el total, si no fuera así, dé el wc normal por archivo con una salida total?
Ehryk
@Ehryk: podrías hacerlo dos veces, una vez de la forma en que lo estabas haciendo grep -vpara soltar las líneas totales, y una vez que sugiero obtener el total total. O puede probar la solución awk en la respuesta editada,
rici
+1: "Al igual que los proyectos de renovación de viviendas, los guiones awk nunca se terminan realmente"
Ehryk
Eso funcionó a las mil maravillas. Mi resultado final: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

Si está ejecutando Linux, wcprobablemente provenga de GNU Coreutils y tenga la --files0-fromopción de leer un archivo (o stdin) que contenga una lista arbitrariamente larga de nombres de archivo terminados en NUL para contar. La documentación de wc de GNU Coreutils dice "Esto es útil cuando la lista de nombres de archivos es tan larga que puede exceder una limitación de longitud de la línea de comando. En tales casos, ejecutar wc a través de xargs no es deseable porque divide la lista en pedazos y hace que wc se imprima un total para cada sublista en lugar de para toda la lista ".

Entonces prueba esto:

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

Editar: dado que su wces del último milenio y no tiene esa opción, aquí hay una solución más portátil, suponiendo que tenga awky no tenga ningún archivo llamado "total". Filtrará la salida de wc, omitiendo las totallíneas y en su lugar resumiéndolas e imprimiendo el total general al final.

Una cosa que no sé es si la gitimplementación del alias tendrá problemas con las comillas simples $1y $2dentro de ellas, que deben pasarse sin cambios 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"; }' ;
}
Mark Plotnick
fuente
No estoy ejecutando Linux, está en el indicador git bash de Git para Windows msysgit.github.io (msysgit).
Ehryk
OKAY. Por lo que el xargsy wcque se está ejecutando es de Cygwin? ¿Se puede pegar la salida de wc --version?
Mark Plotnick el
No son de una instalación completa de cygwin:$ 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
Es un ejecutable completo en Windows,C:\Program Files (x86)\Git\bin\wc.exe
Ehryk
@Ehryk Msysgit es un puerto de las herramientas de Linux, pero tiende a tener versiones antiguas, por lo que puede no tenerlas --files0-from.
Gilles 'SO- deja de ser malvado'
4

El problema es xargsque está dividiendo el comando en varias ejecuciones, por lo que wcestá informando el total de cada vez. Tiene algunas opciones, puede mantener las cosas como están y analizar la wcsalida:

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

Podrías cat los archivos:

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

O puede omitir por xargscompleto (adaptado desde aquí ):

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

Sin embargo, eso se romperá si su lista de archivos es más larga que ARG_MAX .

terdon
fuente
-1
j=0; for i in *.php *.js *.css; do let j+=`wc -l $i | awk {'print $1'}`; done; echo $j;
NilsonCain
fuente