Вопрос или проблема
У меня есть скрипт на 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
Объяснение изменений
-
Группировка команд: Использование фигурных скобок
{ ... }
обеспечивает выполнение всех команд в одной группе. Это позволяетtee
получать весь вывод сразу, что исключает задержку, вызванную фоновой работой процесса. -
Перенаправление ошибок:
2>&1
остается в конце, что позволяет перенаправлять стандартный поток ошибок в стандартный поток вывода, но в этом случае это происходит сразу, как только программа завершает свое выполнение. -
Упрощение: Удаление шага с
rm -f $LOG
упрощает код, так какtee
по умолчанию всегда перезаписывает файл.
Заключение
Изменяя структуру вашего скрипта, вы устраняете проблему, связанную с ожиданием ввода после завершения. Теперь, когда вы выполните скрипт, приглашение командной строки снова будет появляться корректно. Понимание работы перенаправлений и фоновых процессов в bash-контексте поможет вам избегать подобных ситуаций в будущем.