Я хочу перенаправити як stdout, так і stderr процесу в один файл. Як мені це зробити в Bash?
Я хочу перенаправити як stdout, так і stderr процесу в один файл. Як мені це зробити в Bash?
Відповіді:
#!/bin/bash
а не #!/bin/sh
, тому що в башті потрібен bash.
do_something 2>&1 | tee -a some_file
Це збирається перенаправити stderr на stdout і stdout some_file
і надрукувати на stdout.
do_something &>filename
не відповідає . +1.
Ambiguous output redirect.
чому?
$?
більше не стосується статусу виходу do_something
, а статусу виходу tee
.
Ви можете перенаправити stderr на stdout та stdout у файл:
some_command >file.log 2>&1
Див. Http://tldp.org/LDP/abs/html/io-redirection.html
Цей формат є кращим, ніж найпопулярніший &> формат, який працює лише в bash. У оболонці Bourne це можна інтерпретувати як виконання команди у фоновому режимі. Також формат є більш читабельним 2 (є STDERR), переспрямований на 1 (STDOUT).
EDIT: змінив порядок, як зазначено в коментарях
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Тепер простий ехо буде записаний у $ LOG_FILE. Корисно для демонізування.
Автору оригінальної публікації,
Це залежить того, що потрібно досягти. Якщо вам просто потрібно переадресувати вхід / вихід команди, яку ви викликаєте зі свого сценарію, відповіді вже надані. Шахта - це перенаправлення в поточному сценарії, яке впливає на всі команди / вбудовані модулі (включаючи вилки) після згаданого фрагмента коду.
Ще одне класне рішення стосується перенаправлення на std-err / out AND на реєстратор або файл журналу відразу, що включає розділення "потоку" на два. Цю функціональність забезпечує команда 'tee', яка може записувати / додавати відразу до декількох дескрипторів файлів (файлів, розеток, труб тощо): tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Отже, від початку. Припустимо, у нас є термінал, підключений до / dev / stdout (FD №1) та / dev / stderr (FD №2). На практиці це може бути труба, розетка чи що завгодно.
Результат запуску сценарію, що містить вищевказаний рядок та додатково цей:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
... таке:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Якщо ви хочете побачити більш чітке зображення, додайте до сценарію ці 2 рядки:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
доручає оболонці надіслати STDOUT до файлу file.log
, і 2>&1
каже йому перенаправити STDERR (дескриптор файлу 2) на STDOUT (дескриптор файлу 1).
Примітка . Порядок має значення, як вказував liw.fi, 2>&1 1>file.log
не працює.
Цікаво, що це працює:
yourcommand &> filename
Але це дає синтаксичну помилку:
yourcommand &>> filename
syntax error near unexpected token `>'
Ви повинні використовувати:
yourcommand 1>> filename 2>&1
&>>
здається, працює над BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Коротка відповідь: Command >filename 2>&1
абоCommand &>filename
Пояснення:
Розглянемо наступний код, який друкує слово "stdout" до stdout, а слово "stderror" - для stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Зауважте, що оператор "&" повідомляє bash, що 2 - це дескриптор файлу (який вказує на stderr), а не ім'я файлу. Якщо ми залишили "&", ця команда надрукувала б stdout
у stdout та створила файл з назвою "2" і записала stderror
туди.
Експериментуючи з наведеним вище кодом, ви можете самі зрозуміти, як працюють оператори перенаправлення. Наприклад, змінивши, який файл, який з двох дескрипторів 1,2
, перенаправляється на /dev/null
наступні два рядки коду, видаліть все з stdout, а все з stderror відповідно (надрукувавши те, що залишилося).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Тепер ми можемо пояснити, чому рішення, чому наступний код не дає результату:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Щоб справді зрозуміти це, настійно рекомендую прочитати цю веб-сторінку в таблицях дескрипторів файлів . Припускаючи, що ви зробили це читання, ми можемо продовжити. Зауважте, що Bash обробляє зліва направо; таким чином, Bash бачить >/dev/null
спочатку (що таке саме 1>/dev/null
), і встановлює дескриптор файлу 1 вказувати на / dev / null замість stdout. Зробивши це, Bash потім рухається праворуч і бачить 2>&1
. Це встановлює дескриптор файлу 2 вказувати на той самий файл, що і дескриптор файлу 1 (а не на сам дескриптор файлу 1 !!!! (див. Цей ресурс у покажчиках)для отримання додаткової інформації)). Оскільки дескриптор файлу 1 вказує на / dev / null, а дескриптор файлу 2 вказує на той самий файл, що і дескриптор файлу 1, дескриптор 2 файлів тепер також вказує на / dev / null. Таким чином, обидва дескриптори файлів вказують на / dev / null, і тому вихід не відображається.
Щоб перевірити, чи дійсно ви розумієте концепцію, спробуйте відгадати результат, коли ми переключимо порядок переадресації:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
стридер
Міркування тут полягає в тому, що, оцінюючи зліва направо, Баш бачить 2> & 1, і таким чином встановлює дескриптор файлу 2, щоб він вказував на те саме місце, що і дескриптор файлу 1, тобто stdout. Потім він встановлює дескриптор файлу 1 (пам'ятайте, що> / dev / null = 1> / dev / null), щоб вказати на> / dev / null, тим самим видаляючи все, що зазвичай надсилається до стандарту. Таким чином, нам залишається лише те, що не було відправлено на stdout в нижній підшипці (код у дужках) - тобто "stderror". Цікаве, що слід зазначити, що хоч 1 є лише вказівником на stdout, перенаправлення покажчика 2 на 1 через 2>&1
НЕ утворює ланцюжок покажчиків 2 -> 1 -> stdout. Якщо так, в результаті перенаправлення 1 на / dev / null, код2>&1 >/dev/null
дав би ланцюжок вказівників 2 -> 1 -> / dev / null, і, таким чином, код не створив би нічого, на відміну від того, що ми бачили вище.
Насамкінець зазначу, що існує простіший спосіб зробити це:
З розділу 3.6.4 тут ми бачимо, що ми можемо використовувати оператор &>
для перенаправлення як stdout, так і stderr. Таким чином, щоб перенаправити як stderr, так і stdout вихід будь-якої команди на \dev\null
(яка видаляє вихід), ми просто набираємо
$ command &> /dev/null
або у випадку мого прикладу:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Основні вивезення:
2>&1 >/dev/null
! = >/dev/null 2>&1
. Один генерує вихід, а інший - ні!Нарешті подивіться на ці чудові ресурси:
Документація Баша про перенаправлення , пояснення таблиць дескрипторів файлів , вступ до покажчиків
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Це пов’язано: Написання stdOut & stderr у syslog.
Це майже працює, але не від ксінтового; (
Я хотів рішення, щоб вихід з stdout плюс stderr був записаний у лог-файл, а stderr - ще на консолі. Тому мені потрібно було дублювати більш жорсткий вихід через трійник.
Це рішення, яке я знайшов:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
У ситуації, коли "трубопровід" необхідний, ви можете використовувати:
| &
Наприклад:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
або
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Ці рішення на основі bash можуть передавати STDOUT та STDERR окремо (від STDERR "sort -c" або від STDERR до "sort -h").
Наступні функції можуть бути використані для автоматизації процесу перемикання виходів beetwen stdout / stderr та logfile.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Приклад використання всередині сценарію:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ fernando-fabreti
Додавши до того, що ви зробили, я трохи змінив функції і видалив & - закриття, і це працювало на мене.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
У ситуаціях, коли ви розглядаєте можливість використання таких речей, як exec 2>&1
мені легше читати, якщо можливо, переписати код за допомогою таких функцій bash:
function myfunc(){
[...]
}
myfunc &>mylog.log