web-dev-qa-db-ja.com

シェルスクリプト内の関数に変数のスコープを設定するPOSIX準拠の方法

変数のスコープをそれが宣言されている関数に制限するPOSIX準拠の方法はありますか?例:

Testing()
{
    TEST="testing"
}

Testing
echo "Test is: $TEST"

「Test is:」と出力されます。 declare、local、およびtypesetキーワードについて読みましたが、POSIXビルトインが必要なようではありません。

44
John

これは通常、localキーワードで行われます。これは、ご存知のように、POSIXでは定義されていません。これは参考情報です POSIXに「ローカル」を追加することについての議論

ただし、一部のGNU/Linuxディストリビューションでは、/bin/shのデフォルトであるdash(Debian Almquistシェル)がサポートしている、私が知っている最も原始的なPOSIX準拠のシェルでもサポートしています。 FreeBSDとNetBSDは、元のAlmquistシェルであるashを使用しており、これもサポートしています。 OpenBSDは、/bin/shksh実装を使用して、これもサポートしています。したがって、Solarisなどの非GNU非BSDシステム、または標準のkshなどを使用するシステムをサポートすることを目的としない限り、localを使用して問題を回避できます。 (スクリプトの冒頭、Shebang行の下に、厳密にはPOSIX shスクリプトではないことに注意してコメントを付けたいと思うかもしれません。悪さをしないためにです。)以上のことをすべて述べた後、それぞれを確認することをお勧めします。 shをサポートするこれらのすべてのlocal実装のマンページ。正確に機能する方法に微妙な違いがある可能性があるためです。または、localを使用しないでください。

本当に完全にPOSIXに準拠したい場合、または考えられる問題を台無しにしたくない場合、つまりlocalを使用しない場合は、いくつかのオプションがあります。 Lars Brinkhoffの答えは健全です。サブシェルで関数をラップするだけです。ただし、これにより他の望ましくない影響が生じる可能性があります。ちなみに、シェル文法(POSIXごと)では、次のことが可能です。

my_function()
(
  # Already in a sub-Shell here,
  # I'm using ( and ) for the function's body and not { and }.
)

スーパーポータブルにするためにそれを避けるかもしれませんが、一部の古いBourneシェルはPOSIX非準拠でさえあります。 POSIXで許可されていることをお伝えしたいと思います。

別のオプションは、関数本体の最後でunset変数にすることですが、それはnotもちろん古い値を復元するので、実際に望んでいることではないので、変数の外部にリークする関数内の値。あまり役に立たないと思います。

最後に思いがけない奇抜なアイデアは、localを自分で実装することです。シェルにはevalがありますが、これは悪ですが、いくつかの非常識な可能性に道を譲ります。以下は、基本的に古いLispの動的スコープを実装します。最後にいわゆるletを使用する必要がありますが、さらなるクールポイントにはlocalの代わりにキーワードunletを使用します。

# If you want you can add some error-checking and what-not to this.  At present,
# wrong usage (e.g. passing a string with whitespace in it to `let', not
# balancing `let' and `unlet' calls for a variable, etc.) will probably yield
# very very confusing error messages or breakage.  It's also very dirty code, I
# just wrote it down pretty much at one go.  Could clean up.

let()
{
    dynvar_name=$1;
    dynvar_value=$2;

    dynvar_count_var=${dynvar_name}_dynvar_count
    if [ "$(eval echo $dynvar_count_var)" ]
    then
        eval $dynvar_count_var='$(( $'$dynvar_count_var' + 1 ))'
    else
        eval $dynvar_count_var=0
    fi

    eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var
    eval $dynvar_oldval_var='$'$dynvar_name

    eval $dynvar_name='$'dynvar_value
}

unlet()
for dynvar_name
do
    dynvar_count_var=${dynvar_name}_dynvar_count
    eval dynvar_oldval_var=${dynvar_name}_oldval_'$'$dynvar_count_var
    eval $dynvar_name='$'$dynvar_oldval_var
    eval unset $dynvar_oldval_var
    eval $dynvar_count_var='$(( $'$dynvar_count_var' - 1 ))'
done

今することができます:

$ let foobar test_value_1
$ echo $foobar
test_value_1
$ let foobar test_value_2
$ echo $foobar
test_value_2
$ let foobar test_value_3
$ echo $foobar
test_value_3
$ unlet foobar
$ echo $foobar
test_value_2
$ unlet foobar
$ echo $foobar
test_value_1

(ちなみに、unletには、上記に示されていない便宜上、一度に(異なる引数として)任意の数の変数を与えることができます。

これを家で試したり、子供に見せたり、同僚に見せたり、Freenodeの#bashに見せたり、POSIXのメンバーに見せたりしないでください。委員会、それをボーン氏に見せないでください、多分それを父マッカーシーの幽霊に見せて笑わせます。あなたは警告されました、そしてあなたは私からそれを学びませんでした。

編集:

どうやら私は殴られて、FreenodeでIRCボットgreybot#bashに属する)を送信すると、コマンド "posixlocal"により、ローカル変数を取得する方法を示す不明瞭なコードが1つ表示されますPOSIX sh。オリジナルは解読が困難だったので、これはややクリーンアップされたバージョンです:

f()
{
    if [ "$_called_f" ]
    then
        x=test1
        y=test2
        echo $x $y
    else
        _called_f=X x= y= command eval '{ typeset +x x y; } 2>/dev/null; f "$@"'
    fi
}

このトランスクリプトは使用法を示しています。

$ x=a
$ y=b
$ f
test1 test2
$ echo $x $y
a b

したがって、ifフォームのxブランチで変数yおよびthenをローカル変数として使用できます。 elseブランチでさらに変数を追加できます。最初のリストのvariable=のように2回追加し、typesetの引数として1回渡す必要があることに注意してください。 unletなどは必要ありません(「透過的な」実装です)。名前の変更や過度のevalは行われません。したがって、全体的にはよりクリーンな実装のようです。

編集2:

typesetはPOSIXでは定義されておらず、Almquist Shell(FreeBSD、NetBSD、Debian)の実装ではサポートされていません。したがって、上記のハックはこれらのプラットフォームでは機能しません。

63
TaylanUB

最も近いのは、関数本体をサブシェルの中に置くことだと思います。

例えば。これを試して

foo()
{
  ( x=43 ; echo $x )
}

x=42
echo $x
foo
echo $x
9
Lars Brinkhoff

私と一緒に地獄に行きたくなったら、evalの概念をより詳細に実装しました。

これは自動的に準スコープ変数のアカウントを保持し、より使い慣れた構文で呼び出すことができ、ネストされたスコープを離れるときに変数を(単にnullするのではなく)適切に設定解除します。


使用法

ご覧のとおり、あなたはPush_scopeスコープを入力するには、_localは、準ローカル変数を宣言し、pop_scopeスコープを離れます。使用する _unsetは変数を設定解除し、pop_scopeは、そのスコープに再び戻ったときに、設定を解除します。

your_func() {
    Push_scope
    _local x="baby" y="you" z

    x="can"
    y="have"
    z="whatever"
    _unset z

    Push_scope
    _local x="you"
    _local y="like"
    pop_scope

    pop_scope
}

コード

意味不明な変数名のサフィックスはすべて、名前の衝突に対して非常に安全です。

# Simulate entering of a nested variable scope
# To be used in conjunction with Push_scope(), pop_scope(), and _local()
Push_scope() {
    SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D + 1 ))
}

# Store the present value of the specified variable(s), allowing use in a new scope.
# To be used in conjunction with Push_scope(), pop_scope(), and _local()
#
# Parameters:
# $@ : string; name of variable to store the value of
scope_var() {
    for varname_FB94CFD263CF11E89500036F7F345232 in "${@}"; do
        eval "active_varnames_FB94CFD263CF11E89500036F7F345232=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""

        # echo "Active varnames: ${active_varnames_FB94CFD263CF11E89500036F7F345232}"

        case " ${active_varnames_FB94CFD263CF11E89500036F7F345232} " in
            *" ${varname_FB94CFD263CF11E89500036F7F345232} "* )
                # This variable was already stored in a previous call
                # in the same scope. Do not store again.
                # echo "Push \${varname_FB94CFD263CF11E89500036F7F345232}, but already stored."
                :
                ;;

            * )
                if eval "[ -n \"\${${varname_FB94CFD263CF11E89500036F7F345232}+x}\" ]"; then
                    # Store the existing value from the previous scope.
                    # Only variables that were set (including set-but-empty) are stored
                    # echo "Pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}"
                    eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_FB94CFD263CF11E89500036F7F345232}=\"\${${varname_FB94CFD263CF11E89500036F7F345232}}\""
                else
                    # Variable is unset. Do not store the value; an unstored
                    # value will be used to indicate its unset state. The
                    # variable name will still be registered.
                    # echo "Not pushing value of \$${varname_FB94CFD263CF11E89500036F7F345232}; was previously unset."
                    :
                fi

                # Add to list of variables managed in this scope.
                # List of variable names is space-delimited.
                eval "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}${varname_FB94CFD263CF11E89500036F7F345232} \""
                ;;
        esac

        unset active_varnames_FB94CFD263CF11E89500036F7F345232
    done

    unset varname_FB94CFD263CF11E89500036F7F345232
}

# Simulate declaration of a local variable
# To be used in conjunction with Push_scope(), pop_scope(), and _local()
#
# This function is a convenience wrapper over scope_var().
#
# Can be called just like the local keyword.
# Example usage: _local foo="foofoofoo" bar="barbarbar" qux qaz=""
_local() {
    for varcouple_44D4987063D111E8A46923403DDBE0C7 in "${@}"; do
        # Example string: foo="barbarbar"
        varname_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7%%=*}"
        varvalue_44D4987063D111E8A46923403DDBE0C7="${varcouple_44D4987063D111E8A46923403DDBE0C7#*=}"
        varvalue_44D4987063D111E8A46923403DDBE0C7="${varvalue_44D4987063D111E8A46923403DDBE0C7#${varcouple_44D4987063D111E8A46923403DDBE0C7}}"

        # Store the value for the previous scope.
        scope_var "${varname_44D4987063D111E8A46923403DDBE0C7}"

        # Set the value for this scope.
        eval "${varname_44D4987063D111E8A46923403DDBE0C7}=\"\${varvalue_44D4987063D111E8A46923403DDBE0C7}\""

        unset varname_44D4987063D111E8A46923403DDBE0C7
        unset varvalue_44D4987063D111E8A46923403DDBE0C7
        unset active_varnames_44D4987063D111E8A46923403DDBE0C7
    done

    unset varcouple_44D4987063D111E8A46923403DDBE0C7
}

# Simulate unsetting a local variable.
#
# This function is a convenience wrapper over scope_var().
# 
# Can be called just like the unset keyword.
# Example usage: _unset foo bar qux
_unset() {
    for varname_6E40DA2E63D211E88CE68BFA58FE2BCA in "${@}"; do
        scope_var "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
        unset "${varname_6E40DA2E63D211E88CE68BFA58FE2BCA}"
    done
}

# Simulate exiting out of a nested variable scope
# To be used in conjunction with Push_scope(), pop_scope(), and _local()
pop_scope() {
    eval "varnames_2581E94263D011E88919B3D175643B87=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES}\""

    # Cannot iterate over $varnames by setting $IFS; $IFS does not work
    # properly on zsh. Workaround using string manipulation.
    while [ -n "${varnames_2581E94263D011E88919B3D175643B87}" ]; do
        # Strip enclosing spaces from $varnames.
        while true; do
            varnames_old_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87}"
            varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87# }"
            varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87% }"

            if [ "${varnames_2581E94263D011E88919B3D175643B87}" = "${varnames_2581E94263D011E88919B3D175643B87}" ]; then
                break
            fi
        done

        # Extract the variable name for the current iteration and delete it from the queue.
        varname_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87%% *}"
        varnames_2581E94263D011E88919B3D175643B87="${varnames_2581E94263D011E88919B3D175643B87#${varname_2581E94263D011E88919B3D175643B87}}"

        # echo "pop_scope() iteration on \$SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
        # echo "varname: ${varname_2581E94263D011E88919B3D175643B87}"

        if eval "[ -n \""\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}+x}"\" ]"; then
            # echo "Value found. Restoring value from previous scope."
            # echo eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
            eval "${varname_2581E94263D011E88919B3D175643B87}=\"\${SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}}\""
            unset "SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARVALUE_${varname_2581E94263D011E88919B3D175643B87}"
        else
            # echo "Unsetting \$${varname_2581E94263D011E88919B3D175643B87}"
            unset "${varname_2581E94263D011E88919B3D175643B87}"
        fi

        # Variable cleanup.
        unset varnames_old_2581E94263D011E88919B3D175643B87
    done

    unset SCOPE${SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D}_VARNAMES
    unset varname_2581E94263D011E88919B3D175643B87
    unset varnames_2581E94263D011E88919B3D175643B87

    SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D=$(( $SCOPENUM_CEDD88E463CF11E8A72A3F9E5F08767D - 1 ))
}
1
sorbet

スコープを有効にする関数は次のとおりです。

scope() {
  eval "$(set)" command eval '\"\$@\"'
}

スクリプトの例:

x() {
  y='in x'
  echo "$y"
}
y='outside x'
echo "$y"
scope x
echo "$y"

結果:

outside x
in x
outside x
1
user7620483