シェル変数を直列化する方法はありますか?変数があり、$VAR
それをファイルや他の場所に保存してから、後でもう一度読んで同じ値を取得できるようにしたいとします。
これを行うポータブルな方法はありますか? (私はそうは思わない)
Bashまたはzshでこれを行う方法はありますか?
答え1
警告する:これらのソリューションでは、データファイルがスクリプト内のシェルコードとして実行されるため、データファイルの整合性を安全に信頼できることを認識する必要があります。これを保護することは、スクリプトのセキュリティにとって非常に重要です!
1つ以上の変数を直列化するための単純なインライン実装
typeset
はい、bashとzshでは、組み込み関数とパラメータを使用して、簡単に検索可能な方法で変数の内容を直列化できます-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で機能します。 Bashはこれをdeclare
zshがこれを達成するために使用する組み込み関数に変換しますtypeset
が、bashにはエイリアスがあるため、typeset
kshの互換性のためにここで使用できます。
関数を使用したより複雑な一般的な実装
上記の実装は非常に簡単ですが、頻繁に呼び出す場合はユーティリティ関数を簡単に提供できます。また、上記の内容をユーザー定義関数に含めようとすると、変数範囲の指定に関する問題が発生します。このバージョンでは、これらの問題は削除されます。
bash / zshの互換性を維持するために、両方のケースを修正して、コードが両方または両方で機能するようにしますtypeset
。declare
これにより、1つのシェルまたは別のシェルにのみこれを実行した場合に削除できるわずかな量と混乱が追加されます。
そのために関数を使用すること(または他の関数にコードを含めること)の主な問題は、関数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
。最初の項目だけが一致する必要があるため、スティーブン・チャジェラスコメントに正しく指定されています。それ以外の場合、シリアライズされた文字列にリテラル改行文字と単語宣言または組版が含まれている場合も一致します。
元の解析を修正することに加えて失礼、スティーブンド提案文字列解析の問題を解決するだけでなく、データの再インポート時に実行されるアクションをオーバーライドするラッパー関数を使用してアドインを追加するのに役立つフックになる可能性のある脆弱なハッキングです。これは、宣言や組版命令を使用して他のゲームをプレイしないと仮定します。ただし、この関数を他の関数の一部として含めるか、どのデータが作成されるかを制御できない場合は、ロゴが追加されました-g
。エイリアスを使用して同様の操作を実行できます。ザイルズの答え実装。
結果をより便利にするために、引数配列の各単語が変数名であると仮定して、関数に渡された複数の変数を繰り返すことができます。結果は次のとおりです。
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"
答え2
リダイレクト、コマンド置換、およびパラメータ拡張を使用します。スペースと特殊文字を保存するには二重引用符が必要です。末尾はx
コマンド置換から削除される末尾の改行を保存します。
#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}
答え3
すべて直列化 — POSIX
すべてのPOSIXシェルで、次を使用してすべての環境変数をシリアライズできます。export -p
。これには、エクスポートされていないシェル変数は含まれません。同じシェルから読み直し、正確に同じ変数値を取得できるように、出力が正しく引用されます。 POSIX以外の構文を使用するkshなどの他のシェルでは、出力を読み取ることができない可能性があります$'…'
。
save_environment () {
export -p >my_environment
}
restore_environment () {
. ./my_environment
}
一部または完全なシリアル化 — ksh、bash、zsh
Ksh(pdksh / mkshとATT ksh)、bash、zshはより良いツールを提供します。typeset
内蔵。typeset -p
定義されたすべての変数とその値を印刷します(zshは隠し変数の値を省略しますtypeset -H
)。出力には正しい宣言が含まれており、再読み込み時に環境変数をエクスポートし(再読み込み時に変数をすでにエクスポートしている場合はエクスポートをキャンセルしません)、配列を配列などに読み直すことができます。正しく引用された出力もありますが、同じシェル内でのみ読み取ることができます。コマンドラインからシリアライズするための変数配列を渡すことができます。変数を渡さないと、すべての変数が直列化されます。
save_some_variables () {
typeset -p VAR OTHER_VAR >some_vars
}
typeset
bashとzshでは、関数内のステートメントはその関数に制限されているため、関数から回復を実行することはできません。. ./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 () { … }
。
一部のシリアル化 — POSIX
より多くの制御が必要な場合は、変数の内容を手動でエクスポートできます。変数の正確な内容をファイルに印刷するには、printf
組み込み関数を使用します(一部のシェルや改行の追加echo
など、いくつかの特殊な場合)。echo -n
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 スタイルのシェルで再度読み取ることができます。次のコードスニペットは、すべてのPOSIXシェルで動作します。これは文字列変数(およびそれを含むシェルの数値変数(文字列として再度読み込まれます))でのみ機能し、その変数を持つシェルの配列変数を処理しようとしません。
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
}
読み取り専用変数を許可するシェルから読み取り専用変数を再読み込みしようとすると、エラーメッセージが表示されます。
答え4
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
最初と2番目のオプションは、変数の値に文字列が含まれていないと仮定して、すべてのPOSIXシェルで機能します。
"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n"
3番目のオプションはすべてのPOSIXシェルで機能しますが、_
他の変数を定義してみることもできますPWD
。ただし、実際に定義しようとする唯一の変数は、シェル自体によって設定され維持される変数です。したがって、たとえばexport
、変数の1つをインポートすると、$PWD
シェルはその変数をすぐにリセットします。正しい値が得られます。試してみて、PWD=any_value
自分で確認してみてください。
そして、少なくともGNUの場合、デバッグ出力はシェルに戻るために自動的に安全に引用されるので、bash
これは次のハード引用符の数に関係なく機能します。'
"$VAR"
PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file
$VAR
これは、後で有効なスクリプトで保存された値に次のパスを設定できます。
. ./VAR.file