Сохранять вывод команды в файл и в переменную одновременно

Убунту 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

Если вам действительно нужна переменная, содержащая выходные данные команды после завершения вашей команды, почему бы просто не прочитать содержимое файла в переменную в это время, например. long_running_command > /tmp/file.log; IFS= read -rd '' output < /tmp/file.log? Хранение содержимого всего файла в переменной обычно не лучший подход к решению любой проблемы, возможно, есть более эффективные способы сделать то, что вы пытаетесь сделать. Это похоже на проблему XY.

Ed Morton 15.08.2024 12:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Похоже это работа для 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 на stdout
  • tee -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

Спасибо большое, это именно то, что мне нужно :)

Raul Kaubi 17.08.2024 11:11

На самом деле, я забыл упомянуть, что при использовании этой команды «tee» я потеряю код возврата скрипта. Когда я получаю сообщение об ошибке, это дает мне 0 (эхо "$?")

Raul Kaubi 17.08.2024 11:22

@RaulKaubi, возможно, вы захотите узнать, может ли быть полезен массив PIPESTATUS[] (например, см. это; это сложно, поскольку PIPESTATUS[] устанавливается внутри подоболочки и «исчезает» при выходе из подоболочки; одним из вариантов может быть выгрузить содержимое массива в другой файл, а затем обработать этот файл для кодов выхода, например, x=$(./invalid_script | grep hello; typeset -p PIPESTATUS > pipe.out 2>&1) для моих примеров это еще сложнее, поскольку ошибка встроена в цикл for, который генерирует свой собственный код возврата при выходе....

markp-fuso 17.08.2024 17:27

если вам не нужен фактический код возврата, а нужно узнать, произошла ли ошибка, тогда другой вариант — принудительно перенести весь вывод stderr в еще один файл (например, 2>stderr.out), а затем проверить, пуст ли файл; для нескольких команд/каналов вы, вероятно, захотите просмотреть запись каждой отдельной команды в отдельный файл stderrX.out.

markp-fuso 17.08.2024 17:30

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

markp-fuso 17.08.2024 17:32

Хорошо, спасибо. Я что-нибудь придумаю.

Raul Kaubi 18.08.2024 19:58

Другие вопросы по теме