如何在 bash 中运行多行 awk 脚本

如何在 bash 中运行多行 awk 脚本

要启动此脚本,请使用 shell bash 运行 Github 工作流程,并截断 yaml 以提高可读性。我已经尝试了很多方法来使其作为多行工作,因此我可以发表评论。

  set -x
  set -e
  AWK_SOURCE=$( cat <<- AWK
  '
    {
      if ( $1 !~ /delete/ # ensure we are not trying to process deleted files
      && $4 !~ /theme.puml|config.puml/ # do not try to process our theme or custom config
      && $4 ~ /.puml/ ) # only process puml files
      { printf "%s ", $4 } # only print the file name and strip newlines for spaces
    }
    END { print "" } # ensure we do print a newline at the end
  '
  AWK
  )
  GIT_OUTPUT=`git diff-tree -r --no-commit-id --summary ${GITHUB_SHA}`
  AWK_OUPUT=`echo $GIT_OUTPUT | awk -F' ' $AWK_SOURCE`
  echo "::set-output name=files::$GIT_OUTPUT"
  set +e
  set +x

这是我当前的错误

如果我将其作为单行运行,则效果很好

git diff-tree -r --no-commit-id --summary HEAD | awk -F' ' '{ if ( $1 !~ /delete/ && $4 !~ /theme.puml|config.puml/ && $4 ~ /.puml/ ) { printf "%s ", $4 } } END { print "" }'

这是我当前得到的输出/错误,我得到了不同的输出/错误。

shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
+ set -e
++ cat
+ AWK_SOURCE=''\''
  {
    if (  !~ /delete/ # ensure we are not trying to process deleted files
    &&  !~ /theme.puml|config.puml/ # do not try to process our theme or custom config
    &&  ~ /.puml/ ) # only process puml files
    { printf "%s ",  } # only print the file name and strip newlines for spaces
  }
  END { print "" } # ensure we do print a newline at the end
'\'''
++ git diff-tree -r --no-commit-id --summary 6c72c8a8dabf19ae2439ee506b9a4a636027193e
+ GIT_OUTPUT=' create mode 100644 .config/plantuml/config.puml
 create mode 100644 .config/plantuml/theme.puml
 delete mode 100644 config.puml
 create mode 100644 docs/README.md
 create mode 100644 docs/domain-model/README.md
 create mode 100644 docs/domain-model/user.md
 create mode 100644 docs/domain-model/user.puml
 delete mode 100644 theme.puml
 delete mode 100644 user.puml
 delete mode 100644 user.svg'
++ echo create mode 100644 .config/plantuml/config.puml create mode 100644 .config/plantuml/theme.puml delete mode 100644 config.puml create mode 100644 docs/README.md create mode 100644 docs/domain-model/README.md create mode 100644 docs/domain-model/user.md create mode 100644 docs/domain-model/user.puml delete mode 100644 theme.puml delete mode 100644 user.puml delete mode 100644 user.svg
++ awk '-F ' \' '{' if '(' '!~' /delete/ '#' ensure we are not trying to process deleted files '&&' '!~' '/theme.puml|config.puml/' '#' do not try to process our theme or custom config '&&' '~' /.puml/ ')' '#' only process puml files '{' printf '"%s' '",' '}' '#' only print the file name and strip newlines for spaces '}' END '{' print '""' '}' '#' ensure we do print a newline at the end \'
awk: cmd. line:1: '
awk: cmd. line:1: ^ invalid char ''' in expression
+ AWK_OUPUT=

如何保留带注释的多行 awk?

答案1

将代码放在函数中,而不是变量中,如下所示(未经测试,仍有改进空间):

set -x
set -e
do_awk() {
    awk '
        ($1 !~ /delete/) &&                 # ensure we are not trying to process deleted files
        ($4 !~ /theme.puml|config.puml/) && # do not try to process our theme or custom config
        ($4 ~ /.puml/) {                    # only process puml files
            printf "%s ", $4                # only print the file name and strip newlines for spaces
        }
        END { print "" }                    # ensure we do print a newline at the end
    ' "${@:--}"
}
GIT_OUTPUT=$(git diff-tree -r --no-commit-id --summary "$GITHUB_SHA")
AWK_OUPUT=$(printf '%s\n' "$GIT_OUTPUT" | do_awk)
echo "::set-output name=files::$GIT_OUTPUT"
set +e
set +x

答案2

您的主要问题是代码没有被引用,这使得 shell 替换了代码中的awk内容。$4为了保护代码免受 shell 的影响,请确保引用此处文档。您可以通过将起始定界词括在引号中(如<<'AWK'or 中<<"AWK")或将其转义为 来获得带引号的此处文档<<\AWK

这是按照我编写的方式重写您的脚本:

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk '
    $1 !~ /^delete/ && $4 !~ /(theme|config)\.puml$/ && $4 ~ /\.puml$/ {
        name[++n] = $4
    }
    END {
        $0 = ""
        for (i in name) $i = name[i]
        printf "::set-output name=files::%s\n", $0
    }'

请注意,我没有将中间数据存储在变量中。这样做效率低下(你可能不知道多少需要存储在变量中的数据)并且容易出现引用错误,而是在空格上吐出值并调用文件名通配。在这方面,使用$GIT_OUTPUTand不加引号是有问题的,并且特别麻烦,因为如果数据包含反斜杠,则可能会修改数据,具体取决于 shell 的配置。$AWKecho $GIT_OUTPUTecho

关于引用:参见什么时候需要双引号?

我在脚本中使用标准pattern { action }语法来构建一个数组,name其中包含要解析的字符串。在该END块中,我创建一个输出记录 ,$0并使用您用于输出的前缀进行输出echo

你也可以这样做,这给你留下了更多的评论空间:

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk '
    $1 ~ /^delete/ {
        # skip these
        next
    }
    $4 ~ /(theme|config)\.puml$/ {
        # and these...
        next
    }
    $4 ~ /\.puml$/ {
        # pick out filename (we assume no whitespace in filenames)
        name[++n] = $4
    }
    END {
        $0 = ""
        for (i in name) $i = name[i]
        printf "::set-output name=files::%s\n", $0
    }'

如果您想坚持将awk源代码放在此处文档中,我会这样做:

awk_script=$(mktemp) || exit 1
trap 'rm -f "$awk_script"' EXIT

cat <<'AWK_CODE' >"$awk_script"
$1 !~ /^delete/ && $4 !~ /(theme|config)\.puml$/ && $4 ~ /\.puml$/ {
    name[++n] = $4
}
END {
    $0 = ""
    for (i in name) $i = name[i]
    printf "::set-output name=files::%s\n", $0
}
AWK_CODE

git diff-tree -r --no-commit-id --summary "$GITHUB_SHA" |
awk -f "$awk_script"

即,将awk脚本保存到稍后使用调用的临时文件awk -f,并在脚本末尾删除(此处使用trap)。但对于如此短的awk程序,与在单引号字符串中使用脚本(如首先所示)相比,我认为这样做没有任何额外的好处。它很混乱并且包含很多额外的命令除了需要执行的两个中央命令之外,仅用于维护。

答案3

我认为最简单的方法(就可读性和可维护性而言)是将awk脚本发送到临时文件,然后由以下来源获取awk

awksrc=$(mktemp) || exit 1
cat << 'EOF' > "${awksrc}"
{
  if ( $1 !~ /delete/ # ensure we are not trying to process deleted files
       && $4 !~ /theme.puml|config.puml/ # do not try to process our theme or custom config
       && $4 ~ /.puml/ 
  ) # only process puml files
      { printf "%s ", $4 } # only print the file name and strip newlines for spaces
}
END { print "" } # ensure we do print a newline at the end
EOF
echo "$GIT_OUTPUT" | awk -f "${awksrc}" 
rm -f "${awksrc}"

答案4

我从未使用过 GitHub Workflow,但是文档说您可以使用自定义外壳。如果你说:

steps:
  - name: process puml files
    run: <your awk script here>
    shell: awk -f {0}

或其某些排列,您应该能够运行原始 awk 脚本而无需 shell 恶作剧。

相关内容