Bash перенаправление вывода ошибок

����� 16. ���������������
�����/������

� ������� ��-��������� ������ ������� ��� «�����» — stdin (����������), stdout (�����) � stderr (����� ��������� �� ������� ��
�����). ���, � ����� ������ �������� �����, ����� ����
�������������. � ������ ������, ������
«���������������» �������� �������� ����� �� �����,
�������, ���������, �������� ��� ���� ���������� ����� � ��������
(��. ������ 3-1 � ������ 3-2) � �������� ��� �� ����
� ������ ����, �������, ��������� ��� ��������.

� ������ �������� ������ ������ ����������
�����. [1] ����������� ������ stdin, stdout � stderr — 0, 1 � 2, ��������������. ���
�������� �������������� ������, ����������� � 3 �� 9 ��������
����������. ������ �������������� ����������� ����� ���������
�������� ������, �������� �������� � ���� ������ �� stdin, stdout ��� stderr. [2] ��� �������� �������
������������ � ���������� ��������� ����� ������� ����������� �
���������������� � �������������� (��. ������ 16-1).

   COMMAND_OUTPUT >
      # ��������������� stdout (������) � ����.
      # ���� ���� ������������, �� �� ��������, ����� -- ����������������.

      ls -lR > dir-tree.list
      # ������� ����, ���������� ������ ������ ���������.

   : > filename
      # �������� > ������� ���� "filename" �� ������� �����.
      # ���� �� ���������� �������� ����� �� ������������,
      # �� ��������� ����� ���� � ������� ������ (��� �� ������ ���� ������� 'touch').
      # ������ : ��������� ����� � ���� ����������������, �� ������ ������.

   > filename
      # �������� > ������� ���� "filename" �� ������� �����.
      # ���� �� ���������� �������� ����� �� ������������,
      # �� ��������� ����� ���� � ������� ������ (��� �� ������ ���� ������� 'touch').
      # (��� �� ���������, ��� � ���� -- ": >", �� ���� ������� ����������������
      # � ��������� ��������� ���������.)

   COMMAND_OUTPUT >>
      # ��������������� stdout (������) � ����.
      # ������� ����� ����, ���� �� ������������, ����� -- ���������� � ����� �����.


      # ������������ ������� ���������������
      # (����������� ������ �� ������, � ������� ��� �����������):
      # --------------------------------------------------------------------

   1>filename
      # ��������������� ������ (stdout) � ���� "filename".
   1>>filename
      # ��������������� ������ (stdout) � ���� "filename", ���� ����������� � ������ ����������.
   2>filename
      # ��������������� stderr � ���� "filename".
   2>>filename
      # ��������������� stderr � ���� "filename", ���� ����������� � ������ ����������.
   &>filename
      # ��������������� stdout � stderr � ���� "filename".

      #==============================================================================
      # ��������������� stdout, ������ ��� ����� ������.
      LOGFILE=script.log

      echo "��� ������ ����� �������� � ���� \"$LOGFILE\"." 1>$LOGFILE
      echo "��� ������ ����� ��������� � ����� ����� \"$LOGFILE\"." 1>>$LOGFILE
      echo "��� ������ ���� ����� ��������� � ����� ����� \"$LOGFILE\"." 1>>$LOGFILE
      echo "��� ������ ����� �������� �� ����� � �� ������� � ���� \"$LOGFILE\"."
      # ����� ������ ������, ��������� ��������������� ������������� "������������".



      # ��������������� stderr, ������ ��� ����� ������.
      ERRORFILE=script.errors

      bad_command1 2>$ERRORFILE       #  ��������� �� ������ ��������� � $ERRORFILE.
      bad_command2 2>>$ERRORFILE      #  ��������� �� ������ ��������� � ����� $ERRORFILE.
      bad_command3                    #  ��������� �� ������ ����� �������� �� stderr,
                                      #+ � �� ������� � $ERRORFILE.
      # ����� ������ ������, ��������� ��������������� ����� ������������� "������������".
      #==============================================================================



   2>&1
      # ���������������� stderr �� stdout.
      # ��������� �� ������� ���������� ���� ��, ���� � ����������� �����.

   i>&j
      # ���������������� ���� � ������������ ij.
      # ����� � ���� � ������������ i ���������� � ���� � ������������ j.

   >&j
      # ����������������  ���� � ������������ 1 (stdout) � ���� � ������������ j.
      # ����� �� stdout ���������� � ���� � ������������ j.

   0< FILENAME
    < FILENAME
      # ���� �� �����.
      # ������ ������� ">", ����� ����������� � ���������� � ���.
      #
      # grep search-word <filename


   [j]<>filename
      # ���� "filename" ����������� �� ������ � ������, � ����������� � ������������ "j".
      # ���� "filename" �����������, �� �� ���������.
      # ���� ���������� "j" �� ������, ��, ��-���������, ������� ���������� 0, stdin.
      #
      # ��� ���� �� ���������� ����� -- ������ � ���������� ������� � �����.
      echo 1234567890 > File    # �������� ������ � ���� "File".
      exec 3<> File       # ������� "File" � ������� � ������������ 3.
      read -n 4 <&3             # ��������� 4 �������.
      echo -n . >&3             # �������� ������ �����.
      exec 3>&-                 # ������� ���������� 3.
      cat File                  # ==> 1234.67890
      # ������������ ������, �� � ������!



   |
      # �������� (�����).
      # ������������� �������� ��� ����������� ������ � ���� �������.
      # ������ �� ">", �� �� ����� ���� -- ����� ��������.
      # ������������ ��� ����������� ������, ���������, ������ � �������� � ���� ������� (��������).
      cat *.txt | sort | uniq > result-file
      # ���������� ���� ������ .txt �����������, ��������� ������������� ������,
      # ��������� ����������� � ����� "result-file".

�������� ��������������� �/��� ��������� ����� ���������������
� ����� ��������� ������.

command < input-file > output-file

command1 | command2 | command3 > output-file

��. ������ 12-23 � ������ A-17.

����������� ��������������� ���������� ������� � ����
����.

ls -yz >> command.log 2>&1
# ��������� � �������� ����� "yz" � ������� "ls" ����� �������� � ���� "command.log".
# ��������� stderr ������������� � ����.

�������� ������������ ������

n<&-

������� ���������� �������� ����� n.

0<&-, <&-

������� stdin.

n>&-

������� ���������� ��������� ����� n.

1>&-, >&-

������� stdout.

�������� �������� ��������� ����������� �������� ������. ��
���� ������� � �������� ���������. ����� �������������
������������ ������������ — �������� �� ����� �������� ���������
��������.

# � �������� ���������� ������ stderr.

exec 3>&1                              # ��������� ������� "���������" stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # ������� �����. 3 ��� 'grep' (�� �� ��� 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # ������ ������� ��� ��� ���������� ����� ��������.

# ������� S.C.

�������������� �������� � ��������������� �����/������ ��
������� � ���������� D.

16.1. � ������� ������� exec

������� exec <filename �������������� ����
�� stdin �� ����. � ����� ������� ����
����, ������ stdin (������ ��� ����������), �����
������������� �� ����� �����. ��� ���� ����������� ������
���������� �����, ������ �� �������, � ������������� ������
��������� ������ � ������� sed �/��� awk.

������ 16-1. ��������������� stdin � ������� exec

#!/bin/bash
# ��������������� stdin � ������� 'exec'.


exec 6<&0          # ������� �����. #6 �� ����������� ������ (stdin).
                   # �������� stdin.

exec < data-file   # stdin ���������� ������ "data-file"

read a1            # �������� ������ ������ �� "data-file".
read a2            # �������� ������ ������ �� "data-file."

echo
echo "��������� ������ ���� ��������� �� �����."
echo "-----------------------------------------"
echo $a1
echo $a2

echo; echo; echo

exec 0<&6 6<&-
#  ����������������� stdin �� �����. #6, ��� �� ��� �������������� ��������,
#+ � �����. #6 ����������� ( 6<&- ) ���������� ��� ��� ������ ���������.
#
# <&6 6<&-    ���� ��� �� ���������.

echo -n "������� ������  "
read b1  # ������ ������� "read", ��� � ��������� �������, ��������� ������ � �������� stdin.
echo "������, �������� �� stdin."
echo "--------------------------"
echo "b1 = $b1"

echo

exit 0

����������, ����������� exec >filename ��������������
����� �� stdout � �������� ����. ����� �����,
���� ����� �� ������, ������� ������ ������������ �� stdout, ������ ��������� � ����
����.

������ 16-2. ��������������� stdout � ������� exec

#!/bin/bash
# reassign-stdout.sh

LOGFILE=logfile.txt

exec 6>&1           # ������� �����. #6 �� stdout.
                    # �������� stdout.

exec > $LOGFILE     # stdout ���������� ������ "logfile.txt".

# ----------------------------------------------------------- #
# ���� ����� �� ������, � ������ �����, ������������ � ���� $LOGFILE.

echo -n "Logfile: "
date
echo "-------------------------------------"
echo

echo "����� ������� \"ls -al\""
echo
ls -al
echo; echo
echo "����� ������� \"df\""
echo
df

# ----------------------------------------------------------- #

exec 1>&6 6>&-      # ������������ stdout � ������� �����. #6.

echo
echo "== stdout ������������� � �������� ��-��������� == "
echo
ls -al
echo

exit 0

������ 16-3. ������������� ���������������
���������, stdin � stdout, � ������� �������
exec

#!/bin/bash
# upperconv.sh
# �������������� �������� �� ������� ����� � ������� �������.

E_FILE_ACCESS=70
E_WRONG_ARGS=71

if [ ! -r "$1" ]     # ���� �������� ��� ������?
then
  echo "���������� ��������� �� ��������� �����!"
  echo "������� �������������: $0 input-file output-file"
  exit $E_FILE_ACCESS
fi                   #  � ������, ���� ������� ���� ($1) �� �����
                     #+ ��� ���������� ����� ���� ��.

if [ -z "$2" ]
then
  echo "���������� ������ �������� ����."
  echo "������� �������������: $0 input-file output-file"
  exit $E_WRONG_ARGS
fi


exec 4<&0
exec < $1            # ��������� ���� �� �������� �����.

exec 7>&1
exec > $2            # ��������� ����� � �������� ����.
                     # ��������������, ��� �������� ���� �������� ��� ������
                     # (�������� ��������?).

# -----------------------------------------------
    cat - | tr a-z A-Z   # ������� � ������� �������
#   ^^^^^                # ������ �� stdin.
#           ^^^^^^^^^^   # ������ � stdout.
# ������, � stdin � stdout ���� ��������������.
# -----------------------------------------------

exec 1>&7 7>&-       # ������������ stdout.
exec 0<&4 4<&-       # ������������ stdin.

# ����� ��������������, ��������� ������ ��������� �� stdout, ���� � ��������� �������.
echo "������� �� \"$1\" ������������� � ������� �������, ��������� ������� � \"$2\"."

exit 0

Время на прочтение
9 мин

Количество просмотров 348K

Bash-скрипты: начало
Bash-скрипты, часть 2: циклы
Bash-скрипты, часть 3: параметры и ключи командной строки
Bash-скрипты, часть 4: ввод и вывод
Bash-скрипты, часть 5: сигналы, фоновые задачи, управление сценариями
Bash-скрипты, часть 6: функции и разработка библиотек
Bash-скрипты, часть 7: sed и обработка текстов
Bash-скрипты, часть 8: язык обработки данных awk
Bash-скрипты, часть 9: регулярные выражения
Bash-скрипты, часть 10: практические примеры
Bash-скрипты, часть 11: expect и автоматизация интерактивных утилит

В прошлый раз, в третьей части этой серии материалов по bash-скриптам, мы говорили о параметрах командной строки и ключах. Наша сегодняшняя тема — ввод, вывод, и всё, что с этим связано.

image

Вы уже знакомы с двумя методами работы с тем, что выводят сценарии командной строки:

  • Отображение выводимых данных на экране.
  • Перенаправление вывода в файл.

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

Стандартные дескрипторы файлов

Всё в Linux — это файлы, в том числе — ввод и вывод. Операционная система идентифицирует файлы с использованием дескрипторов.

Каждому процессу позволено иметь до девяти открытых дескрипторов файлов. Оболочка bash резервирует первые три дескриптора с идентификаторами 0, 1 и 2. Вот что они означают.

  • 0, STDIN — стандартный поток ввода.
  • 1, STDOUT — стандартный поток вывода.
  • 2, STDERR — стандартный поток ошибок.

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

STDIN

STDIN — это стандартный поток ввода оболочки. Для терминала стандартный ввод — это клавиатура. Когда в сценариях используют символ перенаправления ввода — <, Linux заменяет дескриптор файла стандартного ввода на тот, который указан в команде. Система читает файл и обрабатывает данные так, будто они введены с клавиатуры.

Многие команды bash принимают ввод из STDIN, если в командной строке не указан файл, из которого надо брать данные. Например, это справедливо для команды cat.

Когда вы вводите команду cat в командной строке, не задавая параметров, она принимает ввод из STDIN. После того, как вы вводите очередную строку, cat просто выводит её на экран.

STDOUT

STDOUT — стандартный поток вывода оболочки. По умолчанию это — экран. Большинство bash-команд выводят данные в STDOUT, что приводит к их появлению в консоли. Данные можно перенаправить в файл, присоединяя их к его содержимому, для этого служит команда >>.

Итак, у нас есть некий файл с данными, к которому мы можем добавить другие данные с помощью этой команды:

pwd >> myfile

То, что выведет pwd, будет добавлено к файлу myfile, при этом уже имеющиеся в нём данные никуда не денутся.

Перенаправление вывода команды в файл

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

ls –l xfile > myfile

После выполнения этой команды мы увидим сообщения об ошибках на экране.

Попытка обращения к несуществующему файлу

При попытке обращения к несуществующему файлу генерируется ошибка, но оболочка не перенаправила сообщения об ошибках в файл, выведя их на экран. Но мы-то хотели, чтобы сообщения об ошибках попали в файл. Что делать? Ответ прост — воспользоваться третьим стандартным дескриптором.

STDERR

STDERR представляет собой стандартный поток ошибок оболочки. По умолчанию этот дескриптор указывает на то же самое, на что указывает STDOUT, именно поэтому при возникновении ошибки мы видим сообщение на экране.

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

▍Перенаправление потока ошибок

Как вы уже знаете, дескриптор файла STDERR — 2. Мы можем перенаправить ошибки, разместив этот дескриптор перед командой перенаправления:

ls -l xfile 2>myfile
cat ./myfile

Сообщение об ошибке теперь попадёт в файл myfile.

Перенаправление сообщения об ошибке в файл

▍Перенаправление потоков ошибок и вывода

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

ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent

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

Оболочка перенаправит то, что команда ls обычно отправляет в STDOUT, в файл correctcontent благодаря конструкции 1>. Сообщения об ошибках, которые попали бы в STDERR, оказываются в файле errorcontent из-за команды перенаправления 2>.

Если надо, и STDERR, и STDOUT можно перенаправить в один и тот же файл, воспользовавшись командой &>:

Перенаправление STDERR и STDOUT в один и тот же файл

После выполнения команды то, что предназначено для STDERR и STDOUT, оказывается в файле content.

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

Существует два метода перенаправления вывода в сценариях командной строки:

  • Временное перенаправление, или перенаправление вывода одной строки.
  • Постоянное перенаправление, или перенаправление всего вывода в скрипте либо в какой-то его части.

▍Временное перенаправление вывода

В скрипте можно перенаправить вывод отдельной строки в STDERR. Для того, чтобы это сделать, достаточно использовать команду перенаправления, указав дескриптор STDERR, при этом перед номером дескриптора надо поставить символ амперсанда (&):

#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"

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

Временное перенаправление

Запустим скрипт так, чтобы вывод STDERR попадал в файл.

./myscript 2> myfile

Как видно, теперь обычный вывод делается в консоль, а сообщения об ошибках попадают в файл.

Сообщения об ошибках записываются в файл

▍Постоянное перенаправление вывода

Если в скрипте нужно перенаправлять много выводимых на экран данных, добавлять соответствующую команду к каждому вызову echo неудобно. Вместо этого можно задать перенаправление вывода в определённый дескриптор на время выполнения скрипта, воспользовавшись командой exec:

#!/bin/bash
exec 1>outfile
echo "This is a test of redirecting all output"
echo "from a shell script to another file."
echo "without having to redirect every line"

Запустим скрипт.

Перенаправление всего вывода в файл

Если просмотреть файл, указанный в команде перенаправления вывода, окажется, что всё, что выводилось командами echo, попало в этот файл.

Команду exec можно использовать не только в начале скрипта, но и в других местах:

#!/bin/bash
exec 2>myerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>myfile
echo "This should go to the myfile file"
echo "and this should go to the myerror file" >&2

Вот что получится после запуска скрипта и просмотра файлов, в которые мы перенаправляли вывод.

Перенаправление вывода в разные файлы

Сначала команда exec задаёт перенаправление вывода из STDERR в файл myerror. Затем вывод нескольких команд echo отправляется в STDOUT и выводится на экран. После этого команда exec задаёт отправку того, что попадает в STDOUT, в файл myfile, и, наконец, мы пользуемся командой перенаправления в STDERR в команде echo, что приводит к записи соответствующей строки в файл myerror.

Освоив это, вы сможете перенаправлять вывод туда, куда нужно. Теперь поговорим о перенаправлении ввода.

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

Для перенаправления ввода можно воспользоваться той же методикой, которую мы применяли для перенаправления вывода. Например, команда exec позволяет сделать источником данных для STDIN какой-нибудь файл:

exec 0< myfile

Эта команда указывает оболочке на то, что источником вводимых данных должен стать файл myfile, а не обычный STDIN. Посмотрим на перенаправление ввода в действии:

#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done

Вот что появится на экране после запуска скрипта.

Перенаправление ввода

В одном из предыдущих материалов вы узнали о том, как использовать команду read для чтения данных, вводимых пользователем с клавиатуры. Если перенаправить ввод, сделав источником данных файл, то команда read, при попытке прочитать данные из STDIN, будет читать их из файла, а не с клавиатуры.

Некоторые администраторы Linux используют этот подход для чтения и последующей обработки лог-файлов.

Создание собственного перенаправления вывода

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

Назначить дескриптор для вывода данных можно, используя команду exec:

#!/bin/bash
exec 3>myfile
echo "This should display on the screen"
echo "and this should be stored in the file" >&3
echo "And this should be back on the screen"

После запуска скрипта часть вывода попадёт на экран, часть — в файл с дескриптором 3.

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

Создание дескрипторов файлов для ввода данных

Перенаправить ввод в скрипте можно точно так же, как и вывод. Сохраните STDIN в другом дескрипторе, прежде чем перенаправлять ввод данных.

После окончания чтения файла можно восстановить STDIN и пользоваться им как обычно:

#!/bin/bash
exec 6<&0
exec 0< myfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
y) echo "Goodbye";;
n) echo "Sorry, this is the end.";;
esac

Испытаем сценарий.

Перенаправление ввода

В этом примере дескриптор файла 6 использовался для хранения ссылки на STDIN. Затем было сделано перенаправление ввода, источником данных для STDIN стал файл. После этого входные данные для команды read поступали из перенаправленного STDIN, то есть из файла.

После чтения файла мы возвращаем STDIN в исходное состояние, перенаправляя его в дескриптор 6. Теперь, для того, чтобы проверить, что всё работает правильно, скрипт задаёт пользователю вопрос, ожидает ввода с клавиатуры и обрабатывает то, что введено.

Закрытие дескрипторов файлов

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

#!/bin/bash
exec 3> myfile
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3

После исполнения скрипта мы получим сообщение об ошибке.

Попытка обращения к закрытому дескриптору файла

Всё дело в том, что мы попытались обратиться к несуществующему дескриптору.

Будьте внимательны, закрывая дескрипторы файлов в сценариях. Если вы отправляли данные в файл, потом закрыли дескриптор, потом — открыли снова, оболочка заменит существующий файл новым. То есть всё то, что было записано в этот файл ранее, будет утеряно.

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

Для того, чтобы получить список всех открытых в Linux дескрипторов, можно воспользоваться командой lsof. Во многих дистрибутивах, вроде Fedora, утилита lsof находится в /usr/sbin. Эта команда весьма полезна, так как она выводит сведения о каждом дескрипторе, открытом в системе. Сюда входит и то, что открыли процессы, выполняемые в фоне, и то, что открыто пользователями, вошедшими в систему.

У этой команды есть множество ключей, рассмотрим самые важные.

  • -p Позволяет указать ID процесса.
  • -d Позволяет указать номер дескриптора, о котором надо получить сведения.

Для того, чтобы узнать PID текущего процесса, можно использовать специальную переменную окружения $$, в которую оболочка записывает текущий PID.

Ключ -a используется для выполнения операции логического И над результатами, возвращёнными благодаря использованию двух других ключей:

lsof -a -p $$ -d 0,1,2

Вывод сведений об открытых дескрипторах

Тип файлов, связанных с STDIN, STDOUT и STDERR — CHR (character mode, символьный режим). Так как все они указывают на терминал, имя файла соответствует имени устройства, назначенного терминалу. Все три стандартных файла доступны и для чтения, и для записи.

Посмотрим на вызов команды lsof из скрипта, в котором открыты, в дополнение к стандартным, другие дескрипторы:

#!/bin/bash
exec 3> myfile1
exec 6> myfile2
exec 7< myfile3
lsof -a -p $$ -d 0,1,2,3,6,7

Вот что получится, если этот скрипт запустить.

Просмотр дескрипторов файлов, открытых скриптом

Скрипт открыл два дескриптора для вывода (3 и 6) и один — для ввода (7). Тут же показаны и пути к файлам, использованных для настройки дескрипторов.

Подавление вывода

Иногда надо сделать так, чтобы команды в скрипте, который, например, может исполняться как фоновый процесс, ничего не выводили на экран. Для этого можно перенаправить вывод в /dev/null. Это — что-то вроде «чёрной дыры».

Вот, например, как подавить вывод сообщений об ошибках:

ls -al badfile anotherfile 2> /dev/null

Тот же подход используется, если, например, надо очистить файл, не удаляя его:

cat /dev/null > myfile

Итоги

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

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

Уважаемые читатели! В этом материале даны основы работы с потоками ввода, вывода и ошибок. Уверены, среди вас есть профессионалы, которые могут рассказать обо всём этом то, что приходит лишь с опытом. Если так — передаём слово вам.

Short answer: Command >filename 2>&1 or Command &>filename


Explanation:

Consider the following code which prints the word «stdout» to stdout and the word «stderror» to stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Note that the ‘&’ operator tells bash that 2 is a file descriptor (which points to the stderr) and not a file name. If we left out the ‘&’, this command would print stdout to stdout, and create a file named «2» and write stderror there.

By experimenting with the code above, you can see for yourself exactly how redirection operators work. For instance, by changing which file which of the two descriptors 1,2, is redirected to /dev/null the following two lines of code delete everything from the stdout, and everything from stderror respectively (printing what remains).

$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

Now, we can explain why the solution why the following code produces no output:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

To truly understand this, I highly recommend you read this webpage on file descriptor tables. Assuming you have done that reading, we can proceed. Note that Bash processes left to right; thus Bash sees >/dev/null first (which is the same as 1>/dev/null), and sets the file descriptor 1 to point to /dev/null instead of the stdout. Having done this, Bash then moves rightwards and sees 2>&1. This sets the file descriptor 2 to point to the same file as file descriptor 1 (and not to file descriptor 1 itself!!!! (see this resource on pointers for more info)) . Since file descriptor 1 points to /dev/null, and file descriptor 2 points to the same file as file descriptor 1, file descriptor 2 now also points to /dev/null. Thus both file descriptors point to /dev/null, and this is why no output is rendered.


To test if you really understand the concept, try to guess the output when we switch the redirection order:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null

stderror

The reasoning here is that evaluating from left to right, Bash sees 2>&1, and thus sets the file descriptor 2 to point to the same place as file descriptor 1, ie stdout. It then sets file descriptor 1 (remember that >/dev/null = 1>/dev/null) to point to >/dev/null, thus deleting everything which would usually be send to to the standard out. Thus all we are left with was that which was not send to stdout in the subshell (the code in the parentheses)- i.e. «stderror».
The interesting thing to note there is that even though 1 is just a pointer to the stdout, redirecting pointer 2 to 1 via 2>&1 does NOT form a chain of pointers 2 -> 1 -> stdout. If it did, as a result of redirecting 1 to /dev/null, the code 2>&1 >/dev/null would give the pointer chain 2 -> 1 -> /dev/null, and thus the code would generate nothing, in contrast to what we saw above.


Finally, I’d note that there is a simpler way to do this:

From section 3.6.4 here, we see that we can use the operator &> to redirect both stdout and stderr. Thus, to redirect both the stderr and stdout output of any command to \dev\null (which deletes the output), we simply type
$ command &> /dev/null
or in case of my example:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

Key takeaways:

  • File descriptors behave like pointers (although file descriptors are not the same as file pointers)
  • Redirecting a file descriptor «a» to a file descriptor «b» which points to file «f», causes file descriptor «a» to point to the same place as file descriptor b — file «f». It DOES NOT form a chain of pointers a -> b -> f
  • Because of the above, order matters, 2>&1 >/dev/null is != >/dev/null 2>&1. One generates output and the other does not!

Finally have a look at these great resources:

Bash Documentation on Redirection, An Explanation of File Descriptor Tables, Introduction to Pointers

Часто возникает необходимость, чтобы скрипт командного интерпретатора Bash выводил результат своей работы. По умолчанию он отображает стандартный поток данных — окно терминала. Это удобно для обработки результатов небольшого объёма или, чтобы сразу увидеть необходимые данные.

В интерпретаторе можно делать вывод в файл Bash. Применяется это для отложенного анализа или сохранения массивного результата работы сценария. Чтобы сделать это, используется перенаправление потока вывода с помощью дескрипторов.

Стандартные дескрипторы вывода

В системе GNU/Linux каждый объект является файлом. Это правило работает также для процессов ввода/вывода. Каждый файловый объект в системе обозначается дескриптором файла — неотрицательным числом, однозначно определяющим открытые в сеансе файлы. Один процесс может открыть до девяти дескрипторов.

В командном интерпретаторе Bash первые три дескриптора зарезервированы для специального назначения:

Дескриптор Сокращение Название
0 STDIN Стандартный ввод
1 STDOUT Стандартный вывод
2 STDERR Стандартный вывод ошибок

Их предназначение — обработка ввода/вывода в сценариях. По умолчанию стандартным потоком ввода является клавиатура, а вывода — терминал. Рассмотрим подробно последний.

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

Для того, чтобы перенаправить поток вывода с терминала в файл, используется знак «больше» (>).

#!/bin/bash
echo "Строка 1"
echo "Промежуточная строка" > file
echo "Строка 2" > file

Как результат, «Строка 1» выводится в терминале, а в файл file записывается только «Строка 2»:

Связано это с тем, что > перезаписывает файл новыми данными. Для того, чтобы дописать информацию в конец файла, используется два знака «больше» (>>).

#!/bin/bash
echo "Строка 1"
echo "Промежуточная строка" > file
echo "Строка 2" >> file

Здесь «Промежуточная строка» перезаписала предыдущее содержание file, а «Строка 2» дописалась в его конец.

Если во время использования перенаправления вывода интерпретатор обнаружит ошибку, то он не запишет сообщение о ней в файл.

#!/bin/bash
ls badfile > file2
echo "Строка 2" >> file2

В данном случае ошибка была в том, что команда ls не смогла найти файл badfile, о чём Bash и сообщил. Но вывелось сообщение в терминал, а не записалось в файл. Всё потому, что использование перенаправления потоков указывает интерпретатору отделять мух от котлет ошибки от основной информации.

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

2. Перенаправление потока ошибок

В командном интерпретаторе для обработки сообщений об ошибках предназначен дескриптор STDERR, который работает с ошибками, сформированными как от работы интерпретатора, так и самим скриптом.

По умолчанию STDERR указывает в то же место, что и STDOUT, хотя для них и предназначены разные дескрипторы. Но, как было показано в примере, использование перенаправления заставляет Bash разделить эти потоки.

Чтобы выполнить перенаправление вывода в файл Linux для ошибок, следует перед знаком«больше» указать дескриптор 2.

#!/bin/bash
ls badfile 2> errors
echo "Строка 1" > file3
echo "Строка 2" >> file3

В результате работы скрипта создан файл errors, в который записана ошибка выполнения команды ls, а в file3 записаны предназначенные строки. Таким образом, выполнение сценария не сопровождается выводом информации в терминал.

Пример того, как одна команда возвращает и положительный результат, и ошибку:

ls -lh test badtest 2> errors

Команда ls попыталась показать наличие файлов test и badtest. Первый присутствовал в текущем каталоге, а второй — нет. Но сообщение об ошибке было записано в отдельный файл.

Если возникает необходимость выполнить вывод команды в файл Linux, включая её стандартный поток вывода и ошибки, стоит использовать два символа перенаправления, перед которыми стоит указывать необходимый дескриптор.

ls -lh test test2 badtest 2> errors 1> output

Результат успешного выполнения записан в файл output, а сообщение об ошибке — в errors.

По желанию можно выводить и ошибки, и обычные данные в один файл, используя &>.

ls -lh test badtest &> output

Обратите внимание, что Bash присваивает сообщениям об ошибке более высокий приоритет по сравнению с данными, поэтому в случае общего перенаправления ошибки всегда будут располагаться в начале.

Временные перенаправления в скриптах

Если есть необходимость в преднамеренном формировании ошибок в сценарии, можно каждую отдельную строку вывода перенаправлять в STDERR. Для этого достаточно воспользоваться символом перенаправления вывода, после которого нужно использовать & и номер дескриптора, чтобы перенаправить вывод в STDERR.

#!/bin/bash
echo "Это сообщение об ошибке" >&2
echo "Это нормальное сообщение"

При выполнении программы обычно нельзя будет обнаружить отличия:

Вспомним, что GNU/Linux по умолчанию направляет вывод STDERR в STDOUT. Но если при выполнении скрипта будет перенаправлен поток ошибок, то Bash, как и полагается, разделит вывод.

Этот метод хорошо подходит для создания собственных сообщений об ошибках в сценариях.

Постоянные перенаправления в скриптах

Если в сценарии необходимо перенаправить вывод в файл Linux для большого объёма данных, то указание способа вывода в каждой инструкции echo будет неудобным и трудоёмким занятием. Вместо этого можно указать, что в ходе выполнения данного скрипта должно осуществляться перенаправление конкретного дескриптора с помощью команды exec:

#!/bin/bash
exec 1> testout
echo "Это тест перенаправления всего вывода"
echo "из скрипта в другой файл"
echo "без использования временного перенаправления"

Вызов команды exec запускает новый командный интерпретатор и перенаправляет стандартный вывод в файл testout.

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

#!/bin/bash
exec 2> testerror
echo "Это начально скрипта"
echo "И это первые две строки"
exec 1> testout
echo "Вывод сценария перенаправлен"
echo "из с терминала в другой файл"
echo "но эта строка записана в файл ошибок" >&2

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

Выводы

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

Использование временного и постоянного перенаправлений в сценариях позволяет создавать собственные сообщения об ошибках для записи в отличное от STDOUT место.

Обнаружили ошибку в тексте? Сообщите мне об этом. Выделите текст с ошибкой и нажмите Ctrl+Enter.

07.10.2020
Linux, Команды

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

В Bash и других оболочках Linux при выполнении программы используются три стандартных потока ввода-вывода. Каждый поток представлен числовым дескриптором файла:

  • 0stdin , стандартный поток ввода.
  • 1stdout , стандартный поток вывода.
  • 2stderr , стандартный поток ошибок.

Дескриптор файла — это просто число, представляющее открытый файл.

Входной поток предоставляет информацию программе, как правило, путем ввода с клавиатуры.

Выходные данные программы попадают в стандартный поток ввода, а сообщения об ошибках — в стандартный поток ошибок. По умолчанию на экран выводятся потоки ввода и ошибки.

Перенаправление вывода

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

Потоки могут быть перенаправлены с помощью оператора n> , где n — номер дескриптора файла.

Если n опущено, по умолчанию используется 1 , стандартный выходной поток. Например, следующие две команды одинаковы; оба будут перенаправлять вывод команды ( stdout ) в файл.

command > file
command 1> file

Чтобы перенаправить стандартную ошибку ( stderr ), используйте оператор 2> :

command 2> file

Вы можете записать как stderr и stdout в два отдельных файла:

command 2> error.txt 1> output.txt

Чтобы сообщения об ошибках не отображались на экране, перенаправьте stderr на /dev/null :

command 2> /dev/null

Перенаправление stderr на stdout

При сохранении вывода программы в файл довольно часто перенаправляют stderr на stdout чтобы у вас было все в одном файле.

Чтобы перенаправить stderr на stdout и отправлять сообщения об ошибках в тот же файл, что и стандартный вывод, используйте следующее:

command > file 2>&1

> file перенаправляет stdout в file , а 2>&1 перенаправляет stderr в текущее расположение stdout .

Порядок перенаправления важен. Например, в следующем примере в file перенаправляется только стандартный stdout . Это происходит потому, что стандартный stderr перенаправляется на стандартный stdout до того, как стандартный stdout был перенаправлен в file .

command 2>&1 > file 

Другой способ перенаправить stderr на stdout — использовать конструкцию &> . В Bash &> имеет то же значение, что и 2>&1 :

command &> file

Выводы

Понимание концепции перенаправлений и файловых дескрипторов очень важно при работе в командной строке.

Чтобы перенаправить stderr и stdout , используйте конструкции 2>&1 или &> .

Если у вас есть какие-либо вопросы или отзывы, не стесняйтесь оставлять комментарии.

Понравилась статья? Поделить с друзьями:
  • B7f8c6 bmw ошибка
  • Bash игнорировать ошибки
  • Bas esp w210 ошибка на мерседес
  • Bash ошибка синтаксиса неожиданный конец файла
  • B7f8c3 bmw ошибка