Убунту 22.04
У меня есть длительная команда, которая выводит вывод на консоль.
Я хотел бы немедленно перенаправить вывод этой команды (stdout и stderr) в файл журнала, пока есть какой-либо вывод для команды, и в то же время я хотел бы, чтобы этот же вывод также был сохранен в переменной. Я не хочу, чтобы в консоли печатались какие-либо строки.
В основном я хочу этого:
output=$(${long_running_command} 2>&1)
echo "${output} >> /tmp/file.log"
Вывод сохраняется в переменной, это все хорошо. Единственная проблема с этим кодом выше заключается в том, что выходные данные печатаются в файл журнала только после завершения команды long_running_command.
Я ищу способ добиться того, чтобы запись файла журнала происходила немедленно, когда команда выдает какой-либо вывод.
Я также попробовал это:
output=$(${long_running_command} >> /tmp/file.log 2>&1)
Для этого я могу сразу же получить перенаправление в файл журнала, пока что-то выводится. Но здесь переменная $output совершенно пуста.
Допустим, пример long_running_command следующий:
output=$(for i in 1 2 3 4 5; do echo "$(date): Sleep: $i"; sleep $i; done >> /tmp/file.log 2>&1)
echo "$output"
Пока команда выполняется и я отслеживаю файл журнала, я могу немедленно получить выходные данные, пока это происходит. Но проблема в том, что переменная $output совершенно пуста.
# tail -100f /tmp/file.log
Wed Aug 14 10:40:20 PM EEST 2024: Sleep: 1
Wed Aug 14 10:40:21 PM EEST 2024: Sleep: 2
Wed Aug 14 10:40:23 PM EEST 2024: Sleep: 3
Wed Aug 14 10:40:26 PM EEST 2024: Sleep: 4
Wed Aug 14 10:40:30 PM EEST 2024: Sleep: 5
# output=$(for i in 1 2 3 4 5; do echo "$(date): Sleep: $i"; sleep $i; done >> /tmp/file.log 2>&1)
# echo "$output"
<--- nothing, empty row
Похоже это работа для tee
...
Если вы хотите записать весь вывод (stdout + stderr) в переменную:
$ output = "$(for i in 1 2 3 4 5; do date; ./do_some_stuff; sleep 1; done 2>&1 | tee -a file.log )"
Где:
./do_some_stuff
- не существует, поэтому должна выдать ошибку2>&1
— перенаправить stderr на stdouttee -a file.log
- разделить ввод на 2 потока; один поток добавляется в файл file.log, второй поток отправляется на стандартный выводРезультаты:
$ typeset -p output
declare -- output = "Wed Aug 14 15:01:04 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:05 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:06 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:07 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:08 CDT 2024
bash: ./do_some_stuff: No such file or directory"
$ cat file.log
Wed Aug 14 15:01:04 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:05 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:06 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:07 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:01:08 CDT 2024
bash: ./do_some_stuff: No such file or directory
Один из подходов, если вы хотите, чтобы stderr обращался только к файлу:
$ output = "$(for i in 1 2 3 4 5; do date; ./do_some_stuff; sleep 1; done 2>>file.log | tee -a file.log )"
Где:
-a
— явно добавить stderr в файл file.log, чтобы...2>>file.log
Результаты:
$ typeset -p output
declare -- output = "Wed Aug 14 15:02:21 CDT 2024
Wed Aug 14 15:02:22 CDT 2024
Wed Aug 14 15:02:23 CDT 2024
Wed Aug 14 15:02:25 CDT 2024
Wed Aug 14 15:02:26 CDT 2024"
$ cat file.log
Wed Aug 14 15:02:21 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:02:22 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:02:23 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:02:25 CDT 2024
bash: ./do_some_stuff: No such file or directory
Wed Aug 14 15:02:26 CDT 2024
bash: ./do_some_stuff: No such file or directory
Спасибо большое, это именно то, что мне нужно :)
На самом деле, я забыл упомянуть, что при использовании этой команды «tee» я потеряю код возврата скрипта. Когда я получаю сообщение об ошибке, это дает мне 0 (эхо "$?")
@RaulKaubi, возможно, вы захотите узнать, может ли быть полезен массив PIPESTATUS[]
(например, см. это; это сложно, поскольку PIPESTATUS[]
устанавливается внутри подоболочки и «исчезает» при выходе из подоболочки; одним из вариантов может быть выгрузить содержимое массива в другой файл, а затем обработать этот файл для кодов выхода, например, x=$(./invalid_script | grep hello; typeset -p PIPESTATUS > pipe.out 2>&1)
для моих примеров это еще сложнее, поскольку ошибка встроена в цикл for
, который генерирует свой собственный код возврата при выходе....
если вам не нужен фактический код возврата, а нужно узнать, произошла ли ошибка, тогда другой вариант — принудительно перенести весь вывод stderr в еще один файл (например, 2>stderr.out
), а затем проверить, пуст ли файл; для нескольких команд/каналов вы, вероятно, захотите просмотреть запись каждой отдельной команды в отдельный файл stderrX.out
.
если вы не можете получить рабочее решение по сбору кодов возврата (или определению статуса ошибки), я бы предложил задать новый вопрос
Хорошо, спасибо. Я что-нибудь придумаю.
Если вам действительно нужна переменная, содержащая выходные данные команды после завершения вашей команды, почему бы просто не прочитать содержимое файла в переменную в это время, например.
long_running_command > /tmp/file.log; IFS= read -rd '' output < /tmp/file.log
? Хранение содержимого всего файла в переменной обычно не лучший подход к решению любой проблемы, возможно, есть более эффективные способы сделать то, что вы пытаетесь сделать. Это похоже на проблему XY.