Перенаправити 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.