Примечание: Ответ отражает мое собственное понимание этих механизмов на сегодняшний день, накопленное в ходе исследований и чтения ответов коллег на этом сайте и unix.stackexchange.com, и будет обновляться с течением времени. Не стесняйтесь задавать вопросы или предлагать улучшения в комментариях. Я также предлагаю вам попробовать посмотреть, как системные вызовы работают в оболочке с strace
команда. Также, пожалуйста, не пугайтесь понятия внутренних или системных вызовов - вам не обязательно знать или уметь их использовать, чтобы понять, как работает shell, но они определенно помогают понять.
ТЛ;Д-Р
-
|
каналы не связаны с записью на диске, поэтому у вас нет индекса номер файловой системы на диске (но у вас есть индекс в трубопроводы виртуальная файловая система в пространстве ядра), но перенаправления часто связаны с файлами, которые действительно имеют записи на диске и, следовательно, имеют соответствующий индекс.
- трубы не являются
lseek()
'способен, чтобы команды не могли считывать некоторые данные, а затем перематывать назад, но когда вы перенаправляете с >
или <
обычно это файл, который является lseek()
способный объект, поэтому команды могут перемещаться так, как им заблагорассудится.
- перенаправления - это манипуляции с файловыми дескрипторами, которых может быть много; каналы имеют только два файловых дескриптора - один для левой команды и один для правой команды
- перенаправление в стандартных потоках и каналах буферизуется.
- каналы почти всегда включают разветвление и, следовательно, задействованы пары процессов; перенаправления - не всегда, хотя в обоих случаях результирующие файловые дескрипторы наследуются подпроцессами.
- каналы всегда соединяют файловые дескрипторы (пару), перенаправления - либо используют путь, либо дескрипторы файлов.
- каналы - это метод межпроцессной связи, в то время как перенаправления - это просто манипуляции с открытыми файлами или файлоподобными объектами
- оба используют
dup2()
системные вызовы под капотом для предоставления копий файловых дескрипторов, где происходит фактический поток данных.
- перенаправления могут быть применены "глобально" с помощью
exec
встроенная команда ( см. этот и этот ), так что, если вы это сделаете exec > output.txt
каждая команда будет записывать в output.txt
с тех пор. |
каналы применяются только для текущей команды (что означает либо простую команду, либо подоболочку, например seq 5 | (head -n1; head -n2)
или составные команды (но также обратите внимание, что для таких составных команд количество байтов, которые read()
потребление будет влиять на то, сколько данных осталось на отправляющем конце канала для других команд внутри считывающего конца канала ).
- Когда перенаправление выполняется для файлов, такие вещи, как
echo "TEST" > file
и echo "TEST" >> file
оба используют open()
системный вызов для этого файла (смотрите также) и получить из него дескриптор файла, чтобы передать его в dup2()
. Трубы |
используйте только pipe()
и dup2()
системный вызов.
- Перенаправления требуют разрешений на доступ к файлам и каталогам; анонимные каналы обычно не требуют разрешений (т.Е. Независимо от того, можете вы или нет создать канал), но именованные каналы (созданные с помощью
mkfifo
) действительно включают типичные права доступа к файлам и биты чтения-записи-выполнения.
- Что касается выполняемых команд, каналы и перенаправление - это не более чем файловые дескрипторы - файлоподобные объекты, в которые они могут писать вслепую или манипулировать ими внутренне (что может привести к неожиданному поведению;
apt
например, имеет тенденцию даже не записывать в stdout если он знает, что есть перенаправление).
Вступление
Чтобы понять, чем отличаются эти два механизма, необходимо понять их основные свойства, историю, стоящую за ними, и их корни в языке программирования Си. На самом деле, зная, что такое дескрипторы файлов и как dup2()
и pipe()
работа системных вызовов имеет важное значение, а также lseek()
. Shell задуман как способ сделать эти механизмы абстрактными для пользователя, но копание глубже, чем абстракция, помогает понять истинную природу поведения shell.
Происхождение перенаправлений и каналов
Согласно статье Денниса Ритча Пророческие петроглифы, трубы , полученные из внутренняя записка 1964 года около Малкольм Дуглас Макилрой, в то время , когда они работали над Операционная система Multics. Цитата:
Чтобы выразить мои самые сильные опасения в двух словах:
- У нас должны быть какие-то способы подключения программ, таких как садовый шланг - вкручивать другой сегмент, когда это становится необходимым для обработки данных другим способом. Это также и способ ввода-вывода.
Очевидно, что в то время программы были способны записывать данные на диск, однако это было неэффективно, если выходные данные были большими. Чтобы процитировать объяснение Брайана Кернигана в Конвейер Unix видео :
Во-первых, вам не нужно писать одну большую массивную программу - у вас есть существующие программы меньшего размера, которые уже могут выполнять часть работы ... Во-вторых, возможно, что объем обрабатываемых вами данных не поместится, если вы сохраните его в файле ... потому что помните, мы вернулись в в те дни, когда на дисках этих устройств было, если вам повезет, мегабайт или два data...So конвейеру никогда не приходилось создавать экземпляр всего вывода целиком.
Таким образом, концептуальное различие очевидно: каналы - это механизм, позволяющий программам взаимодействовать друг с другом. Перенаправления - это способ записи в файл на базовом уровне. В обоих случаях shell упрощает эти две задачи, но под капотом происходит очень много всего.
Углубляясь: системные вызовы и внутренняя работа оболочки
Мы начнем с понятия дескриптор файла. Дескрипторы файлов описывают в основном открытый файл (будь то файл на диске, в памяти или анонимный файл), который представлен целым числом. Эти двое стандартные потоки данных (stdin,stdout,stderr) являются файловыми дескрипторами 0,1 и 2 соответственно. Откуда они берутся? Ну, в командах оболочки дескрипторы файлов наследуются от их родительской оболочки. И это верно в целом для всех процессов - дочерний процесс наследует файловые дескрипторы родительского процесса. Для демоны обычно все унаследованные файловые дескрипторы закрываются и/или перенаправляются в другие места.
Вернемся к перенаправлению. Что это такое на самом деле? Это механизм, который сообщает оболочке подготовить дескрипторы файлов для команды (поскольку перенаправления выполняются оболочкой перед выполнением команды) и указать их там, где предложил пользователь. То стандартное определение из перенаправления вывода является
[n]>word
Тот [n]
там указан номер дескриптора файла. Когда вы это сделаете echo "Something" > /dev/null
там подразумевается число 1, и echo 2> /dev/null
.
Под капотом это делается путем дублирования файлового дескриптора с помощью dup2()
системный вызов. Давайте возьмем df > /dev/null
. Оболочка создаст дочерний процесс, в котором df
запускается, но перед этим он откроется /dev/null
как файловый дескриптор #3, и dup2(3,1)
будет выдан, который создает копию файлового дескриптора 3, а копия будет равна 1. Вы знаете, как у вас есть два файла file1.txt
и file2.txt
, и когда вы это сделаете cp file1.txt file2.txt
у вас будет два одинаковых файла, но вы сможете манипулировать ими независимо? Здесь происходит примерно то же самое. Часто вы можете видеть, что перед запуском, bash
будем делать dup(1,10)
чтобы скопировать файловый дескриптор #1, который является stdout
( и эта копия будет fd # 10 ), чтобы восстановить ее позже. Важно отметить, что когда вы рассматриваете встроенные команды (которые являются частью самой оболочки и не имеют файла в /bin
или в другом месте) или простые команды в неинтерактивной оболочке, оболочка не создает дочерний процесс.
И тогда у нас есть такие вещи, как [n]>&[m]
и [n]&<[m]
. Это дублирование файловых дескрипторов, которые используют тот же механизм, что и dup2()
только теперь это в синтаксисе оболочки, удобно доступном для пользователя.
Одна из важных вещей, которые следует отметить в отношении перенаправления, заключается в том, что их порядок не является фиксированным, но имеет важное значение для того, как оболочка интерпретирует то, что хочет пользователь. Сравните следующее:
# Make copy of where fd 2 points , then redirect fd 2$ ls -l /proc/self/fd/ 3>&2 2> /dev/nulltotal 0lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/nulllrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd# redirect fd #2 first, then clone it$ ls -l /proc/self/fd/ 2> /dev/null 3>&2total 0lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/nulll-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/nulllr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
Практическое использование их в сценариях оболочки может быть универсальным:
и многое другое.
Сантехника с pipe()
и dup2()
Итак, как создаются каналы? Через pipe()
системный вызов, который будет принимать в качестве входных данных массив (он же список), называемый pipefd
из двух элементов типа int
(целое число). Эти два целых числа являются файловыми дескрипторами. То pipefd[0]
будет считываемым концом канала и pipefd[1]
будет концом записи. Так что в df | grep 'foo'
, grep
получит копию pipefd[0]
и df
получит копию pipefd[1]
. Но как? Конечно, с помощью магии dup2()
системный вызов. Для df
в нашем примере, скажем pipefd[1]
имеет # 4, поэтому оболочка создаст дочерний элемент, сделайте dup2(4,1)
(помните мой cp
пример ?), а затем выполните execve()
чтобы на самом деле запустить df
. Естественно, df
унаследует дескриптор файла # 1, но не будет знать, что он больше не указывает на терминал, а на самом деле fd #4, который на самом деле является концом канала записи. Естественно, то же самое произойдет и с grep 'foo'
за исключением случаев с разным количеством файловых дескрипторов.
Теперь интересный вопрос: можем ли мы создать каналы, которые также перенаправляют fd # 2, а не только fd # 1? Да, на самом деле это то, что |&
делает в bash. Стандарт POSIX требует, чтобы командный язык оболочки поддерживал df 2>&1 | grep 'foo'
синтаксис для этой цели, но bash
делает |&
также.
Важно отметить, что каналы всегда имеют дело с файловыми дескрипторами. Существует FIFO
или именованный канал, который имеет имя файла на диске и позволяет вам использовать его как файл, но ведет себя как канал. Но в |
типы каналов - это то, что известно как анонимный канал - у них нет имени файла, потому что на самом деле это просто два объекта, соединенных вместе. Тот факт, что мы не имеем дело с файлами, также имеет важное значение: трубы - это не lseek()
- способный. Файлы, находящиеся в памяти или на диске, являются статическими - программы могут использовать lseek()
системный вызов для перехода к байту 120, затем обратно к байту 10, затем вперед до конца. Каналы не являются статическими - они последовательны, и поэтому вы не можете перематывать данные, полученные из них, с помощью lseek()
. Это то, что позволяет некоторым программам знать, читают ли они из файла или из канала, и, таким образом, они могут вносить необходимые коррективы для эффективной работы; другими словами, prog
могу обнаружить, если я это сделаю cat file.txt | prog
или prog < input.txt
. Реальный пример работы этого - хвост.
Два других очень интересных свойства каналов заключаются в том, что у них есть буфер, который в Linux это 4096 байт, и у них на самом деле есть файловая система, определенная в исходном коде Linux ! Они не просто объект для передачи данных, они сами по себе являются структурой данных! На самом деле, потому что существует файловая система pipefs, которая управляет как каналами, так и FIFOs, каналы имеют индекс номер в их соответствующей файловой системе:
# Stdout of ls is wired to pipe$ ls -l /proc/self/fd/ | cat lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd# stdin of ls is wired to pipe$ true | ls -l /proc/self/fd/0lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
В Linux каналы однонаправлены, как и перенаправление. В некоторых Unix-подобных реализациях существуют двунаправленные каналы. Хотя с помощью магии сценариев оболочки вы можете сделать двунаправленные каналы в Linux также.
смотрите также: