bash:グローバル変数コンテキストの保存と復元の実験

bash:グローバル変数コンテキストの保存と復元の実験

1行にいくつかのコンテキストを保存するためにいくつかの実験を行った。

私が望むように正確に動作します。したがって、この投稿の目的は次のとおりです。 1. コミュニティと共有します。 2.醜くて不思議なので、改善するか、まったく異なる解決策を見つけてください。

状況は次のとおりです。グローバル変数があり、関数をローカルに変更して最後に復元できるようにしたいです。

x()
{
    typeset loc=$glob
    glob=<val>
    (...)
    glob=$loc
}

これは私にとってとても冗談で、タイプミスが必要です。

だから私はこれを試しました(利用可能なコード):

#!/bin/bash

glob=1
indent=0

# appliance
trace()
{
   printf "%$((${indent}*3))s %s\n" "" "$1"
}

# the context stuff (creation and destruction)
# in variable, because with a function, I'd need to create a $(sub shell) and it wouldn't work
new_glob='trap restore_context RETURN;typeset loc=$glob;glob'

restore_context()
{
   res=$?
   glob=$loc
   trap - RETURN
}

# common test stuff, to isolate the traces
test_call()
{
   typeset res
   trace "in ${FUNCNAME[1]}, before $1,glob=$glob"
   (( indent++ ))
   eval $1
   res=$?
   (( indent-- ))

   trace "in ${FUNCNAME[1]}, res of $1=$res"
   trace "in ${FUNCNAME[1]}, after $1,glob=$glob"

   return $res
}

# Russian dolls function
f()
{
   eval "$new_glob=6"
   test_call g
   return 16
}

g()
{
   eval "$new_glob=7"
   test_call h
   return 17
}

h()
{
   eval "$new_glob=8"
   trace "in h, glob=$glob"
   i
   return 18
}

i()
{
   trace "in i, glob=$glob"
}

# main
test_call f

このスクリプトはfを呼び出し、fはgを呼び出し、gはhを呼び出します。各関数はグローバル変数を変更して復元します。

出力:

# ./test_rtrap
 in main, before f,glob=1
    in f, before g,glob=6
       in g, before h,glob=7
          in h, glob=8
          in i, glob=8
       in g, res of h=18
       in g, after h,glob=7
    in f, res of g=17
    in f, after g,glob=6
 in main, res of f=16
 in main, after f,glob=1

ここで重要なのは、私()ローカル変数を使用するのと同じように、1ではなく8を印刷します。

この結果を得るために、RETURN にトラップ機能を使用しました。

さて、上記の関数xは次のようになります。

x()
{
   eval "$new_glob=6"
   (...)
}

残念ながら、コードの一部を含む試用版と変数を使用する必要があります。不自然で、かなり神秘的です。しかし、そこで関数を使用するにはサブシェルが必要で、関連する変数コンテキストの問題があるため必要です。

したがって、完璧ではありませんが、きれいではなく冗談ではありませんが、うまくいきます。

醜い評価「$new_glob=6」より良い方法はありますか?

答え1

まあ!少なくとも示された例ではそうである。

new_glob変数eval "$new_glob=6"に置き換えて削除します。local glob=6

つまり、書くより

x()
{
   typeset loc=$glob
   glob=<val>
   (...)
   glob=$loc
}

書く

x()
{
   typeset glob
   glob=<val>
}

オプションでまたはtypesetに置き換えます。localdeclare

バッシュダイナミックレンジ言語。現在の実装戦略はリンクされた記事で説明されています。

より単純な実装は、単純なグローバル変数を使用して動的変数を表すことです。ローカルバインディングは、プログラムに見えないスタックの匿名位置に元の値を格納することによって実行されます。バインド範囲が終了すると、元の値がこの場所から復元されます。実際、ダイナミックレンジの指定はこのように始まった。 Lispの最初の実装では、ローカル変数を実装するためにこの確かな戦略を使用し、GNU Emacs Lispなど、まだ使用されているいくつかの方言ではこれらの慣行が維持されました。

提供されたbashコードをほとんど説明します。

答え2

わかりました(Icarusに感謝します)。ここでIMHO bashは本当に奇妙です。私は基本的にその動作を再実装しました。

Bashのローカル変数はローカル変数ではありません!グローバル/環境変数の値をローカルで変更/設定し、「ローカル定義」機能を終了したときにその値を復元します。これを動的スコープと呼びます。

bashのローカルは「グローバル変数のローカル値」を意味しますが、これは私にとって奇妙です。たとえば、ksh93は動作が異なり、ローカル変数は実際にはローカル変数(つまり隠されたグローバル変数)です。これを語彙的/動的スコープといい、現在までほとんどの言語でこれを使用しています。

これを説明するために別のテストプログラムを作成しました。

if (( $# == 0 )); then
   # run this script with specified shells
   bash -c "$0 -c"
   ksh93 -c "$0 -c"
   exit 0
fi

# which shell am I
readlink /proc/$$/exe

# declarations
local="Dynamic scoping"          # i.e. func i view "local" value
global="Lexical/Static scoping"  # i.e. func i views "global" value

glob="$global"

# non POSIX declarations
function f { typeset glob="$local"; i; }
function i { echo "      $glob"; }

# call
echo "   non POSIX ( function f )"
f

# POSIX declations
pf() { typeset glob="$local"; i; }
pi() { echo "      $glob"; }

# call
echo "   POSIXi ( f() )"
pf

構文的には、これらのコマンド/スクリプトはbashとksh93で実行できます。

関数 f はローカル変数を定義し、i を呼び出します。語彙範囲の場合、関数iは関数fの範囲外で語彙的に定義されるため、グローバル変数にアクセスできます。ダイナミックレンジでは、ローカル変数は生成されず、グローバル変数のみが生成され、関数fは独自の実行寿命中にのみ値を設定します。関数iが呼び出されると、関数fはまだ「アクティブ」なので、グローバル変数value値はまだ関数fに割り当てられた値を持ちます。シェルは、関数fが返されたときにのみ変更された値を復元します。

出力は次のとおりです。

/u-blox/gallery/ubx/det/re6_64/8.0/bin/bash
   non POSIX ( function f )
      Dynamic scoping
   POSIXi ( f() )
      Dynamic scoping
/bin/ksh93
   non POSIX ( function f )
      Lexical/Static scoping
   POSIXi ( f() )
      Dynamic scoping

今問題はbashで語彙の範囲を実装する方法です。 ;-)

関連情報