【问题标题】:Honoring quotes while reading shell arguments from a file从文件中读取 shell 参数时尊重引号
【发布时间】:2015-06-08 22:50:14
【问题描述】:

在 bash 中,我可以将带引号的参数传递给这样的命令:

$ printf '[%s]\n' 'hello world'
[hello world]

但如果参数来自子shell,我无法让它正常工作:

$ cat junk
'hello world'
$ printf '[%s]\n' $(cat junk)
['hello]
[world']

或者:

$ cat junk
hello world
$ printf '[%s]\n' $(cat junk)
[hello]
[world]

或者:

$ cat junk
hello\ world
$ printf '[%s]\n' $(cat junk)
[hello\]
[world]

我该如何正确地做到这一点?

编辑:解决方案也需要处理这种情况:

$ printf '[%s]\n' abc 'hello world'
[abc]
[hello world]

所以这个解决方案不起作用:

$ cat junk
abc 'hello world'
$ printf '[%s]\n' "$(cat junk)"
[abc 'hello world']

Bash quoting issue 的问题已被建议重复。但是,尚不清楚如何应用其接受的答案。以下失败:

$ cat junk
abc 'hello world'
$ FOO=($(cat junk))
$ printf '[%s]\n' "${FOO[@]}"
[abc]
['hello]
[world']

【问题讨论】:

  • “我正在尝试将命令放入变量中,但复杂的情况总是失败!” mywiki.wooledge.org/BashFAQ/050
  • 如果你想要一个通用的解决方案,printf %q 就是它。也就是说:printf -v var_q %q "$var"会将"$var"的内容的eval-safe版本放入"$var_q"
  • @rici:该解决方案在这里确实有效。请参阅上面的编辑。
  • 错误。 abc 'hello world' 的答案是“不要那样做”。使用 NUL 分隔的流来明确且安全地表示 shell 数组。
  • (作为另一个说明——避免 shell 局部变量的全大写变量名;这些是为系统支持的 shell 内置函数和环境变量保留的。请参阅pubs.opengroup.org/onlinepubs/009695399/basedefs/… 的 POSIX 规范,第四段,关于命名约定——记住 shell 变量和环境变量共享一个命名空间)。

标签: arrays bash


【解决方案1】:

这里没有一个好的解决方案,但你可以选择不好的解决方案。


这个答案需要更改文件格式:

对文件使用 NUL 分隔的流是最安全的方法;从字面上看,任何 C 字符串(因此,任何字符串 bash 都可以存储为数组元素)可以以这种方式写入和读取。

# write file as a NUL-delimited stream
printf '%s\0' abc 'hello world' >junk

# read file as an array
foo=( )
while IFS= read -r -d '' entry; do
  foo+=( "$entry" )
done <junk

如果有效参数不能包含换行符,您可能希望省略读取端的 -d '' 并将写入端的 \0 更改为 \n 以使用换行符而不是 NUL。请注意,UNIX 文件名可以包含换行符,因此如果您可能的参数包含文件名,那么这种方法是不明智的。


这个答案几乎实现了类似shell的解析语义:

foo=( )
while IFS= read -r -d '' entry; do
  foo+=( "$entry" )
done < <(xargs printf '%s\0' <junk)

xargs 有一些围绕多行字符串的极端情况,其中它的解析与 shell 的解析方式不同完全。然而,这是一个 99% 的解决方案。


这个答案需要 Python 解释器:

Python 标准库 shlex 模块支持符合 POSIX 的字符串标记化,这比 xargs 实现的更符合标准。请注意,$'foo' 等 bash/ksh 扩展名不受支持。

shlex_split() {
  python -c '
import shlex, sys
for item in shlex.split(sys.stdin.read()):
    sys.stdout.write(item + "\0")
'
}
while IFS= read -r -d '' entry; do
  foo+=( "$entry" )
done < <(shlex_split <junk)

这些答案存在安全风险:

...具体来说,如果junk 的内容可以写成包含对shell 敏感的代码(如$(rm -rf /)),你不想使用它们中的任何一个:

# use declare
declare "foo=($(cat junk))"

# ...or use eval directly
eval "foo=( $(cat junk) )"

如果您想确保foo 以一种可以安全阅读的方式编写,并且您控制写入它的代码,请考虑:

# write foo array to junk in an eval-safe way, if it contains at least one element
{ printf '%q ' "${foo[@]}" && printf '\n'; } >junk;

或者,您可以使用:

# write a command which, when evaluated, will recreate the variable foo
declare -p foo >junk

和:

# run all commands in the file junk
source junk

【讨论】:

  • 酷。我会使用declare -p foo 写出序列化值——它也适用于空数组。如果可能的话,这也是读取数据的好格式;你可以把它想象成 BSON :)
  • 谢谢! FWIW,我刚刚开发了一个小工具,以便更轻松地将这样的 null-delim 列表实际发送到程序:github.com/Abscissa/safeArg
  • @Abscissa,顺便提一下,带有 -0 参数的 GNU xargs 也会这样做,尽管有将长参数列表拆分为多个调用的额外行为
猜你喜欢
  • 1970-01-01
  • 2010-12-15
  • 1970-01-01
  • 2020-08-15
  • 1970-01-01
  • 1970-01-01
  • 2017-05-21
  • 2014-06-28
  • 1970-01-01
相关资源
最近更新 更多