Почему утилита wc генерирует несколько строк с "общим"?

Я использую утилиту wc в оболочке script, которую я запускаю из Cygwin, и я заметил, что в ее выходе имеется более одной строки с "total".

Следующая функция используется для подсчета количества строк в моих исходных файлах:

count_curdir_src() {
    find . '(' -name '*.vb' -o -name '*.cs' ')' \
        -a '!' -iname '*.Designer.*' -a '!' -iname '.svn' -print0 | \
    xargs -0 wc -l
}

Но его вывод для определенного каталога выглядит следующим образом:

$ find . '(' -name '*.vb' -o -name '*.cs' ')' -a '!' -iname '*.Designer.*' -a '!' -iname '.svn' -print0 | xargs -0 wc -l
     19 ./dirA/fileABC.cs
    640 ./dirA/subdir1/fileDEF.cs
    507 ./dirA/subdir1/fileGHI.cs
   2596 ./dirA/subdir1/fileJKL.cs
(...many others...)
     58 ./dirB/fileMNO.cs
     36 ./dirB/subdir1/filePQR.cs
 122200 total
  6022 ./dirB/subdir2/subsubdir/fileSTU.cs
    24 ./dirC/fileVWX.cs
(...)
    36 ./dirZ/Properties/AssemblyInfo.cs
    88 ./dirZ/fileYZ.cs
 25236 total

Похоже, что wc сбрасывается где-то в этом процессе. Это не может быть вызвано символами пробела в именах файлов или именах каталогов, потому что я использую параметр -print0. И это происходит только тогда, когда я запускаю его на самом большом исходном дереве.

Итак, это ошибка в wc, или в Cygwin? Или что-то другое? В man-странице wc говорится:

Печать строк новой строки, слова и байта для каждого FILE и общей строки, если        указано более одного FILE.

В нем ничего не говорится о множественных тотальных строках (промежуточные подсчеты или что-то еще), так кто виноват здесь?

Ответ 1

Вы вызываете wc несколько раз - один раз для каждой "партии" входных аргументов, предоставляемых xargs. Вы получаете одну общую сумму за пакет.

Один из вариантов заключается в использовании временного файла и опции --files0-from для wc:

$ find . '(' -name '*.vb' -o -name '*.cs' ')' -a '!' -iname '*.Designer.*' -a 
    '!' -iname   '.svn' -print0 > files

$ wc --files0-from files

Ответ 2

Что происходит, когда xargs работает wc несколько раз. xargs по умолчанию суммирует столько аргументов, сколько он считает возможным в каждом вызове команды, которую он должен запускать, но если файлов слишком много, он будет запускать команду несколько раз на подмножествах файлов.

Есть несколько способов, которые я вижу, чтобы исправить это. Первое, которое сломается, если у вас слишком много файлов, - это пропустить xargs и использовать оболочку. Это может плохо работать на Cygwin, но будет выглядеть так:

wc -l $(find . '(' -name '*.vb' -o -name '*.cs' ')' \
    -a '!' -iname '*.Designer.*' -a '!' -iname '.svn' )

и вы также потеряете возможности print0.

Другой - использовать awk (или perl) script для обработки вывода вашей комбинации find/xargs, пропустить "итоговые" строки и подвести итог самому себе.

Ответ 3

Длина строки в командной строке гораздо меньше, чем в стандартном окне linux, а xargs должна разделять входные данные для соблюдения этих ограничений. Вы можете проверить пределы с помощью xargs --show-limits:

В cygwin:

$ xargs --show-limits < /dev/null
Your environment variables take up 4913 bytes
POSIX upper limit on argument length (this system): 25039
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 20126
Size of command buffer we are actually using: 25039

В центре:

$ xargs --show-limits < /dev/null
Your environment variables take up 1816 bytes
POSIX upper limit on argument length (this system): 2617576
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2615760
Size of command buffer we are actually using: 131072

И чтобы построить ответ на @JonSkeet, вам не нужно создавать дополнительный файл, вы можете напрямую передать результаты поиска в wc, передав - в качестве аргумента в --files0-from:

find . -name '*.vb' -print0 | wc -l --files0-from=-

Ответ 4

Чтобы избежать генерации нескольких строк с "итоговыми" подсчетами при подаче утилиты wc с огромным количеством путей к файлам в качестве аргументов командной строки, вы можете использовать промежуточный xargs to cat содержимое файлов для stdin wc (см. вывод конвейера find to xargs wc дает необоснованные итоговые значения).

Это обходное решение, если ваша команда wc не имеет --files0-from, как упоминалось в Xavier.

count_curdir_src() (
   export LC_ALL=C
   find . -name '*.vb' -print0 | xargs -0 -n 1000 cat | wc -l 
)