$ 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
肯定是来自。循环解析所有行,直到没有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