Как заставить оба STDOUT и STDERR перейти к терминалу и файлу журнала?

У меня есть сценарий, который будут запускать в интерактивном режиме нетехнические пользователи. Сценарий записывает обновления статуса в STDOUT, чтобы пользователь мог быть уверен, что сценарий работает нормально.

Я хочу, чтобы и STDOUT, и STDERR были перенаправлены на терминал (чтобы пользователь мог видеть, что скрипт работает, а также видеть, возникла ли проблема). Я также хочу, чтобы оба потока были перенаправлены в файл журнала.

Я видел кучу решений в сети. Некоторые не работают, а другие ужасно сложны. Я разработал работоспособное решение (которое я введу в качестве ответа), но оно неуклюже.

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

Обновлено: Перенаправление STDERR в STDOUT и передача результата в tee работает, но это зависит от того, не забывают ли пользователи перенаправить и передать вывод по конвейеру. Я хочу, чтобы ведение журнала было надежным и автоматическим (вот почему я хотел бы иметь возможность встроить решение в сам скрипт).

Для других читателей: аналогичный вопрос: stackoverflow.com/questions/692000/…

pevik 09.03.2016 18:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
120
1
84 630
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Используйте «тройник» для перенаправления в файл и на экран. В зависимости от используемой оболочки вам сначала нужно перенаправить stderr на stdout, используя

./a.out 2>&1 | tee output

или же

./a.out |& tee output

В csh есть встроенная команда под названием «скрипт», которая фиксирует все, что попадает на экран, в файл. Вы запускаете его, набирая «скрипт», затем выполняя все, что вы хотите захватить, затем нажимаете Ctrl-D, чтобы закрыть файл скрипта. Я не знаю эквивалента для sh / bash / ksh.

Кроме того, поскольку вы указали, что это ваши собственные сценарии sh, которые вы можете изменять, вы можете выполнить внутреннее перенаправление, заключив весь сценарий в фигурные скобки или скобки, например

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file

Я не знал, что вы можете заключать команды в скобки в сценариях оболочки. Интересно.

Jamie 28.04.2009 18:15

Я также ценю ярлык Bracket! По какой-то причине 2>&1 | tee -a filename не сохранял stderr в файл из моего скрипта, но он работал нормально, когда я скопировал команду и вставил ее в терминал! Однако трюк с скобками работает нормально.

Ed Brannin 29.07.2009 06:11

Обратите внимание, что различие между stdout и stderr будет потеряно, поскольку tee выводит все на stdout.

Flimm 10.09.2015 16:31

К вашему сведению: команда 'script' доступна в большинстве дистрибутивов (это часть пакета util-linux)

SamWN 27.05.2016 21:59

@Flimm, есть ли способ (любым другим способом) сохранить различие между stdout и stderr?

Gabriel 19.12.2017 06:30

Также работает в Powershell. Это *>&1 | tee filename для перенаправления всех.

Artyom 06.08.2018 14:54

@Gabriel, я считаю, что это резервирует различие, о котором вы говорите: stackoverflow.com/a/53051506/1054322

MatrixManAtYrService 29.10.2018 21:17

@MatrixManAtYrService делает это за счет нестабильных проблем с синхронизацией; см. добавленный мною комментарий. Я не знаю, можно ли надежно разделить stdout и stderr, сохраняя при этом правильную синхронизацию; Я подозреваю, что это невозможно.

Don Hatch 28.10.2019 11:03

@PaulTomlin Я просмотрел историю изменений вопроса и обнаружил, что с самого начала действительно ясно, что то, что было нужно, было способом перенаправить весь вывод скрипта. (Редакция подчеркивает это, но не меняет его.) Вы даете отличный ответ в конце, но я почти пропустил его, потому что вы начали с ответа на другой вопрос. Я бы хотел проголосовать за это, но не могу из-за этого.

Don Hatch 25.12.2019 17:52

@DonHatch Учитывая, что я ответил на этот вопрос, и он был принят в качестве ответа 11 лет назад, я не собираюсь терять из-за этого сон. Кроме того, вы неправильно написали мое имя.

Paul Tomblin 26.12.2019 01:38

@PaulTomblin Надеюсь, вы не возражаете против того, что я удалил слово «сейчас» из вашего ответа, так что ваш ответ больше не подразумевает, что OP изменил значение их вопроса, что (в моем восприятии) неправильно помещало их в отрицательный свет. Я не ищу здесь войны редактирования, но я, являюсь, заинтересован в улучшении вашего ответа, сделав его более уважительным по отношению к OP.

Don Hatch 26.12.2019 02:43

Лично я считаю, что единственное проявленное здесь неуважение - это «улучшить» ответ и в то же время отрицать его.

Paul Tomblin 26.12.2019 19:20

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

Don Hatch 26.01.2020 02:51

Кто-нибудь смог заставить это работать в GNU Make? Я использую GNU Make 4.3 в Windows 10, и пока я делаю тройник, показанный Полом Томблином, прямо в CMD, он отлично работает, когда я пытаюсь передать его в функцию оболочки GNU Make (gnu.org/software/make/manual/html_node/…), он вылетает. Я добавил некоторые отладочные данные в Make и обнаружил, что GNU Make каким-то образом интерпретирует вывод команды перед конвейером как другую команду, которую он должен выполнить, что, конечно же, терпит неудачу.

antred 27.01.2020 14:30

@antred, который будет полностью зависеть от того, какую оболочку вызывает GNU Make. Это баш?

Paul Tomblin 28.01.2020 17:53

@PaulTomblin Оказывается, проблема заключалась в неправильном использовании мной функции оболочки GNU Make. В том месте, где я ее использовал, функция оболочки была ненужной (и, видимо, даже вызывала проблемы). Запуск рецепта напрямую, без функции оболочки, работал нормально. P.S. И оболочка, которую я использовал, была стандартной оболочкой cmd в Windows.

antred 31.01.2020 17:04

Если вам нужно сохранить статус выхода скрипта, вы можете объединить это с ответом Джейсона Сидса / MatrixManAtYrService, чтобы стать: { ... } 1> >(tee -a output.file) 2>&1. Порядок вывода содержащихся скриптов будет сохранен, но запись в output.file будет немного задерживаться.

aff 14.11.2020 15:03

Используйте программу tee и дублируйте stderr в stdout.

 program 2>&1 | tee > logfile

Я создал сценарий под названием «RunScript.sh». Содержимое этого скрипта:

${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log

Я называю это так:

./RunScript.sh ScriptToRun Param1 Param2 Param3 ...

Это работает, но требует, чтобы скрипты приложения запускались через внешний скрипт. Это немного неуклюже.

Вы потеряете группировку аргументов, содержащих пробелы с $ 1 $ 2 $ 3 ..., вы должны использовать (с кавычками): "$ @"

NVRAM 28.10.2009 19:10

для перенаправления stderr на stdout добавьте это по вашей команде: 2>&1 Для вывода на терминал и входа в файл следует использовать tee.

Оба вместе будут выглядеть так:

 mycommand 2>&1 | tee mylogfile.log

Обновлено: для встраивания в свой скрипт вы сделаете то же самое. Итак, ваш сценарий

#!/bin/sh
whatever1
whatever2
...
whatever3

закончится как

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log

Обратите внимание, что различие между stdout и stderr будет потеряно, поскольку tee выводит все на stdout.

Flimm 10.09.2015 16:32

Используйте команду script в своем скрипте (скрипт man 1)

Создайте оболочку оболочки (2 строки), которая устанавливает script (), а затем вызывает exit.

Часть 1: wrap.sh

#!/bin/sh
script -c './realscript.sh'
exit

Часть 2: realscript.sh

#!/bin/sh
echo 'Output'

Результат:

~: sh wrap.sh 
Script started, file is typescript
Output
Script done, file is typescript
~: cat typescript 
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output

Script done on fr. 12. des. 2008 kl. 18.07 +0100
~:

Год спустя вот старый bash-скрипт для регистрации чего угодно. Например,
teelog make ... записывает в журнал сгенерированное имя журнала (и посмотрите также трюк для регистрации вложенных make).

#!/bin/bash
me=teelog
Version = "2008-10-9 oct denis-bz"

Help() {
cat <<!

    $me anycommand args ...

logs the output of "anycommand ..." as well as displaying it on the screen,
by running
    anycommand args ... 2>&1 | tee `day`-command-args.log

That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).

The default log file name is made up from "command" and all the "args":
    $me cmd -opt dir/file  logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
    $me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/\$USER-xx.log .

The log file has a header like
    # from: command args ...
    # run: date pwd etc.
to show what was run; see "From" in this file.

Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
    command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.

Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.

To log all "make" s, including nested ones like
    cd dir1; \$(MAKE)
    cd dir2; \$(MAKE)
    ...
export MAKE = "$me make"

!
  # See also: output logging in screen(1).
    exit 1
}


#-------------------------------------------------------------------------------
# bzutil.sh  denisbz may2008 --

day() {  # 30mar, 3mar
    /bin/date +%e%h  |  tr '[A-Z]' '[a-z]'  |  tr -d ' '
}

edate() {  # 19 May 2008 15:56
    echo `/bin/date "+%e %h %Y %H:%M"`
}

From() {  # header  # from: $*  # run: date pwd ...
    case `uname` in Darwin )
        mac = " mac `sw_vers -productVersion`"
    esac
    cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate`  in $PWD `uname -n` $mac `arch` 

!
    # mac $PWD is pwd -L not -P real
}

    # log name: day-args*.log, change this if you like --
logfilename() {
    log=`day`
    [[ $1 == "sudo" ]]  &&  shift
    for arg
    do
        log = "$log-${arg##*/}"  # basename
        (( ${#log} >= 100 ))  &&  break  # max len 100
    done
            # no blanks etc in logfilename please, tr them to "-"
    echo $logdir/` echo "$log".log  |  tr -C '.:+=[:alnum:]_\n' - `
}

#-------------------------------------------------------------------------------
case "$1" in
-v* | --v* )
    echo "$0 version: $Version"
    exit 1 ;;
"" | -* )
    Help
esac

    # scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
    export "$1"
    shift
done

: ${logdir=.}
[[ -w $logdir ]] || {
    echo >&2 "error: $me: can't write in logdir $logdir"
    exit 1
    }
: ${log=` logfilename "$@" `}
[[ -f $log ]]  &&
    /bin/mv "$log" "/tmp/$USER-${log##*/}"


case ${0##*/} in  # basename
log | Log )  # both to log, stderr to caller's stderr too --
{
    From "$@"
    "$@"
} > $log  2> >(tee /dev/stderr)  # bash only
    # see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;

* )
#-------------------------------------------------------------------------------
{
    From "$@"  # header: from ... date pwd etc.

    "$@"  2>&1  # run the cmd with stderr and stdout both to the log

} | tee $log
    # mac tee buffers stdout ?

esac

Приближается полдесятилетия спустя ...

Я считаю, что это «идеальное решение», к которому стремится ОП.

Вот один лайнер, который вы можете добавить в начало вашего сценария Bash:

exec > >(tee -a $HOME/logfile) 2>&1

Вот небольшой скрипт, демонстрирующий его использование:

#!/usr/bin/env bash

exec > >(tee -a $HOME/logfile) 2>&1

# Test redirection of STDOUT
echo test_stdout

# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist

(Примечание: это работает только с Bash. нет будет работать с / bin / sh.)

Адаптировано из здесь; оригинал, насколько я могу судить, не улавливал STDERR в файле журнала. Исправлено примечанием от здесь.

Обратите внимание, что различие между stdout и stderr будет потеряно, поскольку tee выводит все на stdout.

Flimm 10.09.2015 16:32

@Flimm stderr может быть перенаправлен на другой процесс tee, который снова может быть перенаправлен на stderr.

jarno 31.08.2017 19:45

@Flimm, я написал здесь предложение Ярно: stackoverflow.com/a/53051506/1054322

MatrixManAtYrService 29.10.2018 21:37

Это решение, как и большинство других решений, предложенных до сих пор, подвержено гонкам. То есть, когда текущий сценарий завершается и возвращается либо к приглашению пользователя, либо к некоторому вызывающему сценарию более высокого уровня, тройник, который работает в фоновом режиме, все еще будет работать и может выдавать последние несколько строк на экран и файл журнала поздно (то есть на экран после приглашения и в файл журнала после того, как ожидается, что файл журнала будет завершен).

Don Hatch 28.10.2019 10:58

Мне нравится это решение. Но это влияет на сценарий с помощью apt-get (он не может подключиться к серверам или apt-get early не завершает обновление до второго apt-get). Я не знаю почему. (Похоже, что происходит состояние гонки.) Все, что я знаю, это то, что когда я закомментировал строку, скрипт работает стабильно. Это не только это решение, но и другие, с тройником или без него. Итак, все сводится к следующему: некоторые команды чувствительны к этому. Не знаю почему.

Lance Kind 21.05.2020 05:25

Шаблон

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

Это перенаправляет и stdout, и stderr отдельно, и отправляет отдельный копии stdout и stderr вызывающей стороне (которая может быть вашим терминалом).

  • В zsh он не перейдет к следующему оператору, пока не закончат работу tee.

  • В bash вы можете обнаружить, что последние несколько строк вывода выглядят как после, независимо от того, какой оператор будет следующим.

В любом случае правильные биты попадают в нужные места.


Объяснение

Вот сценарий (хранится в ./example):

#! /usr/bin/env bash
the_cmd()
{
    echo out;
    1>&2 echo err;
}

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

Вот сеанс:

$ foo=$(./example)
    err

$ echo $foo
    out

$ cat stdout.txt
    out

$ cat stderr.txt
    err

Вот как это работает:

  1. Оба процесса tee запускаются, их stdins назначаются файловым дескрипторам. Поскольку они заключены в замены процесса, пути к этим файловым дескрипторам подставляются в вызывающую команду, поэтому теперь это выглядит примерно так:

the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14

  1. the_cmd запускается, записывая stdout в первый файловый дескриптор и stderr во второй.

  2. В случае bash, как только the_cmd завершает работу, немедленно выполняется следующий оператор (если ваш терминал является вызывающим, вы увидите, что появляется ваше приглашение).

  3. В случае zsh после завершения the_cmd оболочка ожидает завершения обоих процессов tee, прежде чем продолжить. Подробнее об этом здесь.

  4. Первый процесс tee, который читает из стандартного вывода the_cmd, записывает копию этого стандартного вывода обратно вызывающей стороне, потому что это то, что делает tee. Его выходы не перенаправляются, поэтому они возвращаются к вызывающему без изменений.

  5. Второй процесс tee перенаправляет свой stdout на stderr вызывающего абонента (что хорошо, потому что стандартный ввод читается из стандартного потока the_cmd). Поэтому, когда он записывает в свой стандартный вывод, эти биты переходят в стандартный поток вызывающего объекта.

Это сохраняет stderr отдельно от stdout как в файлах, так и в выводе команды.

Если первый tee записывает какие-либо ошибки, они будут отображаться как в файле stderr, так и в stderr команды, если второй tee записывает какие-либо ошибки, они будут отображаться только в stderr терминала.

Это выглядит действительно полезным, и я хочу этого. Однако я не уверен, как воспроизвести использование скобок (как показано в первой строке) в пакетном скрипте Windows. (tee доступен в рассматриваемой системе.) Я получаю сообщение об ошибке: «Процесс не может получить доступ к файлу, потому что он используется другим процессом».

Agi Hammerthief 12.06.2019 18:23

Это решение, как и большинство других решений, предложенных до сих пор, подвержено гонкам. То есть, когда текущий сценарий завершается и возвращается либо к приглашению пользователя, либо к некоторому вызывающему сценарию более высокого уровня, тройник, который работает в фоновом режиме, все еще будет работать и может выдавать последние несколько строк на экран и файл журнала поздно (то есть на экран после приглашения и в файл журнала после того, как ожидается, что файл журнала будет завершен).

Don Hatch 28.10.2019 10:59

@DonHatch Можете ли вы предложить решение этой проблемы?

pylipp 15.12.2019 17:22

Я также был бы заинтересован в тестовом примере, который делает гонку очевидной. Не то чтобы я сомневался, но трудно попытаться избежать этого, потому что я не видел, чтобы это произошло.

MatrixManAtYrService 15.12.2019 20:40

@pylipp У меня нет решения. Я был бы заинтересован в очень.

Don Hatch 20.12.2019 18:46

@MatrixManAtYrService Для демонстрации в интерактивной оболочке bash: { ( echo "to stdout"; echo "to stderr" >&2 ) > >(sleep 1; tee stdout.txt); } 2> >(sleep 2; tee stderr.txt >&2 ). Приглашение оболочки возвращается немедленно; "to stdout" через 1 секунду, "to stderr" через 2 секунды. В правильном решении подсказка не появится до тех пор, пока не появится «to stderr».

Don Hatch 20.12.2019 18:53

@DonHatch Спасибо за тест. Мне интересно, что он проходит с этим ответом в zsh, но не работает в bash. Как бы то ни было, я предпринял еще одну попытку: gist.github.com/mattrixman/e796abd41a086f2990926b27bea5c4ce Как вы думаете, это решит проблему гонки? Если это так, я могу опубликовать его как отдельный ответ (поскольку он работает в случае zsh).

MatrixManAtYrService 21.12.2019 00:02

Добрая душа в обмене стеками unix / linux помогла мне понять разницу в zsh: unix.stackexchange.com/a/558315/146169

MatrixManAtYrService 21.12.2019 08:18

@MatrixManAtYrService Я посмотрел на вашу суть, со стадами, которые сообщают от процессов тройника, что они выполнены. Это умно. Вроде работает, и я не вижу дыр в логике. Хотя это действительно кажется очень неудобным. Возможно, это можно рассматривать как доказательство существования, пока мы продолжаем думать о том, есть ли рецепт лучше.

Don Hatch 21.12.2019 10:08

@pylipp, думаю, я наконец нашел хорошее решение. См. Ответ, который я только что опубликовал.

Don Hatch 21.12.2019 12:59

@DonHatch Спасибо за отзыв. Я согласен, что это неудобно. Я могу повозиться, сколько из этой возни можно спрятать под ковриком в моем .bashrc. Обычно я защитник bash и ему подобных, но это не должно быть так сложно ...

MatrixManAtYrService 21.12.2019 20:57

Обновлено: Я вижу, что сошел с рельсов и в итоге ответил на вопрос, отличный от заданного. Ответ на настоящий вопрос находится в конце ответа Пола Томблина. (Если вы по какой-то причине хотите улучшить это решение для перенаправления stdout и stderr по отдельности, вы можете использовать технику, которую я описываю здесь.)


Мне нужен был ответ, который сохранял бы различие между stdout и stderr. К сожалению, все ответы, данные до сих пор, сохраняют это различие подвержены гонкам: они рискуют увидеть в программах неполный ввод, как я отмечал в комментариях.

Думаю, я наконец нашел ответ, который сохраняет различие, не предрасположен к гонкам, да и не так уж и неудобен.

Первый строительный блок: поменять местами stdout и stderr:

my_command 3>&1 1>&2 2>&3-

Второй строительный блок: если бы мы хотели фильтровать (например, тройник) только stderr, мы могли бы добиться этого, поменяв местами stdout и stderr, отфильтровав, а затем поменяв местами обратно:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

Теперь остальное легко: мы можем добавить фильтр stdout в начале:

{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

или в конце:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter

Чтобы убедить себя, что обе вышеперечисленные команды работают, я использовал следующее:

alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'

Выход:

...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr

и моя подсказка возвращается сразу после "teed stderr: to stderr", как и ожидалось.

Сноска о zsh:

Вышеупомянутое решение работает в bash (и, возможно, в некоторых других оболочках, я не уверен), но не в zsh. Есть две причины, по которым он не работает в zsh:

  1. синтаксис 2>&3- не распознается zsh; это должно быть переписано как 2>&3 3>&-
  2. в zsh (в отличие от других оболочек), если вы перенаправляете файловый дескриптор который уже открыт, в некоторых случаях (я не совсем понимаю, как он решает) вместо этого он выполняет встроенное поведение, подобное тройнику. Чтобы избежать этого, вы должны закрыть каждый fd до перенаправляя его.

Так, например, мое второе решение нужно переписать для zsh как {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter (которое работает и в bash, но ужасно многословно).

С другой стороны, вы можете воспользоваться таинственным встроенным неявным teeing в zsh, чтобы получить гораздо более короткое решение для zsh, которое вообще не запускает tee:

my_command >&1 >stdout.txt 2>&2 2>stderr.txt

(Я бы не догадался из документов, которые я обнаружил, что >&1 и 2>&2 - это то, что запускает неявную игру zsh; я обнаружил это методом проб и ошибок.)

Я поигрался с этим в bash, и он хорошо работает. Просто предупреждение для пользователей zsh с привычкой предполагать совместимость (как и я), там он ведет себя по-другому: gist.github.com/MatrixManAtYrService/…

MatrixManAtYrService 21.12.2019 22:29

@MatrixManAtYrService Я считаю, что разобрался с ситуацией с zsh, и оказалось, что в zsh есть гораздо более аккуратное решение. См. Мою правку «Сноска о zsh».

Don Hatch 24.12.2019 13:00

Спасибо за столь подробное объяснение решения. Вы также знаете, как получить код возврата при использовании функции (my_function) во вложенной фильтрации stdout / stderr? Я сделал { { my_function || touch failed;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter, но мне кажется странным создавать файл как индикатор отказа ...

pylipp 25.01.2020 00:11

@pylipp Я не нахожу. Вы можете задать это как отдельный вопрос (возможно, с более простым конвейером).

Don Hatch 26.01.2020 02:53

Вот решение, работающее для bash путем перенаправления, путем объединения решения «kvantour, MatrixManAtYrService» и «Jason Sydes»:

#!/bin/bash
exec 1> >(tee x.log) 2> >(tee x.err >&2)

echo "test for log"
echo "test for err" 1>&2

Сохраните приведенный выше сценарий как x.sh. После запуска ./x.sh x.log включает только stdout, а x.err включает только stderr.

Другие вопросы по теме