Этот вопрос может показаться немного глупым, но я не могу видеть разницу между перенаправлением и трубами.
Перенаправление используется для перенаправления stdout / stdin / stderr, например. ls > log.txt
,
Трубы используются для вывода вывода команды в качестве входных данных для другой команды, например. ls | grep file.txt
,
Но почему есть два оператора для одного и того же?
Почему бы просто не написать ls > grep
чтобы передать выход, разве это не просто переадресация? Что мне не хватает?
Труба используется для передачи вывода на другой программа или полезность,
Переадресация используется для передачи вывода на файл или поток,
Пример: thing1 > thing2
против thing1 | thing2
thing1 > thing2
- Ваша оболочка будет запускать программу с именем
thing1
- Все что
thing1
выходы будут помещены в файл, называемый thing2
, (Примечание - если thing2
существует, он будет перезаписан)
Если вы хотите передать результат из программы thing1
к программе, называемой thing2
, вы можете сделать следующее:
thing1 > temp_file && thing2 < temp_file
которые бы
- запустить программу с именем
thing1
- сохраните вывод в файл с именем
temp_file
- запустить программу с именем
thing2
, притворяясь, что человек на клавиатуре напечатал содержимое temp_file
как вход.
Однако это неудобно, поэтому они сделали трубы более простым способом сделать это. thing1 | thing2
делает то же самое, что и thing1 > temp_file && thing2 < temp_file
EDIT, чтобы предоставить более подробную информацию на вопрос в комментарии:
Если >
пытался «перейти к программе» и «написать в файл», это может вызвать проблемы в обоих направлениях.
Первый пример: Вы пытаетесь записать файл. Там уже существует файл с таким именем, которое вы хотите перезаписать. Однако файл является исполняемым. Предположительно, он попытается выполнить этот файл, передав вход. Вам нужно будет сделать что-то вроде записи вывода в новое имя файла, а затем переименуйте файл.
Второй пример: Как отметил Флориан Дёш, если есть другая команда в другом месте системы с тем же именем (то есть в пути выполнения). Если вы планируете создать файл с этим именем в текущей папке, вы застряли.
В-третьих: если вы неправильно написали команду, это не предупредит вас о том, что команда не существует. Прямо сейчас, если вы набираете ls | gerp log.txt
он скажет вам bash: gerp: command not found
, Если >
означало и то и другое, оно просто создало бы для вас новый файл (тогда предупреждайте, что он не знает, что делать с 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.
Наконец, вы действительно делаете что-то действительно похожее на ваш пример, используя перенаправление «здесь строка» (только семейство bash) и обратные ссылки:
grep blah <<<`ls`
даст тот же результат, что и
ls | grep blah
Но я думаю, что версия перенаправления сначала будет считывать весь вывод ls в буфер (в памяти), а затем кормить этот буфер для grep по одной строке за раз, тогда как версия с каналами будет принимать каждую строку от ls по мере ее появления, и передать эту строку в grep.
Примечание. Ответ отражает мое собственное понимание этих механизмов в актуальном состоянии, накопленное за исследование и чтение ответов со стороны сверстников на этом сайте и unix.stackexchange.com, и со временем будет обновляться. Не стесняйтесь задавать вопросы или предлагать улучшения в комментариях. Я также предлагаю вам попробовать посмотреть, как syscalls работают в оболочке с strace
команда. Также, пожалуйста, не пугайтесь понятия внутренних органов или системных вызовов - вам не обязательно знать или использовать их, чтобы понять, как оболочка что-то делает, но они определенно помогают понять.
TL; DR
|
трубы не связаны с записью на диск, поэтому не имеют индекс количество дисковой файловой системы (но есть inode в pipefs виртуальная файловая система в пространстве ядра), но перенаправления часто связаны с файлами, которые имеют записи на диске и поэтому имеют соответствующий индексный дескриптор.
- трубы не
lseek()
', поэтому команды не могут читать некоторые данные, а затем перематывать назад, но когда вы перенаправляете >
или <
обычно это файл, который lseek()
способный объект, поэтому команды могут перемещаться, как им удобно.
- перенаправления - это манипуляции с файловыми дескрипторами, которых может быть много; трубы имеют только два дескриптора файла - один для левой команды и один для правой команды
- перенаправление на стандартные потоки и трубы буферизуются.
- трубы почти всегда связаны с форкированием, перенаправлением - не всегда
- трубы всегда имеют дело с файловыми дескрипторами, перенаправлением - либо используют фактические файлы с именем файла на диске, либо файловые дескрипторы.
- трубы являются методом межпроцессной связи, а перенаправления - это просто манипуляции с открытыми файлами или файловыми объектами
- оба используют
dup2()
syscalls под капотом для предоставления копий файловых дескрипторов, где происходит фактический поток данных.
- перенаправления могут применяться «глобально» с помощью
exec
встроенная команда (см. это а также это ), так что если вы это сделаете exec > output.txt
каждая команда будет писать output.txt
С тех пор. |
трубы применяются только для текущей команды (что означает либо простую команду, либо подоболочку, подобную seq 5 | (head -n1; head -n2)
или составные команды.
- Когда перенаправление выполняется в файлах,
echo "TEST" > file
а также echo "TEST" >> file
оба используют open()
syscall в этом файле (смотрите также) и получить от него файловый дескриптор, чтобы передать его dup2()
, трубы |
использовать только pipe()
а также dup2()
Системный вызов.
Введение
Чтобы понять, как эти два механизма отличаются друг от друга, необходимо понять их основные свойства, историю, стоящую за ними, и их корни в языке программирования C. Фактически, зная, какие файловые дескрипторы есть, и как dup2()
а также pipe()
работа системных вызовов имеет важное значение, а также lseek()
, Shell предназначена для того, чтобы сделать эти механизмы абстрактными для пользователя, но копание глубже абстракции помогает понять истинную природу поведения оболочки.
Происхождение перенаправления и трубопроводов
Согласно статье Денниса Ритче Пророческие петроглифы, трубы возникли из Внутренняя памятка 1964 года от Малькольм Дуглас Макилрой, в то время, когда они работали над Многофункциональная операционная система, Цитата:
Чтобы выразить свою сильную озабоченность в двух словах:
- У нас должны быть некоторые способы подключения программ, таких как садовый шланг, - винт в другом сегменте, когда он становится, когда становится необходимым массировать данные по-другому. Это также способ ввода-вывода.
Очевидно, что в то время, когда программы были способны записывать на диск, однако это было неэффективно, если выход был большим. Процитировать объяснение Брайана Кернигана в Трубопровод Unix видео :
Во-первых, вам не нужно писать одну большую массовую программу - у вас есть существующие более мелкие программы, которые могут уже выполнять части задания ... Другое дело, что количество данных, которые вы обрабатываете, не поместится, если вы сохранили его в файле ... потому что помните, что мы вернулись в те дни, когда диски на этих вещах имели, если вам повезло, мегабайт или две данные ... Поэтому конвейеру никогда не приходилось создавать экземпляр всей продукции ,
Таким образом, концептуальная разница очевидна: трубы - это механизм, с помощью которого программы разговаривают друг с другом. Перенаправления - это способ записи в файл на базовом уровне. В обоих случаях оболочка делает эти две вещи легкими, но под капотом происходит много всего.
Переход глубже: системные вызовы и внутренние действия оболочки
Начнем с понятия дескриптор файла, Файловые дескрипторы описывают в основном открытый файл (будь то файл на диске или в памяти или анонимный файл), который представлен целым числом. Два стандартные потоки данных (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
Практическое использование этих сценариев оболочки может быть универсальным:
и многие другие.
Сантехника с pipe()
а также dup2()
Итак, как создаются трубы? С помощью pipe()
Системный вызов, который будет принимать в качестве входного массива (aka list), называемый 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()
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 также.
Смотрите также:
Чтобы добавить к другим ответам, есть и тонкая смысловая разница - например. трубы закрываются более легко, чем перенаправления:
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 и т. Д.) И одинаково в обоих случаях, но канал соединяет потоки из двух процессов, где перенаправление соединяет потоки между процессом и файлом, поэтому вы может видеть источник как сходства, так и различий.
Постскриптум Если вы так же любопытны и / или удивлены этими примерами, как и я, вы можете продолжить копать дальше 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`