是否可以打印命令以便可以解析输出以创建相同的命令?

U. *_*ndl 7 bash parameter quoting

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

% 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

澄清

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

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

> X=(echo "Bob's car is named \"Bobby\"")
Run Code Online (Sandbox Code Playgroud)

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

echo 鲍勃的车名为“鲍比”

一种可能的正确输出可能是

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

mur*_*uru 6

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'
Run Code Online (Sandbox Code Playgroud)


Sté*_*las 5

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

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

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

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

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

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

readarray -td '' args < file
Run Code Online (Sandbox Code Playgroud)

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

然后做:

"${args[@]}"
Run Code Online (Sandbox Code Playgroud)

来执行命令。

或者甚至使用 GNU xargs

xargs -r0a file env
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

那么反序列化就是:

eval "$serialised"
Run Code Online (Sandbox Code Playgroud)

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

进而:

"${args[@]}"
Run Code Online (Sandbox Code Playgroud)

再次运行该命令。

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


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

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

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

print -C var | read -C var_copy
Run Code Online (Sandbox Code Playgroud)