В чем разница между "Перенаправление" и "Труба"?
Этот вопрос может показаться немного глупым, но я не вижу разницы между перенаправлением и каналами.
Перенаправление используется для перенаправления stdout/stdin/stderr, например ls > log.txt
,
Каналы используются для выдачи вывода команды в качестве ввода для другой команды, например ls | grep file.txt
,
Но почему есть два оператора для одного и того же?
Почему бы просто не написать ls > grep
чтобы пропустить вывод, разве это не просто перенаправление? Чего мне не хватает?
9 ответов
Pipe is used to pass output to another program or utility.
Redirect is used to pass output to either a file or stream.
Пример: thing1 > thing2
против thing1 | thing2
thing1 > thing2
- Your shell will run the program named
thing1
- Everything that
thing1
outputs will be placed in a file calledthing2
, (Note - ifthing2
exists, it will be overwritten)
If you want to pass the output from program thing1
to a program called thing2
, you could do the following:
thing1 > temp_file && thing2 < temp_file
which would
- run program named
thing1
- save the output into a file named
temp_file
- run program named
thing2
, pretending that the person at the keyboard typed the contents oftemp_file
as the input.
Однако, это неуклюже, поэтому они сделали трубы как более простой способ сделать это. thing1 | thing2
делает то же самое, что и thing1 > temp_file && thing2 < temp_file
РЕДАКТИРОВАТЬ, чтобы предоставить более подробную информацию к вопросу в комментарии:
Если >
пытался быть одновременно "передать в программу" и "записать в файл", это могло вызвать проблемы в обоих направлениях.
Первый пример: вы пытаетесь записать в файл. Уже существует файл с таким именем, который вы хотите перезаписать. Тем не менее, файл является исполняемым. Предположительно, он попытается выполнить этот файл, передав входные данные. Вам нужно будет сделать что-то наподобие записи результата в новое имя файла, а затем переименовать файл.
Второй пример: как отметил Флориан Диш, что если в другом месте системы есть другая команда с таким же именем (которая находится в пути выполнения). Если вы намеревались создать файл с таким именем в вашей текущей папке, вы застряли.
В-третьих: если вы неправильно введете команду, она не предупредит вас о том, что команда не существует. Прямо сейчас, если вы введете ls | gerp log.txt
это скажет вам bash: gerp: command not found
, Если >
подразумевал и то и другое, он просто создал бы новый файл для вас (а затем предупредил, что не знает, что делать с log.txt
).
Из Руководства по системному администрированию Unix и Linux:
Перенаправление
Оболочка интерпретирует символы <,> и >> как инструкции для перенаправления ввода или вывода команды в файл или из него.
трубы
Чтобы подключить STDOUT одной команды к STDIN другой, используйте | символ, широко известный как труба.
Итак, моя интерпретация такова: если это команда для команды, используйте канал. Если вы выводите в файл или из файла, используйте перенаправление.
Если смысл foo > bar
будет зависеть от того, есть ли команда с именем bar
это сделало бы использование перенаправления намного сложнее и более подвержено ошибкам: каждый раз, когда я хочу перенаправить в файл, мне сначала приходилось проверять, есть ли команда с именем, похожим на мой целевой файл.
Примечание. Ответ отражает мое собственное понимание этих механизмов на сегодняшний день, накопленное в ходе исследований и прочтения ответов коллегами на этом сайте и https://unix.stackexchange.com/, и будет обновляться с течением времени. Не стесняйтесь задавать вопросы или предлагать улучшения в комментариях. Я также предлагаю вам попытаться увидеть, как системные вызовы работают в оболочке с strace
команда. Также, пожалуйста, не пугайтесь понятия внутренних систем или системных вызовов - вам не нужно знать или использовать их, чтобы понять, как работает оболочка, но они определенно помогают пониманию.
TL; DR
|
каналы не связаны с записью на диске, поэтому не имеют номера инода дисковой файловой системы (но имеют инод в виртуальной файловой системе pipefs в пространстве ядра), но перенаправления часто включают файлы, которые имеют записи на диске и, следовательно, имеют соответствующие инод.- трубы не
lseek()
'так что команды не могут прочитать некоторые данные и затем вернуться назад, но когда вы перенаправляете с>
или же<
обычно это файл, которыйlseek()
способный объект, поэтому команды могут перемещаться, как им угодно. - перенаправления - это манипуляции с файловыми дескрипторами, которых может быть много; у каналов есть только два файловых дескриптора - один для левой команды и один для правой команды
- перенаправление на стандартные потоки и каналы буферизируется.
- трубы почти всегда включают в себя разветвление и, следовательно, участвуют пары процессов; перенаправления - не всегда, хотя в обоих случаях результирующие файловые дескрипторы наследуются подпроцессами.
- каналы всегда соединяют файловые дескрипторы (пара), перенаправления - либо используют путь, либо файловые дескрипторы.
- каналы - это метод межпроцессного взаимодействия, а перенаправления - это просто манипуляции с открытыми файлами или файловыми объектами.
- оба нанимают
dup2()
системные вызовы под капотом предоставляют копии файловых дескрипторов, где происходит фактический поток данных. - перенаправления могут применяться "глобально" с
exec
встроенная команда (см. это и это), так что если вы делаетеexec > output.txt
каждая команда будет писать вoutput.txt
С тех пор.|
каналы применяются только для текущей команды (что означает либо простую команду, либо подобную подоболочке)seq 5 | (head -n1; head -n2)
или составные команды. Когда перенаправление выполняется на файлы, такие вещи, как
echo "TEST" > file
а такжеecho "TEST" >> file
оба используютopen()
syscall для этого файла ( см. также) и получить дескриптор файла из него, чтобы передать егоdup2()
, трубы|
использовать толькоpipe()
а такжеdup2()
Системный вызов.Что касается выполняемых команд, каналы и перенаправление - это не более, чем файловые дескрипторы - файловые объекты, в которые они могут писать вслепую или манипулировать ими внутри (что может привести к неожиданным действиям;
apt
например, имеет тенденцию даже не писать в стандартный вывод, если он знает, что существует перенаправление).
Введение
Чтобы понять, как эти два механизма различаются, необходимо понять их основные свойства, историю этих двух механизмов и их корни в языке программирования Си. На самом деле, зная, что такое файловые дескрипторы и как dup2()
а также pipe()
Системные вызовы работа имеет важное значение, а также lseek()
, Оболочка предназначена для того, чтобы сделать эти механизмы абстрактными для пользователя, но копание глубже, чем абстракция, помогает понять истинную природу поведения оболочки.
Происхождение перенаправлений и труб
Согласно статье Денниса Ритча " Пророческие петроглифы", трубы возникли из внутренней записки 1964 года Малкольма Дугласа Макилроя, когда они работали над операционной системой Multics. Цитата:
Чтобы выразить мои самые сильные опасения в двух словах:
- У нас должно быть несколько способов подключения таких программ, как садовый шланг - вкручивайте другой сегмент, когда это становится необходимым, когда необходимо массировать данные другим способом. Это тоже путь ИО.
Очевидно, что в то время программы могли записывать на диск, однако это было неэффективно, если выходной объем был большим. Цитировать объяснение Брайана Кернигана в видео Unix Pipeline:
Во-первых, вам не нужно писать одну большую массивную программу - у вас есть более мелкие программы, которые могут уже выполнять часть работы... Другое - то, что возможно, что объем обрабатываемой вами информации не будет соответствовать вы сохранили его в файле... потому что помните, мы вернулись во времена, когда диски на этих вещах имели, если вам повезло, один или два мегабайта данных... Таким образом, конвейер никогда не должен был создавать весь вывод,
Таким образом, концептуальное различие очевидно: каналы - это механизм, заставляющий программы общаться друг с другом. Перенаправления - это способ записи в файл на базовом уровне. В обоих случаях оболочка облегчает эти две вещи, но под капотом происходит много всего.
Идем глубже: системные вызовы и внутренняя работа оболочки
Начнем с понятия файлового дескриптора. Файловые дескрипторы описывают в основном открытый файл (будь то файл на диске, или в памяти, или анонимный файл), который представлен целым числом. Два стандартных потока данных (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/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-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>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
Их практическое использование в сценариях оболочки может быть универсальным:
- сохранение вывода в переменную программы, которая пишет только в stderr
- поменять местами stderr и stdout
- отделение четных строк ввода от нечетных строк ввода
и многие другие.
Сантехника с 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? Да на самом деле вот что |&
делает в Баш. Стандарт POSIX требует поддержки командного языка оболочки df 2>&1 | grep 'foo'
синтаксис для этой цели, но bash
делает |&
также.
Важно отметить, что каналы всегда имеют дело с файловыми дескрипторами. Существует FIFO
или именованный канал, который имеет имя файла на диске и позволяет использовать его как файл, но ведет себя как канал. Но |
типы каналов - это то, что известно как анонимный канал - у них нет имени файла, потому что на самом деле это просто два объекта, соединенных вместе. Тот факт, что мы не имеем дело с файлами, также имеет важное значение: каналы не lseek()
"состояние. Файлы в памяти или на диске являются статичными - программы могут использовать lseek()
syscall, чтобы перейти к байту 120, затем вернуться к байту 10, затем переместиться полностью до конца. Трубы не являются статичными - они последовательные, и поэтому вы не можете перематывать данные, полученные с их помощью. lseek()
, Это то, что заставляет некоторые программы знать, читают ли они из файла или из конвейера, и, таким образом, они могут вносить необходимые корректировки для эффективной производительности; другими словами, prog
могу определить, могу ли я cat file.txt | prog
или же prog < input.txt
, Настоящий пример работы - это хвост.
Два других очень интересных свойства конвейеров - это то, что у них есть буфер, который в Linux составляет 4096 байт, и у них фактически есть файловая система, как определено в исходном коде Linux! Они не просто объект для передачи данных, они сами представляют собой структуру данных! Фактически, поскольку существует файловая система pipefs, которая управляет как каналами, так и FIFO, каналы имеют номер инода в соответствующей файловой системе:
# 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/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-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/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
В Linux каналы являются однонаправленными, как перенаправление. В некоторых Unix-подобных реализациях существуют двунаправленные каналы. Хотя, используя магию сценариев оболочки, вы можете создавать двунаправленные каналы и в Linux.
Смотрите также:
- Как сделать вывод любой команды оболочки небуферизованным?
- Пример Википедии о том, как создается конвейер в C с использованием
pipe()
системный вызов иdup2()
, - почему трубы используются вместо перенаправления ввода
- В чем различия между &> и 2>&1
- Перенаправления, такие как
<<
,<<<
реализованы как анонимные (несвязанные) временные файлы вbash
а такжеksh
, пока< <()
использует анонимные каналы;/bin/dash
использует трубы для<<
, См. В чем разница между <<, <<< и <<в bash?
Между двумя операторами существует существенная разница:
ls > log.txt
-> Эта команда отправляет вывод в файл log.txt.ls | grep file.txt
-> Эта команда отправляет вывод команды ls в grep с помощью pipe (|
), а команда grep ищет файл file.txt во входных данных, предоставленных ей предыдущей командой.
Если бы вам пришлось выполнить ту же задачу, используя первый сценарий, то это было бы:
ls > log.txt; grep 'file.txt' log.txt
Так что труба (с |
) используется для отправки вывода другой команде, тогда как перенаправление (с >
) используется для перенаправления вывода в какой-либо файл.
Существует большая синтаксическая разница между ними:
- Перенаправление - это аргумент программы
- Труба разделяет две команды
Вы можете думать о перенаправлениях так: cat [<infile] [>outfile]
, Это означает, что порядок не имеет значения: cat <infile >outfile
такой же как cat >outfile <infile
, Вы даже можете смешивать перенаправления с другими аргументами: cat >outfile <infile -b
а также cat <infile -b >outfile
оба отлично в порядке. Также вы можете связать вместе несколько входов или выходов (входы будут считываться последовательно, а весь вывод записывается в каждый выходной файл): cat >outfile1 >outfile2 <infile1 <infile2
, Целью или источником перенаправления может быть либо имя файла, либо имя потока (например, &1, по крайней мере, в bash).
Но каналы полностью отделяют одну команду от другой, вы не можете смешивать их с аргументами:
[command1] | [command2]
Канал принимает все данные, записанные в стандартный вывод команды 1, и отправляет их на стандартный ввод команды 2.
Вы также можете объединить трубопровод и перенаправление. Например:
cat <infile >outfile | cat <infile2 >outfile2
Первый cat
будет читать строки из infile, затем одновременно записывать каждую строку в outfile и отправлять ее на второй cat
,
Во-вторых cat
стандартный ввод сначала читает из канала (содержимое infile), затем читает из infile2, записывая каждую строку в outfile2. После запуска outfile будет копией infile, а outfile2 будет содержать infile, за которым следует infile2.
Наконец, вы действительно делаете что-то действительно похожее на ваш пример, используя перенаправление "here string" (только семейство bash) и обратные ссылки:
grep blah <<<`ls`
даст тот же результат, что и
ls | grep blah
Но я думаю, что версия перенаправления сначала прочитает все выходные данные ls в буфер (в памяти), а затем передаст этот буфер в grep по одной строке за раз, тогда как конвейерная версия будет принимать каждую строку из ls по мере ее появления, и передайте эту строку в grep.
Чтобы добавить к другим ответам, есть небольшая семантическая разница - например, каналы закрываются легче, чем перенаправления:
seq 5 | (head -n1; head -n1) # just 1
seq 5 > tmp5; (head -n1; head -n1) < tmp5 # 1 and 2
seq 5 | (read LINE; echo $LINE; head -n1) # 1 and 2
В первом примере, когда первый вызов head
заканчивается, он закрывает трубу, и seq
завершается, поэтому нет ввода для второго head
,
Во втором примере, голова занимает первую строку, но когда она закрывает свою собственную stdin
файл остается открытым для следующего вызова.
Третий пример показывает, что если мы используем read
чтобы избежать закрытия канала, он все еще доступен внутри подпроцесса.
Таким образом, "поток" - это то, через что мы шунтируем данные (stdin и т. Д.), И он одинаков в обоих случаях, но канал соединяет потоки из двух процессов, где перенаправление соединяет потоки между процессом и файлом, так что вы Можно увидеть источник как сходства, так и различия.
PS Если вы так же любопытны и / или удивлены этими примерами, как и я, вы можете углубиться в это, используя trap
чтобы увидеть, как разрешаются процессы, например:
(trap 'echo seq EXITed >&2' EXIT; seq 5) | (trap 'echo all done' EXIT; (trap 'echo first head exited' EXIT; head -n1)
echo '.'
(trap 'echo second head exited' EXIT; head -n1))
Иногда первый процесс закрывается до 1
печатается, иногда после.
Мне также было интересно использовать exec <&-
закрыть поток от перенаправления, чтобы приблизить поведение канала (хотя и с ошибкой):
seq 5 > tmp5
(trap 'echo all done' EXIT
(trap 'echo first head exited' EXIT; head -n1)
echo '.'
exec <&-
(trap 'echo second head exited' EXIT; head -n1)) < tmp5`
Я столкнулся с проблемой в C сегодня. По существу, у Pipe есть иная семантика для перенаправлений, даже когда они отправляются в stdin
, На самом деле, я думаю, учитывая различия, трубы должны идти куда-то еще, кроме stdin
, чтобы stdin
и давайте назовем это stdpipe
(чтобы сделать произвольный дифференциал) могут быть обработаны по-разному.
Учти это. При передаче одной программы в другую fstat
кажется, возвращает ноль как st_size
несмотря на ls -lha /proc/{PID}/fd
показывая, что есть файл. При перенаправлении файла это не так (по крайней мере, в Debian wheezy
, stretch
а также jessie
ваниль и убунту 14.04
, 16.04
ваниль.
если ты cat /proc/{PID}/fd/0
с перенаправлением вы сможете повторять, чтобы читать столько раз, сколько хотите. Если вы сделаете это с каналом, вы заметите, что при втором последовательном запуске задачи вы не получите тот же результат.
То, что находится справа от и
<
ожидается, что это имя устройства. Поэтому оболочка воспримет это буквально и не будет пытаться дальше оценивать (за исключением того, что вы используете , которая сама по себе является именованным каналом). Например,cat < a.txt
эквивалентноcat a.txt
.Вещи с обеих сторон
stuff1 | stuff2
будет далее оцениваться оболочкой. Концептуально выводstuff1
будет записано в устройстве, и имя этого устройства будет указано в качестве последнего аргумента. Еслиstuff2
не ожидает имя устройства в качестве последнего аргумента, вы можете получить ошибку.
Очевидно, что трубопровод|
является более общим, чем перенаправление>
.
С точки зрения реализации, конвейерная передача использует более эффективный способ (буферизация) для передачи информации и позволяет передаваемым по конвейеру командам выполняться одновременно.
Две другие операции, которые стоит включить в сравнение:
- подстановку процессаЗамена процесса
- Замена команды
Если вам нужно передать стандартный вывод нескольких команд, вам нужно использовать подстановку процесса.