"пока голова -n 1" курьезы

Некоторые эксперименты по кодированию(проводимые при попытке найти более короткий ответ на вопрос о кодировании) привели к нескольким интересным сюрпризам:

seq 2 | while head -n 1 ; do : ; done

Вывод (нажмите Control-C, или он будет тратить циклы процессора вечно):

1
^C

То же самое, но с использованием перенаправленного входного файла вместо конвейерного ввода:

seq 2 > two
while head -n 1 ; do : ; done < two

Выход (нажмите Control-C):

1
2
^C

Вопросы:

  1. Почему while петля не остановить путь seq 2 | head -n 1 было бы?

  2. Почему перенаправленный ввод будет производить больше, чем ввод по каналу?


Выше код был протестирован с dash а также bash на недавнем Lubuntu. И то и другое seq а также head взяты из пакета coreutils (версия 8.25-2ubuntu2).

Способ обойти необходимость нажатия (Ctrl-C):

timeout .1 sh -c "seq 2 > two ; while head -n 1 ; do : ; done < two"

1
2

timeout .1 sh -c "seq 2 | while head -n 1 ; do : ; done"

1

2 ответа

Решение

head -n 1 если ему предоставлен пустой поток на stdin, он находится в пределах своих прав и спецификаций для немедленного выхода со статусом успешного завершения.

Таким образом:

seq 2 | while head -n 1 ; do : ; done

... может юридически зацикливаться вечно, так как head -n 1 не требуется для выхода с ненулевым состоянием и, таким образом, для завершения цикла. (Ненулевой статус выхода требуется стандартом только в том случае, если "произошла ошибка", а файл, имеющий меньше строк, чем запрашивается для вывода, не определяется как ошибка).

Действительно, это явно:

Если файл содержит меньше, чем число строк, он должен быть полностью скопирован в стандартный вывод. Это не должно быть ошибкой.


Теперь, если ваша реализация head после его первого вызова (печать содержимого первой строки) оставляет указатель файла в очереди в начале второй строки, когда он выходит, (что абсолютно не требуется), тогда второй экземпляр цикла будет затем прочитайте эту вторую строку и испустите ее. Опять же, однако, это деталь реализации, которая зависит от того, пишут ли ваши люди head Реализация выбрала либо:

  1. Прочитайте агрессивно большой блок, но испустите только его часть. (Более эффективная реализация.)
  2. Или цикл за символом, чтобы потреблять только одну строку.

Реализатор вполне может решать, какой из этих реализаций следовать, основываясь на критериях, доступных только во время выполнения.


Теперь, скажем, ваш head всегда пытается прочитать 8kb блоков одновременно. Как же тогда он мог оставить указатель в очереди на вторую строку? [ * - кроме поиска в обратном направлении, что некоторые реализации делают, когда передается файл, но которое не требуется стандартом; спасибо Робу Мейхоффу за указатель здесь]

Это может произойти, если одновременный вызов seq только написал и очистил одну строку, когда первый read происходит

Очевидно, что это очень чувствительная ко времени ситуация - состояние гонки - и также зависит от неуказанных деталей реализации (seq сбрасывает свой вывод между строк - который, как seq не указывается как часть POSIX или любого другого стандарта, является полностью вариантом между платформами).

Принятый ответ правильный. не возвращает ненулевое значение для ввода (даже без ввода)

Но я обнаружил еще кое-что любопытное


Я нашел способ сделать это правильно.

      seq 10 | while head -c 4 | ifne -n false; do : ; done;

К сожалению, с этой конструкцией мало что можно сделать, поскольку выходные данные перемещаются по телу конструкции.while.

Одно из применений, которое я нашел, заключалось в вставке символа через каждые x байт. (включая хвост)

      /> printf '12345678910' | { while head -c 2 | ifne -n false; do printf 'a'; done; }
/> 12a34a56a78a91a0a

вам, вероятно, следует использоватьsed 's/.\{4\}/&a/g'вместо

Вот немного более полезный вариант, который займет 2 байта входных данных и затем поместит их куда-нибудь:

      printf '12345678910' | { while true; do head -c 2 < /dev/stdin | ifne -n false >> file.txt || break; done;

вам, вероятно, следует использоватьsplit --filterвместо

Еще один очень странный вариант использования, когда вы пытаетесь позвонитьheadвнутри цикла while с/dev/stdin.

      /> printf '12345678910' | { while head -c 2 | ifne -n false; do head -c 3 </dev/stdin | ifne -n false >> every3.txt || break; done > every2.txt; }
/> cat every2.txt
12670
/> cat every3.txt
345891

Который, как вы можете видеть, циклически повторяется каждые 2 байта, а затем каждые 3 байта.12 345 67 891 0

вам, вероятно, следует использоватьbbeвместо

вы могли бы использовать его как своего рода индикатор прогресса бедняков

      /> printf '12345678910' | while head -c 2 | ifne -n false; do echo "2 bytes travelled" > /dev/stderr ; done > /dev/null;
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled
2 bytes travelled # imperfect because actually only 1 byte travelled here

вам, вероятно, следует использоватьpvвместо


Что вообще можно сделать с этой конструкцией...

¯\_(ツ)_/¯

Другие вопросы по тегам