web-dev-qa-db-ja.com

bashスクリプトで配列をエクスポートする

次のように、bashスクリプトから別のbashスクリプトに配列をエクスポートできません。

export myArray[0]="Hello"
export myArray[1]="World"

このように書いても問題はありません:

export myArray=("Hello" "World")

いくつかの理由で、配列を複数行に初期化する必要があります。解決策はありますか?

47
Remiii

配列変数は(まだ)エクスポートされない場合があります。

Ubuntu 10.04のbashバージョン4.1.5のマンページから。

Chet Ramey(2011年現在のbashメンテナー)からの次のステートメントは、おそらくこの「バグ」に関する最も公式なドキュメントです。

配列変数を環境にエンコードする良い方法は実際にはありません。

http://www.mail-archive.com/[email protected]/msg01774.html

46
lesmana

TL; DR:エクスポート可能な配列は直接ではありませんbash-4.3まではサポートされますが、(効果的に)配列をエクスポートできます次の2つの方法のいずれかです。

  • 子スクリプトの呼び出し方法の簡単な変更
  • エクスポートされた関数を使用して、配列の初期化を格納し、子スクリプトを簡単に変更します。

または、bash-4.3がリリースされるまで待つことができます(2014年2月の開発/ RC状態では、変更ログのARRAY_EXPORTを参照してください)。 更新:この機能は、4.3で有効になっているではなくです。ビルド時にARRAY_EXPORTを定義すると、ビルドは失敗します。作成者は 述べている この機能を完了する予定はありません。


最初に理解することは、bash環境(より適切には コマンド実行環境 )はPOSIX環境の概念とは異なることです。 POSIX環境 は、型指定されていないname=valueペアのコレクションであり、プロセスからその子へ さまざまな方法で渡すことができます (実質的に制限された形式 [〜#〜] ipc [〜#〜] )。

Bash実行環境は、型付き変数、読み取り専用およびエクスポート可能なフラグ、配列、関数などを備えた事実上のスーパーセットです。これは、set(bash builtin)とenvまたはprintenvの出力が異なる理由の一部を説明しています。

invoke別のbashシェルで新しいプロセスを開始すると、bash状態がいくらか失われます。ただし、スクリプトをドットソース化すると、スクリプトは同じ環境で実行されます。または、( )を介してサブシェルを実行すると、環境も保持されます(プロセス環境を使用して再初期化するのではなく、bashフォークが完全な状態を保持するため)。


@lesmanaの回答で参照されている制限は、POSIX環境が単に余分な意味のないname=valueペアであり、型付き変数をエンコードまたはフォーマットする合意された方法がないために発生します。関数に関する興味深いbashの癖については以下を参照 、およびbash-4.3の今後の変更(提案されたアレイ機能は放棄されました)。

declare -p(組み込み)を使用してこれを行う簡単な方法がいくつかあり、bash環境の一部を1つ以上のdeclareステートメントのセットとして出力します。名前"。これは基本的な serialisation ですが、 complexity の一部は他の回答が暗示しています。 declare -pは、配列インデックス、スパース配列、および面倒な値の引用を保持します。配列の単純なシリアル化では、値を1行ずつダンプし、read -a myarrayを使用して復元することができます(read -aは自動的にインデックスを割り当てるため、連続する0インデックス付き配列で動作します)。

これらのメソッドでは、配列を渡すスクリプトを変更する必要はありません。

declare -p array1 array2 > .bash_arrays       # serialise to an intermediate file
bash -c ". .bash_arrays; . otherscript.sh"    # source both in the same environment

上記のbash -c "..."形式のバリエーションは、変数を設定するためにcrontabで(誤って)使用されることがあります。

代替手段は次のとおりです。

declare -p array1 array2 > .bash_arrays       # serialise to an intermediate file
BASH_ENV=.bash_arrays otherscript.sh          # non-interactive startup script

または、ワンライナーとして:

BASH_ENV=<(declare -p array1 array2) otherscript.sh

最後の1つはプロセス置換を使用して、declareコマンドの出力をrcスクリプトとして渡します。 (このメソッドはbash-4.0以降でのみ機能します。以前のバージョンは無条件にfstat() rcファイルを使用し、read()に返されたサイズを一度に使用します; FIFOはサイズ0を返すため、期待どおりには機能しません。)

非インタラクティブシェル(シェルスクリプト)では、BASH_ENV変数が指すファイルは automatically sourced です。履歴/ POSIXモードの場合、bashは#!/bin/shを尊重しないため、BASH_ENVではなく、シバンを使用して明示的に「bash」を呼び出すことで、bashが正しく呼び出されることを確認する必要があります。

すべての配列名に共通の接頭辞が付いている場合、列挙する代わりにdeclare -p ${!myprefix*}を使用してそれらのリストを展開できます。

このメソッドを使用して全体bash環境をエクスポートおよび再インポートしないでください。一部の特別なbash変数と配列は読み取り専用であり、特別な変数を変更すると他の副作用が生じる可能性があります。

(配列定義をエクスポート可能な変数にシリアル化し、evalを使用することで、少し不快なことをすることもできますが、evalの使用を推奨しません...

$ array=([1]=a [10]="b c")
$ export scalar_array=$(declare -p array)
$ bash # start a new Shell
$ eval $scalar_array
$ declare -p array
declare -a array='([1]="a" [10]="b c")'


上記で言及したように、興味深い癖があります:環境を介した関数のエクスポートの特別なサポート:

function myfoo() {
    echo foo
}

この動作を有効にするためにexport -fまたはset +aを使用すると、printenvで表示される(プロセス)環境になります。

myfoo=() { echo foo
}

変数はfunctionname(または下位互換性の場合はfunctioname())であり、値は() { functionbody }です。後続のbashプロセスが開始すると、そのような各環境変数から関数が再作成されます。 bash-4.2ソースファイルvariables.cを覗いてみると、() {で始まる変数が特別に処理されていることがわかります。 (declare -fでこの構文を使用して関数を作成することは禁止されています。)更新:Shellshock」 セキュリティ問題はこの機能に関連します。緩和策として環境から。

それでも読み続けると、#if 0で始まり#if ARRAY_EXPORTで終わる変数をチェックする([(または))保護コードと、「配列変数はまだエクスポートされていない可能性があります」が表示されます。 幸いなことに、現在の開発バージョンのbash-4.3rc2では、インデックス付き配列(非連想配列)をエクスポートする機能はが有効になっています。 上記のように、この機能は有効になりそうにありません。

これを使用して、必要な配列データを復元する関数を作成できます。

% function sharearray() {
    array1=(a b c d)
}

% export -f sharearray 

% bash -c 'sharearray; echo ${array1[*]}'

したがって、前のアプローチと同様に、次のコマンドで子スクリプトを呼び出します。

bash -c "sharearray; . otherscript.sh"

または、適切なポイントを追加することにより、子スクリプトでsharearray関数を条件付きで呼び出すことができます。

[ "`type -t sharearray`" = "function" ] && sharearray

sharearray関数にはdeclare -aがないことに注意してください。配列を暗黙的にlocalにした場合、これは望ましくありません。 bash-4.2は、変数を明示的にグローバルにするdeclare -gをサポートしているため、(declare -ga)を使用できます。 (連想配列requiredeclare -Aから、bash-4.2より前の連想配列に対してこのメ​​ソッドを使用することはできません。)GNU parallelドキュメントには便利なバリエーションがありますこの方法については、 manページ--envの説明を参照してください。


また、あなたの質問は、export自体に問題がある可能性があることを示しています。名前は、作成または変更した後にエクスポートできます。 「エクスポート可能」は、変数のフラグまたはプロパティです。便宜上、1つのステートメントで設定およびエクスポートすることもできます。 bash-4.2までは、exportは名前のみを想定し、単純(スカラー)変数または関数名のいずれかがサポートされます。

(将来)配列をエクスポートできたとしても、選択したインデックス(スライス)のエクスポートはサポートされない可能性があります(配列はまばらなので、許可できない理由はありません)。 bashは構文declare -a name[0]もサポートしていますが、添え字は無視され、「名前」は単純に通常のインデックス付き配列です。

31
mr.spuratic

うん他の答えがこれをそれほど複雑にした理由はわかりません。 Bashには、これに対するほぼサポートが組み込まれています。

エクスポートスクリプトで:

myArray=( '  foo"bar  ' $'\n''\nbaz)' )  # an array with two nasty elements

myArray="${myArray[@]@Q}" ./importing_script.sh

(配列要素内の空白を正しく処理するには、二重引用符が必要です。)

importing_script.shにエントリすると、myArray環境変数の値はこれらの正確な26バイトで構成されます。

'  foo"bar  ' $'\n\\nbaz)'

次に、以下が配列を再構成します。

eval "myArray=( ${myArray} )"

注意!eval環境変数のソースを信頼できない場合は、myArrayのようにしないでください。このトリックは "Little Bobby Tables" 脆弱性を示します。誰かがmyArrayの値を) ; rm -rf / #に設定すると想像してください。

8
Matt Whitlock

Lesmanaが報告したように、アレイをエクスポートすることはできません。そのため、環境を通過する前にそれらをシリアル化する必要があります。このシリアル化は、文字列のみが収まる他の場所でも便利です(su -c 'string'、ssh Host 'string')。これを行う最短のコード方法は、「getopt」を悪用することです

# preserve_array(arguments). return in _RET a string that can be expanded
# later to recreate positional arguments. They can be restored with:
#   eval set -- "$_RET"
preserve_array() {
    _RET=$(getopt --Shell sh --options "" -- -- "$@") && _RET=${_RET# --}
}

# restore_array(name, payload)
restore_array() {
   local name="$1" payload="$2"
   eval set -- "$payload"
   eval "unset $name && $name=("\$@")"
}

次のように使用します。

foo=("1: &&& - *" "2: two" "3: %# abc" )
preserve_array "${foo[@]}"
foo_stuffed=${_RET}
restore_array newfoo "$foo_stuffed"
for elem in "${newfoo[@]}"; do echo "$elem"; done

## output:
# 1: &&& - *
# 2: two
# 3: %# abc

これは、未設定/スパース配列には対処しません。 restore_arrayの2つの 'eval'呼び出しを削減できる場合があります。

1
smoser

あなたは(こんにちは!)これを使用できますが、ファイルを書く必要はありません、ubuntu 12.04、bash 4.2.24

また、複数行の配列をエクスポートできます。

猫>> exportArray.sh

function FUNCarrayRestore() {
    local l_arrayName=$1
    local l_exportedArrayName=${l_arrayName}_exportedArray

    # if set, recover its value to array
    if eval '[[ -n ${'$l_exportedArrayName'+dummy} ]]'; then
        eval $l_arrayName'='`eval 'echo $'$l_exportedArrayName` #do not put export here!
    fi
}
export -f FUNCarrayRestore

function FUNCarrayFakeExport() {
    local l_arrayName=$1
    local l_exportedArrayName=${l_arrayName}_exportedArray

    # prepare to be shown with export -p
    eval 'export '$l_arrayName
    # collect exportable array in string mode
    local l_export=`export -p \
        |grep "^declare -ax $l_arrayName=" \
        |sed 's"^declare -ax '$l_arrayName'"export '$l_exportedArrayName'"'`
    # creates exportable non array variable (at child Shell)
    eval "$l_export"
}
export -f FUNCarrayFakeExport

ターミナルbashでこの例をテストします(bash 4.2.24で動作します):

source exportArray.sh
list=(a b c)
FUNCarrayFakeExport list
bash
echo ${list[@]} #empty :(
FUNCarrayRestore list
echo ${list[@]} #profit! :D

私はそれを改善するかもしれません ここ

PS .:誰かがクリア/改善/ makeItRunFasterをクリアした場合、私は知りたい/見たいです! :D

0
Aquarius Power

Much私の以前の試みのすべての問題を指摘してくれた@stéphane-chazelasのおかげで、これは配列を標準出力または変数にシリアル化するように動作するようになりました。

この手法は(declare -a/declare -pとは異なり)入力をシェル解析しないため、シリアル化されたテキストへのメタキャラクターの悪意のある挿入に対して安全です。

注:read\<newlines>文字ペアを削除するため、改行はエスケープされません。そのため、代わりに-d ...を読み取りに渡す必要があり、エスケープされていない改行は保持されます。

これらはすべてunserialise関数で管理されます。

2つのマジックキャラクター、フィールドセパレーターとレコードセパレーターが使用されます(複数の配列を同じストリームにシリアル化できるようにするため)。

これらの文字はFSおよびRSとして定義できますが、エスケープされた改行はnewlineによって削除されるため、どちらもread文字として定義できません。

エスケープ文字は\バックスラッシュである必要があります。これは、read文字として認識されるのを避けるためにIFSによって使用されるものです。

serialise"$@"を標準出力にシリアライズし、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
0
Sam Liddicott

スペースのない値を持つ配列の場合、単純な関数セットを使用して各配列要素を反復処理し、配列を連結しました。

_arrayToStr(){
    array=($@)

    arrayString=""
    for (( i=0; i<${#array[@]}; i++ )); do
        if [[ $i == 0 ]]; then
            arrayString="\"${array[i]}\""
        else
            arrayString="${arrayString} \"${array[i]}\""
        fi
    done

    export arrayString="(${arrayString})"
}

_strToArray(){
    str=$1

    array=${str//\"/}
    array=(${array//[()]/""})

    export array=${array[@]}
}

最初の関数は、開き括弧と閉じ括弧を追加し、すべての二重引用符をエスケープすることにより、配列を文字列に変換します。 2番目の関数は、引用符と括弧を削除し、ダミー配列に配置します。

配列をエクスポートするには、元の配列のすべての要素を渡します。

array=(foo bar)
_arrayToStr ${array[@]}

この時点で、配列は値$ arrayStringにエクスポートされています。宛先ファイルに配列をインポートするには、配列の名前を変更し、逆の変換を行います。

_strToArray "$arrayName"
newArray=(${array[@]})
0
markantonio37

別の投稿を編集していて、間違えました。ああとにかく、おそらくこれが役立つかもしれませんか? https://stackoverflow.com/a/11944320/1594168

シェルの配列形式はbashまたは他のシェルの側では文書化されていないため、プラットフォームに依存しない方法でシェル配列を返すことは非常に難しいことに注意してください。バージョンを確認し、すべてのシェル配列を他のプロセスが解決できるファイルに連結する単純なスクリプトを作成する必要があります。

ただし、ホームに戻したいアレイの名前がわかっている場合、少し汚い方法があります。

私が持っているとしましょう

MyAry[42]="whatever-stuff";
MyAry[55]="foo";
MyAry[99]="bar";

だから家に持ち帰りたい

name_of_child=MyAry
take_me_home="`declare -p ${name_of_child}`";
export take_me_home="${take_me_home/#declare -a ${name_of_child}=/}"

サブプロセスから確認することで、エクスポートされていることがわかります

echo ""|awk '{print "from awk =["ENVIRON["take_me_home"]"]";  }'

結果:

from awk =['([42]="whatever-stuff" [55]="foo" [99]="bar")']

どうしても必要な場合は、env varを使用してダンプします。

env > some_tmp_file

それから

別のスクリプトを実行する前に、

# This is the magic that does it all
source some_tmp_file
0
GreenFox

@ mr.spuraticの_BASH_ENV_の使用に基づいて、ここで_$@_から_script -f -c_をトンネリングします

_script -c <command> <logfile>_は、別のpty(およびプロセスグループ)内でコマンドを実行するために使用できますが、構造化された引数を_<command>_に渡すことはできません。

代わりに、_<command>_は、systemライブラリ呼び出しの引数となる単純な文字列です。

外側のbashの_$@_を、スクリプトによって呼び出されたbashの_$@_にトンネルする必要があります。

_declare -p_は_@_を取ることができないので、ここではマジックbash変数___を使用します(ダミーの最初の配列値はbashによって上書きされるため)。これにより、重要な変数を踏みにじることがなくなります。

概念実証:BASH_ENV=<( declare -a _=("" "$@") && declare -p _ ) bash -c 'set -- "${_[@]:1}" && echo "$@"'

「しかし、あなたはbashに引数を渡しています-実際、私はそうですが、これらは既知の文字の単純な文字列です。ここではscriptで使用しています。

Shell=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile

これにより、このラッパー関数_in_pty_が得られます。

in_pty() { Shell=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile }

または、Makefileの構成可能な文字列としてのこの関数なしのラッパー:

in_pty=bash -c 'Shell=/bin/bash BASH_ENV=<( declare -a _=("" "$$@") && declare -p _ && echo '"'"'set -- "$${_[@]:1}"'"'"') script -qfc '"'"'"$$@"'"'"' /tmp/logfile' --

_..._

$(in_pty) test --verbose $@ $^

0
Sam Liddicott