$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0
В приведенном выше примере безымянного канала awk
ошибка не будет обнаружена из безымянного канала.
$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'
Чтобы awk
распознать эту ошибку, я мог бы использовать приведенный выше код, отправив управляющую последовательность и некоторую информацию об ошибке.
Учитывая, что file
уже известно много типов файлов, есть ли разумная последовательность управления, которую можно использовать для этого приложения, чтобы awk
не было неправильной обработки последовательности управления ошибками как из законного файла? Спасибо.
решение1
Если вы commandmayfail
используете стандартную команду Unix, то добавления перед управляющей последовательностью собственного сложного текста (например, __ERROR__CMDFAIL__:
) будет достаточно, чтобы awk
понять разницу.
Однако если вы также включаете свое собственное и/или фирменное программное обеспечение, вам будет трудно дать общую строку. Возможно (хотя маловероятно), что одна из ваших фирменных команд использует такую строку. Вам следует просмотреть общую настройку сообщений об ошибках и создать строку, которая вряд ли будет использоваться.
Если commandmayfail
это file
, как следует из вопроса, то может быть достаточно использовать строку без :
.
решение2
Если вывод cmdmayfail
относительно мал и сама команда завершается независимо от других частей вашего кода, то вы можете сохранить ее вывод в переменной и передать статус выхода в качестве самой первой строки. Код в <()
будет выглядеть так:
out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"
Вам awk
следует getline<f
получить статус выхода. Последовательный getline<f
будет читать фактический вывод cmdmayfail
.
Ограничения:
- Переменные в Bash не могут хранить нулевые символы.
$()
удалит все завершающие символы новой строки; затемprintf
добавит ровно один. Громоздкий трюк, чтобы избежать этого:out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
Тот факт, что вы обрабатываете вывод cmdmayfail
в BEGIN
блоке, может указывать на то, что вы ожидаете cmdmayfail
преждевременного завершения. Возможно, этого решения будет достаточно.
В общем случае cmdmayfail
может работать "бесконечно" (т. е. пока вы его не прекратите), и вам может понадобиться прочитать его вывод во время обработки ("бесконечного") stdin awk
. В таком случае приведенное выше решение не сработает.
Вы можете добавить каждую строку вывода из cmdmayfail
некоторой фиксированной строки статуса (например OK
, ) и в конце добавить строку со статусом выхода cmdmayfail
. Код в <()
будет выглядеть так:
cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
Пример:
$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7
Затем ваш awk
код должен getline<f
и проверить, является ли он OK
. Если это так, следующая строка ( getline<f
снова) cmdmayfail
точно из. Цикл для разбора всех строк, пока не будет no, OK
когда вы этого ожидаете. Затем это статус выхода.
Это будет работать нормально, если только cmdmayfail
не будет генерироватьсянеполная строка. Пример:
$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
В зависимости от реализации sed
инструмент может
- вообще игнорировать неполную строку, или
- обработать его и добавить недостающий символ новой строки, или
- обрабатывайте его как есть.
По сути вы будете
- пропустить некоторую часть вывода, или
- не знать, что линия была неполной, или
- получить строку с прикрепленным к ней статусом выхода.
В случае (3) printf '\n%s\n' "${PIPESTATUS[0]}"
может помочь. Он сгенерирует дополнительную пустую строку, если последняя строка из cmdmayfail
завершена; таким образом ваш awk
код сможет это определить.
Рассмотрим случай, когда cmdmayfail
был принудительно завершен в середине строки. Тогда вам может не понадобиться анализировать неполную строку. Проблема в том, чтобы узнать, была awk
ли форма строки cmdmayfail
завершена или нет, вам нужно протестировать следующую (статусную) строку. Реализация полезной логики для этого awk
может быть как минимум неудобной.
Лучше всего обнаружить неполную линию как можно раньше,read
в Bash можно сделать это. Недостаток — read
медленный (и помните, что переменные Bash не могут хранить нулевые символы). Пример решения:
# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail
Затем вам нужно расшифровать пользовательский протокол внутри awk
. Строки идут парами. (См. примеры ниже, чтобы понять протокол более интуитивно.)
- Прочитайте первую строку из пары (
getline<f
). - Сохраните первую строку в переменной (
first=$0
). - Прочитайте вторую строку из пары (
getline<f
). - Проанализируйте первую строку (
$first
).- Если это
C
так, то второй (текущий$0
) — это полная строка изcmdmayfail
, вы можете ее проанализировать. - Если это
I
то вторая строка неполная изcmdmayfail
, вы можете или не можете захотеть ее разобрать. ОжидайтеE
в следующей паре. - Если это
E
так, то второй — это статус выхода изcmdmayfail
. Дальнейших пар ожидать не стоит.
- Если это
- Петля.
Примечание: я использовал eval "$@"
внутри функции. То, что вы пишете после, encerr
будет оценено во второй раз, поэтому обычно вы хотели бы запустить что-то вроде
encerr 'cmd1 -opt foo'
или
encerr "cmd1 -opt foo"
или даже
encerr 'cmd1 -opt foo | cmd2'
По сути, это форма, которую вы используете для запуска удаленных команд с помощью ssh
. Сравните:
ssh a@b 'cmd1 -opt foo | cmd2'
Или вы можете построить функцию следующим образом:
encerr () { "$@" | …
и назовем это так:
encerr cmd1 -opt foo
По сравнению с sudo
:
sudo cmd1 -opt foo
Примеры (используя исходную функцию с eval
):
успех с пустым выводом
$ encerr true E 0
сбой с пустым выводом
$ # I'm redirecting stderr so "command not found" doesn't obfuscate the example $ encerr nonexisting-command-foo1231234 2>/dev/null E 127
успех после завершения линий
$ encerr 'date; sleep 1; date' C Mon Sep 30 09:07:40 CEST 2019 C Mon Sep 30 09:07:41 CEST 2019 E 0
отказ после завершения строк
$ encerr 'printf "foo\nbar\n"; false' C foo C bar E 1
успех после неполной линии
$ encerr 'printf "foo bar\n89 baz"' C foo bar I 89 baz E 0
сбой после неполной строки
$ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33' C I The first line was empty and this one was interru E 33