澄清

澄清

这个问题听起来可能很复杂,但事实上并非如此!考虑:

% f() { echo "$@"; }
% f a
a
% f cmd -o"value with space"
cmd -ovalue with space
% f cmd -ovalue with space
cmd -ovalue with space
% f cmd -o'value with "quotes"'
cmd -ovalue with "quotes"
% f cmd -ovalue with "quotes"
cmd -ovalue with quotes

显然,“带空间的值”只是一个参数的属性丢失了;同样,双引号在重新输入时也会被“吃掉”。

所需的输出是可以再次用作输入以产生相同输出的输出。

我不认为 BASH 中内置了允许这样做的东西,对吗?

澄清

如果我想要做什么并不明显:我有一个存储在 shell 数组中的命令,并且我想将这样的数组打印到标准输出,以便用户可以复制并粘贴输出以在 shell 提示符(或script),以便重现数组中的相同命令。

考虑这个(愚蠢的)例子:

> X=(echo "Bob's car is named \"Bobby\"")

一个普通的echo "${X[@]}"会输出

echo 鲍勃的车名为“鲍比”

尽管可能的正确输出可能是

echo 鲍勃的汽车名为“鲍比”'

答案1

期间转型的选择之一Bash 中的参数扩展是(似乎从 Bash 4.4 开始可用;旧版本输出“错误替换”):

${parameter@operator}
扩张要么是价值的转变范围或有关信息范围本身,取决于运算符的值。每个操作员是一个字母:

[...]
Q
扩展是一个字符串,其值为范围以可重复用作输入的格式引用。

bash-5.2$ f() { echo "${@@Q}"; }
bash-5.2$ f cmd -o'value with "quotes"'
'cmd' '-ovalue with "quotes"'
bash-5.2$ f cmd -ovalue with "quotes"
'cmd' '-ovalue' 'with' 'quotes'

答案2

您正在寻找的功能称为序列化序列化美式英语)。

这里一个简单的命令是一个由一个或多个字符串组成的数组,因此它可以归结为序列化一个数组。

如果命令是外部命令,则存在进一步的限制,即字符串不能包含 NUL 字节,因为它们作为 C 样式字符串传递给系统execve()调用。在大多数 shell 中,即使对于不涉及execve()系统调用的命令(例如内置命令或函数),也有相同的限制,唯一的例外是 zsh shell。

因此,如果您可以假设命令参数不包含 NUL 字节,则序列化很容易:您只需以 NUL 分隔来打印它们:

print0() {
  [ "$#" -eq 0 ] ||
    printf '%s\0' "$@"
}
print0 cmd -o"value with space" > file

bash4.4 或更高版本中,将其作为参数列表读回只是:

readarray -td '' args < file

在哪里-d ''将 NUL 字节设置为分隔符-t从值中删除分隔符,这在当前版本的 bash 中并不是绝对必要的,因为当前版本的 bash 无法在变量中存储 NUL。

然后做:

"${args[@]}"

来执行命令。

或者甚至使用 GNU xargs

xargs -r0a file env

但请注意,除了 zsh 之外,您不能像在所有其他 shell 中一样将该序列化的结果存储在变量中,shell 变量的值中不能有 NUL 字节。

JSON、XML、YAML 是用于序列化复杂数据结构的常用格式,但它们也有自己的问题(例如,JSON 字符串必须由字符组成,而参数字符串是任意字节的数组),更重要的是,很少有 shell具有对解析它们的内置支持(ksh93v-beta 版本对解析 JSON 有一些实验性支持,但这有很多错误并在较新的版本中被删除)。

一些语言具有内置的序列化格式。例如,php有一个serialize()以及相应的unserialize()功能,但php没有很好的 API 来执行命令。

解释语言中的常见方法是将序列化为代码。例如,这就是Data::Dumperin所做的事情。如果您有一个带有和作为参数的perl数组,则可以将其存储为,然后只需评估该 perl 代码即可取回该数组。cmd-ovalue with space@array = ("cmd", "-o value with space")

在 bash 或 zsh 等类似 Korn 的 shell 中,这很容易完成,因为这正是所做typeset -p的。在 zsh 中,您可以在函数typeset -p argv中执行此serialise操作,但不能执行此操作,typeset -p @因为它@不是变量。在 中bash,位置参数未映射到argv变量,您仍然可以使用临时数组。

serialise() {
  local args
  args=( "$@" )
  typeset -p args
}
serialised=$(serialise cmd -o"value with space")

那么反序列化就是:

eval "$serialised"

这将创建$args数组(请注意,如果在函数中运行,该数组将是该函数的本地数组)。

进而:

"${args[@]}"

再次运行该命令。

请注意,反序列化必须使用与序列化完成时相同的 shell 版本、相同的操作系统和相同的区域设置来完成。看这是“转义变量以用作另一个脚本的内容”的答案有关如何序列化字符串的更多详细信息。


为了完整起见,ksh93 具有比其他 shell 更复杂的数据结构,包括多维数组、结构和对象,因此有内置的序列化和反序列化支持。

  • 序列化:print -C var
  • 反序列化:read -C var

例如,您可以使用以下命令复制变量:

print -C var | read -C var_copy

答案3

要添加到其他答案,bash(和 zsh)对此printf有:%q

%q 导致 printf 以可重复用作 shell 输入的格式输出相应的参数。

$ f() { printf '%q ' "$@"; echo; }
$ f cmd -o"value with space" -o'"'\' -o"'" -o"'\\" -o'\' $'\n'
cmd -ovalue\ with\ space -o\"\' -o\' -o\'\\ -o\\ $'\n' 

以上是它如何与这些 shell 中的内置函数一起工作。除此之外,printfcoreutils 中的 GNU 也支持这一点,但输出是引用的而不是转义的:

$ f() { /bin/printf '%q ' "$@"; echo; }
$ f cmd -o"value with space" -o'"'\' -o"'" -o"'\\" -o'\' $'\n'
cmd '-ovalue with space' '-o"'\''' "-o'" '-o'\''\' '-o\' ''$'\n' 
请注意,“printf”的工作方式是“重复”格式字符串,同时还有剩余参数。因此,在这些示例中,末尾有一个尾随空格。

相关内容