Comment obtenir un seul total de lignes avec `wc -l`?

12

J'ai ajouté un alias git pour me donner le nombre de lignes de fichiers spécifiques dans mon historique:

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

Cependant, wc -lrapporte plusieurs totaux, de sorte que si j'ai plus de ~ 100 000 lignes, il rapporte le total pour eux, puis passe à autre chose. Voici un exemple:

<100k lignes (sortie souhaitée)

$ 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 lignes (devaient être acheminées vers grep "total")

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

Comment puis-je obtenir un vrai total wc -l, pas une série de sous-totaux?

Ehryk
la source
Selon stackoverflow.com/questions/2501402/… le problème est avec xargs, non wc. Je suis toujours intéressé par la façon de le réparer, et je ne vois pas de bonne solution dans les réponses.
Ehryk
3
Votre version de prend-elle en wccharge l' --files0-fromoption? Ensuite, vous pouvez le faire{ git ls-files -z ${1} | wc -l --files0-from=- ; }
Mark Plotnick
@MarkPlotnick Je pense que cela mérite d'être une réponse.
terdon
Nan. wc: unrecognized option '--files0-from=-'
Ehryk

Réponses:

12

Essayez ceci, et excuses pour être évident:

cat *.cs | wc -l

ou, avec git:

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

Si vous voulez réellement que la sortie ressemble à une wcsortie, avec à la fois des nombres individuels et une somme, vous pouvez utiliser awkpour additionner les lignes individuelles:

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

Cela ne sera pas aussi bien aligné que wcsi cela vous importait. Pour ce faire, vous devez lire l'intégralité de l'entrée et l'enregistrer, calculer le total, puis utiliser le total pour calculer la largeur du champ avant d'utiliser cette largeur de champ pour imprimer une sortie formatée des lignes mémorisées. Comme les projets de rénovation domiciliaire, les awkscripts ne sont jamais vraiment terminés.

(Note aux éditeurs enthousiastes: l'expression régulière dans la première awkcondition est dans le cas où il y a un fichier dont le nom commence par "total" et un espace; sinon, la condition aurait pu être la plus simple $2 == "total".)

rici
la source
Cela fonctionne, mais il affiche uniquement le total ( git ls-files -z ${1} | xargs -0 cat | wc -l). Cependant, je manque le nombre de lignes par fichier que wc -l fournit comme dans mon premier exemple ci-dessus. Est-il possible de tirer le meilleur parti des deux mondes ici?
Ehryk
Ou, si c'est trop difficile, que diriez-vous d'un commutateur tel que s'il le cassait: donnez simplement le total, sinon, donnez le wc normal par fichier avec une sortie totale?
Ehryk
@Ehryk: vous pouvez simplement le faire deux fois, une fois comme vous le faisiez grep -vpour supprimer le total des lignes, et une fois comme je suggère d'obtenir le total. Ou vous pouvez essayer la solution awk dans la réponse modifiée,
rici
+1: "Comme les projets de rénovation domiciliaire, les scripts awk ne sont jamais vraiment terminés."
Ehryk
Cela a fonctionné comme un charme. Mon résultat 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 vous utilisez Linux, votre wcvient probablement de GNU Coreutils et a une --files0-fromoption pour lire un fichier (ou stdin) contenant une liste arbitrairement longue de noms de fichiers terminés par NUL à compter. La documentation de GNU Coreutils wc indique: "Ceci est utile lorsque la liste des noms de fichiers est si longue qu'elle peut dépasser une limite de longueur de ligne de commande. Dans de tels cas, l'exécution de wc via xargs n'est pas souhaitable car elle divise la liste en morceaux et fait imprimer wc un total pour chaque sous-liste plutôt que pour la liste entière. "

Essayez donc ceci:

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

Edit: Étant donné wcque vous êtes du dernier millénaire et que vous n'avez pas cette option, voici une solution plus portable, en supposant que vous avez awket n'avez pas de fichiers nommés "total". Il filtrera la sortie de wc, en omettant toutes les totallignes et en les résumant et en imprimant le grand total à la fin.

Une chose que je ne sais pas, c'est si l' gitimplémentation de l' alias aura des problèmes avec les guillemets simples $1et $2intérieurs, qui doivent être passés inchangés 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
la source
Je n'exécute pas Linux, c'est dans l'invite git bash de Git pour Windows msysgit.github.io (msysgit).
Ehryk
D'ACCORD. Donc, le xargset wcvous courez sont de Cygwin? Pouvez-vous coller la sortie de wc --version?
Mark Plotnick
Ils ne proviennent pas d'une installation complète 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
C'est un exécutable complet sur Windows,C:\Program Files (x86)\Git\bin\wc.exe
Ehryk
@Ehryk Msysgit est un portage des outils Linux, mais il a tendance à avoir d'anciennes versions, il se peut donc que ce ne soit pas le cas --files0-from.
Gilles 'SO- arrête d'être méchant'
4

Le problème est de savoir si xargsla commande est divisée en plusieurs exécutions. Le wcrapport est donc à chaque fois généré. Vous avez quelques options, vous pouvez garder les choses telles qu'elles sont et analyser la wcsortie:

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

Vous pouvez attraper les fichiers:

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

Ou vous pouvez sauter xargscomplètement (adapté d' ici ):

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

Cela cassera si votre liste de fichiers est plus longue que ARG_MAX .

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