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

контроль последовательности ошибок в безымянном канале
$ 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инструмент может

  1. вообще игнорировать неполную строку, или
  2. обработать его и добавить недостающий символ новой строки, или
  3. обрабатывайте его как есть.

По сути вы будете

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

В случае (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. Строки идут парами. (См. примеры ниже, чтобы понять протокол более интуитивно.)

  1. Прочитайте первую строку из пары ( getline<f).
  2. Сохраните первую строку в переменной ( first=$0).
  3. Прочитайте вторую строку из пары ( getline<f).
  4. Проанализируйте первую строку ( $first).
    • Если это Cтак, то второй (текущий $0) — это полная строка из cmdmayfail, вы можете ее проанализировать.
    • Если это Iто вторая строка неполная из cmdmayfail, вы можете или не можете захотеть ее разобрать. Ожидайте Eв следующей паре.
    • Если это Eтак, то второй — это статус выхода из cmdmayfail. Дальнейших пар ожидать не стоит.
  5. Петля.

Примечание: я использовал 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
    

Связанный контент