В чем разница между "Перенаправлением" и "каналом"?

Этот вопрос может показаться немного глупым, но я действительно не вижу разницы между перенаправлением и каналами.

Перенаправление используется для перенаправления stdout/stdin/stderr, например ls > log.txt.

Каналы используются для передачи выходных данных команды в качестве входных данных для другой команды, например ls | grep file.txt.

Но почему существуют два оператора для одного и того же?

Почему бы просто не написать ls > grep чтобы передать выходные данные, разве это не просто своего рода перенаправление? Чего мне не хватает?

Канал используется для передачи выходных данных в другой программа или утилита.

Перенаправление используется для передачи выходных данных либо в файл или поток.

Пример: thing1 > thing2 против thing1 | thing2

thing1 > thing2

  1. Ваша оболочка запустит программу с именем thing1
  2. Все, что thing1 выходные данные будут помещены в файл с именем thing2. (Примечание - если thing2 существует, он будет перезаписан)

Если вы хотите передать выходные данные из программы thing1 к программе под названием thing2, вы могли бы сделать следующее:

thing1 > temp_file && thing2 < temp_file

который бы

  1. запустите программу с именем thing1
  2. сохраните выходные данные в файл с именем temp_file
  3. запустите программу с именем thing2, делая вид , что человек за клавиатурой набрал содержимое temp_file в качестве входных данных.

Однако это неуклюже, поэтому они сделали трубы как более простой способ сделать это. 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 это сделало бы использование перенаправления намного сложнее и более подверженным ошибкам: каждый раз, когда я хочу перенаправить на файл, я сначала должен был проверить, есть ли команда с именем, подобным моему целевому файлу.

Между этими двумя операторами существует существенная разница:

  1. ls > log.txt> -- Эта команда отправляет выходные данные в log.txt файл.

  2. ls | grep file.txt> -- Эта команда отправляет выходные данные ls в команду grep с помощью pipe (|), и команда grep выполняет поиск file.txt в поле ввода, предоставленном ему предыдущей командой.

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

ls > log.txt; grep 'file.txt' log.txt

Таким образом, труба (с |) используется для отправки выходных данных другой команде, тогда как перенаправление (с >) используется для перенаправления выходных данных в какой-либо файл.

Примечание: Ответ отражает мое собственное понимание этих механизмов на сегодняшний день, накопленное в ходе исследований и чтения ответов коллег на этом сайте и 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. Цитата:

Чтобы выразить мои самые сильные опасения в двух словах:

  1. У нас должны быть какие-то способы подключения программ, таких как садовый шланг - вкручивать другой сегмент, когда это становится необходимым для обработки данных другим способом. Это также и способ ввода-вывода.

Очевидно, что в то время программы были способны записывать данные на диск, однако это было неэффективно, если выходные данные были большими. Чтобы процитировать объяснение Брайана Кернигана в Конвейер 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 также.

смотрите также:

Между ними существует большая синтаксическая разница:

  1. Перенаправление - это аргумент для программы
  2. Канал разделяет две команды

Вы можете думать о перенаправлениях следующим образом: 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]

Канал принимает все, что записано в стандартный вывод из command1, и отправляет его на стандартный ввод command2.

Вы также можете комбинировать конвейер и перенаправление. Например:

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 1seq 5 > tmp5; (head -n1; head -n1) < tmp5   # 1 and 2seq 5 | (read LINE; echo $LINE; head -n1)   # 1 and 2

В первом примере, когда первый вызов к head заканчивается, он закрывает трубу, и seq завершается, поэтому для второго ввода нет доступных входных данных head.

Во втором примере head потребляет первую строку, но когда она закрывается, ее собственная stdin труба, файл остается открытым для использования при следующем вызове.

Третий пример показывает, что если мы используем read чтобы избежать закрытия канала, он по-прежнему доступен в рамках подпроцесса.

Таким образом, "поток" - это то, через что мы передаем данные (stdin и т.д.), И он одинаков в обоих случаях, но канал соединяет потоки из двух процессов, где перенаправление соединяет потоки между процессом и файлом, так что вы можете видеть источник как сходства, так и различий.

P.S. Если вам так же интересно и / или удивлены этими примерами, как и мне, вы можете углубиться в них, используя 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. По сути, каналы также имеют различную семантику для перенаправлений, даже когда они отправляются на stdin. На самом деле я думаю, что, учитывая различия, трубы должны идти куда-то еще, кроме stdin, так что stdin и давайте назовем это stdpipe (для создания произвольного дифференциала) можно обрабатывать по-разному.

Подумайте об этом. При передаче выходных данных одной программы в другую fstat кажется, возвращает ноль в качестве st_size несмотря на ls -lha /proc/{PID}/fd показывая, что есть файл. При перенаправлении файла это не так (по крайней мере, в debian wheezy, stretch и jessie ваниль и ubuntu 14.04, 16.04 ваниль.

Если ты cat /proc/{PID}/fd/0 с помощью перенаправления вы сможете повторять чтение столько раз, сколько захотите. Если вы сделаете это с помощью канала, вы заметите, что при втором последовательном запуске задачи вы не получите тот же результат.