tech-tips/Языки программирования/Shell/Цикл статей от RUVDS/Bash-скрипты, часть 4- ввод и вывод - Хабр.md

358 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[https://habr.com/ru/company/ruvds/blog/326594/](https://habr.com/ru/company/ruvds/blog/326594/)
---
В прошлый раз, в третьей части этой серии материалов по bash-скриптам, мы говорили о параметрах командной строки и ключах. Наша сегодняшняя тема — ввод, вывод, и всё, что с этим связано.
Вы уже знакомы с двумя методами работы с тем, что выводят сценарии командной строки:
- Отображение выводимых данных на экране.
- Перенаправление вывода в файл.
Иногда что-то надо показать на экране, а что-то — записать в файл, поэтому нужно разобраться с тем, как в 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`, при этом уже имеющиеся в нём данные никуда не денутся.
![[MT_u64e-cQwiS9VlBwtI0AfCRZ7KlpkMOwGpc7Xp6e-ckueCQDeO8cptohQVGRVNz9B5a8oHtqTnUBM9EcXQ7PzGSS3quPbBLNaxNjZum_pTfrOOTYj2ULQr0jlMcaPZCcx-80VO8k1EUL5B8Q]]
еренаправление вывода команды в файл_
Пока всё хорошо, но что если попытаться выполнить что-то вроде показанного ниже, обратившись к несуществующему файлу `xfile`, задумывая всё это для того, чтобы в файл `myfile` попало сообщение об ошибке.
```
ls l xfile > myfile
```
После выполнения этой команды мы увидим сообщения об ошибках на экране.
![[xkDHMNwO2v8IG9lBR5GZZ-MuiHIOn9c53-l_XbXvijkCdhM15yCA0Nndl_uHDPif1Y1MxUgy_5JrBv9Wquc6DSrbTRXNzsRf5VsoeFghoVXf8k-NGVdZLhQFYmnr2JeZ5SYDq_BhHacvCXs5nw]]
опытка обращения к несуществующему файлу_
При попытке обращения к несуществующему файлу генерируется ошибка, но оболочка не перенаправила сообщения об ошибках в файл, выведя их на экран. Но мы-то хотели, чтобы сообщения об ошибках попали в файл. Что делать? Ответ прост — воспользоваться третьим стандартным дескриптором.
## STDERR
`STDERR` представляет собой стандартный поток ошибок оболочки. По умолчанию этот дескриптор указывает на то же самое, на что указывает `STDOUT`, именно поэтому при возникновении ошибки мы видим сообщение на экране.
Итак, предположим, что надо перенаправить сообщения об ошибках, скажем, в лог-файл, или куда-нибудь ещё, вместо того, чтобы выводить их на экран.
### ▍Перенаправление потока ошибок
Как вы уже знаете, дескриптор файла `STDERR —` 2. Мы можем перенаправить ошибки, разместив этот дескриптор перед командой перенаправления:
```
ls -l xfile 2>myfile
cat ./myfile
```
Сообщение об ошибке теперь попадёт в файл `myfile`.
![[2KC8_kWlp5a15ruyBlvN8RkdX9Sncf8RztKz55HgpGesffvbK8wQW2MEoaCjEJRB97Tq3IU4KTzGhmz7oARoisV8ShxFHCAJTb1pkJuZwmLI_bfjeGZyALBQdSPOfFMkyOCVgnk-zDqPN9QFEA]]
еренаправление сообщения об ошибке в файл_
### ▍Перенаправление потоков ошибок и вывода
При написании сценариев командной строки может возникнуть ситуация, когда нужно организовать и перенаправление сообщений об ошибках, и перенаправление стандартного вывода. Для того, чтобы этого добиться, нужно использовать команды перенаправления для соответствующих дескрипторов с указанием файлов, куда должны попадать ошибки и стандартный вывод:
```
ls l myfile xfile anotherfile 2> errorcontent 1> correctcontent
```
![[oqXLEuYY5u6b5m6-FgwkG-YG2DIHOAjFMT-vYFQytzLMZJAxUy7DuCleOHeFZKTeV2kEmoVMdhGMb7MjNTjtVDA-4uYv2RZc8g_kpo5QcSjPIKexL8xLDqK5-aOEoOMaL98D7hao0GOUDerW2Q]]
еренаправление ошибок и стандартного вывода_
Оболочка перенаправит то, что команда `ls` обычно отправляет в `STDOUT`, в файл `correctcontent` благодаря конструкции `1>`. Сообщения об ошибках, которые попали бы в `STDERR`, оказываются в файле `errorcontent` из-за команды перенаправления `2>`.
Если надо, и `STDERR`, и `STDOUT` можно перенаправить в один и тот же файл, воспользовавшись командой `&>`:
![[2938XZKz95Stm-lttdbvTf6yaz-io5o8Wt5xlCmdsrvQRUcQ27HKX2ETI89QA6wxonMzWiCqdFMlOU0Z8q-hrf0P0EaSXXlx9z0rBLyzszL-6D8IBoXwiVxFXZtayXPiUJ29llCBdOdXp38JMA]]
еренаправление STDERR и STDOUT в один и тот же файл_
После выполнения команды то, что предназначено для `STDERR` и `STDOUT`, оказывается в файле `content`.
## Перенаправление вывода в скриптах
Существует два метода перенаправления вывода в сценариях командной строки:
- Временное перенаправление, или перенаправление вывода одной строки.
- Постоянное перенаправление, или перенаправление всего вывода в скрипте либо в какой-то его части.
### ▍Временное перенаправление вывода
В скрипте можно перенаправить вывод отдельной строки в `STDERR`. Для того, чтобы это сделать, достаточно использовать команду перенаправления, указав дескриптор `STDERR`, при этом перед номером дескриптора надо поставить символ амперсанда (`&`):
```
#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"
```
Если запустить скрипт, обе строки попадут на экран, так как, как вы уже знаете, по умолчанию ошибки выводятся туда же, куда и обычные данные.
![[vZui5QFtOOOwTKR5duEthXs3sKEOK31e85_FuQ9ygfVo1GXH3ojQDKYizQSv_XYC7pVXPGAhJRXp9keiz6JEX7ZMYL838xfNQXxrZ-HnlGGuA8iu95mx88pEmwlSWTNNX4vzNrjb2ZQeR68IRw]]
_Временное перенаправление_
Запустим скрипт так, чтобы вывод `STDERR` попадал в файл.
```
./myscript 2> myfile
```
Как видно, теперь обычный вывод делается в консоль, а сообщения об ошибках попадают в файл.
![[FLgdZTOJynq_Uigre-Mclmf6ESccvVebTBczs-tkDSaeSs6eZQI4i-q4vFqQI9J-2NYva8l2X-tNjQAolqKbvtXhuxHi4ZYadtp1at0eOTstD7mVy13VD6TOPr_mm5NGtiZL57P2eLhL6gKM2A]]
_Сообщения об ошибках записываются в файл_
### ▍Постоянное перенаправление вывода
Если в скрипте нужно перенаправлять много выводимых на экран данных, добавлять соответствующую команду к каждому вызову `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"
```
Запустим скрипт.
![[bsfDHNdPnsaOMIUZuqpOeUzyiJvmNFqmLFSEnnnKIWAlGsGfMPxxTQclRZcp1IuPtYwfQPB0gePTWAzwhixTPElZfm-0l4u7qlh6s9XghFXDN2ABawkhyd136HO2K1oQCXtPonUxzYbPTCmIsQ]]
еренаправление всего вывода в файл_
Если просмотреть файл, указанный в команде перенаправления вывода, окажется, что всё, что выводилось командами `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
```
Вот что получится после запуска скрипта и просмотра файлов, в которые мы перенаправляли вывод.
![[pkDrsO-90p-aoSP2NngxJsK7X1PxdZEAX_SC6xgcz7R_VhIm-kWzm3B-1virrHcZ_uMDBlCPR3vsEpf7_LbSTH1V9V9Vlh8WZxK38ERpgcJGvpz19w6b0cpCdTcGwUEx65FdkIf7r6fzwGiGbA]]
еренаправление вывода в разные файлы_
Сначала команда `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
```
Вот что появится на экране после запуска скрипта.
![[eDQbaQ1jCzG8iB-HXt0r2LBtZryyGj3aRdnxjN4hmfDZvDWV1zJqMzlz7hFyCX692MWJTKbDSSt81xTpGPLLqndZwmAvs_rYfPWQMTMOQHKJZ3SqB9pOo9T09dFUr5AD2sZw64D-DK4Hl7DIBg]]
еренаправление ввода_
В одном из предыдущих материалов вы узнали о том, как использовать команду `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`.
![[faCFAF7pvOg4Gb_2u4MHOcUvGsZUj3nmaFNM3I96ohph7I4eG8ax3u2w4Nf3blBSSWTEdrzxTftzyzCafYxDj7aS3LNVMd2sJxAjC9ztn4tFW9OVN8HM0OHXYUvE3WrktWUgWcByzBUAIIo2wQ]]
еренаправление вывода, используя собственный дескриптор_
## Создание дескрипторов файлов для ввода данных
Перенаправить ввод в скрипте можно точно так же, как и вывод. Сохраните `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
```
Испытаем сценарий.
![[ASf0YHSBrC-5W-uhWf8914FSGhA4u5u59NrqTPt2dpifIGNJ-Fpj9OAK_XfauzkpxaOU8729wEr-177L6ujoXnvFv1PiBaOWlXLwleUVzV9IEFdX8rgrMB7D0cto20j1egBlyn5qT0oDg4fLeQ]]
еренаправление ввода_
В этом примере дескриптор файла 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
```
После исполнения скрипта мы получим сообщение об ошибке.
![[-yp803VfhFjaIy1RDFQKeJedQMrbGdJxyvcU_Iikdx8eQoZBET64TioTH4hiejpdx6c4GxauOYTwgal2g1I7O8jX59attXzWAcu3DH2lMAgF7I8QLVfCVS_a9hI3lWKRQX9jayVbG5fJ8EJXzw]]
опытка обращения к закрытому дескриптору файла_
Всё дело в том, что мы попытались обратиться к несуществующему дескриптору.
Будьте внимательны, закрывая дескрипторы файлов в сценариях. Если вы отправляли данные в файл, потом закрыли дескриптор, потом — открыли снова, оболочка заменит существующий файл новым. То есть всё то, что было записано в этот файл ранее, будет утеряно.
## Получение сведений об открытых дескрипторах
Для того, чтобы получить список всех открытых в Linux дескрипторов, можно воспользоваться командой `lsof`. Во многих дистрибутивах, вроде Fedora, утилита `lsof` находится в `/usr/sbin`. Эта команда весьма полезна, так как она выводит сведения о каждом дескрипторе, открытом в системе. Сюда входит и то, что открыли процессы, выполняемые в фоне, и то, что открыто пользователями, вошедшими в систему.
У этой команды есть множество ключей, рассмотрим самые важные.
- `p` Позволяет указать `ID` процесса.
- `d` Позволяет указать номер дескриптора, о котором надо получить сведения.
Для того, чтобы узнать `PID` текущего процесса, можно использовать специальную переменную окружения `$$`, в которую оболочка записывает текущий `PID`.
Ключ `-a` используется для выполнения операции логического `И` над результатами, возвращёнными благодаря использованию двух других ключей:
```
lsof -a -p $$ -d 0,1,2
```
![[pZmbYEv-md9wDFqCgKHxOE24vVVhyliwk862ye4V8DAMtXt0JovYi1Eo0QtTE_ruiSZEeTqZmJOze3I_ni1KQfG6SkSs991adZs9zAK-dc77pE1bpSebhyay2FJPBJdck5EPFqSzBXmmn5wVXA]]
_Вывод сведений об открытых дескрипторах_
Тип файлов, связанных с `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
```
Вот что получится, если этот скрипт запустить.
![[RPDfGTvjcpMfZKDMMUOrONU3B12jZhXnTzZlcfuFttnojMNrdCenJFrgZMTyZXfdU4Vj2GErTVGXTwvrtEbdYxkpjLoL0gHDfXwv2cX2fHjHEnH0Bnp-bQDHjDG5QU3xXBWAHcU-qYLP_c4f8g]]
росмотр дескрипторов файлов, открытых скриптом_
Скрипт открыл два дескриптора для вывода (`3` и `6`) и один — для ввода (`7`). Тут же показаны и пути к файлам, использованных для настройки дескрипторов.
## Подавление вывода
Иногда надо сделать так, чтобы команды в скрипте, который, например, может исполняться как фоновый процесс, ничего не выводили на экран. Для этого можно перенаправить вывод в `/dev/null`. Это — что-то вроде «чёрной дыры».
Вот, например, как подавить вывод сообщений об ошибках:
```
ls -al badfile anotherfile 2> /dev/null
```
Тот же подход используется, если, например, надо очистить файл, не удаляя его:
```
cat /dev/null > myfile
```
## Итоги
Сегодня вы узнали о том, как в сценариях командной строки работают ввод и вывод. Теперь вы умеете обращаться с дескрипторами файлов, создавать, просматривать и закрывать их, знаете о перенаправлении потоков ввода, вывода и ошибок. Всё это очень важно в деле разработки bash-скриптов.
В следующий раз поговорим о сигналах Linux, о том, как обрабатывать их в сценариях, о запуске заданий по расписанию и о фоновых задачах.
Уважаемые читатели! В этом материале даны основы работы с потоками ввода, вывода и ошибок. Уверены, среди вас есть профессионалы, которые могут рассказать обо всём этом то, что приходит лишь с опытом. Если так — передаём слово вам.