Принудительно сбрасывать вывод в файл, пока скрипт bash все еще запущен

89

У меня есть небольшой скрипт, который ежедневно вызывается crontab с помощью следующей команды:

/homedir/MyScript &> some_log.log

Проблема с этим методом заключается в том, что some_log.log создается только после завершения MyScript. Я хотел бы сбрасывать вывод программы в файл во время ее работы, чтобы я мог делать такие вещи, как

tail -f some_log.log

и следить за прогрессом и т. д.

оламундо
источник
Нам нужно будет иметь описание - или, если возможно, код - того, что именно делает ваш небольшой скрипт ...
ChristopheD
7
Чтобы отключить буферизацию скриптов Python, вы можете использовать «python -u». Чтобы разблокировать сценарии Perl, см. Ответ Грега Хьюгилла ниже. И так далее ...
Элоичи
Если вы можете редактировать скрипт, вы обычно можете явно очистить выходной буфер внутри скрипта, например, в Python с помощью sys.stdout.flush().
drevicko

Ответы:

28

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

Крис Додд
источник
23
Я действительно не понимаю этого ответа.
Альфонсо Сантьяго
2
Чтобы лучше понять, почему стандартный вывод ведет себя так, посетите stackoverflow.com/a/13933741/282728 . Краткая версия - по умолчанию при перенаправлении в файл stdout полностью буферизуется; он записывается в файл только после сброса. Stderr нет - он пишется после каждого символа '\ n'. Одним из решений является использование команды 'script', рекомендованной пользователем3258569 ниже, чтобы stdout сбрасывался после каждого конца строки.
Alex
Говоря очевидное, и десять лет спустя, но это комментарий, а не ответ, и это не должно быть общепринятым ответом.
RealHandy
84

Я нашел решение здесь . Используя пример OP, вы в основном запускаете

stdbuf -oL /homedir/MyScript &> some_log.log

а затем буфер очищается после каждой строки вывода. Я часто совмещаю это с nohupвыполнением длительных заданий на удаленной машине.

stdbuf -oL nohup /homedir/MyScript &> some_log.log

Таким образом, ваш процесс не будет отменен при выходе из системы.

Мартин Вибуш
источник
1
Не могли бы вы добавить ссылку на какую-нибудь документацию для stdbuf? Основываясь на этом комментарии, похоже, что он недоступен в некоторых дистрибутивах. Не могли бы вы уточнить?
Иск Фонда Моники
1
stdbuf -o регулирует буферизацию стандартного вывода. Другие параметры - это -i и -e для stdin и stderr. L устанавливает буферизацию строки. Можно также указать размер буфера или 0, если буферизация не требуется.
Сеппо Энарви
5
эта ссылка больше не доступна.
Fzs2
2
@NicHartley: stdbufявляется частью GNU coreutils, документацию можно найти на gnu.org
Тор,
В случае, если это кому-то поможет, используйте, export -f my_function а затем, stdbuf -oL bash -c "my_function -args"если вам нужно запустить функцию вместо скрипта
анонимный
28
script -c <PROGRAM> -f OUTPUT.txt

Ключ -f. Цитата из сценария человека:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

Работать в фоновом режиме:

nohup script -c <PROGRAM> -f OUTPUT.txt
user3258569
источник
Вот это да! Решение, которое работает busybox! (моя оболочка потом замерзает, но все равно)
Виктор Сергиенко
9

Вы можете использовать teeдля записи в файл без необходимости промывки.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null
зубчатый
источник
2
Это по-прежнему буферизует вывод, по крайней мере, в моей среде Ubuntu 18.04. Содержимое в конечном итоге записывается в файл в любом случае, но я думаю, что OP запрашивает метод, с помощью которого они могут более точно отслеживать прогресс до того, как файл будет завершен, и этот метод не позволяет этого больше, чем перенаправление вывода делает.
mltsy
3

Это не функция bash, поскольку все, что делает оболочка, - это открывает файл, о котором идет речь, а затем передает дескриптор файла в качестве стандартного вывода скрипта. Что вам нужно сделать, так это убедиться, что вывод из вашего скрипта сбрасывается чаще, чем вы сейчас.

Например, в Perl это можно сделать, установив:

$| = 1;

См. Perlvar для получения дополнительной информации об этом.

Грег Хьюгилл
источник
2

Буферизация вывода зависит от того, как ваша программа /homedir/MyScriptреализована. Если вы обнаружите, что вывод буферизируется, вы должны принудительно использовать его в своей реализации. Например, используйте sys.stdout.flush (), если это программа на Python, или используйте fflush (stdout), если это программа C.

Мидас
источник
2

Это поможет?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

Это немедленно отобразит уникальные записи из access.log с помощью утилиты stdbuf .

Ондра Жижка
источник
Единственная проблема заключается в том, что stdbuf кажется какой-то старой утилитой, которая недоступна в новых дистрибутивах.
Ондра Жижка
..не в моем busybox :(
Campa
На самом деле у меня есть stdbufдоступ к Ubuntu на данный момент, не знаю, где я его взял.
Ondra ižka
У меня stdbuf в Centos 7.5
Макс
1
В Ubuntu 18.04 stdbufявляется частью coreutilus(обнаруживается с помощью apt-file search /usr/bin/stdbuf).
Rmano 02
1

Как только что заметили здесь проблему, вам нужно подождать, пока программы, которые вы запускаете из своего скрипта, завершат свою работу.
Если в вашем скрипте вы запускаете программу в фоновом режиме, вы можете попробовать что-то еще.

В общем, вызов syncперед выходом позволяет очистить буферы файловой системы и может немного помочь.

Если в сценарии вы запускаете некоторые программы в фоновом режиме ( &), вы можете дождаться их завершения, прежде чем выходить из сценария. Чтобы получить представление о том, как он может работать, вы можете увидеть ниже

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=${!}   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait ${!}            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=${!}   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

Поскольку waitработает как с заданиями, так и с PIDчислами, ленивое решение следует поместить в конец скрипта.

for job in `jobs -p`
do
   wait $job 
done

Сложнее ситуация, если вы запускаете что-то, что запускает что-то еще в фоновом режиме, потому что вам нужно искать и ждать (если это так) конца всего дочернего процесса: например, если вы запускаете демон, вероятно, это не так ждать его окончания :-).

Заметка:

  • wait $ {!} означает «дождаться завершения последнего фонового процесса», где $!PID последнего фонового процесса. Таким образом, установка wait ${!}сразу после этого program_2 &эквивалентна выполнению напрямую program_2без отправки в фоновом режиме с помощью&

  • С помощью wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    
Хастур
источник
1

Спасибо @user3258569, возможно, скрипт - единственное, что работает busybox!

А вот после этого снаряд у меня замерз. В поисках причины я обнаружил эти большие красные предупреждения «не используйте в неинтерактивных оболочках» на странице руководства по скрипту :

scriptв первую очередь предназначен для интерактивных терминальных сессий. Когда stdin не является терминалом (например:) echo foo | script, тогда сеанс может зависнуть, поскольку интерактивная оболочка в сеансе сценария пропускает EOF и scriptне знает, когда закрыть сеанс. См. Раздел ПРИМЕЧАНИЯ для получения дополнительной информации.

Правда. script -c "make_hay" -f /dev/null | grep "needle"замораживал для меня оболочку.

В соответствии с предупреждением, я подумал, что echo "make_hay" | scriptПРОЙДЕТ EOF, поэтому я попытался

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

и это сработало!

Обратите внимание на предупреждения на странице руководства. Это может не сработать для вас.

Виктор Сергиенко
источник
0

Альтернативой stdbuf является то, что awk '{print} END {fflush()}' я бы хотел, чтобы для этого был встроенный bash. Обычно в этом нет необходимости, но в более старых версиях могут быть ошибки синхронизации bash в файловых дескрипторах.

Брайан Крисман
источник
-2

Не знаю, сработает ли это, но как насчет звонка sync?

вилка
источник
1
syncявляется операцией низкоуровневой файловой системы и не имеет отношения к буферизованному выводу на уровне приложения.
Грег Хьюгилл,
2
syncпри необходимости записывает все грязные буферы файловой системы в физическое хранилище. Это внутреннее для ОС; приложения, работающие поверх ОС, всегда видят целостное представление о файловой системе независимо от того, записаны ли блоки диска в физическое хранилище. Что касается исходного вопроса, приложение (сценарий), вероятно, буферизует вывод во внутреннем буфере приложения, и ОС даже не узнает (пока), что вывод фактически предназначен для записи в стандартный вывод. Таким образом, гипотетическая операция типа «синхронизация» не сможет «проникнуть в» скрипт и извлечь данные.
Грег Хьюгилл,
-2

У меня была эта проблема с фоновым процессом в Mac OS X с использованием StartupItems. Вот как я это решаю:

Если я сделаю, sudo ps auxя увижу, что mytoolзапускается.

Я обнаружил, что (из-за буферизации) при выключении Mac OS X mytoolникогда не передает вывод в sedкоманду. Однако, если я выполняю sudo killall mytool, то mytoolпередаю вывод sedкоманде. Следовательно, я добавил к stopслучаю, StartupItemsкоторый выполняется при выключении Mac OS X:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;
Freeman
источник
Это действительно не лучший ответ Freeman, поскольку он очень специфичен для вашей среды. OP хочет контролировать вывод, а не убивать его.
Грей
-3

ну нравится это или нет, вот как работает перенаправление.

В вашем случае вывод (то есть ваш сценарий завершен) вашего сценария перенаправлен в этот файл.

Что вы хотите сделать, так это добавить эти перенаправления в свой сценарий.


источник