"пока голова -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
Вопросы:
Почему
while
петля не остановить путьseq 2 | head -n 1
было бы?Почему перенаправленный ввод будет производить больше, чем ввод по каналу?
Выше код был протестирован с 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
Реализация выбрала либо:
- Прочитайте агрессивно большой блок, но испустите только его часть. (Более эффективная реализация.)
- Или цикл за символом, чтобы потреблять только одну строку.
Реализатор вполне может решать, какой из этих реализаций следовать, основываясь на критериях, доступных только во время выполнения.
Теперь, скажем, ваш 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
вместо
Что вообще можно сделать с этой конструкцией...
¯\_(ツ)_/¯