Запис вихідних даних у файл журналу та консоль


98

У оболонці Unix у мене є файл env (файл env визначає параметри, необхідні для запуску скрипта користувача, як ім'я та шлях журналу журналу, перенаправлення виходів та помилок у файл журналу, деталі підключення до бази даних тощо ), який перенаправляє всі результати ( ехо-повідомлення ) та помилки у файлі журналу із запущеного сценарію, використовуючи такий код:

exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

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

Але є деякі вибіркові результати користувача, які я хочу відображати як у консолі, так і у файлі журналу. Але через вищезазначений код я не можу цього зробити.

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

Чи є спосіб отримати вихідні дані як у консолі, так і у файлі журналу, не видаляючи наведені вище коди?

Відповіді:


105
exec 3>&1 1>>${LOG_FILE} 2>&1

надсилатиме вихідні дані stdout та stderr у файл журналу, але також залишив би вас fd 3, підключеним до консолі, щоб ви могли це зробити

echo "Some console message" 1>&3

щоб написати повідомлення просто на консоль, або

echo "Some console and log file message" | tee /dev/fd/3

написати повідомлення як на консоль, так і на файл журналу - teeнадсилає свої результати як на власний fd 1 (який тут є LOG_FILE), так і на файл, якому ви наказали писати (що тут є fd 3, тобто на консоль).

Приклад:

exec 3>&1 1>>${LOG_FILE} 2>&1

echo "This is stdout"
echo "This is stderr" 1>&2
echo "This is the console (fd 3)" 1>&3
echo "This is both the log and the console" | tee /dev/fd/3

надрукував би

This is the console (fd 3)
This is both the log and the console

на консолі і поставити

This is stdout
This is stderr
This is both the log and the console

у файл журналу.


2
Це спрацювало так, як ви запропонували. Але я не зрозумів tee / dev / fd / 3 . Я знаю, що tee пише повідомлення у файл журналу та консолі, але я точно не розумів / dev / fd / 3, що використовується після tee
abinash shrestha

@shrestha, це незвичне використання трійника, я згоден. Це /dev/fd/3ім'я файлу, яке посилається на "поточно відкритий fd 3", тому tee /dev/fd/3буде писати все, що надходить на його stdin, у fd 1, а також fd 3 ( /dev/fd/3файл). Fd 1 підключений до файлу журналу, fd 3 - до консолі.
Ян Робертс,

Крім того, ви хочете лише писати у файл, а не на консолі, спробуйте: echo "blah" | tee file1.txt | tee file2.txt >/dev/null "Blah" не буде розміщено у файлах file1.txt & file2.txt, але не буде записано на консоль.
danger89

Це мені дуже допомогло. Хоча я помітив щось, що може бути поза темою, але, можливо, хтось із вас знає причини цього. Я виконую R-сценарії з bash-сценарію. Висновок консолі різних функцій R кольоровий (як я це визначив). при використанні вищевказаного підходу для перенаправлення вихідних даних на консоль І журнал, вихід консолі більше не забарвлюється. Що може бути причиною цього?
dieHellste

1
@dieHellste деякі програми можуть виявляти, коли їх вихід передається в інший процес (у цьому випадку tee, який, у свою чергу, пише в термінал), а не переходити безпосередньо до терміналу, і налаштовувати їх вихід на відповідність.
Ян Робертс,

43

Так, ви хочете використовувати tee:

трійник - читання зі стандартного вводу та запис у стандартний вихід та файли

Просто передайте свою команду в трійник і передайте файл як аргумент, наприклад:

exec 1 | tee ${LOG_FILE}
exec 2 | tee ${LOG_FILE}

Це одночасно друкує вихід у STDOUT і записує той самий результат у файл журналу. Див. Для man teeотримання додаткової інформації.

Зверніть увагу, що це не буде писати stderr у файл журналу, тому, якщо ви хочете об'єднати два потоки, використовуйте:

exec 1 2>&1 | tee ${LOG_FILE}

2
Вищезгадане рішення не спрацювало. У мене є файл redirect.env як: ##### redirect.env ###### export LOG_FILE = log.txt exec 1 2> & 1 | трійник -a $ {LOG_FILE} exec 1 | трійник -a $ {LOG_FILE} exec 2 | tee -a $ {LOG_FILE} ########## і робочий файл містив такий код: ##### output.sh ##### #! / bin / sh. redirect.env echo "Дійсний вихід" ech "недійсний результат" ############## але я отримую помилку: #### redirect.env: рядок 3: exec: 1: не знайдено redirect.env: рядок 5: exec: 1: не знайдено redirect.env: рядок 6: exec: 2: не знайдено #### і у файлі журналу я також отримую ту ж помилку. Я роблю щось не так?
abinash shrestha

Це важко сказати, оскільки нові рядки зачищені у вашому коментарі. Не могли б ви додати вставку коду десь на зразок gist ?
Джон Кернс,

1
Я додав файли до посилання UnixRedirect . Пов’язані файли - redirect.env та output.sh
abinash shrestha

1
Здається, цей код не працює (принаймні, більше не працює). Зазвичай ви отримувалиscript.sh: line 5: exec: 1: not found
tftd

39

Я спробував відповідь Джунті, але також отримав

exec: 1: не знайдено

помилка. Це найкраще для мене працює ( підтверджено, що це працює також у zsh):

#!/bin/bash
LOG_FILE=/tmp/both.log
exec > >(tee ${LOG_FILE}) 2>&1
echo "this is stdout"
chmmm 77 /makeError

Потім файл /tmp/both.log містить

this is stdout
chmmm command not found 

/Tmp/both.log додається, якщо ви не видалите -a з трійника.

Підказка: >(...)це заміна процесу. Це дозволяє execв teeкоманді , як якщо б це був файл.


1
Це спрацювало як принада для мене, тоді як інші відповіді були вражені або пропущені.
Jay Taylor

Дякуємо, що поділилися цим фрагментом. Здається, це працює чудово (порівняно з іншими відповідями)!
tftd

Це працює, але тепер моя оболонка інша після виконання.
Джош Усре

@JoshUsre Якщо вам потрібна допомога або ви хочете допомогти іншим користувачам SO, будь ласка, поясніть, що відрізняється, і опишіть свою оболонку, версію, ОС тощо.
alfonx

1
Якщо вам не потрібно розрізняти stdout та stderr, ви можете об'єднати оператори з exec > >(tee ${LOG_FILE}) 2>&1.
darkdragon

5

Я хотів відобразити журнали на stdout та файл журналу разом із позначкою часу. Жодна з наведених відповідей не спрацювала для мене. Я використав заміну процесу та команду exec і придумав такий код. Зразки журналів:

2017-06-21 11:16:41+05:30 Fetching information about files in the directory...

Додайте наступні рядки у верхній частині сценарію:

LOG_FILE=script.log
exec > >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done)
exec 2> >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done >&2)

Сподіваюся, це комусь допоможе!


Чи можна реєструвати всі помилки програми, щоб увійти в хост-каталог, щоб це допомогло devops ....
Prasad

3

для журналу ви можете дати дату введення в текстові дані. наступний код може допомогти

# declaring variables

Logfile="logfile.txt"   
MAIL_LOG="Message to print in log file"  
Location="were is u want to store log file"

cd $Location   
if [ -f $Logfile ]  
then   
echo "$MAIL_LOG " >> $Logfile

else        

touch $Logfile   
echo "$MAIL_LOG" >> $Logfile    

fi  

ouput: 2. Файл журналу буде створений під час першого запуску та продовжуватиме оновлення з наступних запусків. Якщо у майбутньому запуску файл журналу відсутній, скрипт створить новий файл журналу.


1

Я знайшов спосіб отримати бажаний результат. Хоча це може бути дещо неортодоксальним способом. Як би там не було. У файлі redir.env у мене є такий код:

#####redir.env#####    
export LOG_FILE=log.txt

      exec 2>>${LOG_FILE}

    function log {
     echo "$1">>${LOG_FILE}
    }

    function message {
     echo "$1"
     echo "$1">>${LOG_FILE}
    }

Тоді у власне скрипті я маю такі коди:

#!/bin/sh 
. redir.env
echo "Echoed to console only"
log "Written to log file only"
message "To console and log"
echo "This is stderr. Written to log file only" 1>&2

Тут ехо виводиться лише на консоль, журнал виводить лише на файл журналу, а повідомлення на вихід - як на файл журналу, так і на консоль.

Після запуску вищезазначеного файлу сценарію я маю такі результати:

У консолі

У консолі
Echoed для консолі лише
для консолі та журналу

Для журналу

Файл журналу Написано лише для файлу журналу
Це складніше. Написано лише для файлу журналу
Для консолі та журналу

Сподіваюся, що це допоможе.


1

Спробуйте це, це зробить роботу:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)

Це exec > >(tee -a ${log_file} )ідеально підходить для моїх потреб. Попередні рішення вище переривали б частини мого сценарію, які змушують вийти, якщо вони не вдалися. Дякую
MitchellK

1
    #
    #------------------------------------------------------------------------------
    # echo pass params and print them to a log file and terminal
    # with timestamp and $host_name and $0 PID
    # usage:
    # doLog "INFO some info message"
    # doLog "DEBUG some debug message"
    # doLog "WARN some warning message"
    # doLog "ERROR some really ERROR message"
    # doLog "FATAL some really fatal message"
    #------------------------------------------------------------------------------
    doLog(){
        type_of_msg=$(echo $*|cut -d" " -f1)
        msg=$(echo "$*"|cut -d" " -f2-)
        [[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
        [[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
        [[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well

        # print to the terminal if we have one
        test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg"

        # define default log file none specified in cnf file
        test -z $log_file && \
            mkdir -p $product_instance_dir/dat/log/bash && \
                log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
        echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg" >> $log_file
    }
    #eof func doLog

0

Я вважаю дуже корисним додати до файлу журналу як stdout, так і stderr. Я був радий бачити рішення від alfonx with exec > >(tee -a), бо мені було цікаво, як це зробити за допомогоюexec . Я натрапив на креативне рішення з використанням синтаксису here-doc та .: /unix/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -скрипт

Я виявив, що в zsh рішення тут-doc може бути змінено за допомогою конструкції "multios" для копіювання виводу в stdout / stderr та файл журналу:

#!/bin/zsh
LOG=$0.log
# 8 is an arbitrary number;
# multiple redirects for the same file descriptor 
# triggers "multios"
. 8<<\EOF /dev/fd/8 2>&2 >&1 2>>$LOG >>$LOG
# some commands
date >&2
set -x
echo hi
echo bye
EOF
echo not logged

Він не такий читабельний, як execрішення, але він має ту перевагу, що дозволяє реєструвати лише частину сценарію. Звичайно, якщо ви опустите EOF, то весь сценарій виконується з реєстрацією. Я не впевнений, як zshреалізує multios, але у нього може бути менше накладних витрат, ніж tee. На жаль, здається, що не можна використовувати мультио з exec.

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