将系统命令的输出分配给变量

72
我想在 awk 脚本中运行 system 命令,并将其输出存储在变量中。我一直在尝试做这件事,但是命令的输出总是进入 shell,我无法捕获它。有没有办法可以做到这一点?
示例:
$ date | awk --field-separator=! {$1 = system("strip $1"); /*more processing*/}

应当调用strip系统命令,并将输出赋值给$1以供进一步处理,而不是发送到shell并将命令的返回码赋值给$1。目前的情况是将输出发送到shell并将命令的返回码赋值给$1


3
提示:输出不会发送到shell,而是发送到终端/控制台。Shell不会读取其子进程的任何输出 - 它们只共享与同一tty相关联的文件描述符。 - William Pursell
6个回答

75

注意:Coprocess 是 GNU awk 特有的。无论如何,另一种选择是使用 getline。

cmd = "strip "$1
while ( ( cmd | getline result ) > 0 ) {
  print  result
} 
close(cmd)

调用close(cmd)将会防止awk在多次调用后抛出以下错误:

fatal: cannot open pipe `…' (Too many open files)


谢谢。这样,我可以从我的答案中删除&符号。看起来更酷。但是我只写给在Linux中使用的人,所以gawk不可用不应该成为问题吧? - Sahas
是的,这应该不是问题。但你应该检查文档,看看 coprocess 是否仅在某些版本的 gawk 中可用。我记不清了。 - ghostdog74
从版本3.1开始,RedHat有3.1.5版本。不管怎样,我会按照你建议的方式进行操作,除非我想要将某些内容发送到命令的标准输入(stdin),这时候协程(coprocess)会很有帮助。 - Sahas
1
Awk 令我惊叹不已。 - Dan Moulding
2
请注意,如果您在上述代码上有一个for循环,则close(cmd)是必需的,因为我发现awk1018次迭代后会中断(这可能取决于您的系统)。 - champost

49
awk中运行系统命令可以使用system()或者cmd | getline。我更喜欢cmd | getline,因为它允许您将值捕获到变量中:
$ awk 'BEGIN {"date" |  getline mydate; close("date"); print "returns", mydate}'
returns Thu Jul 28 10:16:55 CEST 2016
更一般地,您可以将命令设置为变量:
awk 'BEGIN {
       cmd = "date -j -f %s"
       cmd | getline mydate
       close(cmd)
     }'

请注意,在拥有多个结果时,使用close()是非常重要的,以防止出现“打开文件太多”的错误(感谢mateuscb在评论中指出)。


使用system()函数,命令输出会自动打印,并且你可以捕获的值是其返回代码:

$ awk 'BEGIN {d=system("date"); print "returns", d}'
Thu Jul 28 10:16:12 CEST 2016
returns 0
$ awk 'BEGIN {d=system("ls -l asdfasdfasd"); print "returns", d}'
ls: cannot access asdfasdfasd: No such file or directory
returns 2

10
如果您不添加close(),并且有多个结果,则可能会出现“打开文件太多”的错误。如果您有一个较长的命令,可以使用cmd =“date -j -f%s”cmd | getline mydate; close(cmd) - mateuscb
1
@mateuscb 非常感谢您的反馈。我已经更新了问题并包含了您有用的评论。 - fedorqui
2
感谢提醒使用close()命令,它非常有帮助。如果没有使用close(),在多个结果的情况下,有时会得到错误的日期结果。而使用close()后,我的多个日期结果都能正确显示。 - csu007
4
在我多次调用 awk 内部函数时,执行 cmd | getline var 时,close(cmd) 对我非常关键。当第二次调用并触发 getline 时,var 不再被填充。 - one-liner
1
close(cmd):非常有帮助。首先,它释放文件描述符。其次:它还“刷新”stdout,从而使显示更好(但每次操作调用close也会花费一点“时间”。然而,这个“代价”应该被支付)。 - Olivier Dulac

33

问题已解决。

我们使用awk的双向I/O

{
  "strip $1" |& getline $1
}

将$1传递给strip,getline从strip接收输出并将其返回到$1


5
如果您需要多次调用同一条命令,我们必须关闭该命令(http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_26.html#SEC29)。 - mcoolive
3
这与awk无关,而是特指gawk(gnu awk): "使用gawk,可以打开到另一个进程的双向管道"。 - Olivier Dulac

6
gawk '{dt=substr($4,2,11); gsub(/\//," ",dt); "date -d \""dt"\" +%s"|getline ts; print ts}'

19
如果您发布答案,您应该解释不同的部分(您做了什么以及为什么有效)。这样其他人就可以从您的答案中学习。对于某些人来说,这句话可能是自我解释的。但对于其他人来说,很难准确理解您所做的事情。 - t.niese
1
注意:在使用getline时,应该同时使用close(cmd),否则如果运行大量数据,结果会出错。更多信息请参见 - Devaroop

5

当你需要处理grep输出时,可以使用这个:

echo "some/path/exex.c:some text" | awk -F: '{ "basename "$1"" |& getline $1; print $1 " ==> " $2}'

选项-F:告诉awk使用:作为字段分隔符

"basename "$1""在第一个字段上执行shell命令basename

|& getline $1读取上一个shell命令的输出结果到子流中

output:
exex.c ==> some text

3

我正在使用macOS的awk,同时我也需要命令的退出状态。所以,我扩展了@ghostdog74的解决方案来获取退出状态:

如果退出状态非零则退出:

cmd = <your command goes here>
cmd = cmd" ; printf \"\n$?\""

last_res = ""
value = ""        

while ( ( cmd | getline res ) > 0 ) {

    if (value == "") {
        value = last_res
    } else {
        value = value"\n"last_res
    }

    last_res = res
}

close(cmd)

# Now `res` has the exit status of the command
# and `value` has the complete output of command

if (res != 0) {
    exit 1
} else {
    print value
}

所以,基本上我只是把cmd改成了在新行上打印命令的退出状态。在执行上述的while循环后,res将包含该命令的退出状态,value将包含该命令的完整输出。
老实说,这不是一种非常整洁的方式,我自己也想知道是否有更好的方法。

2
不错的技巧,将返回值添加为最后一行。但是也许更简单的方法是:tmpfile="somename" ; cmd="thingyouwant >" tmpfile ; res=system(cmd) ; close(cmd) 然后使用简单的getline解析tmpfile以获取thingyouwant的输出?(然后用另一个cmd="rm " tmpfile删除它(您也可以使用system(cmd)和close(cmd)) - Olivier Dulac
是的,这样更清晰。我建议你也添加一个新答案。我现在无法测试其速度和正确性,但如果适用于我的代码,我将尝试使用该方法。等我回去时再看看。 - Mihir Luthra
我相信“close(cmd)”返回退出状态。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接