bash または zsh でシェル変数をシリアル化する

bash または zsh でシェル変数をシリアル化する

シェル変数をシリアル化する方法はありますか? 変数があり$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 の相互互換性を維持するために、 と の両方のケースを修正しtypesetdeclareコードがどちらかのシェルまたは両方のシェルで機能するようにすることに注意してください。これにより、かさばりと混乱が生じますが、これを 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最初の引数として追加することであることに注意してください。最初の出現のみに一致する必要があるのは、ステファン・シャゼラスコメントで正しく指摘されていますが、そうでない場合、シリアル化された文字列にリテラルの改行とそれに続く単語declareまたはtypesetが含まれる場合にも一致します。

最初の解析を修正するだけでなく失言ステファンも提案されたこれをハックするより簡単な方法は、文字列の解析に関する問題を回避するだけでなく、ラッパー関数を使用してデータをソースに戻すときに実行されるアクションを再定義することで、追加機能を追加するための便利なフックになる可能性があります。これは、declareコマンドまたはtypesetコマンドで他のゲームをプレイしていないことを前提としていますが、この機能を独自の別の関数の一部として含めている場合や、書き込まれるデータとフラグが追加されているかどうかを制御できない場合に、このテクニックを実装する方が簡単です-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これにはエクスポートされていないシェル変数は含まれません。出力は適切に引用符で囲まれているため、同じシェルで読み戻すと、まったく同じ変数値を取得できます。出力は別のシェルでは読み取れない可能性があります。たとえば、ksh は非 POSIX$'…'構文を使用します。

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
}

typesetbash と 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などのいずれかの の値をインポートしたとしても$PWD、シェルはとにかくそれらをすぐに正しい値にリセットするだけです。実際に試して、PWD=any_value自分で確認してください。

また、少なくとも GNU ではbash、デバッグ出力はシェルへの再入力のために自動的に安全引用符で囲まれるため、次のコードは'ハード引用符の数に関係なく機能します"$VAR"

 PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file

$VAR次のパスが有効なスクリプトでは、後で保存した値に設定できます。

. ./VAR.file

関連情報