Проблема перенаправления вывода в файл журнала и консоль в bash-скрипте

Вопрос или проблема

У меня есть скрипт на shell, который успешно перенаправляет весь вывод в лог-файл и в стандартный вывод (консоль) одновременно. Однако, когда он завершается, кажется, что он ожидает ввода от пользователя с клавиатуры (экспериментально, любая клавиша работает)… Итак, я запускаю скрипт, вижу вывод, он завершается, и мне нужно нажать, например, пробел, прежде чем появится приглашение терминала. Ввод echo $? затем дает правильный код выхода.

Основы скрипта следующие:

#!/bin/bash
LOG="./test.log"
rm -f $LOG
exec > >(tee $LOG) 2>&1
if [[ "$1" = "T" ]]; then
 echo "это было правдой..."
 exit 0
else
 echo "это было ложью..."
 exit 100
fi

Любая помощь будет приветствоваться… я не только не хочу нажимать пробел, но и хотел бы понять, что происходит?

Дополнение: похоже, что мне нужно нажать Enter, чтобы вернуть приглашение терминала. Судя по моему ограниченному опыту использования команды ps, похоже, что мой скрипт завершился, и оболочка bash находится в состоянии прерываемого сна S. Кажется, что, возможно, вывод приглашения терминала был потреблен или неправильно перенаправлен? Я не вижу причин, почему…

Я могу воспроизвести вашу проблему на своем компьютере. Я вижу, что $PS1 выводится в момент выполнения tee $LOG, но не после завершения скрипта. После выполнения скрипта командная строка пустая, но вы на самом деле можете ввести вашу следующую команду, и она будет выполнена.

Это выглядит так, будто подстановка процесса >() запускает tee в фоновом режиме. Затем exec перенаправляет весь вывод на этот фоновый процесс. Но tee из фона все еще выводит свой вывод в терминал. Вы можете увидеть что-то подобное, если выполните syslog | tee&

В момент, когда весь вывод оболочки поступил в фоновый процесс tee, bash вывел командный запрос $PS1, что означает, что вы уже можете вводить новые команды. И только после того, как был выведен командный запрос, ваша команда echo вывела свой вывод в фоновый процесс tee, и затем tee вывел его в терминал.

Это как будто вы запустили весь скрипт в фоновом режиме.

Примерно так я вижу, почему ваш командный запрос не был выведен после выполнения всего скрипта. Как echo "это было правдой..." | tee $LOG&. Здесь & означает “запустить предшествующую команду в фоновом режиме”.

Попробуйте следующий код. Он сделает то же самое, но без этой проблемы.

#!/bin/bash
LOG="./test.log"
rm -f $LOG
{
    if [[ "$1" = "T" ]]; then
     echo "это было правдой..."
     exit 0
    else
     echo "это было ложью..."
     exit 100
    fi
} | tee $LOG 2>&1

Кроме того, по умолчанию tee перезаписывает файл, так что выглядит так, что rm -f $LOG не требуется.

Ответ или решение

Проблема с выводом в лог файл и консоль в bash скрипте

Введение

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

Описание проблемы

Как указано в вашем вопросе, ваш bash скрипт использует следующую конструкцию для перенаправления вывода:

#!/bin/bash
LOG="./test.log"
rm -f $LOG
exec > >(tee $LOG) 2>&1
if [[ "$1" = "T" ]]; then
 echo "twas true..."
 exit 0
else
 echo "twas false..."
 exit 100
fi

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

Причины возникновения проблемы

Причина заключается в использовании process substitution (>(...)), которое создает новый экземпляр процесса tee в фоновом режиме. Когда скрипт выполняется, командный интерпретатор bash начинает отправлять вывод в этот фоновой процесс. Однако так как процесс tee все еще выполняется и ожидает завершения всех свойств, это может привести к ситуации, когда терминал не отображает приглашение командной строки, пока все данные не будут обработаны.

Решение проблемы

Чтобы избежать этой проблемы, вы можете изменить структуру скрипта, используя группировку команд с помощью фигурных скобок и отправлять вывод в tee прямо вместо использования exec. Пример кода:

#!/bin/bash
LOG="./test.log"
# Удаление лог-файла, если он существует, не обязательно, так как tee по умолчанию перезаписывает файл
{
    if [[ "$1" = "T" ]]; then
        echo "twas true..."
        exit 0
    else
        echo "twas false..."
        exit 100
    fi
} | tee $LOG 2>&1

Объяснение изменений

  1. Группировка команд: Использование фигурных скобок { ... } обеспечивает выполнение всех команд в одной группе. Это позволяет tee получать весь вывод сразу, что исключает задержку, вызванную фоновой работой процесса.

  2. Перенаправление ошибок: 2>&1 остается в конце, что позволяет перенаправлять стандартный поток ошибок в стандартный поток вывода, но в этом случае это происходит сразу, как только программа завершает свое выполнение.

  3. Упрощение: Удаление шага с rm -f $LOG упрощает код, так как tee по умолчанию всегда перезаписывает файл.

Заключение

Изменяя структуру вашего скрипта, вы устраняете проблему, связанную с ожиданием ввода после завершения. Теперь, когда вы выполните скрипт, приглашение командной строки снова будет появляться корректно. Понимание работы перенаправлений и фоновых процессов в bash-контексте поможет вам избегать подобных ситуаций в будущем.

Оцените материал
Добавить комментарий

Капча загружается...