如何在getline管道中获取命令的退出状态?

时间:2014-01-23 00:15:18

标签: awk posix getline

在POSIX awk中,如何通过command 处理其输出后,从command | getline var 获取退出状态(返回代码)?如果exit 1以非零退出状态退出,我希望我的awk脚本command

例如,假设我有一个名为foo.awk的awk脚本,如下所示:

function close_and_get_exit_status(cmd) {
    # magic goes here...
}
BEGIN {
    cmd = "echo foo; echo bar; echo baz; false"
    while ((cmd | getline line) > 0)
        print "got a line of text: " line
    if (close_and_get_exit_status(cmd) != 0) {
        print "ERROR: command '" cmd "' failed" | "cat >&2"
        exit 1
    }
    print "command '" cmd "' was successful"
}

然后我想要发生以下事情:

$ awk -f foo.awk
got a line of text: foo
got a line of text: bar
got a line of text: baz
ERROR: command 'echo foo; echo bar; echo baz; false' failed
$ echo $?
1

根据POSIX specification for awkcommand | getline返回1表示成功输入,0表示文件结束,-1表示错误。如果command以非零退出状态退出,则不是错误,因此不能用于查看command是否已完成且已失败。

类似地,close()不能用于此目的:close()仅在关闭失败时返回非零,而不是在关联命令返回非零退出状态时返回非零。 (在gawk中,close(command)返回command的退出状态。这是我想要的行为,但我认为它违反了POSIX规范,并且并非awk的所有实现都以这种方式运行。)

awk system()函数返回命令的退出状态,但据我所知,没有办法使用getline

4 个答案:

答案 0 :(得分:4)

最简单的方法是在命令执行后从shell回显退出状态,然后使用getline读取它。 e.g。

$ cat tst.awk    
BEGIN {
    cmd = "echo foo; echo bar; echo baz; false"

    mod = cmd "; echo \"$?\""
    while ((mod | getline line) > 0) {
        if (numLines++)
            print "got a line of text: " prev
        prev = line
    }
    status = line
    close(mod)

    if (status != 0) {
        print "ERROR: command '" cmd "' failed" | "cat >&2"
        exit 1
    }
    print "command '" cmd "' was successful"
}

$ awk -f tst.awk
got a line of text: foo
got a line of text: bar
got a line of text: baz
ERROR: command 'echo foo; echo bar; echo baz; false' failed
$ echo $?
1

如果有人正在阅读此内容并考虑使用getline,请务必阅读http://awk.freeshell.org/AllAboutGetline并完全了解所有注意事项以及首先执行此操作的含义。

答案 1 :(得分:2)

不是理想的解决方案,但您可以这样做:

"command || echo failure" | getline var; ... if( var == "failure" ) exit;

有一些歧义,你必须选择字符串“failure”,使得命令永远不会生成相同的字符串,但这可能是一个适当的解决方法。

答案 2 :(得分:1)

以下是可怕的复杂,但它:

  • 符合POSIX(主要是 - fflush()尚未加入POSIX标准,but it will be且广泛可用)
  • 是通用的(无论命令发出什么类型的输出都可以使用)
  • 不会引入任何处理延迟。只有在命令打印 next 行之后,此问题的接受答案才会使行可用。如果命令缓慢输出行并且响应性很重要(例如,IDS系统打印的偶然事件应触发防火墙更改或电子邮件通知),则此答案可能比接受的答案更合适。

基本方法是在命令完成后回显退出状态/返回值。如果最后一行不为零,则退出awk脚本并显示错误。为了防止代码误将命令输出的一行文本输入到退出状态,命令输出的每行文本前面都会加上一个后来被删除的字母。

function stderr(msg) { print msg | "cat >&2"; }
function error(msg) { stderr("ERROR: " msg); }
function fatal(msg) { error(msg); exit 1; }

# Wrap cmd so that each output line of cmd is prefixed with "d".
# After cmd is done, an additional line of the format "r<ret>" is
# printed where "<ret>" is the integer return code/exit status of the
# command.
function safe_cmd_getline_wrap(cmd) {
    return                                                  \
        "exec 3>&1;"                                        \
        "ret=$("                                            \
        "    exec 4>&1;"                                    \
        "    { ( "cmd" ) 4>&-; echo $? >&4; } 3>&- |"       \
        "    awk '{print\"d\"$0;fflush()}' >&3 4>&-;"       \
        ");"                                                \
        "exec 3>&-;"                                        \
        "echo r${ret};"
}

# like "cmd | getline line" except:
#   * if getline fails, the awk script exits with an error
#   * if cmd fails (returns non-zero), the awk script exits with an
#     error
#   * safe_cmd_getline_close(cmd) must be used instead of close(cmd)
function safe_cmd_getline(cmd,        wrapped_cmd,ret,type) {
    wrapped_cmd = safe_cmd_getline_wrap(cmd)
    ret = (wrapped_cmd | getline line)
    if (ret == -1) fatal("failed to read line from command: " cmd)
    if (ret == 0) return 0
    type = substr(line, 1, 1)
    line = substr(line, 2)
    if (type == "d") return 1
    if (line != "0") fatal("command '" cmd "' failed")
    return 0
}
function safe_cmd_getline_close(cmd) {
    if (close(safe_cmd_getline_wrap(cmd))) fatal("failed to close " cmd)
}

你使用上面这样的:

cmd = "ls no-such-file"
while (safe_cmd_getline(cmd)) {
    print "got a line of text: " line
}
safe_cmd_getline_close(cmd)

答案 3 :(得分:1)

如果您有mktemp命令,则可以将退出状态存储在一个临时文件中:

#!/bin/sh
set -e
file=$(mktemp)
finish() {
    rm -f "$file"
}
trap 'finish' EXIT
trap 'finish; trap - INT; kill -s INT $$' INT
trap 'finish; trap - TERM; kill $$' TERM

awk -v file="$file" 'BEGIN{
    o_cmd="echo foo; echo bar; echo baz; false"
    cmd = "("o_cmd "); echo $? >\""file"\""
    print cmd
    while ((cmd | getline) > 0) {
        print "got a line of text: " $0
    }
    close(cmd)
    getline ecode <file; close(file)
    print "exit status:", ecode
    if(ecode)exit 1
}'