Предыдущий урок: bash скрипты для начинающих — урок №8: Цикл по строкам файла.
До этого урока каждый наш скрипт возвращал результат выполнения непосредственно в консоль. Это удобно если нам нужно получить результат здесь и сейчас. Но скрипты часто используются в автоматическом режиме, в таком случае результат стоит сохранить в файл, чтобы после можно было с ним ознакомиться.
Какие есть типы перенаправлений?
> — redirect stdout (стандартный вывод) в файл, перезаписывая его, если он существует.
>> — добавляет stdout в конец файла, оставляя существующие данные.
2> — перенаправляет стандартный поток ошибок (stderr) в файл.
&> или > file 2>&1 — перенаправляют оба потока (stdout и stderr) в один файл.
Перенаправление вывода
- Шаг 1: Перенаправляем вывод
- Шаг 2: Перенаправляем стандартные ошибки
- Шаг 3: Перенаправляем оба потока
Создадим новый файл скрипта и сделаем его исполняемым:
[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: Функции.