Как работает "(head; tail) <файл"?

(через qaru.site/info/68312/...)

Как работает (head; tail) < file? Обратите внимание, что cat file | (head;tail) не работает.

Кроме того, почему (head; wc -l) < file дает 0 для вывода wc?

Примечание. Я понимаю, как работают голова и хвост. Только не тонкости, связанные с этими конкретными вызовами.

Ответ 1

OS X

Для OS X вы можете посмотреть исходный код для head и исходный код для tail, чтобы выяснить, что происходит. В случае tail вам нужно посмотреть forward.c.

Итак, оказывается, что head не делает ничего особенного. Он просто считывает свои данные, используя библиотеку stdio, поэтому он читает буфер за раз и может слишком много читать. Это означает, что cat file | (head; tail) не будет работать для небольших файлов, где буферизация head позволяет читать некоторые (или все) из последних 10 строк.

С другой стороны, tail проверяет тип входного файла. Если это обычный файл, tail подходит к концу и читает назад, пока не найдет достаточное количество строк для испускания. Вот почему (head; tail) < file работает с любым обычным файлом независимо от его размера.

Linux

Вы можете посмотреть источник для head и tail на Linux тоже, но проще просто использовать strace, например:

(strace -o /tmp/head.trace head; strace -o /tmp/tail.trace tail) < file

Взгляните на /tmp/head.trace. Вы увидите, что команда head пытается заполнить буфер (из 8192 байт в моем тесте), читая со стандартного ввода (дескриптор файла 0). В зависимости от размера file он может или не может заполнять буфер. В любом случае, допустим, что он читает 10 строк в первом чтении. Затем он использует lseek для резервного копирования дескриптора файла до конца 10-й строки, по существу "нечитающего" лишних байтов, которые он читает. Это работает, потому что файловый дескриптор открыт в обычном, доступном для поиска файле. Таким образом, (head; tail) < file будет работать для любого файла с возможностью поиска, но он не сделает работу cat file | (head; tail).

С другой стороны, tail не пытается (в моем тестировании) искать конец и читать назад, как это происходит на OS X. По крайней мере, он не читает все пути назад к началу файл.

Вот мой тест. Создайте небольшой 12-строчный входной файл:

yes | head -12 | cat -n > /tmp/file

Затем попробуйте (head; tail) < /tmp/file в Linux. Я получаю это с GNU coreutils 5.97:

     1  y
     2  y
     3  y
     4  y
     5  y
     6  y
     7  y
     8  y
     9  y
    10  y
    11  y
    12  y

Но в OS X я получаю следующее:

     1  y
     2  y
     3  y
     4  y
     5  y
     6  y
     7  y
     8  y
     9  y
    10  y
     3  y
     4  y
     5  y
     6  y
     7  y
     8  y
     9  y
    10  y
    11  y
    12  y

Ответ 2

в скобках создаем subshell, который является еще одним экземпляром интерпретатора для запуска команд, которые находятся внутри, что интересно, что подоболочка действует как единственная команда stdin/stdout; в этом случае он будет сначала подключать stdin к head, который перекликается с первыми 10 строками и закрывает канал, тогда подседка соединяет свой stdin с tail, который потребляет остальное и записывает последние 10 строк в stdout, но подглазу принимает оба выхода и записывает их как свои собственные stdout, и почему он кажется объединенным.

стоит упомянуть, что такой же эффект может быть достигнут при группировке команд, такой как { head; tail; } < file, которая дешевле, потому что не создает другого экземпляра bash.

Ответ 3

Все они должны работать как ожидалось, если файл достаточно велик. Команда head будет потреблять определенный объем ввода (а не только то, что ему нужно, поскольку он буферизует его ввод), и если это не оставляет достаточно ввода для команды tail, это не сработает.

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

Сравните несколько прогонов следующей команды:

for i in `seq 1 10`; do echo "foo"; done | (head -n1; wc -l)

Команда wc должна каждый раз видеть различную величину файла.

При использовании < для ввода данных это не похоже на то, что этот parallelism существует (предположительно bash читает весь ввод, затем передает его в головную команду).

Ответ 4

команда заголовка отображает первые 10 (по умолчанию) строк файла. И команда хвоста отображает последние 10 (по умолчанию) строк файла. Предположим, что если файл имеет только 3 строки, также нет проблем, эта команда отобразит эти строки. Но если у вас более 10 строк, то обе команды будут отображать только по умолчанию 10 строк. Количество строк по умолчанию будет изменено с помощью опций -n, n, + n. (см. справочную страницу)