Если вы уже освоились с основами терминала, возможно, вы уже готовы к тому, чтобы комбинировать изученные команды. Иногда выполнения команд оболочки по одной вполне достаточно для решения некоей задачи, но в некоторых случаях вводить команду за командой слишком утомительно и нерационально. В подобной ситуации нам пригодятся некоторые особые символы, вроде угловых скобок.



Для оболочки, интерпретатора команд Linux, эти дополнительные символы — не пустая трата места на экране. Они — мощные команды, которые могут связывать воедино различные фрагменты информации, разделять то, что было до этого цельным, и делать ещё много всего. Одна из самых простых, и, в то же время, мощных и широко используемых возможностей оболочки — это перенаправление стандартных потоков ввода/вывода.

Три стандартных потока ввода/вывода


Для того, чтобы понять то, о чём мы будем тут говорить, важно знать, откуда берутся данные, которые можно перенаправлять, и куда они идут. В Linux существует три стандартных потока ввода/вывода данных.

Первый — это стандартный поток ввода (standard input). В системе это — поток №0 (так как в компьютерах счёт обычно начинается с нуля). Номера потоков ещё называют дескрипторами. Этот поток представляет собой некую информацию, передаваемую в терминал, в частности — инструкции, переданные в оболочку для выполнения. Обычно данные в этот поток попадают в ходе ввода их пользователем с клавиатуры.

Второй поток — это стандартный поток вывода (standard output), ему присвоен номер 1. Это поток данных, которые оболочка выводит после выполнения каких-то действий. Обычно эти данные попадают в то же окно терминала, где была введена команда, вызвавшая их появление.

И, наконец, третий поток — это стандартный поток ошибок (standard error), он имеет дескриптор 2. Этот поток похож на стандартный поток вывода, так как обычно то, что в него попадает, оказывается на экране терминала. Однако, он, по своей сути, отличается от стандартного вывода, как результат, этими потоками, при желании, можно управлять раздельно. Это полезно, например, в следующей ситуации. Есть команда, которая обрабатывает большой объём данных, выполняя сложную и подверженную ошибкам операцию. Нужно, чтобы полезные данные, которые генерирует эта команда, не смешивались с сообщениями об ошибках. Реализуется это благодаря раздельному перенаправлению потоков вывода и ошибок.

Как вы, вероятно, уже догадались, перенаправление ввода/вывода означает работу с вышеописанными потоками и перенаправление данных туда, куда нужно программисту. Делается это с использованием символов > и < в различных комбинациях, применение которых зависит от того, куда, в итоге, должны попасть перенаправляемые данные.

Перенаправление стандартного потока вывода


Предположим, вы хотите создать файл, в который будут записаны текущие дата и время. Дело упрощает то, что имеется команда, удачно названная date, которая возвращает то, что нам нужно. Обычно команды выводят данные в стандартный поток вывода. Для того, чтобы эти данные оказались в файле, нужно добавить символ > после команды, перед именем целевого файла. До и после > надо поставить пробел.

При использовании перенаправления любой файл, указанный после > будет перезаписан. Если в файле нет ничего ценного и его содержимое можно потерять, в нашей конструкции допустимо использовать уже существующий файл. Обычно же лучше использовать в подобном случае имя файла, которого пока не существует. Этот файл будет создан после выполнения команды. Назовём его date.txt. Расширение файла после точки обычно особой роли не играет, но расширения помогают поддерживать порядок. Итак, вот наша команда:

$ date > date.txt

Нельзя сказать, что сама по себе эта команда невероятно полезна, однако, основываясь на ней, мы уже можем сделать что-то более интересное. Скажем, вы хотите узнать, как меняются маршруты вашего трафика, идущего через интернет к некоей конечной точке, ежедневно записывая соответствующие данные. В решении этой задачи поможет команда traceroute, которая сообщает подробности о маршруте трафика между нашим компьютером и конечной точкой, задаваемой при вызове команды в виде URL. Данные включают в себя сведения обо всех маршрутизаторах, через которые проходит трафик.

Так как файл с датой у нас уже есть, будет вполне оправдано просто присоединить к этому файлу данные, полученные от traceroute. Для того, чтобы это сделать, надо использовать два символа >, поставленные один за другим. В результате новая команда, перенаправляющая вывод в файл, но не перезаписывающая его, а добавляющая новые данные после старых, будет выглядеть так:

$ traceroute google.com >> date.txt

Теперь нам осталось лишь изменить имя файла на что-нибудь более осмысленное, используя команду mv, которой, в качестве первого аргумента, передаётся исходное имя файла, а в качестве второго — новое:

$ mv date.txt trace1.txt

Перенаправление стандартного потока ввода


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

Предположим, имеется два файла: list1.txt и list2.txt, каждый из которых содержит неотсортированный список строк. В каждом из списков имеются уникальные для него элементы, но некоторые из элементов список совпадают. Мы можем найти строки, которые имеются и в первом, и во втором списках, применив команду comm, но прежде чем её использовать, списки надо отсортировать.

Существует команда sort, которая возвращает отсортированный список в терминал, не сохраняя отсортированные данные в файл, из которого они были взяты. Можно отправить отсортированную версию каждого списка в новый файл, используя команду >, а затем воспользоваться командой comm. Однако, такой подход потребует как минимум двух команд, хотя то же самое можно сделать в одной строке, не создавая при этом ненужных файлов.

Итак, мы можем воспользоваться командой < для перенаправления отсортированной версии каждого файла команде comm. Вот что у нас получилось:

$ comm <(sort list1.txt) <(sort list2.txt)

Круглые скобки тут имеют тот же смысл, что и в математике. Оболочка сначала обрабатывает команды в скобках, а затем всё остальное. В нашем примере сначала производится сортировка строк из файлов, а потом то, что получилось, передаётся команде comm, которая затем выводит результат сравнения списков.

Перенаправление стандартного потока ошибок


И, наконец, поговорим о перенаправлении стандартного потока ошибок. Это может понадобиться, например, для создания лог-файлов с ошибками или объединения в одном файле сообщений об ошибках и возвращённых некоей командой данных.

Например, что если надо провести поиск во всей системе сведений о беспроводных интерфейсах, которые доступны пользователям, у которых нет прав суперпользователя? Для того, чтобы это сделать, можно воспользоваться мощной командой find.

Обычно, когда обычный пользователь запускает команду find по всей системе, она выводит в терминал и полезные данные и ошибки. При этом, последних обычно больше, чем первых, что усложняет нахождение в выводе команды того, что нужно. Решить эту проблему довольно просто: достаточно перенаправить стандартный поток ошибок в файл, используя команду 2> (напомним, 2 — это дескриптор стандартного потока ошибок). В результате на экран попадёт только то, что команда отправляет в стандартный вывод:

$ find / -name wireless 2> denied.txt

Как быть, если нужно сохранить результаты работы команды в отдельный файл, не смешивая эти данные со сведениями об ошибках? Так как потоки можно перенаправлять независимо друг от друга, в конец нашей конструкции можно добавить команду перенаправления стандартного потока вывода в файл:

$ find / -name wireless 2> denied.txt > found.txt

Обратите внимание на то, что первая угловая скобка идёт с номером — 2>, а вторая без него. Это так из-за того, что стандартный вывод имеет дескриптор 1, и команда > подразумевает перенаправление стандартного вывода, если номер дескриптора не указан.

И, наконец, если нужно, чтобы всё, что выведет команда, попало в один файл, можно перенаправить оба потока в одно и то же место, воспользовавшись командой &>:

$ find / -name wireless &> results.txt

Итоги


Тут мы разобрали лишь основы механизма перенаправления потоков в интерпретаторе командной строки Linux, однако даже то немногое, что вы сегодня узнали, даёт вам практически неограниченные возможности. И, кстати, как и всё остальное, что касается работы в терминале, освоение перенаправления потоков требует практики. Поэтому рекомендуем вам приступить к собственным экспериментам с > и <.

Уважаемые читатели! Знаете ли вы интересные примеры использования перенаправления потоков в Linux, которые помогут новичкам лучше освоиться с этим приёмом работы в терминале?

Комментарии (24)


  1. Akon32
    28.08.2017 13:47
    -1

    Linux: перенаправление

    А почему такое краткое, ни о чём не говорящее, название?


    И, если не ошибаюсь, всё вышеизложенное справедливо не только для оболочек Linux, BSD и т.п., но даже и для командной строки Windows.


    1. Self_Perfection
      28.08.2017 14:25
      +1

      Ошибаетесь. В статье приведены примеры, использующие конструкции, специфичные для bash:


      comm <(sort list1.txt) <(sort list2.txt)
      find / -name wireless &> results.txt

      Кстати, ru_vds, стоит обратить внимание возле этих примеров на башеспецифичность. А то бывает люди напарываются, когда пишут в shebang #!/bin/sh, проверяют на системе, в которой /bin/sh -> bash, а потом скрипт внезапно не работает на другой системе, где /bin/sh не bash.


      1. asergrisa
        28.08.2017 14:36

        Эм… А разве bash специфичен для Linux? Он ведь работает в уйме других операционных систем.


        1. siziyman
          28.08.2017 15:10

          Ваш ответ/замечание не противоречит тому, что написал Self_Perfection: баш-специфичность и платформозависимость/специфичность — понятия абсолютно ортогональные, можно сидеть как под линуксом в csh, так и под макосью в bash, как примитивный пример.


        1. saboteur_kiev
          30.08.2017 18:28

          «для командной строки Windows.»
          «специфичные для bash»

          В первом случае подразумевается не платформа windows, а командная строка windows (cmd.exe).


      1. hdfan2
        28.08.2017 15:09

        Для Windows перенаправление stderr в stdout имеет вид:

        command >out.txt 2>&1


    1. hdfan2
      28.08.2017 15:05

      Да, зря выкинули каламбур из названия исходной статьи. Тем более, что перевести его очень просто (что-то типа «Двигаемся (или „двигаясь“, или „идём“) в правильном (пере)направлении»).


    1. saboteur_kiev
      28.08.2017 17:42

      В варианте > и < — да, но в *nix перенаправлений гораздо больше и работают они лучше, чем в статье.
      Даже если поставить какой-нить git-bash и выполнять шелл-скрипты в нем, эта виртуализация настолько замедляет процесс, что простой скрипт используемый парочку конвейеров, и в линуксе выполняющийся за пару секунд, в винде может выполняться десятки секунд.

      И да, — в *nix есть возможность обращаться к различным устройствам и процессам через имя файла на виртуальных файловых системах, типа /dev/null, /dev/random/
      и например вывести в чужой STDOUT например так:
      echo hello >> /proc/xxx/fd/1


  1. kt97679
    28.08.2017 17:09
    +2

    comm <(sort list1.txt) <(sort list2.txt)
    
    это не перенаправление, а process substitution: tldp.org/LDP/abs/html/process-sub.html


    1. ZyXI
      28.08.2017 19:06

      Там и дальше неверно:


      Круглые скобки тут имеют тот же смысл, что и в математике. Оболочка сначала обрабатывает команды в скобках, а затем всё остальное. В нашем примере сначала производится сортировка строк из файлов, а потом то, что получилось, передаётся команде comm, которая затем выводит результат сравнения списков.

      никакого «оболочка сначала обрабатывает …» нет, в случае с process substitution она просто создаст подоболочку, соединит stdout (или stdin при >()) подоболочки с некоторым дескриптором основной оболочки, заменит <()/>() на «путь» к этому самому дескриптору и продолжит выполнение.


      Главное тут то, что выполняться все три команды из примера будут параллельно. Можно проверить так:


      1. time bash -c 'echo <(sleep 1) <(sleep 1)' завершится немедленно, выведя эти самые пути*. Если бы оболочка сначала обрабатывала что?либо в скобках, то time показал бы как минимум одну секунду.
      2. time bash -c 'cat <(sleep 1) <(sleep 1)' покажет одну секунду, а не две: команды обрабатываются параллельно.

      * У меня почему?то /dev/fd/63 /dev/fd/62: аналогичная команда zsh, во?первых, выведет пути по порядку, во?вторых, возьмёт дескрипторы поменьше и, в третьих, использует /proc/self/fd (хотя я не знаю, что менее вероятно: отсутствие /dev/fd или /proc/self/fd, так что это не принципиально): /proc/self/fd/12 /proc/self/fd/13. «Почему?то» относится в первую очередь к «выведет пути по порядку»: strace -e execve -f $shell -c 'echo <(sleep 1) <(sleep 2)' явно показывает, что zsh сначала пустит sleep 1, а bash сначала пустит sleep 2.


  1. saboteur_kiev
    28.08.2017 17:31
    +2

    Первый — это стандартный поток ввода (standard input). В системе это — поток №0
    Номера потоков ещё называют дескрипторами
    Второй поток — это стандартный поток вывода (standard output), ему присвоен номер 1.
    Это поток данных, которые оболочка выводит после выполнения каких-то действий.
    И, наконец, третий поток — это стандартный поток ошибок (standard error), он имеет дескриптор 2.


    Давайте все же грамотно формулизируем.

    1. Для каждой запускаемой CLI оболочки, по умолчанию открывается три дескриптора #0 — STDIN, #1 — STDOUT, #2 — STDERR, связанные с файлами-устройств, где STDOUT и STDERR изначально выводят на «экран»

    Потоки не называются дескрипторами, они ими являются. Если я в моем баш скрипте открою какой-либо файл или еще один поток, он будет #3, #4 и так далее.

    Именно поэтому использование "&>" небезопасно.
    Если нужно перенаправить stdout и stderr, обычно пишут ">file.log 2>&1"

    2. Можно также уточнить, что STDOUT и STDERR это не тот поток данных, которые «оболочка выводит после выполнения каких-либо действий.»

    Это программист, который написал свою программу/утилиту должен выводить информацию пользователю используя print(STDOUT, «text»), либо print(STDERR,«error text»). Или не писать никуда, тогда программа/команда ничего и не выведет на экран. Или писать сразу в лог-файл (можно также помнить, что многие команды и скрипты могут запускать без оболочки, например через cron, где вместо stdout будет локальная почта. Или через rsh, где stdout вообще не будет и будет ошибка.

    3. Также можно было все-таки указать все виды перенаправлений, включая
    "<<", "<<<" и конечно главную фичу *никс консоли "|".


    1. evvlasov
      29.08.2017 18:00

      Подскажите, пожалуйста, как нагуглить про эту главную фичу? Я видел её в скриптах, но ни черта не понял. Быстрый поиск не помог, я даже не в курсе, как этот символ в никсах называется, я в основном с вендой работаю.


      1. stasal
        29.08.2017 19:08

        (anonymous) pipe / pipeline / конвейер


      1. saboteur_kiev
        30.08.2017 12:18

        Это называется «pipe», или «конвейер», когда вы перенаправляете вывод на ввод между программами. И так можно много раз. Например
        cat file.txt | sort -r | cut -d ":" -f 1,3 | grep «line1» | wc -l

        * выводим file.txt и перенаправляем его в
        * сортировку наоборот, отсортированный текст перенаправляем в
        * команду cut, которая берет 1 и 3 столбец и выводит дальше в
        * grep, который отсекает все, кроме строк где есть «line1» и перенаправляет в
        * wc который просто считает количество строк и полученное число перенаправляет в
        * stdout, который уже без дальнейших перенаправлений выводится пользователю.

        А вот так можно это число записать в числовую переменную:
        typedef -i LINE1_LINES=`cat file.txt | sort -r | cut -d ":" -f 1,3 | grep «line1» | wc -l`

        И работать с ним в циклах или условиях.


        1. Apcel
          30.08.2017 16:52

          cat здесь не нужен, sort сам умеет считывать данные из файлов.


          1. saboteur_kiev
            30.08.2017 18:26

            Это искусственный пример множественных перенаправлений, а не рабочая команда.

            Если отталкиваться от результата, тут sort вообще не делает ничего полезного, и если все оптимизировать, то достаточно только grep и wc


    1. ZyXI
      29.08.2017 23:16

      Именно поэтому использование "&>" небезопасно.
      Если нужно перенаправить stdout и stderr, обычно пишут ">file.log 2>&1"

      Чем оно «небезопасно»? Сравните ls -lAh /proc/self/fd в случаях &>/tmp/1.la и >/tmp/2.la 2>&1:


      итого 0
      lrwx------ 1 zyx 64 авг 29 23:04 0 -> /dev/pts/6
      l-wx------ 1 zyx 64 авг 29 23:04 1 -> /tmp/1.la
      l-wx------ 1 zyx 64 авг 29 23:04 2 -> /tmp/1.la
      lr-x------ 1 zyx 64 авг 29 23:04 3 -> /proc/6017/fd/
      l-wx------ 1 zyx 64 авг 29 23:04 4 -> pipe:[21043]
      итого 0
      lrwx------ 1 zyx 64 авг 29 23:04 0 -> /dev/pts/6
      l-wx------ 1 zyx 64 авг 29 23:04 1 -> /tmp/2.la
      l-wx------ 1 zyx 64 авг 29 23:04 2 -> /tmp/2.la
      lr-x------ 1 zyx 64 авг 29 23:04 3 -> /proc/5833/fd/
      l-wx------ 1 zyx 64 авг 29 23:04 4 -> pipe:[21043]

      Абсолютно никакой разницы. Про буферизацию точно не скажу, но ей вроде libc занимается и куда ведут дескрипторы ей глобально всё равно: для stdout буферизацию она всё равно сделает, а для stderr нет, пока не попросите обратного, тогда как с write() буферизации не будет вообще, какой бы дескриптор вы не взяли. Какая ещё разница возможна между данными конструкциями я не представляю.


      &> писать проще, быстрее и понятнее (я длительное время не понимал, почему 2>&1 нужно писать после: нужно просто дескрипторы воспринимать как ссылки, а перенаправление 2>&1 как присваивание по ссылке 2 значения по ссылке 1). Есть только одна проблема: это bash’изм, с #!/bin/sh такое писать нельзя.


      1. saboteur_kiev
        30.08.2017 12:19

        Если вы пишете скрипт, в котором вы открыли еще какой-то файл (еще один дескриптор), то &> будет перенаправлять не только STDOUT и STDERR, но еще и этот дескриптор.
        Я не говорю про «2>&1», я говорю именно про "&>"


        1. ZyXI
          31.08.2017 18:44
          +1

          Как?то такого не замечаю:


          % bash -c 'exec 5>/tmp/test3fd ; ls -lAh /proc/self/fd &>/tmp/test3fdredir'
          % cat /tmp/test3fdredir
          итого 0
          lrwx------ 1 zyx zyx 64 авг 31 18:41 0 -> /dev/pts/6
          l-wx------ 1 zyx zyx 64 авг 31 18:41 1 -> /tmp/test3fdredir
          l-wx------ 1 zyx zyx 64 авг 31 18:41 2 -> /tmp/test3fdredir
          lr-x------ 1 zyx zyx 64 авг 31 18:41 3 -> /proc/8502/fd
          l-wx------ 1 zyx zyx 64 авг 31 18:41 4 -> pipe:[21043]
          l-wx------ 1 zyx zyx 64 авг 31 18:41 5 -> /tmp/test3fd
          % cat /tmp/test3fd
          % # Здесь пусто

          Не могли бы вы написать пример скрипта, в котором демонстрируется описываемая проблема?


          1. saboteur_kiev
            01.09.2017 14:37

            Спасибо за пинок. 30 минут сделали меня мудрее. Действительно нет никакой проблемы с дескрипторами.


  1. saipr
    29.08.2017 15:05

    Для полного счастья не хватает команды tee, которая дублирует стандартный вывод в файл:


    $date |tee tektate.txt
    Вт авг 29 15:00:00 MSK 2017
    $

    Сравните:


    $cat tektate.txt
    Вт авг 29 15:00:00 MSK 2017
    $


    1. Lennonenko
      30.08.2017 02:15

      не совсем так — tee позволяет отобразить вывод в файл И в терминал
      например, когда во время работы скрипта пользователь должен видеть вывод, но в то же время этот вывод нужен для дальнейшей работы скрипта


    1. saboteur_kiev
      30.08.2017 12:21

      tee позволяет размножить стандартный вывод в несколько потоков. По умолчанию размножается в указанный файл и STDOUT, но можно
      date | tee -a file1.txt file2.txt
      и получим вывод и на экран и в два файла.


      1. saipr
        30.08.2017 13:04

        Все так!