bash скрипты для начинающих — урок №9: Перенаправление вывода.

Предыдущий урок: bash скрипты для начинающих — урок №8: Цикл по строкам файла.

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

Какие есть типы перенаправлений?

> — redirect stdout (стандартный вывод) в файл, перезаписывая его, если он существует.

>> — добавляет stdout в конец файла, оставляя существующие данные.

2> — перенаправляет стандартный поток ошибок (stderr) в файл.

&> или > file 2>&1 — перенаправляют оба потока (stdout и stderr) в один файл.

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

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

[root@waky bash_practice]# touch script_9.sh
[root@waky bash_practice]# chmod +x script_9.sh
[root@waky bash_practice]#

Шаг 1: Перенаправляем вывод

Для примера используем скрипт из седьмого урока, только немного модифицируем его. Содержимое файла script_9.sh:

#!/bin/bash

stdout_log=script_9.log

for script_file in $(find ./ -type f -name "*.sh")
do
    echo "time: $(date); file name: $script_file" > $stdout_log
done

Мы используем перенаправление с перезаписью (>) стандартного вывода (stdout). Проверим лог файл script_9.log:

[root@waky bash_practice]# cat script_9.log
time: Thu Dec 25 02:51:47 AM MSK 2025; file name: ./script_9.sh
[root@waky bash_practice]#

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

#!/bin/bash

stdout_log=script_9.log

for script_file in $(find ./ -type f -name "*.sh")
do
    echo "time: $(date); file name: $script_file" >> $stdout_log
done

Проверим результат:

[root@waky bash_practice]# ./script_9.sh
[root@waky bash_practice]# cat script_9.log
time: Thu Dec 25 02:51:47 AM MSK 2025; file name: ./script_9.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./first_script.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_2.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_3.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_4.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_5.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_6.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_7.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_8.sh
time: Thu Dec 25 02:59:20 AM MSK 2025; file name: ./script_9.sh
[root@waky bash_practice]#

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

#!/bin/bash

stdout_log=script_9.log

cat /dev/null > $stdout_log

for script_file in $(find ./ -type f -name "*.sh")
do
    echo "time: $(date); file name: $script_file" >> $stdout_log
done

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

Мы использовали упрощенный вид перенаправления — без указания потока, который нужно перенаправить. Прежде чем переходить к перенаправлению ошибок проговорим, что стандартный вывод (stdout) это первый поток, ошибки (stderr) — второй поток.

Мы не указывали номер потока, так как по умолчанию, если не указан номер, перенаправление применяется к stdout. Поправим скрипт указав номер потока в явном виде:

#!/bin/bash

stdout_log=script_9.log

cat /dev/null > $stdout_log

for script_file in $(find ./ -type f -name "*.sh")
do
    echo "time: $(date); file name: $script_file" 1>> $stdout_log
done

Убедимся, что результат работы скрипта не изменился:

[root@waky bash_practice]# ./script_9.sh
[root@waky bash_practice]# cat script_9.log
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./first_script.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_2.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_3.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_4.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_5.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_6.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_7.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_8.sh
time: Thu Dec 25 03:12:36 AM MSK 2025; file name: ./script_9.sh
[root@waky bash_practice]#

Шаг 2: Перенаправляем стандартные ошибки

Отредактируем наш скрипт следующим образом:

#!/bin/bash

stdout_log=script_9.log

cat /dev/null > $stdout_log

ls *.jpg 1>> $ stdout_log

Теперь вместо sh файлов будем использовать картинки jpg, которых нет в текущей директории.

Запустим скрипт и посмотрим, что из этого вышло:

[root@waky bash_practice]# ./script_9.sh
ls: cannot access '*.jpg': No such file or directory
[root@waky bash_practice]# cat script_9.log
[root@waky bash_practice]#

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

#!/bin/bash

stdout_log=script_9.log
stderr_log=script_9.errlog

cat /dev/null > $stdout_log
cat /dev/null > $stderr_log

ls *.jpg 2>> $stderr_log

Запустим скрипт и проверим лог ошибок:

[root@waky bash_practice]# ./script_9.sh
[root@waky bash_practice]# cat script_9.errlog
ls: cannot access '*.jpg': No such file or directory
[root@waky bash_practice]#

Как мы и хотели, ошибки были перенаправлены в отдельный файл.

Шаг 3: Перенаправляем оба потока

Для перенаправления потоков в разные файлы используем конструкцию команда> stdout.txt 2> stderr.txt:

#!/bin/bash

stdout_log=script_9.log
stderr_log=script_9.errlog

cat /dev/null > $stdout_log
cat /dev/null > $stderr_log

ls *.sh >> $stdout_log 2>> $stderr_log
ls *.jpg 1>> $stdout_log 2>> $stderr_log

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

[root@waky bash_practice]# ./script_9.sh
[root@waky bash_practice]# cat script_9.log
first_script.sh
script_2.sh
script_3.sh
script_4.sh
script_5.sh
script_6.sh
script_7.sh
script_8.sh
script_9.sh
[root@waky bash_practice]# cat script_9.errlog
ls: cannot access '*.jpg': No such file or directory
[root@waky bash_practice]#

Если мы хотим и вывод и ошибки перенаправлять в один файл используем конструкцию &> или 2>&1:

#!/bin/bash

stdout_log=script_9.log
stderr_log=script_9.errlog

cat /dev/null > $stdout_log
cat /dev/null > $stderr_log

ls *.sh &>> $stdout_log
ls *.jpg >> $stdout_log 2>&1

Проверим результат:

[root@waky bash_practice]# ./script_9.sh
[root@waky bash_practice]# cat script_9.log
first_script.sh
script_2.sh
script_3.sh
script_4.sh
script_5.sh
script_6.sh
script_7.sh
script_8.sh
script_9.sh
ls: cannot access '*.jpg': No such file or directory
[root@waky bash_practice]#

Оба потока были перенаправлены в один файл.

Итоги:

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

Следующий урок: bash скрипты для начинающих — урок №10: Функции.