Как перенаправить стандартный вывод в файл, а стандартный вывод - в другой?

32

Как я могу достичь

cmd >> file1 2>&1 1>>file2

То есть stdout и stderr должны перенаправлять на один файл (file1), и только stdout (file2) должен перенаправлять на другой (оба в режиме добавления)?

Сварна Гаури
источник

Ответы:

41

Проблема в том, что когда вы перенаправляете свой вывод, он больше не доступен для следующего перенаправления. Вы можете передать teeв подоболочку, чтобы сохранить выходные данные для второго перенаправления:

( cmd | tee -a file2 ) >> file1 2>&1

или если вы хотите увидеть вывод в терминале:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Чтобы не добавлять stderr первого teeв file1, вы должны перенаправить stderr вашей команды в некоторый файловый дескриптор (например, 3), а затем снова добавить это в stdout:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(спасибо @ фра-сан)

pLumo
источник
16

С zsh:

cmd >& out+err.log > out.log

В режиме добавления:

cmd >>& out+err.log >> out.log

В zshи при условии, что mult_iosопция не была отключена, когда дескриптор файла (здесь 1) перенаправляется несколько раз для записи, тогда оболочка реализует встроенную функцию teeдля дублирования вывода для всех целей.

Стефан Шазелас
источник
Я не могу понять, что out+errи outчто здесь значит. Имена файлов? Потоки будут перенаправлены?
Гроностай
@gronostaj Думаю, что команда читаетcmd >& file1 > file2
Исаак
Это решение сохранит порядок, в котором был сгенерирован вывод. Чтобы на самом деле хранить stdout и stderr (в таком порядке), вам нужен другой подход.
Исаак
1
@Isaac, порядок не обязательно будет сохранен, так как вывод stdout будет проходить через канал (процессу, который перенаправляет его в каждый файл), а вывод stderr будет идти непосредственно в файл. В любом случае, это не похоже на то, что OP запрашивает вывод stderr после stdout.
Стефан Шазелас
3

Вы можете: пометить stdout (используя UNBUFFERED sed, то есть:) sed -u ..., заставить stderr также перейти к stdout (без тегов, так как он не прошел через это пометку sed), и, таким образом, иметь возможность различать 2 в результирующем лог-файле.

Следующее: медленно (его можно серьезно оптимизировать, используя для примера сценарий perl вместо while ...; do ...; done, для примера, который будет порождать подоболочки и команды в каждой строке!), Странно (кажется, мне нужно 2 {} этапов для того, чтобы в одном stdout переименовать, а затем в другом добавить к нему stderr «свернул») и т. д. Но это: « доказательство концепции », которое будет пытаться сохранить порядок вывода максимально возможного количества stdout & stderr:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file
Оливье Дюлак
источник
Это действительно сложно понять. (1) Почему вы используете такой сложный случай использования ( ls unknown) для печати чего-либо на stderr? >&2 echo "error"было бы хорошо. (2) teeможно добавлять к нескольким файлам одновременно. (3) Почему бы не просто catвместо grep "^"? (4) ваш скрипт потерпит неудачу, когда вывод stderr начнется с _stdout_. (5) почему?
pLumo
@RoVo: первые 2 прокомментированные строки показывают алгоритм, более простой, чем пример доказательства концепции. 1): это ls loopбудет выводить как на stdout, так и на stderr, смешанные (альтернативно), в контролируемом порядке, чтобы мы могли проверить, что мы сохранили порядок stderr / stdout, несмотря на пометку stdout 2): возможно, gnu tail, но не регулярный хвост (напр., на aix.). 3): grep "^" также показывает оба имени файла. 4): это может быть изменено переменной. 5): извилистый пример работает на старых oses (ex, old aix), где я его тестировал (нет доступных perl).
Оливье Дюлак
(1) многократное эхо для stderr и stout было бы хорошо, но хорошо, не важно, было бы легче читать. (3) согласен, (4) уверен, но он потерпит неудачу, если начнется с того, что содержит переменная. (5) Понятно.
pLumo
@RoVo Я согласен с твоим 1). для 4) переменная может быть настолько сложной, насколько это необходимо для исчезновения pb (например: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Оливье Дюлак
1
Конечно, это не очень вероятно, но в любом случае это может привести к возникновению проблемы с безопасностью позже. И тогда вы можете удалить эту строку перед печатью в файл ;-)
pLumo
2

Если порядок вывода должен быть следующим: stdout, то stderr ; нет решения только с перенаправлением.
Stderr должен быть сохранен во временном файле

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

Описание:

Единственный способ перенаправить один вывод (например, stdout или stderr) на два файла - это воспроизвести его. Команда teeявляется правильным инструментом для воспроизведения содержимого файлового дескриптора. Итак, первоначальной идеей иметь один вывод для двух файлов было бы использовать:

... |  tee file1 file2

Это воспроизводит stdin of tee для обоих файлов (1 и 2), оставляя выходные данные tee по-прежнему неиспользованными. Но нам нужно добавить (использовать -a) и только одну копию. Это решает обе проблемы:

... | tee -a file1 >>file2

Чтобы предоставить teestdout (тот, который нужно повторить), нам нужно использовать stderr непосредственно из команды. С одной стороны, если порядок не важен (порядок вывода (скорее всего) будет сохранен как сгенерированный, в зависимости от того, что выводится первым, он будет сохранен первым). Или:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

Вариант 2 работает только в некоторых оболочках. Вариант 3 использует дополнительную подоболочку (медленнее), но использует имена файлов только один раз.

Но если stdout должен быть первым (какой бы вывод заказа не был сгенерирован), нам нужно сохранить stderr, чтобы добавить его в файл в конце (первое опубликованное решение).

Исаак
источник
1
Или хранить в памяти, как это spongeделает:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Стефан Шазелас
1

В интересах разнообразия:

Если ваша система поддерживает /dev/stderr, то

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

буду работать. Стандартный вывод команды cmd отправляется как в stdout, так и в stderr конвейера. Стандартная ошибка cmdобходит tee и выходит из stderr конвейера.

Так

  • стандартный вывод трубопровода - это только стандартный вывод cmd, и
  • stderr конвейера - это stdout и stderr из cmdсмешанного.

Тогда просто отправить эти потоки в правильные файлы.

Как и в случае почти любого подобного подхода (включая ответ Стефана ), file1строки могут быть не в порядке.

Скотт
источник