2016-08-14 3 views
2

То, что я сделал до сих пор является:bash: Как добавить строку в строки stderr и объединить оба stdout и stderr в точном порядке и сохранить в одной переменной в bash?

#!/bin/bash 

exec 2> >(sed 's/^/ERROR= /') 

var=$(
     sleep 1 ; 
     hostname ; 
     ifconfig | wc -l ; 
     ls /sfsd; 
     ls hasdh; 
     mkdir /tmp/asdasasd/asdasd/asdasd; 
     ls /tmp ; 
) 

echo "$var" 

Это предварять ERROR = в начале каждой строки ошибки, но отображает все ошибки, а затем стандартный вывод, (не в порядке, в котором он был выполнен).

Если мы пропустим сохранение вывода в переменной и выполните команды напрямую, выход поступит в желаемом порядке.

Любое мнение экспертов будет оценено по достоинству.

ответ

3

Команда sed выполняется асинхронно из остальной части оболочки; его выход переходит в стандартную ошибку, как только он обрабатывает свой вход из команд в подстановке команд. Однако стандартный вывод этих команд записывается в $var и не отображается до тех пор, пока не запустится команда echo.

Даже если вы не захватывали вывод, есть шанс стандартная ошибка и стандартный вывод из этих команд не будет появляться, как вы ожидаете, потому что sed команды, которая в конечном счете производит сообщения об ошибках не может быть запланированное ОС, когда вы ожидаете, что это будет, задерживая появление сообщений об ошибках.

Когда вы запускаете команду обычным способом с терминала, стандартная ошибка этой команды и стандартная выходная точка указывают на тот же файл: сам терминал. Таким образом, запись в файл поддерживает порядок, в котором они происходят в программе. Как только вы подключаете один или другой к другому процессу, вы теряете контроль над тем, как эти два сплайсируются вместе, если когда-либо. В вашем случае вы перенаправляете стандартную ошибку на sed, которая записывает измененные строки обратно в стандартный вывод. Но у вас нет контроля над тем, когда ОС запускает sed для запуска и когда ваша оболочка работает, поэтому вы не можете контролировать порядок написания строк.

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

tag_error() { sed 's/^/ERROR= /'; } 

hostname 2> >(tag_error) 
{ ifconfig | wc -l ; } 2> >(tag_error) 
# etc 

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

(ruakh has covered, как совместить это с захватом стандартный вывод, так что я не буду беспокоить, добавив его сейчас. Посмотрите на его ответ.)

+0

поэтому вопрос остается нерешенным, как сделать это? – avg598

+1

Вы не можете; маркеров нет, чтобы указать, какие строки идут от 'sed', а строки в' $ var' соответствуют каждой отдельной команде в подстановке команд. Относительный вывод объединяется только в «обычный» случай, потому что stdout каждого процесса и stderr - это тот же самый дескриптор файла. – chepner

+0

Все, что я хотел сделать, это отметить строки, которые нужно идентифицировать из stdout и которые находятся в stderr и в то же время сохраняют порядок. Есть ли другой способ сделать это? – avg598

2

Одно из возможных решений было бы поставить команды в массиве затем выполнить их в пределах петля:

declare -a cmds=('sleep 1' 'hostname' 'eval ifconfig | wc -l' 'ls /sfsd' 'ls /tmp' 'ls hasdh') 

for i in "${cmds[@]}"; do 
    $i 2> >(sed -E 's/^/ERROR=/') 
done 

При возникновении ошибки печать должна выполняться в том же порядке, что и при выполнении. Используя команду, такую ​​как sh script.sh внутри массива, следует также выявить любые stdout или stderr из полученного внешнего скрипта. Для команды с каналами также потребуется eval.

+0

Но это не сработает, если одна из команд - запустить файл сценария, который дает как stderr, так и stdout. – avg598

+0

Я не уверен, что я последую за ним ... Если в массиве была такая команда, как 'sh myscript.sh', она все равно выдавала бы как' stdout', так и 'stderr'. –

+0

Хорошо, позвольте мне проверить, а пока проверьте это [link] (http://stackoverflow.com/questions/38947524/bash-how-to-store-output-of-set-of-commands-in-3-variables- из-ERR-комбинированный). Основная цель этого - в этой ссылке. – avg598

4

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

Вы видите, что я имею в виду?

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

var=$(
    exec 2> >(sed 's/^/ERROR= /') 
    sleep 1 
    hostname 
    ifconfig | wc -l 
    ls /sfsd 
    ls hasdh 
    mkdir /tmp/asdasasd/asdasd/asdasd 
    ls /tmp 
) 

echo "$var" 

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

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

var=$(
    sleep 1 2> >(sed 's/^/ERROR= /') 
    hostname 2> >(sed 's/^/ERROR= /') 
    { ifconfig | wc -l ; } 2> >(sed 's/^/ERROR= /') 
    ls /sfsd 2> >(sed 's/^/ERROR= /') 
    ls hasdh 2> >(sed 's/^/ERROR= /') 
    mkdir /tmp/asdasasd/asdasd/asdasd 2> >(sed 's/^/ERROR= /') 
    ls /tmp 2> >(sed 's/^/ERROR= /') 
) 

echo "$var" 

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

Вы можете улучшить читаемость немного, создав функцию-оболочку для простой команды (не трубопроводный) случай:

var=$(
    function fix-stderr() { 
     "[email protected]" 2> >(sed 's/^/ERROR= /') 
    } 

    fix-stderr sleep 1 
    fix-stderr hostname 
    fix-stderr eval 'ifconfig | wc -l' # using eval to get a simple command 
    fix-stderr ls /sfsd 
    fix-stderr ls hasdh 
    fix-stderr mkdir /tmp/asdasasd/asdasd/asdasd 
    fix-stderr ls /tmp 
) 

echo "$var" 
+0

Ваш ответ объясняет много вещей, которые случались неожиданно, спасибо. – avg598

+0

Одна проблема с добавлением '2>> (sed 's/^/:: ERROR :: /')' в каждой строке, если мы хотим выполнить другой файл сценария, мы будем выполнять './test.sh 2>> (sed 's/^/:: ERROR :: /') ', который по-прежнему дает случайный упорядоченный вывод. – avg598

+0

@ avg598: Да, это так. Вся концепция «потоков» Unix чрезвычайно эффективна, но она не предлагает какой-либо концепции сохранения хронологического порядка между отдельными потоками. Вам нужно будет изменить свой './Test.sh', чтобы поддерживать те же функции. – ruakh

Смежные вопросы