Перенаправити stderr і stdout на Bash


678

Я хочу перенаправити як stdout, так і stderr процесу в один файл. Як мені це зробити в Bash?


2
Я хотів би сказати, що це напрочуд корисне питання. Багато людей не знають, як це зробити, оскільки їм не доводиться це робити часто, і це не найкраще задокументоване поведінка Баша.
Robert Wm Ruedisueli

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

Відповіді:


762

Погляньте тут . Має бути:

yourcommand &>filename

(переспрямовує і те, stdoutі stderrім’я файлу).


29
Цей синтаксис застарілий відповідно до Вікі Bash Hackers . Є це?
Салман фон Аббас

20
За даними wiki.bash-hackers.org/scripting/obsolete , це здається застарілим у тому сенсі, що він не є частиною POSIX, але сторінка bash man не згадує про те, що він буде видалений з bash найближчим часом. На головній сторінці вказується параметр '&>' over '> &', який інакше еквівалентний.
чепнер

13
Я думаю, ми не повинні використовувати &>, оскільки його немає в POSIX, а звичайні оболонки, такі як "тире", не підтримують його.
Сем Уоткінс

27
Додатковий натяк: Якщо ви використовуєте це в скрипті, переконайтеся, що він починається з, #!/bin/bashа не #!/bin/sh, тому що в башті потрібен bash.
Tor Klingberg

8
Або & >> додати, а не перезаписати.
Олександр Гончій

448
do_something 2>&1 | tee -a some_file

Це збирається перенаправити stderr на stdout і stdout some_file і надрукувати на stdout.


16
У AIX (ksh) ваше рішення працює. Прийнята відповідь do_something &>filenameне відповідає . +1.
Затримано

11
@Daniel, але це питання стосується конкретно баш
Джон Ла Руй

3
Я розумію, Ambiguous output redirect.чому?
Олександр Холден Далі

1
У мене є сценарій рубіну (який я не хочу жодним чином змінювати), який друкує повідомлення про помилки жирним червоним кольором. Потім цей рубіновий скрипт викликається з мого сценарію bash (який я можу змінити). Коли я використовую вище, він друкує повідомлення про помилки в простому тексті за вирахуванням форматування. Чи є можливість зберегти екранне форматування та отримати вихід (як stdout, так і stderr) у файлі?
атлантис

8
Зауважте, що (за замовчуванням) це має побічний ефект, який $?більше не стосується статусу виходу do_something, а статусу виходу tee.
Flimm

255

Ви можете перенаправити 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: змінив порядок, як зазначено в коментарях


47
Це перенаправляє stderr до вихідного stdout, а не до файлу, куди йде stdout. Поставте '2> & 1' після '> file.log', і воно працює.

1
Яка перевага такого підходу над some_command &> file.log?
ubermonkey

6
Якщо ви хочете додати файл, тоді це потрібно зробити так: ехо "foo" 2> & 1 1 >> bar.txt AFAIK немає способу додавання за допомогою &>
SlappyTheFish

9
Argh, вибачте, відлуння "foo" 1 >> bar.txt 2> & 1
SlappyTheFish

11
Я думаю, що тлумачення того, що 2> & 1 перенаправляє stderr до stdout, є помилковим; Я вважаю, що точніше сказати, що він надсилає stderr туди, куди зараз йде stdout. Таким чином, місце 2> і 1 після першого переадресації є важливим.
jdg

201
# 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). На практиці це може бути труба, розетка чи що завгодно.

  • Створіть FD №3 та №4 та вкажіть на те саме "розташування", що і №1 та №2 відповідно. Зміна FD №1 відтепер не впливає на FD №3. Тепер FD №3 та №4 вказують на STDOUT та STDERR відповідно. Вони будуть використовуватися як справжні термінали STDOUT та STDERR.
  • 1>> (...) перенаправляє STDOUT на команду в паренах
  • parens (sub-shell) виконує зчитування 'tee' з STDOUT (pipe) exec і перенаправляє команду 'logger' через іншу трубу до під-оболонки в parenns. Одночасно він копіює той самий вхід у FD №3 (термінал)
  • друга частина, дуже схожа, стосується того, щоб зробити один і той же трюк для STDERR та FD №2 та №4.

Результат запуску сценарію, що містить вищевказаний рядок та додатково цей:

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

1
лише один виняток. у першому прикладі ви написали: exec 1 <> $ LOG_FILE. це призводить до того, що оригінальний файл журналу завжди написаний на власній основі. для реального входу кращим способом є: exec 1 >> $ LOG_FILE, тому журнал завжди додається.
Znik

4
Це правда, хоча це залежить від намірів. Мій підхід полягає у тому, щоб завжди створювати унікальний і часовий файл журналів. Інше - додавати. Обидва способи "логротабельні". Я віддаю перевагу окремим файлам, які потребують меншого розбору, але, як я вже сказав, те, що робить ваш човен плаваючим :)
quizac

1
Ваше друге рішення - інформативне, але що з усім кодом очищення? Це не здається актуальним, і якщо так, то лише заплутайте інакше хороший приклад. Я також хотів би, щоб це було трохи перероблено, щоб FDs 1 і 2 не перенаправлялися на реєстратор, а швидше 3 і 4, щоб все, що викликає цей скрипт, може маніпулювати 1 і 2 далі за загальним припущенням, stdout == 1 і stderr == 2, але мій короткий експеримент говорить про те, що це складніше.
JFlo

1
Мені це подобається краще з кодом очищення. Це може трохи відволіктись від основного прикладу, але позбавлення його зробить приклад неповним. У мережі вже багато прикладів без обробки помилок або, принаймні, доброзичливої ​​ноти, що для її використання потрібно близько ста рядків коду, щоб бути безпечним.
Золтан К.

1
Я хотів детальніше ознайомитись з кодом очищення. Це частина сценарію, яка демонструє, що ерго стає несприйнятливим до сигналу HANG-UP. 'tee' та 'logger' - це процеси, породжені тим самим PPID, і вони успадковують HUP-пастку від основного скрипту bash. Отже, після відмирання основного процесу вони передаються у спадок за допомогою init [1]. Вони не стануть зомбі (defunc). Код очищення гарантує, що всі фонові завдання будуть знищені, якщо основний сценарій відмирає. Це також стосується будь-якого іншого процесу, який, можливо, був створений і працює у фоновому режимі.
квізак

41
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не працює.


21

Цікаво, що це працює:

yourcommand &> filename

Але це дає синтаксичну помилку:

yourcommand &>> filename
syntax error near unexpected token `>'

Ви повинні використовувати:

yourcommand 1>> filename 2>&1

10
&>>здається, працює над BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735

15

Коротка відповідь: 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

Основні вивезення:

  • Дескриптори файлів ведуть себе як вказівники (хоча дескриптори файлів не є такими, як покажчики файлів)
  • Перенаправлення дескриптора файлу "a" на дескриптор файлу "b", який вказує на файл "f", викликає дескриптор файлу "a" на те саме місце, що і дескриптор файлу b - файл "f". НЕ утворює ланцюжок покажчиків a -> b -> f
  • Через вищезазначене порядок має значення 2>&1 >/dev/null! = >/dev/null 2>&1. Один генерує вихід, а інший - ні!

Нарешті подивіться на ці чудові ресурси:

Документація Баша про перенаправлення , пояснення таблиць дескрипторів файлів , вступ до покажчиків


Дескриптори файлів (0, 1, 2) просто зміщуються в таблицю. Якщо використовується 2> & 1, ефект є слотом FD [2] = dup (1), тому там, де FD [1] вказував FD [2], тепер вказує на. Якщо ви зміните FD [1] на вказівку на / dev / null, FD [1] змінюється, але він не змінює слот FD [2] (який вказує на stdout). Я використовую термін dup (), тому що це системний виклик, який використовується для дублювання дескриптора файлу.
PatS

11
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.

Це майже працює, але не від ксінтового; (


Я здогадуюсь, що це не працює через "/ dev / fd / 3 У дозволі відмовлено". Зміна на> і 3 може допомогти.
quizac

6

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

Це рішення, яке я знайшов:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Перший swap stderr і stdout
  • потім додайте stdout до файлу журналу
  • труба stderr до tee і додайте його також до файлу журналу

До речі, для мене це не спрацювало (лог-файл порожній). | трійник не впливає. Натомість у мене це працює за допомогою stackoverflow.com/questions/692000/…
Ярослав Булатов,

4

У ситуації, коли "трубопровід" необхідний, ви можете використовувати:

| &

Наприклад:

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").



1

Наступні функції можуть бути використані для автоматизації процесу перемикання виходів 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"

коли я використовую ваші функції, і він намагається відновити стандартні виходи, я отримую ехо: помилка в
записі

щоб працювати над вашим сценарієм, я повинен був прокоментувати ці рядки, і я змінив порядок: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher

Прикро це чути. Я не отримую жодних помилок під час роботи в CentOS 7, bash 4.2.46. Я зазначив посилання, де я отримав ці команди. Це: посилання: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Фернандо Фабреті

Я виконую ці команди на AIX, тому, мабуть, тому. Я додав пост для зробленого виправлення.
thom shumacher

1

@ 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"

1

У ситуаціях, коли ви розглядаєте можливість використання таких речей, як exec 2>&1мені легше читати, якщо можливо, переписати код за допомогою таких функцій bash:

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

Для tcsh я повинен використовувати таку команду:

command >& file

Якщо використовується command &> file, це призведе до помилки "Недійсна команда".

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