перенаправити КОПІЮВАННЯ stdout для входу файлу з самого bash script


235

Я знаю, як перенаправити stdout у файл:

exec > foo.log
echo test

це поставить 'тест' у файл foo.log.

Тепер я хочу перенаправити вихід у файл журналу І тримати його у stdout

тобто це можна зробити тривіально поза сценарієм:

script | tee foo.log

але я хочу заявити про це в самому сценарії

я намагався

exec | tee foo.log

але це не спрацювало.


3
Ваше питання погано сформульоване. Коли ви викликаєте "exec> foo.log", версією сценарію є файл foo.log. Я думаю , що ви маєте в виду , що ви хочете , щоб висновок піти в foo.log і на термінал, так як збирається foo.log це відбувається на стандартний висновок.
Вільям Перселл

що я хотів би зробити, це використовувати | на 'exec'. це було б ідеально для мене, тобто "exec | tee foo.log", на жаль, ви не можете використовувати перенаправлення труб під час виклику exec
Віталій Кушнер,

Відповіді:


297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Зауважте, що це bash, ні sh. Якщо ви будете викликати скрипт із sh myscript.sh, ви отримаєте помилку в рядках з syntax error near unexpected token '>'.

Якщо ви працюєте з сигнальними пастками, можливо, ви хочете скористатися tee -iопцією, щоб уникнути порушення виходу, якщо сигнал надходить. (Дякую JamesThomasMoon1979 за коментар.)


Інструменти, які змінюють свій вихід, залежно від того, записують вони в трубу чи термінал (наприклад, lsвикористовуючи кольори та колонний вихід), виявлять вищезгадану конструкцію як сенс, який вони виводять у трубу.

Існують варіанти примусового колоризації / колонізації (наприклад ls -C --color=always). Зауважте, що це призведе до того, що кольорові коди записуються також у файл реєстрації, що робить його менш читабельним.


5
Трійник у більшості систем буферний, тому вихід може не надходити до завершення сценарію. Крім того, оскільки цей трійник працює в допоміжній оболонці, а не дочірньому процесі, очікування не може використовуватися для синхронізації виводу в процес виклику. Те , що ви хочете , це небуферізованних версія трійника схожа на bogomips.org/rainbows.git/commit / ...

14
@Barry: POSIX вказує, що teeне слід буферувати його вихід. Якщо він робить буфер для більшості систем, він порушується на більшості систем. Це проблема teeреалізації, а не моє рішення.
DevSolar

3
@Sebastian: execдуже потужний, але також дуже активний . Ви можете "створити резервну копію" поточного stdout до іншого файлового сценарію, а потім відновити його згодом. "Підручник з виконання програм" з Google ", там є багато вдосконалених речей.
DevSolar

2
@AdamSpiers: Я також не впевнений, про що йшов Баррі. Bash - execце документально не починати нові процеси, >(tee ...)є стандартом по імені підміна труб / процес, і &в перенаправлення , звичайно , не має нічого спільного з ... :-) фоновий?
DevSolar

11
Я пропоную перейти -iдо tee. В іншому випадку переривання сигналу (пастки) порушить stdout в основному сценарії. Наприклад, якщо у вас є, trap 'echo foo' EXITа потім натисніть ctrl+c, ви не побачите " foo ". Тому я би змінив відповідь на exec &> >(tee -ia file).
JamesThomasMoon1979

173

Прийнята відповідь не зберігає STDERR як окремий дескриптор файлу. Це означає

./script.sh >/dev/null

не буде виводитися barна термінал, лише в файл реєстрації та

./script.sh 2>/dev/null

виведе fooі barв термінал. Зрозуміло, що це не така поведінка, яку звичайний користувач може очікувати. Це можна виправити, скориставшись двома окремими трійними процесами, приєднаними до одного файлу журналу:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Зверніть увагу, що вищезазначене спочатку не обрізає файл журналу - якщо ви хочете, щоб цю поведінку слід додати

>foo.log

на початок сценарію.)

Специфікація POSIX.1-2008tee(1) вимагає, щоб вихід був небуферним, тобто навіть не буферизованим рядком, тому в цьому випадку можливо, що STDOUT і STDERR могли опинитися в одному рядку foo.log; однак це може статися і на терміналі, тому файл журналу буде вірним відображенням того, що можна було побачити на терміналі, якщо не точне дзеркало його. Якщо ви хочете, щоб рядки STDOUT були чітко відокремлені від рядків STDERR, подумайте про використання двох файлів журналів, можливо, з префіксами штампу дати у кожному рядку, щоб згодом дозволити хронологічну повторну збірку.


Чомусь у моєму випадку, коли сценарій виконується з виклику системної системи (), два підпроцеси tee продовжують існувати навіть після виходу основного сценарію. Тож мені довелося додати такі пастки:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko

15
Я пропоную перейти -iдо tee. В іншому випадку переривання сигналу (пастки) порушить stdout у сценарії. Наприклад, якщо ви trap 'echo foo' EXITпотім натиснете ctrl+c, ви не побачите " foo ". Тому я би змінив відповідь на exec > >(tee -ia foo.log).
JamesThomasMoon1979

На основі цього я зробив декілька маленьких "джерельних" сценаріїв. Можна використовувати їх у такому сценарії, як . logабо . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Сем Уоткінс

1
Проблема цього методу полягає в тому, що повідомлення будуть STDOUTз’являтися спочатку як пакетні, а потім повідомлення STDERR. Вони не переплітаються, як зазвичай очікували.
CMCDragonkai

28

Рішення для busbox, macOS bash та не-bash оболонок

Прийнята відповідь, безумовно, найкращий вибір для баш. Я працюю в середовищі Busybox без доступу до bash, і він не розуміє exec > >(tee log.txt)синтаксис. Це також не робиться exec >$PIPEналежним чином, намагаючись створити звичайний файл з такою ж назвою, що і названа труба, яка виходить з ладу і зависає.

Сподіваємось, це було б корисно для когось іншого, хто не має башти.

Крім того, для всіх, хто використовує іменовану трубку, це безпечно rm $PIPE, оскільки це від’єднує трубу від VFS, але процеси, які використовують її, все ще підтримують посилання на неї, поки вони не будуть закінчені.

Зауважте, використання $ * не обов'язково є безпечним.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here

Це єдине рішення, яке я бачив до цих пір, що працює на Mac
Майк Багліо-молодший

19

Всередині файлу сценарію введіть усі команди в круглі дужки, наприклад:

(
echo start
ls -l
echo end
) | tee foo.log

5
педантично, також можна використовувати брекети ( {})
glenn jackman

ну так, я вважав це, але це не перенаправлення поточної схеми оболонки, її вид накрутки, ви насправді запускаєте нижню частину корпусу і робите регулярне перенаправлення на неї. працює думка. Я розділений з цим і рішенням "хвіст -f foo.log &". зачекаємо трохи, щоб побачити, чи може бути краще одна поверхня. якщо не ймовірно збиратися влаштовуватися;)
Віталій Кушнер

8
{} виконує список у поточному середовищі оболонки. () виконує список у середовищі додаткової оболонки.

Блін. Дякую. Прийнята відповідь там не працювала для мене, намагаючись запланувати сценарій для виконання під MingW в системі Windows. Думаю, він скаржився на безрезультатну заміну процесу. Ця відповідь спрацювала чудово, після наступної зміни, щоб захопити як stderr, так і stdout: `` `-) | tee foo.log +) 2> & 1 | tee foo.log
Джон Картер

14

Простий спосіб зробити журнал скриптів bash до syslog. Вихід сценарію доступний як через, так /var/log/syslogі через stderr. syslog додасть корисні метадані, включаючи часові позначки.

Додайте цей рядок вгорі:

exec &> >(logger -t myscript -s)

Крім того, надішліть журнал в окремий файл:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Для цього потрібно moreutils(для tsкоманди, яка додає часові позначки).


10

Використовуючи прийняту відповідь, мій сценарій повертався виключно рано (відразу після 'exec>> (tee ...)'), залишаючи решту мого сценарію у фоновому режимі. Оскільки я не міг змусити це рішення працювати так, як я знайшов інше рішення / вирішення проблеми:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Це змушує вихідний текст із сценарію переходити з процесу, через трубу в піддослідний процес 'tee', який записує все на диск і до оригінальної версії сценарію.

Зауважте, що "exec &>" перенаправляє і stdout, і stderr, ми можемо перенаправити їх окремо, якщо нам подобається, або змінити на "exec>", якщо ми просто хочемо stdout.

Навіть ви трубу вилучили з файлової системи на початку сценарію, вона продовжуватиме працювати, поки процеси не закінчаться. Ми просто не можемо посилатися на нього за допомогою імені файлу після рядка rm.


Подібний відповідь як друга ідея від Девіда Z . Погляньте на його коментарі. +1 ;-)
олібре

Добре працює. Я не розумію $logfileчастини tee < ${logfile}.pipe $logfile &. Зокрема, я намагався змінити це, щоб захопити повний розширений рядок журналу команд (від set -x) до файлу, показуючи лише рядки без ведучого "+" у stdout, змінивши на, (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &але отримав повідомлення про помилку стосовно $logfile. Чи можете ви пояснити teeрядок трохи детальніше?
Кріс Джонсон

Я перевірив це, і здається, що ця відповідь не зберігає STDERR (він об'єднаний з STDOUT), тому якщо ви покладаєтесь на те, що потоки будуть відокремленими для виявлення помилок або іншого перенаправлення, слід переглянути відповідь Адама.
HeroCC


1

Не можу сказати, що мені комфортно будь-яке рішення, засноване на exec. Я вважаю за краще використовувати tee безпосередньо, тому я роблю сценарій виклику сам з tee, коли запит:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

Це дозволяє вам зробити це:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Ви можете налаштувати це, наприклад, зробити tee = false за замовчуванням, зробити TEE тримати файл журналу замість цього і т. Д. Я думаю, що це рішення схоже на jbarlow, але простіше, можливо, у мене є обмеження, на які я ще не стикався.


-1

Жодне з них не є ідеальним рішенням, але ось кілька речей, які ви можете спробувати:

exec >foo.log
tail -f foo.log &
# rest of your script

або

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

Другий залишив би файл труби, що сидить навколо, якщо щось не так у вашому сценарії, що може бути, а може і не бути проблемою (тобто, можливо, ви зможете rmце згодом у батьківській оболонці).


1
хвост залишить запущений процес позаду у другому сценарії, трійник блокується, або вам потрібно буде запустити його з & в такому випадку він залишить процес, як у 1-му.
Віталій Кушнер

@Vitaly: ой, забув тло tee- я відредагував. Як я вже сказав, це не є ідеальним рішенням, але фонові процеси загинуть, коли закінчується їх батьківська оболонка, тож вам не доведеться турбуватися про них, коли вони будуть навішувати ресурси.
David Z

1
Yikes: це виглядає привабливо, але вихід хвоста -f також збирається foo.log. Ви можете це виправити, виконавши хвіст -f перед exec, але хвіст все ще залишається запущеним після того, як закінчується батьків. Вам потрібно явно вбити його, ймовірно, у пастку 0.
Вільям Перселл

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