ダッシュ(および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でも機能させる方法です。シェルがスクリプトを実行しているテストを回避することは可能ですか?
Ksh88とそのすべてのクローンは動的スコープを行い、ksh88の場合は1990年以降、local
の場合は1994年(そしてpdksh
の場合は1989年に多くのksh88 APIを実装)以来、bash
をサポートしています。
参照しているksh
は_ksh93
_です。これは、わずかに異なり、互換性のないAPIを使用したDavidKornによるゼロからの新しい実装です。
そのシェルを単にksh
ではなく_ksh93
_と呼ぶのは良い考えです。ksh
だけで数十年の間_ksh88
_ APIを意味していたからです(ksh93実装以外はすべて実装されています)。 _ksh93
_は、そのコードがオープンソースとしてリリースされた2000年から数年後まで広く使用されていませんでした。
ksh93のtypeset
はstaticスコープのみを実行します。 POSIXは、動的スコープであるという理由でksh88のtypeset
/local
を指定することに反対していました(ただし、Cのようなほとんどの言語はstaticスコープを実行しますが、dynamicスコープはより自然にサブシェルまたは環境で有効になるシェル)。これは、静的コーピングを行うようにksh93が書き直された理由を説明しています。
その後、他のほとんどのシェルがla ksh88のスコープを実装したため、ksh93は今では奇妙なものになっています。そして皮肉なことに、POSIXの唯一の合理的なオプションは、動的スコープを指定することです。 David Kornは当初、ksh93での動的スコープの実装を拒否しましたが、POSIXメーリングリストのlocal
キーワード/ビルトインでそれを検討できると述べていましたが、それが完全に行われる前に引退しました。
AT&Tのksh93v-ベータ版と最終バージョンは、_ksh93
_が呼び出されたときに動的スコープ(local
とtypeset
を含む煩わしい形式の関数)を実行する実験的な「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"
}
_
これは私にはより良いデザインのように思われ、そのコードは動的スコープを実行するシェルでも問題なく機能します(異なる関数定義構文に対処する必要があります)。
参考文献:
local
は使用されていません(none)。
私はnotが完全に機能する解決策を見つけましたが、(少なくとも)dash、bash、およびzshで機能する部分的な解決策を見つけました。環境変数を設定して関数を呼び出すだけです。
IFS=123 testlocal
( オンラインでお試しください! )でテストできます。
Shell ksh(使用されているサーバーではksh93)は、二重の個性を持っているため、もう少し複雑です。しかし、両方の性格は上記のリンクに示されています。