web-dev-qa-db-ja.com

ローカルコマンドを移植可能に機能させてkshbashとzshをダッシュ​​する方法はありますか?

ダッシュ(およびbash zshやその他のシェル)では、コマンドlocalが機能して、変数スコープをその関数(場合によっては子孫)に制限します。これにより、変数をその関数に限定する可能性が可能になります内部のみ(場合によっては子孫関数呼び出し)。

例えば:

testlocal(){ 
                local IFS
                IFS=123
                echo "internal  IFS = $IFS"
                testdescend
}

testdescend(){
                echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external  IFS = $IFS"

ダッシュ(およびbashとzsh)で実行すると、この出力が生成されます。

$ dash ./script
internal  IFS = 123
descended IFS = 123
external  IFS = abc

これは、IFSが関数(および子孫)に対してローカルのままであることを意味します。

ただし、kshは異なり、localを受け入れません。

$ ksh ./script
./mt2[3]: local: not found [No such file or directory]

Wordの「local」を「typeset」に変更すると、スクリプトは(ほぼ)kshで機能しますが、dashはtypesetを認識しません。

また、kshでスコープを動的(呼び出された関数に降順)にするには、Word functionを使用して関数を定義する必要があります。

function testlocal { 
                     typeset IFS
                     IFS=123
                     echo "internal IFS = $IFS"
                     testdescend
                   }

これは、ダッシュへの移植性をさらに複雑にします。

問題は、元のスクリプトをkshでも機能させる方法です。シェルがスクリプトを実行しているテストを回避することは可能ですか?

5
Isaac

Ksh88とそのすべてのクローンは動的スコープを行い、ksh88の場合は1990年以降、localの場合は1994年(そしてpdkshの場合は1989年に多くのksh88 APIを実装)以来、bashをサポートしています。

参照しているkshは_ksh93_です。これは、わずかに異なり、互換性のないAPIを使用したDavidKornによるゼロからの新しい実装です。

そのシェルを単にkshではなく_ksh93_と呼ぶのは良い考えです。kshだけで数十年の間_ksh88_ APIを意味していたからです(ksh93実装以外はすべて実装されています)。 _ksh93_は、そのコードがオープンソースとしてリリースされた2000年から数年後まで広く使用されていませんでした。

ksh93のtypesetstaticスコープのみを実行します。 POSIXは、動的スコープであるという理由でksh88のtypeset/localを指定することに反対していました(ただし、Cのようなほとんどの言語はstaticスコープを実行しますが、dynamicスコープはより自然にサブシェルまたは環境で有効になるシェル)。これは、静的コーピングを行うようにksh93が書き直された理由を説明しています。

その後、他のほとんどのシェルがla ksh88のスコープを実装したため、ksh93は今では奇妙なものになっています。そして皮肉なことに、POSIXの唯一の合理的なオプションは、動的スコープを指定することです。 David Kornは当初、ksh93での動的スコープの実装を拒否しましたが、POSIXメーリングリストのlocalキーワード/ビルトインでそれを検討できると述べていましたが、それが完全に行われる前に引退しました。

AT&Tのksh93v-ベータ版と最終バージョンは、_ksh93_が呼び出されたときに動的スコープ(localtypesetを含む煩わしい形式の関数)を実行する実験的な「bash」モード(実際にはデフォルトで有効)でコンパイルできます。 bashとして。 そのbashモードはksh2020 でデフォルトで無効になります local/declareへのtypesetのエイリアスは、bashモードがコンパイルされていなくても保持されます (ただし、静的スコープは使用されます)。

次に、そのベータリリースとそのbashモードを別にすると、ksh93 onlyは静的スコープを行い、kshスタイルの構文(_function name { code; }_)で宣言された関数でのみ実行されます。 Bourneスタイルの構文(f() command)で宣言された関数はスコープまったくを行わないため、混乱します。これは、_. name [args]_で呼び出されるソースファイルまたはksh関数でも同じです。それらの中で、typesetは関数のスコープで新しい変数を宣言しません(その関数にはスコープがありません)。グローバルスコープまたはkshのスコープのいずれかになる現在のスコープ内の変数のタイプを更新するだけです。そのBourneスタイルが(最終的に)kshスタイル関数内から呼び出された場合のスタイル関数。

Bourneスタイルの関数のコードは、呼び出された場所に埋め込まれている/コピーされた/ソースされているかのように実行されます。

_var=global
function ksh_function {
  typeset var=private
  echo "ksh1: $var"
  bourne_function
  echo "ksh2: $var"
  other_ksh_function other
  echo "ksh3: $var"
  . other_ksh_function other_invoked_with_dot
  echo "ksh4: $var"
}
bourne_function() {
  typeset var=set-from-bourne-function
}
function other_ksh_function {
  echo "other: $var"
  var=$1
}
ksh_function
echo "global: $var"
_

与える:

_ksh1: private
ksh2: set-from-bourne-function
other: global
ksh3: set-from-bourne-function
other: set-from-bourne-function
ksh4: other_invoked_with_dot
global: other
_

Ksh93では、サブシェルを使用するか、 that locvar proof of concept または--exportのように、自分で変数スタックを実装する以外に動的スコープを設定することはできません。すべてのコマンド(環境を介した外部コマンドを含む、kshスタイルの関数で宣言された関数を含む)に渡される変数。

testlocal関数のみが(testdescendではなく)ローカルスコープが必要な特定のケースでは、説明されているようにshdef + kshdefアプローチを使用するか そこに または次のようなことを行います

_case $KSH_VERSION in
  (*" 93"*)
    fn_with_local_scope() {
      alias local=typeset
      eval "function $1 {
        $(cat)
      }"
    }
  ;;
  (*)
    fn_with_local_scope() {
      eval "$1() {
        $(cat)
      }"
    }
  ;;
esac
_

そして、関数を次のように宣言します。

_fn_with_local_scope testlocal << '}'
  local IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend
}

testdescend(){
  echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external IFS = $IFS"
_

(そして、_fn_with_local_scope_で宣言された関数でのみlocalを使用します)。

これは

_internal IFS = 123
descended IFS = 123
external IFS = abc
_

すべてのシェルで(yashをサポートするには、localの最新バージョン(2.48以降)が必要です)。

または、ローカル変数もエクスポートしても問題ない場合(ksh93のみ):

_case $KSH_VERSION in
  (*" 93"*)
    fn() {
      alias local='typeset -x'
      eval "function $1 {
        $(cat)
      }"
    }
  ;;
  (*)
    fn() {
      eval "$1() {
        $(cat)
      }"
    }
  ;;
esac

fn testlocal << '}'
  local IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend
}

fn testdescend << '}'
  echo "descended IFS = $IFS"
}

IFS=abc
testlocal
echo "external IFS = $IFS"
_

さて、Cやksh93のような静的スコープを持つ言語でそのようなことをするなら、あなたは次のようなことをするでしょう:

_function testlocal {
  typeset IFS
  IFS=123
  echo "internal IFS = $IFS"
  testdescend "$IFS"
}

function testdescend {
  typeset IFS="$1" # explicitly get the value $IFS from the caller
  echo "descended IFS = $IFS"
}
_

これは私にはより良いデザインのように思われ、そのコードは動的スコープを実行するシェルでも問題なく機能します(異なる関数定義構文に対処する必要があります)。

参考文献:

2

部分的な解決策

localは使用されていません(none)。

私はnotが完全に機能する解決策を見つけましたが、(少なくとも)dash、bash、およびzshで機能する部分的な解決策を見つけました。環境変数を設定して関数を呼び出すだけです。

IFS=123 testlocal

オンラインでお試しください! )でテストできます。

Shell ksh(使用されているサーバーではksh93)は、二重の個性を持っているため、もう少し複雑です。しかし、両方の性格は上記のリンクに示されています。

0
Isaac