Почему sed q работает иначе при чтении из канала?

25

Я создал тестовый файл с именем «test», который содержит следующее:

xxx
yyy
zzz

Я запустил команду:

(sed '/y/ q'; echo aaa; cat) < test

и я получил:

xxx
yyy
aaa
zzz

Затем я побежал:

cat test | (sed '/y/ q'; echo aaa; cat)

и получил:

xxx
yyy
aaa

Вопрос

sedчитает и печатает, пока не встретит строку с 'y', затем остановится. В первом случае, но не во втором, кошка читает и печатает все остальное.

Может кто-нибудь объяснить, что явление стоит за этой разнице в поведении?

Я также заметил, что это работает в Ubuntu 16.04 и Centos 6, но в Centos 7 ни одна из команд не выводит «zzz».

Антти Куусела
источник
Я предполагаю, что cat(в под-оболочке) может повторно использовать дескриптор файла в первом случае, потому что stdin связан с реальным файлом. Во втором случае stdin из канала, а не из реального файла. Обратите внимание, что также (sed '/y/ q'; echo aaa; cat) < <(cat test)не печатает zzz.
Мартин Найолт
1
Более простой пример: (head -n1; head -n1) < testиcat test | (head -n1; head -n1)
Мартин Найолт

Ответы:

22

Когда входной файл доступен для поиска (например, чтение из обычного файла) или недоступен для поиска (например, чтение из канала), sed(и другие стандартные утилиты) будут вести себя по-другому (см. INPUT FILESРаздел « Чтение » в этой ссылке ).

Цитата из документа:

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

Итак, в:

(sed '/y/ q'; echo aaa; cat) < test

sedвыполнил qкоманду uit до достижения EOF, поэтому он оставил смещение файла в начале zzzстроки, поэтому catможно продолжить печать оставшихся строк (GNU sed не совместим с POSIX в некоторых условиях, см. ниже).

И продолжаем из дока:

Для файлов, которые нельзя найти, состояние смещения файла в описании открытого файла для этого файла не указано

В этом случае поведение не определено. Большинство стандартных инструментов, включающих, sedбудут потреблять ввод как можно больше. Он читает проход и передает yyyстроку qбез восстановления смещения файла, поэтому ничего не остается cat.


GNU sedне соответствует стандарту, зависит от реализации stdio системы и версии glibc:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

Здесь результат был получен от Mac OSX 10.11.6, виртуальных машин Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, которые работают на Openstack с бэкэндом CEPH.

В этих системах вы можете использовать -uопцию для достижения стандартного поведения:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

и для трубы:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

что приводит к ужасно неэффективной производительности, потому что sedприходится читать по одному байту за раз. Частичный вывод из strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...
cuonglm
источник
1
Для GNU sedэто зависит от реализации stdio системы. В системах GNU (с GNU libc) GNU sedбудет совместимым, так как exit()будет искать файлы, управляемые stdio.
Стефан Шазелас
@ StéphaneChazelas: как это проверить? С моей Centos 7.2, Ubuntu 14.04 VM, sedне совместима, мой ноутбук manjaro имеет, все имеют ту же sed версию 4.2.2
cuonglm
@ StéphaneChazelas: Похоже, что-то случилось под капотом. На моих виртуальных машин, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'не показывают , что не lseek()было выполнено, в то время как в моем manjaro lseek()была вызвана прежде exit_group().
cuonglm
Я полагаю, что это до версии GNU libc. Вы можете проверить с помощью main() { char buf[999]; gets(buf); }'программы.
Стефан Шазелас
1
@ StéphaneChazelas: подтверждено. Обе мои виртуальные машины имеют 2,17 и 2,19, а у моей Манджаро - 2,23. Это считается ошибкой glibc? У вас есть какая-либо информация об изменениях между версиями glibc
cuonglm