Примусово змити вихідні дані у файл, поки скрипт 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', рекомендованої користувачем нижче, для того, щоб stdout змивався після кожного кінця рядка.
Олексій

Зазначаючи очевидне, і через десять років, але це коментар, а не відповідь, і це не повинно бути прийнятою відповіддю.
RealHandy

84

Я знайшов вирішення цього питання тут . На прикладі OP ви в основному запускаєте

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

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

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

Таким чином ваш процес не скасовується при виході з системи.


1
Не могли б ви додати посилання на якусь документацію для stdbuf? На основі цього коментаря здається, що він недоступний на деяких дистрибутивах. Не могли б ви пояснити?
Позовні фонди Моніки

1
stdbuf -o регулює буферизацію stdout. Інші варіанти: -i та -e для stdin та stderr. L встановлює буферизацію рядків. Можна також вказати розмір буфера або 0 для відсутності буферизації.
Seppo Enarvi

5
це посилання більше не доступне.
Fzs2

2
@NicHartley: stdbufє частиною coreutils GNU, документацію можна знайти на gnu.org
Тор,

Якщо це комусь допоможе, скористайтесь export -f my_function і тоді, stdbuf -oL bash -c "my_function -args"якщо вам потрібно запустити функцію замість скрипта
Anonymous

28
script -c <PROGRAM> -f OUTPUT.txt

Ключ -f. Цитата з man script:

-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

Оце Так! Рішення, яке працює busybox! (моя оболонка згодом замерзає, але що завгодно)
Віктор Сергієнко

9

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

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null

2
Це все ще буферизує вихід, принаймні в моєму середовищі Ubuntu 18.04. Зміст в кінцевому підсумку записується у файл у будь-який спосіб, але я думаю, що операційна система запитує метод, за допомогою якого вони можуть точніше відстежувати прогрес до завершення запису файлу, і цей метод не дозволяє цього, окрім перенаправлення вихідних даних робить.
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 здається якоюсь старою утилітою, яка недоступна на нових дистрибутивах.
Ondra Žižka

..но в моєму зайнятому вікні :(
Кампа,

Насправді я зараз маю stdbufдоступ до Ubuntu, не впевнений, де його взяв.
Ondra Žižka

У мене є stdbuf в Centos 7.5
макс.

1
В Ubuntu 18.04 stdbufє частиною coreutilus(знайдено з apt-file search /usr/bin/stdbuf).
Rmano,

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" | scriptEOF, тому спробував

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

і це спрацювало!

Зверніть увагу на попередження на сторінці довідок. Це може не спрацювати для вас.


0

Альтернативою stdbuf є те, що awk '{print} END {fflush()}' я хотів би, щоб для цього був вбудований bash. Зазвичай це не повинно бути необхідним, але в старих версіях можуть бути помилки синхронізації bash у дескрипторах файлів.


-2

Не знаю, чи могло б це спрацювати, але як щодо дзвінків sync?


1
syncє операцією файлової системи низького рівня і не пов'язана з буферизованим виведенням на рівні програми.
Greg Hewgill

2
syncза потреби записує будь-які брудні буфери файлової системи у фізичне сховище. Це внутрішнє для ОС; програми, що працюють поверх ОС, завжди бачать узгоджений вигляд файлової системи, незалежно від того, записані дискові блоки у фізичне сховище. Що стосується оригінального питання, програма (сценарій), ймовірно, буферизує висновок у внутрішньому буфері програми, і ОС навіть навіть не знатиме (поки), що вихід фактично призначений для запису в stdout. Отже, гіпотетична операція типу "синхронізація" не зможе "дотягнутися" до сценарію та витягти дані.
Грег Хьюгілл,

-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
    ;;

Це насправді не є гарною відповіддю Фрімена, оскільки це дуже специфічно для вашого оточення. OP хоче контролювати вихід, а не вбивати його.
Сірий

-3

добре подобається це чи ні, так працює переспрямування.

У вашому випадку вихід (тобто ваш сценарій закінчився) вашого сценарію перенаправлено на цей файл.

Що ви хочете зробити, це додати ці перенаправлення у ваш сценарій.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.