Подстановка процесса в grep без ожидаемых результатов

Допустим, у меня есть программа, которая выводит:

abcd
l33t
1234

который я буду моделировать с помощью printf 'abcd\nl33t\n1234\n'. Я хотел бы дать этот вывод двум программам одновременно. Моя идея состояла бы в том, чтобы использовать замену процесса с tee. Допустим, я хочу передать копию вывода grep:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >&2) | grep '[0-9]'

Я получаю следующее с Bash 4.1.2 (Linux, CentOS 6.5), и это нормально:

l33t
1234
abcd
l33t

Но если подстановка процесса не перенаправляется на stderr (т. е. без >&2), вот так:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]') | grep '[0-9]'

Затем я получаю:

l33t
1234
l33t

Это похоже на то, как stdout из подстановки процесса (первый grep) используется процессом после конвейера (второй grep). За исключением того, что второй grep уже читает данные сам по себе, поэтому я думаю, что он не должен принимать во внимание данные из первого grep. Если я не ошибаюсь (а я точно ошибаюсь).

Что мне не хватает?


person vdavid    schedule 13.04.2017    source источник
comment
@cxw Linux, CentOS 6.5   -  person vdavid    schedule 13.04.2017
comment
Возможный обходной путь: ... | tee >(grep '[a-z]') >(grep '[0-9]') > /dev/null | cat. Требуется cat. Без него оболочка может запросить до завершения обоих grep процессов.   -  person Socowi    schedule 13.04.2017


Ответы (2)


Объяснение

Что касается командной строки, то подстановка процесса — это просто способ создание специального имени файла. (См. также документы.) Таким образом, второй конвейер выглядит примерно так: :

printf 'abcd\nl33t\n1234\n' | tee /dev/fd/nn | grep '[0-9]'

где nn — номер файлового дескриптора. Полный вывод printf идет на /dev/fd/nn, а также идет на grep '[0-9]'. Поэтому печатаются только числовые значения.

Что касается процесса внутри >(), он наследует стандартный вывод своего родителя. В этом случае этот стандартный вывод находится внутри канала. Поэтому вывод grep '[a-z]' проходит через конвейер так же, как и стандартный вывод tee. В результате конвейер в целом пропускает только строки, содержащие числа.

Когда вы вместо этого пишете в stderr (>&2), вы пропускаете последний этап конвейера. Поэтому вывод grep '[a-z]' на stderr идет на терминал.

Исправление

Чтобы исправить это без использования stderr, вы можете использовать другой псевдоним для вашего экрана. Например.:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >/dev/tty ) | grep '[0-9]'
                                               # ^^^^^^^^^

который дает мне вывод

l33t
1234
abcd
l33t

Тестирование этого

Чтобы разобраться с этим, я запустил echo >(ps). Процесс ps был дочерним по отношению к процессу bash, запускающему конвейер.

я тоже побежал

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]')

без | grep '[0-9]' в конце. В моей системе я вижу

abcd    <--- the output of the tee
l33t         ditto
1234         ditto
abcd    <--  the output of the grep '[a-z]'
l33t         ditto

Все пять строк входят в файл grep '[0-9]'.

person cxw    schedule 13.04.2017
comment
Конечно, он был отброшен, я думаю, что он должен был написать только l33t и 1234 из grep '[0-9]'. Но каким-то образом есть l33t, который, я думаю, исходит от grep '[a-z]'. - person vdavid; 13.04.2017
comment
@vdavid Хорошо --- понял! Спасибо за вопрос; Я узнал некоторые вещи. :) Взгляните и дайте мне знать, что вы думаете. - person cxw; 13.04.2017
comment
В этом есть смысл. Я не знал, что stdout был передан его родителю. Спасибо, что узнали это. Я думаю, мне просто нужно поиграть с файловыми дескрипторами, например. exec 3>&1 ; printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >&3) | grep '[0-9]' 3>&- - person vdavid; 13.04.2017

После tee у вас есть два потока

abcd
l33t
1234

1-й grep (>(grep '[a-z]' >&2) отфильтровывает

abcd
l33t

и выводит результат в его(!!!) stderr, который все еще подключен к вашему терминалу...

Итак, еще одна простая демонстрация:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >&2) | grep '[0-9]'

это печатает

l33t
1234
abcd
l33t

теперь добавьте wc -l

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' >&2) | grep '[0-9]' | wc -l 

и вы получите

abcd
l33t
       2

где хорошо видно:

abcd
l33t

является stderr 1-го grep, но stdout 2-го grep перенаправляется на wc и печатает

2

Теперь еще один тест:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' ) | cat -

вывод

abcd
l33t
1234
abcd
l33t

например две строки из grep и полный ввод из print

считать:

printf 'abcd\nl33t\n1234\n' | tee >(grep '[a-z]' ) | cat - | wc -l

вывод

5
person jm666    schedule 13.04.2017
comment
Ответ и формулировка @cxw приятнее. :) но, возможно, демка wc тоже поможет в понимании ;) - person jm666; 13.04.2017