Понимание конвейерных команд в Unix / Linux

16

У меня есть две простые программы: Aи B. Aзапускается первым, затем Bполучает «стандартный вывод» Aи использует его в качестве «стандартного ввода». Предположим, я использую операционную систему GNU / Linux, и самый простой способ сделать это будет:

./A | ./B

Если бы мне пришлось описать эту команду, я бы сказал, что это команда, которая принимает входные данные (то есть читает) от производителя ( A) и записывает в потребителя ( B). Это правильное описание? Я что-то пропустил?

nihulus
источник
Связанный: В каком порядке запускаются командные команды?
G-Man говорит: «Восстанови Монику»
Это не команда, это объект kenerl, созданный процессом bash, который используется как стандартный вывод процесса A и стандартный вывод как B. Два процесса запускаются почти одновременно.
德里克 薯条 德里克
1
@ 炸鱼 Вы правы - ядро ​​конвейера - это объект в файловой системе pipefs, но с точки зрения самой оболочки - технически это команда конвейера
Сергей Колодяжный,

Ответы:

26

Единственное, что в вашем вопросе является неправильным, так это то, что вы говорите

Сначала запускается A, затем B получает выход A

Фактически обе программы будут запущены практически одновременно. Если нет ввода для того, Bкогда он пытается прочитать, он будет блокироваться до тех пор, пока нет ввода для чтения. Аналогично, если никто не читает выходные данные A, их записи будут блокироваться до тех пор, пока их выходные данные не будут прочитаны (некоторые из них будут буферизованы каналом).

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

Идиоматический способ описать A | Bэто то, что это конвейер, содержащий две программы. Выходные данные, полученные на стандартном выходе первой программы, доступны для чтения на стандартном входе второй («[выходные данные] Aпередаются на [входные данные] B»). Оболочка выполняет необходимые сантехнические работы, чтобы это произошло.

Если вы хотите использовать слова «потребитель» и «производитель», я думаю, это тоже нормально.

Тот факт, что это программы, написанные на C, не имеет значения. Тот факт, что это Linux, macOS, OpenBSD или AIX, не имеет значения.

Кусалананда
источник
2
Запись во временный файл использовалась в DOS, поскольку она не поддерживала несколько процессов.
CSM
2
@AlexVong Обратите внимание, что ваш пример с временным файлом не совсем эквивалентен. Программа может выбрать поиск по содержимому файла, но данные, поступающие из канала, недоступны для поиска. Лучше было бы использовать mkfifoдля создания именованного канала, затем запустить B в фоновом режиме чтения из канала, а затем A записать в него. Хотя это и есть придирки, так как эффект будет таким же.
Кусалананда
2
@AlexVong Упрощения, сделанные в этой статье, отделяют ее от реальных конвейеров; параллельное выполнение действительно семантическое, а не оптимизация. Это разумное объяснение монадической оценки или композиции лживым детям для кого-то, кто видел конвейеры оболочки, но это не верно в другом направлении. Вариант Кусалананды FIFO ближе, но части распространения ошибок модели действительно важны и не воспроизводимы. (все, о чем я говорю как о ком-то, кто очень
Майкл Гомер
6
@AlexVong Нет, это совсем не так. Это не в состоянии объяснить даже что-то простое, как yes | sed 10q
Дядя Билли
1
@UncleBilly Я согласен с твоим примером. Это показывает, что параллельное выполнение действительно требуется, также замечено Майклом. В противном случае мы получим прекращение.
Алекс Вонг
2

Термин, обычно используемый в документации, - это «конвейер», который состоит из одной или нескольких команд, см. Определение POSIX. Так что с технической точки зрения это две команды, которые у вас есть, два подпроцесса для оболочки (либо fork()+exec()внешние команды, либо подоболочки)

Что касается части производитель-потребитель , конвейер может быть описан этим шаблоном, так как:

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

Сергей Колодяжный
источник