如何从 awk 中调用 bash 函数?

如何从 awk 中调用 bash 函数?

这有点棘手;我正在努力找出解决这个问题的最佳方法。我有几种方法,但它们看起来确实很老套,我想要一些更优雅的方法。

我想解析一个空格分隔的文件,忽略#comment lines并抱怨任何没有恰好 4 个字段的非空行。这很容易awk

awk '/^#/ {next}; NF == 0 {next}; NF != 4 {exit 1}; (dostuff)'

诀窍就是我想要的有了数据,实际上是将其设置为变量bash,然后运行bash函数,除非 $2 包含特定值。

这是一些伪代码(大部分是真实的但混合语言)来解释我的意思:

# awk
/^#/ {next}
NF == 0 {next}
NF != 4 {exit 1}
$2 == "manual" {next}
# bash
NAME=$1
METHOD=$2
URL=$3
TAG=$4
complicated_bash_function_that_calls_lots_of_external_commands
# then magically parse the next line with awk.

我不知道如何在没有一些丑陋的解决方法的情况下做到这一点,例如为文件的每一行单独调用awk或。 sed(最初我把问题提出为“如何从 awk 中调用 bash 函数或从 bash 中调用 awk 的每个输出行?”)

也许可以将 bash 函数修改为自己的脚本,并使其接受上述参数 1、2、3、4。不过,我不确定如何从 awk 中调用它;因此我的问题标题。

我实际上会做什么更喜欢要做的就是将所有内容放在一个文件中,并将其设为 bash 脚本 -awk从内部调用bash而不是bashawk.但我仍然需要打电话给bash 功能从 awk 中 - 对于输入文件的每个非注释行一次。

我怎样才能做到这一点?

答案1

您可以通过将awk的输出传输到while read循环中来完成您想要的操作。例如:

awk '/^#/ {next}; NF == 0 {next}; NF != 4 {exit 1} ; {print}' | 
    while read -r NAME METHOD URL TAG ; do
        :  # do stuff with $NAME, $METHOD, $URL, $TAG
        echo "$NAME:$METHOD:$URL:$TAG"
    done

if [ "$PIPESTATUS" -eq 1 ] ; then
    : # do something to handle awk's exit code
fi

测试用:

$ cat input.txt 
# comment
NAME METHOD URL TAG
a b c d
1 2 3 4
x y z
a b c d

$ ./testawk.sh input.txt 
NAME:METHOD:URL:TAG
a:b:c:d
1:2:3:4

请注意,它在第五个x y z输入行正确退出。


值得指出的是,因为while循环是管道的目标,所以它在子 shell 中执行,因此无法更改其父脚本的环境(包括环境变量)。

如果需要,则不要使用管道,而是使用重定向和进程替换:

while read -r NAME METHOD URL TAG ; do
  :  # do stuff with $NAME, $METHOD, $URL, $TAG
  echo "$NAME:$METHOD:$URL:$TAG"
done < <(awk '(/^#/ || NF == 0) {next};
              NF != 4 {
                printf "%s:%s:Wrong number of fields\n", FILENAME, NR > "/dev/stderr";
                exit 1
               };
              {print}' input.txt)

# getting the exit code from the <(...) requires bash 4.4 or newer:
wait $!

if [ "$?" -ne 0 ] ; then
 : # something went wrong in the process substitution, deal with it
fi

或者,您可以使用coproc内置命令在后台作为协进程运行 awk 脚本:

# By default, array var $COPROC holds the co-process' stdout and
# stdin file descriptors.   See `help coproc`.
coproc {
  awk '(/^#/ || NF == 0) {next};
       NF != 4 {
         printf "%s:%s:Wrong number of fields\n", FILENAME, NR > "/dev/stderr";
         exit 1
       };
       {print}' input.txt
}
awkpid="$!"
#declare -p COPROC # uncomment to see the FDs

while read -r NAME METHOD URL TAG ; do
  echo "$NAME:$METHOD:$URL:$TAG"
done <&"${COPROC[0]}"

wait "$awkpid"
echo "$?"

答案2

cas 的答案很好,但是如果您实际上需要再次解析 awk 中的输出,并且希望从第一个 awk 命令中执行此操作,那么您可以在 awk 中使用奇妙的管道命令语法:

awk '
{
  cmd = "echo name:tag:url:method" # (very simple example)
  while (cmd | getline)
  {
    #process output ($0)
    print
  }
  close(cmd)
}
'

相关内容