Como obtenho um total único de linhas com `wc -l`?

12

Adicionei um alias do git para me fornecer a contagem de linhas de arquivos específicos no meu histórico:

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

No entanto, wc -lestá relatando vários totais, de modo que, se eu tiver mais de ~ 100k linhas, ele informará o total para elas e depois seguirá em frente. Aqui está um exemplo:

<100k linhas (saída desejada)

$ 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 linhas (teve que canalizar para grep "total")

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

Como obtenho um total verdadeiro wc -l, e não uma série de subtotais?

Ehryk
fonte
De acordo com stackoverflow.com/questions/2501402/…, o problema é com xargs, não wc. Ainda estou interessado em como corrigi-lo e não vejo uma boa solução nas respostas.
Ehryk
3
Sua versão do wcsuporta a --files0-fromopção? Então você pode fazer{ git ls-files -z ${1} | wc -l --files0-from=- ; }
Mark Plotnick
@ MarkPlotnick Acho que merece ser uma resposta.
terdon
Não. wc: unrecognized option '--files0-from=-'
Ehryk

Respostas:

12

Tente isso e peça desculpas por ser óbvio:

cat *.cs | wc -l

ou, com git:

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

Se você realmente deseja que a saída se pareça com a wcsaída, com contagens individuais e uma soma, você pode usar awkpara adicionar as linhas individuais:

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

Isso não será alinhado tão bem quanto o wcfaz, caso isso seja importante para você. Para fazer isso, você precisará ler toda a entrada e salvá-la, calculando o total e, em seguida, usar o total para calcular a largura do campo antes de usá-lo para imprimir uma saída formatada das linhas lembradas. Como projetos de reforma de casas, os awkscripts nunca são realmente finalizados.

(Observação para editores entusiasmados: a expressão regular na primeira awkcondição ocorre no caso de haver um arquivo cujo nome comece com "total" e um espaço; caso contrário, a condição poderia ter sido muito mais simples $2 == "total".)

rici
fonte
Isso funciona, mas gera o total apenas ( git ls-files -z ${1} | xargs -0 cat | wc -l). No entanto, estou faltando a contagem de linhas por arquivo que wc -l fornece como no meu primeiro exemplo acima. Alguma maneira de obter o melhor dos dois mundos aqui?
Ehryk
Ou, se isso é muito difícil, que tal um comutador que o interrompa: apenas forneça o total, se não o for, forneça o wc normal por arquivo com uma saída total?
Ehryk
@Ehryk: você poderia fazê-lo duas vezes, uma vez do jeito que você estava fazendo isso grep -vpara soltar o total de linhas, e uma vez do jeito que eu sugiro obter o total total. Ou você pode tentar a solução awk na resposta editada,
rici
+1: "Como nos projetos de reforma de residências, os scripts awk nunca são realmente finalizados".
Ehryk
Isso funcionou como um encanto. Meu 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 01/02
7

Se você estiver executando o Linux, wcprovavelmente é proveniente do GNU Coreutils e tem a --files0-fromopção de ler um arquivo (ou stdin) contendo uma lista arbitrariamente longa de nomes de arquivos terminados em NUL a serem contados. A documentação do GNU Coreutils wc diz "Isso é útil quando a lista de nomes de arquivos é tão longa que pode exceder uma limitação do comprimento da linha de comando. Nesses casos, executar o wc via xargs é indesejável, porque divide a lista em pedaços e faz com que o wc seja impresso". um total para cada sublist e não para a lista inteira ".

Então tente o seguinte:

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

Edit: Como você wcé do último milênio e não tem essa opção, aqui está uma solução mais portátil, supondo que você tenha awke não tenha nenhum arquivo chamado "total". Ele filtrará a saída de wc, omitindo todas as totallinhas, resumindo-as e imprimindo o total geral no final.

Uma coisa que não sei é se a gitimplementação do alias terá problemas com aspas simples $1e $2internas, que precisam ser passadas inalteradas 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
fonte
Não estou executando o linux, ele está no prompt do git bash do Git para Windows msysgit.github.io (msysgit).
Ehryk
ESTÁ BEM. Então o xargse wcvocê está correndo são da Cygwin? Você pode colar a saída de wc --version?
Mark Plotnick
Eles não são de uma instalação completa do 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 31/01
É um completo no Windows executável,C:\Program Files (x86)\Git\bin\wc.exe
Ehryk
@Ehryk Msysgit é uma porta das ferramentas Linux, mas tende a ter versões antigas, por isso pode não ter --files0-from.
Gilles 'SO- stop be evil'
4

O problema é xargsdividir o comando em várias execuções, assim wccomo reportar o total de cada vez. Você tem algumas opções, você pode manter as coisas como estão e analisar a wcsaída:

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

Você pode criar os arquivos:

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

Ou você pode pular xargscompletamente (adaptado daqui ):

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

Isso será interrompido se sua lista de arquivos for maior que ARG_MAX .

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