如何使嵌入 YAML 的 shell 脚本安全地应对不同引号的问题?

How to make YAML-embedded shell script safe against problems with different quotes?

我有一些使用 YAML 文件配置的应用程序。该应用程序会进行一些处理,并支持挂钩在处理前后执行 shell 代码。这主要是为了执行执行实际业务的外部脚本文件,但也可以例如也导出环境变量。看起来事情只是简单地转发到一个 shell 呼叫与配置的字符串。

需要注意的重要一点是,一个钩子是专门调用的,以防在处理过程中出现任何问题。在这种情况下,应用程序会向配置的 shell 脚本提供一些额外的错误详细信息。这是通过读取 YAML 配置的必要部分并将特殊关键字的简单字符串替换为运行时实际可用的内容来完成的。这些关键字遵循语法 {...}。我的配置中的内容如下所示:

on_error:
    - |
      export BGM_ERR_VAR_CONFIG_PATH="{configuration_filename}"
      export BGM_ERR_VAR_REPO="{repository}"
      export BGM_ERR_VAR_ERROR_MSG="{error}"
      export BGM_ERR_VAR_ERROR_OUT="{output}"

      '/path/to/script.sh 'some_arg' '[...]' [...]

最初,这些关键字应该作为被调用脚本中的参数进行转发,但我的脚本已经需要一些其他参数,所以我决定使用环境变量进行转发。不过,应该不会对我的问题造成太大影响。

问题是任何事情都可能出错,尤其是占位符 {output} 可能包含任意复杂的错误消息。它很可能是执行的 shell 命令的混合体,在大多数情况下使用单引号,以及使用双引号实现应用程序的编程语言的堆栈跟踪。使用我上面的配置,这会导致最终执行无效的 shell 代码:

[2021-10-12 07:18:46,073] ERROR: /bin/sh: 13: Syntax error: Unterminated quoted string

以下是应用记录的所有执行内容:

[2021-10-12 07:18:46,070] DEBUG: export BGM_ERR_VAR_CONFIG_PATH="/path/to/some.yaml"
export BGM_ERR_VAR_REPO="HOST:PARENT/CHILD"
export BGM_ERR_VAR_ERROR_MSG="Command 'borg check --prefix arch- --debug --show-rc --umask 0007 HOST:PARENT/CHILD' returned non-zero exit status 2."
export BGM_ERR_VAR_ERROR_OUT="using builtin fallback logging configuration
35 self tests completed in 0.04 seconds
SSH command line: ['ssh', '-F', '/[...]/.ssh/config', 'HOST', 'borg', 'serve', '--umask=007', '--debug']
RemoteRepository: 169 B bytes sent, 66 B bytes received, 3 messages sent
Connection closed by remote host
Traceback (most recent call last):
  File "borg/archiver.py", line 177, in wrapper"

'/path/to/script.sh '[...]' '[...]' '[...]' '[...]'

我自己的脚本的参数在引用方面是安全的,那些只是硬编码路径、关键字等,没有任何动态。问题应该是用于抛出异常的 python 文件路径的双引号。 OTOH,如果我只在我的环境变量中使用单引号,那么它们将会中断,因为调用的输出 shell 命令也使用单引号。

那么,如何在这种情况下将 {output} 安全转发到环境变量中?

我想过使用一些 subshell ="$(...)"sed 来规范化引号,但我想出的一切都导致命令行出现与以前完全相同的引号问题. printf and its %q 转义引号也是如此。看来我需要一些能够处理任意单个参数并将它们再次加入某个字符串或类似内容的东西。此外,事情不应该太复杂,以免最终使 YAML 配置膨胀。

以下可能有效,但会丢失双引号:

export BGM_ERR_VAR_ERROR_OUT="$(echo "{output}")"

怎么样?

export BGM_ERR_VAR_ERROR_OUT="$(cat << EOT
{output}
EOT
)"

还有什么吗?谢谢!

为了避免所有替换问题,我建议不要使用替换,而是将值作为环境变量转发。这假定您可以控制调用代码,根据您的解释我认为这是正确的。

由于环境变量按照惯例是大写的,所以将您的值放在小写名称中是非常安全的,然后您可以简单地做

on_error:
    - |
      export BGM_ERR_VAR_CONFIG_PATH="$configuration_filename"
      export BGM_ERR_VAR_REPO="$repository"
      export BGM_ERR_VAR_ERROR_MSG="$error"
      export BGM_ERR_VAR_ERROR_OUT="$output"

      '/path/to/script.sh 'some_arg' '[...]' [...]

调用代码需要相应地修改环境,以便它包含预期值。这是转发值的最安全方法,因为它保证根本不会将值解释为 bash 语法。

如果这不可能,下一个最好的办法可能是使用 heredoc,尽管它带有引号以避免处理内容中的任何内容——您可以使用 read 来避免不必要的 cat:

on_error:
    - |
      read -r -d '' BGM_ERR_VAR_CONFIG_PATH <<'EOF'
      {configuration_filename}
      EOF
      export BGM_ERR_VAR_CONFIG_PATH

      # ... snip: other variables ...

      '/path/to/script.sh 'some_arg' '[...]' [...]

这里您唯一需要注意的是,内容可能不包含一行 EOF。调用代码需要确保这一点。