Answers:
警告:使用这些解决方案中的任何一种,您都需要意识到,您信任数据文件的完整性是安全的,因为它们将作为脚本中的Shell代码执行。保护它们对脚本的安全至关重要!
是的,在bash和zsh中,您都可以使用typeset
内置-p
函数和自变量以易于检索的方式序列化变量的内容。输出格式使您可以简单地source
将输出取回。
# You have variable(s) $FOO and $BAR already with your stuff
typeset -p FOO BAR > ./serialized_data.sh
您可以稍后在脚本中或在另一个脚本中将您的东西重新找回:
# Load up the serialized data back into the current shell
source serialized_data.sh
这将适用于bash,zsh和ksh,包括在不同的shell之间传递数据。Bash会将其转换为内置declare
函数,而zsh则通过Bash将其转换为内置函数,typeset
但由于bash对此具有别名,因此我们可以typeset
在这里使用它来实现ksh兼容性。
上面的实现确实很简单,但是如果您经常调用它,则可能需要给自己一个实用程序函数以使其变得更容易。此外,如果您尝试在自定义函数中包含上述内容,则会遇到变量范围问题。此版本应消除这些问题。
注意所有这些,为了保持庆典/ zsh的交叉兼容性,我们将在固定的两种情况typeset
,并declare
因此代码应该在一个或两个壳工作。如果您仅对一个或另一个外壳执行此操作,则会增加一些体积和混乱。
为此使用函数(或将代码包含在其他函数中)的主要问题是,typeset
函数生成的代码在从函数内部返回到脚本时会默认创建局部变量,而不是全局变量。
可以使用以下几种技巧之一解决此问题。我最初试图解决此问题的方法是解析序列化过程的输出,sed
以添加-g
标志,以便所创建的代码在返回时定义全局变量。
serialize() {
typeset -p "$1" | sed -E '0,/^(typeset|declare)/{s/ / -g /}' > "./serialized_$1.sh"
}
deserialize() {
source "./serialized_$1.sh"
}
请注意,时髦的sed
表达式仅匹配“ typeset”或“ declare”的首次出现,并添加-g
为第一个参数。仅需要匹配第一个匹配项,因为正如StéphaneChazelas在注释中正确指出的那样,否则它也将匹配序列化字符串包含文字换行符后跟“声明”或“排版”一词的情况。
除了纠正我的初步分析失礼,斯特凡还提出一个较不脆的方式来破解这不仅侧步骤,解析字符串的问题,但可能是一个有用的钩子使用的包装功能重新定义了行动,以添加额外的功能假设您没有使用声明或排版命令玩任何其他游戏,但是在您将此功能作为自己或其他功能的一部分包含在内的情况下,更容易实现此技术。您无法控制要写入的数据以及是否-g
添加了标志。别名也可以完成类似的操作,有关实现请参见Gilles的答案。
为了使结果更加有用,我们可以通过假设参数数组中的每个单词都是变量名来遍历传递给函数的多个变量。结果变成这样:
serialize() {
for var in $@; do
typeset -p "$var" > "./serialized_$var.sh"
done
}
deserialize() {
declare() { builtin declare -g "$@"; }
typeset() { builtin typeset -g "$@"; }
for var in $@; do
source "./serialized_$var.sh"
done
unset -f declare typeset
}
无论使用哪种解决方案,用法都将如下所示:
# Load some test data into variables
FOO=(an array or something)
BAR=$(uptime)
# Save it out to our serialized data files
serialize FOO BAR
# For testing purposes unset the variables to we know if it worked
unset FOO BAR
# Load the data back in from out data files
deserialize FOO BAR
echo "FOO: $FOO\nBAR: $BAR"
declare
是bash
等价ksh
的typeset
。bash
,在这方面zsh
也typeset
如此支持,typeset
更加便携。export -p
是POSIX,但不带任何参数,并且其输出依赖于外壳程序(尽管已为POSIX外壳程序很好地指定了它,例如,将bash或ksh称为sh
)。请记住引用您的变量;在这里使用split + glob运算符没有意义。
-E
仅在某些BSD中可以找到sed
。变量值可能包含换行符,因此sed 's/^.../.../'
不能保证可以正常工作。
a=$'foo\ndeclare bar' bash -c 'declare -p a'
for install将输出以开头的行declare
。它可能会更好做declare() { builtin declare -g "$@"; }
调用之前source
(和取消之后)
shopt -s expandalias
在不交互时进行操作。使用函数,您还可以增强declare
包装器,以便仅还原您指定的变量。
使用重定向,命令替换和参数扩展。需要双引号来保留空格和特殊字符。尾随x
保存尾随换行符,否则将在命令替换中将其删除。
#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}
在任何POSIX Shell中,都可以使用序列化所有环境变量export -p
。这不包括未导出的shell变量。输出使用正确的引号引起来,因此您可以在同一外壳中将其读回并获得完全相同的变量值。输出可能无法在另一个shell中读取,例如ksh使用非POSIX $'…'
语法。
save_environment () {
export -p >my_environment
}
restore_environment () {
. ./my_environment
}
Ksh(pdksh / mksh和ATT ksh),bash和zsh都typeset
内置了更好的功能。typeset -p
打印出所有已定义的变量及其值(zsh省略了用隐藏的变量的值typeset -H
)。输出包含正确的声明,以便在回读时导出环境变量(但如果在回读时已导出变量,则不会取消导出),以便将数组读回为数组,等等。这里也是输出被正确引用,但仅保证在同一外壳中可读。您可以在命令行上传递一组变量以进行序列化。如果您不传递任何变量,则全部序列化。
save_some_variables () {
typeset -p VAR OTHER_VAR >some_vars
}
在bash和zsh中,无法从函数进行恢复,因为typeset
函数内的语句的作用域是该函数。您需要. ./some_vars
在要使用变量值的上下文中运行,请注意,导出时全局变量将被重新声明为全局变量。如果要读回函数中的值并导出它们,则可以声明一个临时别名或函数。在zsh中:
restore_and_make_all_global () {
alias typeset='typeset -g'
. ./some_vars
unalias typeset
}
在bash中(使用declare
而不是typeset
):
restore_and_make_all_global () {
alias declare='declare -g'
shopt -s expand_aliases
. ./some_vars
unalias declare
}
在ksh中,typeset
用定义的函数声明局部变量,用定义的函数声明function function_name { … }
全局变量function_name () { … }
。
如果需要更多控制,可以手动导出变量的内容。要将变量的内容准确地打印到文件中,请使用printf
内置功能(echo
有一些特殊情况,例如echo -n
在某些shell上并添加了换行符):
printf %s "$VAR" >VAR.content
您可以使用来读回该内容$(cat VAR.content)
,除了命令替换会删除尾随的换行符。为了避免出现这种皱纹,请安排输出不要以换行符结尾。
VAR=$(cat VAR.content && echo a)
if [ $? -ne 0 ]; then echo 1>&2 "Error reading back VAR"; exit 2; fi
VAR=${VAR%?}
如果要打印多个变量,可以用单引号将它们引起来,并用替换所有嵌入的单引号'\''
。可以将这种形式的引用读回到任何Bourne / POSIX样式的shell中。以下代码段可在任何POSIX Shell中使用。它仅适用于字符串变量(以及包含它们的shell中的数字变量,尽管它们将作为字符串读回),但不会尝试处理具有它们的shell中的数组变量。
serialize_variables () {
for __serialize_variables_x do
eval "printf $__serialize_variables_x=\\'%s\\'\\\\n \"\$${__serialize_variables_x}\"" |
sed -e "s/'/'\\\\''/g" -e '1 s/=.../=/' -e '$ s/...$//'
done
}
这是另一种方法,它不派生子进程,但在字符串操作上较重。
serialize_variables () {
for __serialize_variables_var do
eval "__serialize_variables_tail=\${$__serialize_variables_var}"
while __serialize_variables_quoted="$__serialize_variables_quoted${__serialize_variables_tail%%\'*}"
[ "${__serialize_variables_tail%%\'*}" != "$__serialize_variables_tail" ]; do
__serialize_variables_tail="${__serialize_variables_tail#*\'}"
__serialize_variables_quoted="${__serialize_variables_quoted}'\\''"
done
printf "$__serialize_variables_var='%s'\n" "$__serialize_variables_quoted"
done
}
请注意,在允许只读变量的shell上,如果尝试回读只读变量,则会收到错误消息。
$PWD
和的变量$_
-请在下面查看您自己的注释。
typeset
别名typeset -g
?
非常感谢@stéphane-chazelas指出了我以前的尝试中的所有问题,这现在似乎可以将数组序列化为stdout或变量。
此技术不会解析输入(不同于declare -a
/ declare -p
),因此可以安全地防止在序列化文本中恶意插入元字符。
注意:由于read
会删除\<newlines>
字符对,因此-d ...
不能转义换行符,因此必须将其传递给read,然后保留未转义的换行符。
所有这些都在unserialise
功能中进行管理。
使用了两个魔术字符,即字段分隔符和记录分隔符(以便可以将多个数组序列化到同一流)。
这些字符可以定义为FS
,RS
但都不能定义为newline
字符,因为转义的换行符被删除read
。
转义字符必须为\
反斜杠,因为这是read
为了避免将该字符识别为IFS
字符。
serialise
将序列化为"$@"
stdout,serialise_to
将序列化为中的变量$1
serialise() {
set -- "${@//\\/\\\\}" # \
set -- "${@//${FS:-;}/\\${FS:-;}}" # ; - our field separator
set -- "${@//${RS:-:}/\\${RS:-:}}" # ; - our record separator
local IFS="${FS:-;}"
printf ${SERIALIZE_TARGET:+-v"$SERIALIZE_TARGET"} "%s" "$*${RS:-:}"
}
serialise_to() {
SERIALIZE_TARGET="$1" serialise "${@:2}"
}
unserialise() {
local IFS="${FS:-;}"
if test -n "$2"
then read -d "${RS:-:}" -a "$1" <<<"${*:2}"
else read -d "${RS:-:}" -a "$1"
fi
}
并反序列化:
unserialise data # read from stdin
要么
unserialise data "$serialised_data" # from args
例如
$ serialise "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
Now is the time;For all good men;To drink $drink;At the `party`;Party Party Party:
(没有尾随换行符)
读回去:
$ serialise_to s "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
$ unserialise array "$s"
$ echo "${array[@]/#/$'\n'}"
Now is the time
For all good men
To drink $drink
At the `party`
Party Party Party
要么
unserialise array # read from stdin
Bash read
尊重转义字符\
(除非您传递-r标志)以除去字符的特殊含义,例如用于输入字段分隔或行定界。
如果要序列化数组而不是单纯的参数列表,则只需将数组作为参数列表传递:
serialise_array "${my_array[@]}"
您可以unserialise
像在循环中那样使用read
它,因为它只是一个包装的读取-但请记住,流不是用换行符分隔的:
while unserialise array
do ...
done
bash
,并zsh
使其作为$'\xxx'
。尝试使用bash -c $'printf "%q\n" "\t"'
bash -c $'printf "%q\n" "\u0378"'
$IFS
未修改,现在无法正确还原空数组元素。实际上,使用不同的IFS值并-d ''
避免逃脱换行符会更有意义。例如,:
用作字段分隔符,仅将其和反斜杠转义并用于IFS=: read -ad '' array
导入。
read
。backslash-newline for read
是一种将逻辑线延伸到另一条物理线的方法。编辑:啊,我看到您已经提到换行符的问题。
printf 'VAR=$(cat <<\'$$VAR$$'\n%s\n'$$VAR$$'\n)' "$VAR" >./VAR.file
另一种方法是确保您处理所有这样的'
硬引号:
sed '"s/'"'/&"&"&/g;H;1h;$!d;g;'"s/.*/VAR='&'/" <<$$VAR$$ >./VAR.file
$VAR
$$VAR$$
或搭配export
:
env - "VAR=$VAR" sh -c 'export -p' >./VAR.file
假设变量的值不包含字符串,则第一个和第二个选项可在任何POSIX Shell中使用:
"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n"
第三个选项适用于任何POSIX Shell,但可以尝试定义其他变量,例如_
或PWD
。不过,事实是,它可能尝试定义的唯一变量是由Shell本身设置和维护的-因此,即使您确实导入export
了其中任何一个的值$PWD
,例如-Shell也会将它们简单地重置为无论如何,立即获得正确的值-尝试做PWD=any_value
,自己看看吧。
并且因为-至少对于GNU- bash
调试输出会自动安全地引用以重新输入到shell,所以无论在'
硬引号中有多少,此操作都有效"$VAR"
:
PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file
$VAR
以后可以在以下路径有效的任何脚本中将其设置为保存的值:
. ./VAR.file
$$
是正在运行的Shell的PID,您是否得到了错误的引号和均值\$
?使用here文档的基本方法可以起作用,但这是棘手的,而不是单行的材料:无论选择什么作为结束标记,都必须选择字符串中未出现的内容。
$VAR
包含时,第二个命令不起作用%
。第三个命令并不总是适用于包含多行的值(即使在添加明显缺失的双引号之后)。
env
。我仍然很好奇您对多行的意思- sed
删除每行直到遇到VAR=
最后一行-因此所有行都$VAR
继续传递。您能提供一个打破它的例子吗?
VAR
)未更改,PWD
或者未更改_
某些shell定义的名称。第二种方法需要bash;的输出格式-v
未标准化(破折号,ksh93,mksh和zsh均无效)。
几乎相同,但有些不同:
从您的脚本中:
#!/usr/bin/ksh
save_var()
{
(for ITEM in $*
do
LVALUE='${'${ITEM}'}'
eval RVALUE="$LVALUE"
echo "$ITEM=\"$RVALUE\""
done) >> $cfg_file
}
restore_vars()
{
. $cfg_file
}
cfg_file=config_file
MY_VAR1="Test value 1"
MY_VAR2="Test
value 2"
save_var MY_VAR1 MY_VAR2
MY_VAR1=""
MY_VAR2=""
restore_vars
echo "$MY_VAR1"
echo "$MY_VAR2"
以上时间已通过测试。
'
,*
等
echo "$LVALUE=\"$RVALUE\""
应该保留换行符,并且cfg_file中的结果应类似于:MY_VAR1 =“ Line1 \ nLine 2”因此,当评估MY_VAR1时,它将也包含换行符。当然,如果您的储值本身包含"
char,则可能会遇到问题。但是,也可以注意这一点。