web-dev-qa-db-ja.com

* nixのオブジェクト指向シェル

序文:私はbashが大好きで、なんらかの論争や聖戦を始めるつもりはありません。うまくいけば、これは非常に単純な質問ではありません。

この質問は、スーパーユーザーの この投稿 にいくらか関連していますが、OPが彼の求めていることを本当に理解していないと思います。 FreeBSD、linux、OS Xではbash、Windowsではcygwinを使用しています。私は最近、WindowsでPowerShellを使用した広範な経験も持っています。

* nix用のシェルはありますか?すでに利用可能または作業中ですが、bashと互換性がありますが、オブジェクト指向のスクリプトのレイヤーをミックスに追加しますか?私が知っている唯一のことは、pythonコンソールです。ただし、私の知る限り、標準のシェル環境へのアクセスは提供されていません。たとえば、cd ~lsだけではなく、pythonコンソール内でchmod +x fileすることはできません。これらのタスクを実行するには、標準のUNIXバイナリではなくpythonを使用するか、pythonコードを使用してバイナリを呼び出す必要があります。

そのようなシェルは存在しますか?

42

シェルの3つの望ましい機能を考えることができます。

  • インタラクティブな操作性:一般的なコマンドはすばやく入力できるはずです。完了; ...
  • プログラミング:データ構造;同時実行(ジョブ、パイプなど)。 ...
  • システムアクセス:ファイル、プロセス、ウィンドウ、データベース、システム構成などの操作.

Unixシェルはインタラクティブな側面に集中する傾向があり、システムアクセスのほとんどと外部ツールへのプログラミングの一部を下請けにしています。

  • bc 簡単な数学
  • openssl 暗号化用
  • sedawk などのテキスト処理
  • nc 基本的なTCP/IPネットワーク用
  • FTPの場合はftp
  • 基本的なメールの場合、mailMailmailxなど
  • cron(スケジュールされたタスクの場合)
  • wmctrl 基本的なXウィンドウ操作
  • dcop KDE≤3.xライブラリの場合
  • dbus ツール(dbus-*または qdbus )(KDE≥4などの最新のデスクトップ環境を含む)さまざまなシステム情報および構成タスク

多くのことは、適切な引数またはパイプ入力を使用してコマンドを呼び出すことで実行できます。これは非常に強力なアプローチです。すべてがうまく機能しない単一のプログラムよりも、うまく機能するタスクごとに1つのツールを用意することをお勧めします。

UNIXシェルの主な制限は、「オブジェクト指向スクリプティング」要件の後にあるものだと思います。1つのコマンドから次のコマンドへの情報の保持、またはより複雑な方法でコマンドを組み合わせるのは得意ではありません。パイプライン。特に、プログラム間通信はテキストベースであるため、アプリケーションを組み合わせることができるのは、データを互換性のある方法でシリアル化する場合のみです。これは祝福であり、呪いでもあります。すべてがテキストのアプローチでは、単純なタスクをすばやく簡単に実行できますが、より複雑なタスクの障壁が高くなります。

インタラクティブなユーザビリティは、プログラムの保守性にも逆らって実行されます。対話型プログラムは短く、引用をほとんど必要とせず、変数の宣言やタイピングなどに煩わされないようにする必要があります。保守可能なプログラムは読み取り可能(省略形が多くない)である必要があります(読み取り可能である必要があります)文字列、関数名、変数名などです)、変数宣言や型付けなどの整合性チェックが必要です。

要約すると、シェルは到達するのが難しい妥協です。さて、これで、例に続く暴言セクションは終了です。


  • Perlシェル(psh) 「Unixシェルのインタラクティブな性質をPerlの能力と組み合わせます」。単純なコマンド(パイプラインも含む)をシェル構文で入力できます。その他はすべてPerlです。プロジェクトは長い間開発されていません。使用可能ですが、純粋なPerl(スクリプト用)または純粋なシェル(対話式またはスクリプト用)で使用することを検討するまでには至っていません。

  • IPython は対話型の改良されたPythonコンソール、特に数値計算と並列計算を対象としています。これは比較的新しいプロジェクトです。 。

  • irb(インタラクティブRuby) はRuby Python consoleと同等。

  • scsh は、従来UNIXシェル(文字列、プロセス、ファイル)で見られた種類のシステムバインディングを備えたスキーム実装(つまり、適切なプログラミング言語)です。ただし、インタラクティブシェルとして使用できるようにすることは目的としていません。

  • zsh は、改善された対話型シェルです。その長所は、対話性(コマンドラインエディション、完了、簡潔だが不可解な構文で実行される一般的なタスク)です。そのプログラミング機能は(kshと同等に)それほど優れていませんが、端末制御、正規表現、ネットワークなどのための多数のライブラリが付属しています。

  • fish は、UNIXスタイルのシェルでのクリーンなスタートです。それはより良いプログラミングやシステムアクセス機能を備えていません。これはshとの互換性を損なうため、より優れた機能を進化させる余地がありますが、それは起こりませんでした。


補遺:UNIXツールボックスのもう1つの部分は、多くのものをファイルとして扱います。

  • ほとんどのハードウェアデバイスはファイルとしてアクセスできます。
  • Linuxでは、/sysは、より多くのハードウェアおよびシステム制御を提供します。
  • 多くのUNIXバリアントでは、プロセス制御は/proc ファイルシステム。
  • Fuse を使用すると、新しいファイルシステムを簡単に作成できます。オンザフライでファイル形式を変換したり、さまざまなネットワークプロトコルを介してファイルにアクセスしたり、アーカイブ内を調べたりするための既存のファイルシステムがすでに存在します。

おそらく、UNIXシェルの将来は、コマンドによるシステムアクセス(およびコマンドを結合するための制御構造)の改善ではなく、ファイルシステムによるシステムアクセスの改善(多少異なる方法で結合する)です。シェルパイプ)はまだです)。

クラスやオブジェクトをbashに実装するために、多くのbashコードは必要ありません。

100行と言います。

Bashには連想配列があり、これを使用して、継承、メソッド、およびプロパティを持つ単純なオブジェクトシステムを実装できます。

したがって、次のようなクラスを定義できます。

class Queue N=10 add=q_add remove=q_remove

このキューのインスタンスを作成するには、次のようにします。

class Q:Queue N=100

または

inst Q:Queue N=100

クラスは配列で実装されているため、classinstは実際には同義語です-JavaScriptのようなものです。

このキューにアイテムを追加するには、次のようにします。

$Q add 1 2 aaa bbb "a string"

変数Xに項目を削除するには、次のようにします。

$Q remove X

そして、オブジェクトの構造をダンプすることは次のように行うことができます:

$Q dump

これは次のようなものを返します:

Q {
      parent=Queue {
                     parent=ROOT {
                                   this=ROOT
                                   0=dispatch ROOT
                                 }
                     class=Queue
                     N=10
                     add=q_add
                     remove=q_remove
                     0=dispatch Queue
                   }
      class=Q
      N=4
      add=q_add
      remove=q_remove
      0=dispatch Q
      1=
      2=ccc ddd
      3=
      4=
    }

クラスは、次のようなクラス関数を使用して作成されます。

class(){
    local _name="$1:"                            # append a : to handle case of class with no parent
    printf "$FUNCNAME: %s\n" $_name
    local _this _parent _p _key _val _members
    _this=${_name%%:*}                           # get class name
    _parent=${_name#*:}                          # get parent class name
    _parent=${_parent/:/}                        # remove handy :
    declare -g -A $_this                         # make class storage
    [[ -n $_parent ]] && {                       # copy parent class members into this class
        eval _members=\"\${!$_parent[*]}\"       # get indices of members
        for _key in $_members; do                # inherit members from parent
            eval _val=\"\${$_parent[$_key]}\"    # get parent value
            eval $_this[$_key]=\"$_val\"         # set this member
        done
    }
    shift 1

    # overwrite with specific values for this object
    ROOT_set $_this "$@" "0=dispatch $_this" "parent=${_parent:-ROOT}" "class=$_this"
}

注:新しいクラスまたはインスタンスを定義するときに、メンバーの値または関数をオーバーライドできます。

Bashの連想配列には、この作業を適切に行うための癖があります。$ Q [0]}は$ Qと同じです。つまり、配列名を使用してメソッドディスパッチ関数を呼び出すことができます。

dispatch(){
    local _this=$1 _method=$2 _fn
    shift 2
    _fn="$_this[$_method]"                       # reference to method name
    ${!_fn} $_this "$@"
}

欠点は、データに[0]を使用できないため、キュー(この場合)がindex = 1から始まることです。あるいは、「q + 0」のような連想インデックスを使用することもできます。

getおよびsetメンバーに対して、次のようなことを行うことができます。

# basic set and get for key-value members
ROOT_set(){                                       # $QOBJ set key=value
    local _this=$1 _exp _key _val
    shift
    for _exp in "$@"; do
        _key=${_exp%%=*}
        _val="${_exp#*=}"
        eval $_this[$_key]=\"$_val\"
    done
}

ROOT_get(){                                       # $QOBJ get var=key
    local _this=$1 _exp _var _key
    shift
    for _exp in "$@"; do
        _var=${_exp%%=*}
        _key=${_exp#*=}
        eval $_var=\"\${$_this[$_key]}\"
    done
}

そしてdumpオブジェクト構造にするために、私はこれを作りました:

注:これはbashのOOPの場合は必要ありませんが、オブジェクトがどのように作成されるかを確認するのはいいことです。

# dump any object
obj_dump(){                                      # obj_dump <object/class name>
    local _this=$1 _j _val _key; local -i _tab=${2:-(${#_this}+2)}  # add 2 for " {"
    _tab+=2                                      # hanging indent from {
    printf "%s {\n" $_this
    eval "_key=\"\${!$_this[*]}\""
    for _j in $_key; do                          # print all members
        eval "_val=\"\${$_this[\$_j]}\""
        case $_j in
            # special treatment for parent
            parent) printf "%*s%s=" $_tab "" $_j; ${!_val} dump $(( _tab+${#_j}+${#_val}+2 ));;
                 *) printf "%*s%s=%s\n" $_tab "" $_j "$_val";;
        esac
    done
    (( _tab-=2 ))
    printf "%*s}\n" $_tab ""
    return 0
}

私のOOPデザインはオブジェクト内のオブジェクトを考慮していません-継承されたクラスを除きます。それらを個別に作成するか、class()のような特別なコンストラクタを作成することができます。* obj_dump *を検出するために変更する必要がありますそれらを再帰的に出力するための内部クラス。

ああ!そしてclass関数を簡略化するために手動でROOTクラスを定義します:

declare -gA ROOT=(    \
  [this]=ROOT         \
  [0]="dispatch ROOT" \
  [dump]=obj_dump     \
  [set]="ROOT_set"    \
  [get]="ROOT_get"    \
)

いくつかのキュー関数を使用して、次のようないくつかのクラスを定義しました。

class Queue          \
    in=0 out=0 N=10  \
    dump=obj_dump    \
    add=q_add        \
    empty=q_empty    \
    full=q_full      \
    peek=q_peek      \
    remove=q_remove

class RoughQueue:Queue     \
    N=100                  \
    shove=q_shove          \
    head_drop=q_head_drop

いくつかのQueueインスタンスを作成し、それらを機能させました:

class Q:Queue N=1000
$Q add aaa bbb "ccc ddd"
$Q peek X
$Q remove X
printf "X=%s\n" "$X"
$Q remove X
printf "X=%s\n" "$X"
$Q remove X
printf "X=%s\n" "$X"


class R:RoughQueue N=3
$R shove aa bb cc dd ee ff gg hh ii jj
$R dump
14
philcolbourn

ksh93t +は、bourne/posixシェル構文を保持しながら、いくつかのOO概念を導入しています: http://blog.fpmurphy.com/2010/05/ksh93-using-types-to- create-object-orientated-scripts.html

7
jlliagre

IPython は驚くほど便利に使用できます。

標準シェル機能:ジョブ制御、readline編集と履歴、エイリアス、catlscdおよびpwd、ページャー統合、プレフィックスを付けてシステムコマンドを実行とともに !または有効化%rehashx、python変数に割り当て可能なコマンド出力、pythonシェル変数として使用可能な値。

Python固有:最後のコマンドの結果の再利用、ドキュメントとソースへのクイックアクセス、モジュールの再読み込み、デバッガー。あなたがそれに夢中なら、いくつかのクラスターサポート。

とはいえ、複雑なパイプの実行はPythonでは行われません。 posixシェルも使用し、値を前後に渡すための接着剤を使用します。

5
Tobu

これは使用と設定が少し簡単で、argsなどと名前が付けられています。 https://github.com/uudruid74/bashTheObjects

私の回答は、別の回答の基本的な例の1つに従う例で更新していますが、この構文を使用しています。サンプルプログラムは似ていますが、すべての変数にクラス名をプレフィックスする必要はありません(これはkindofメソッドが示すようにこれを知っています)。構文はずっとシンプルだと思います!

まず、クラスファイル。インスタンス変数のデフォルトはオプションであり、これらの値をコンストラクターに渡さない場合にのみ使用されます。

class Person
    public show
    public set
    public Name
    public Age
    public Sex
    inst var Name "Saranyan"
    inst var Age 10
    inst var Sex "Male"

Person::Person { :; }
Person::set() { :; }
Person::Name() { println $Name }
Person::Age() { println $Age }
Person::Sex() { println $Sex }
Person::show() {
    Person::Name
    Person::Age
    Person::Sex
}

次に、使用例を示します。

#!/bin/bash
source static/oop.lib.sh

import Person

new Person Christy Name:"Christy" Age:21 Sex:"female"
new Person Evan Name:"Evan" Age:41 Sex:"male"

println "$(Evan.Name) is a $(Evan.Sex) aged $(Evan.Age)"
println "$(Christy.Name) is a $(Christy.Sex) aged $(Christy.Age)"
println "Stats for Evan ..."
Evan.show

assert 'kindof Person Evan'
assert '[ $Evan = $Evan ]'
assert 'kindof Person Christy'
assert '[ $Evan = $Christy ]'

ノート:

  1. その最後のアサーションは失敗します。上記の例とは異なり、ライブラリはまだオブジェクトの割り当てをサポートしていませんが、追加するのはそれほど難しくありません。今後のコンテナー/イテレーターサポートと共に、TO-DOに配置します。

インポートステートメントは技術的には必須ではありませんが、最初のnewを待つのではなく、指定されたポイントでクラスを強制的にロードします。これは初期化に役立ちます正しい順序で物事。複数のインスタンス変数を一度に簡単に設定できることに注意してください。

デバッグレベル、コンストラクター、デストラクター、サブクラス化、および基本的なreflectionシステムもあり、print/printlnでエコーを置き換えます(ダッシュで始まる変数を出力しようとしないでください)。 githubの例は、クラスからHTMLを生成するCGIとして実行されていることを示しています。

ライブラリ自体(oop.lib.sh)はそれほど単純ではありませんが(400行以上、11K)、ライブラリを含めて忘れてしまいます。

2
Evan Langlois

Rubyを使用する Rush と、Perlに基づく Psh があります。

2
OneOfOne

Linuxに PowerShell Core Edition をインストールできます。マイクロソフトが積極的に開発しているクロスプラットフォームの.NET Coreフレームワークで動作します。

2
Trevor Sullivan

jq は、このような一種のオブジェクト指向レイヤーとして非常にうまく機能します。

2
Abbafei

これはPythonベースのオブジェクト指向シェルですが、Golangに近い構文を持っています: https://github.com/alexst07/Shell-plus-plus

たとえば、catchを試してください。

try {
  git clone [email protected]:alexst07/Shell-plus-plus.git
} catch InvalidCmdException as ex {
  print("git not installed [msg: ", ex, "]")
}

クラスと演算子のオーバーロード:

class Complex {
  func __init__(r, i) {
    this.r = r
    this.i = i
  }

  func __add__(n) {
    return Complex(n.r + this.r, n.i + this.i)
  }

  func __sub__(n) {
    return Complex(n.r - this.r, n.i - this.i)
  }

  func __print__() {
    return string(this.r) + " + " + string(this.i) + "i"
  }
}

c1 = Complex(2, 3)
c2 = Complex(1, 2)
c = c1 + c2

print(c)

また、bashと同様のコマンドを使用できます。

echo "Test" | cat # simple pipeline
ls src* | grep -e "test" # using glob

# using variables content as command
cip = "ipconfig"
cgrep = ["grep", "-e", "10\..*"]
${cip} | $@{cgrep} # pass an array to command
1
Alex

誰かがオブジェクト指向プログラミングの基本(プロパティとメソッド)だけを望んでいる場合は、本当に単純なフレームワークでうまくいくでしょう。

オブジェクトを使用して「Hello World」というテキストを表示するとします。まず、表示するテキストのプロパティを持ち、このテキストを設定して表示するメソッドを持つオブジェクトクラスを作成します。クラスの複数のインスタンスがどのように連携するかを示すために、テキストを表示するための2つのメソッドを追加しました。1つは末尾にNewLineがあり、もう1つはそれがない場合です。

クラス定義ファイル:EchoClass.class

# Define properties
<<InstanceName>>_EchoString="Default text for <<InstanceName>>"

# Define methods
function <<InstanceName>>_SetEchoString()
{
  <<InstanceName>>_EchoString=$1
}

function <<InstanceName>>_Echo()
{
  # The -ne parameter tells echo not to add a NewLine at the end (No Enter)
  echo -ne "$<<InstanceName>>_EchoString"
}

function <<InstanceName>>_EchoNL()
{
  echo "$<<InstanceName>>_EchoString"
}

「<<InstanceName>>」という単語に注意してください。これは後で置き換えられて、クラスオブジェクトの複数のインスタンスを作成します。オブジェクトのインスタンスを使用する前に、実際にそれを作成する関数が必要です。物事をシンプルに保つために、それは次の名前の別のスクリプトになります:ObjectFramework.lib

# 1st parameter : object instance name
# 2nd parameter : object instance class

function CreateObject()
{
  local InstanceName=$1
  local ObjectClass=$2
  # We will replace all occurences of the text "<<InstanceName>>" in the class file 
  # to the value of the InstanceName variable and store it in a temporary file
  local SedString='s/<<InstanceName>>/'$InstanceName'/g '$ObjectClass'.class'
  local TmpFile=$ObjectClass'_'$InstanceName'.tmp'
  sed $SedString > $TmpFile

  # The file will contain code which defines variables (properties) and functions (methods)
  # with the name we gave to our object instance via the 1st parameter of this function
  # ... we run this code so the variables and functions are actually defined in runtime
  source "$TmpFile"

  # Than remove the temp file as we don't need it any more
  rm "$TmpFile"
}

これで、クラス定義ファイルと、このファイルのコピーを作成するCreateObject関数があり、テキスト "<<InstanceName>>"を任意の名前に置き換えました。

次のスクリプトで新しいオブジェクトを使用してみましょう:HelloWorld.sh(HelloWorld.shは実行可能である必要があります。他の2つのファイルで実行する必要はありません)

# Define the CreateObject function via the lib file we created
source ObjectFramework.lib

# Create two instances of the EchoClass class
CreateObject MyHello EchoClass
CreateObject MyWorld EchoClass

# Call the SetEchoString method of the two objects. In reality these are 
# just two identical functions named differently and setting different
# variables (remember the <<InstanceName>>_EchoString variable?)
MyHello_SetEchoString "Hello "
MyWorld_SetEchoString "World"

# Finally we call the Echo and EchoNL (NewLine) methods
MyHello_Echo
MyWorld_EchoNL

HelloWorld.shスクリプトを実行すると、「Hello World」というテキストが表示されます(そして、NewLineが追加されます)。この結果に誰も感心することはありませんが、これは次のように簡単ではないことがわかります。

幸せなコーディング!

1
vandor76
## implemantion of base class
function Class()
{
    base=${FUNCNAME}
    this=${1}
    Class_setCUUID $this
    for method in $(compgen -A function)
    do
        export ${method/#$base\_/$this\_}="${method} ${this}"
    done

}

function copyCUUID()
{
        export ${2}_CUUID=$(echo $(eval "echo \$${1}_CUUID"))

}

function Class_setCUUID()
{
        export ${1}_CUUID=$(uuid)
}

function Class_getCUUID()
{
        echo $(eval "echo \$${2}_CUUID")
}


function Class_setProperty()
{
        export ${1}_${2}=${3}
}

function Class_getProperty()
{
        echo $(eval "echo \$${1}_${2}")
}

function Class_Method()
{
        echo "function ${1}_${2}()
        {
        echo null
        }
        " > /tmp/t.func
        . /tmp/t.func
        rm /tmp/t.func


}

function Class_setMethod()
{
        export ${1}_${2}=${1}_${2}
}


function Class_getMethod()
{
        $(eval "echo \$${1}_${2}")
}


function Class_equals()
{
        base="Class"
        this=${2}

    copyCUUID ${1} ${2}
    for method in $(compgen -A function)
    do
        export ${method/#$base\_/$this\_}="${method} ${1}"
    done


}

参照に基づいてbashにooの概念を導入しようとしたところ http://hipersayanx.blogspot.in/2012/12/object-oriented-programming-in-bash.html

source ./oobash

Class person
$person_setProperty Name "Saranyan"
$person_setProperty Age 10
$person_setProperty Sex "Male"
function person_show()
{
$person_getProperty Name
$person_getProperty Age
$person_getProperty Sex
}
$person_setMethod show

$person_equals person1
$person1_getMethod show
$person1_equals person3
$person_getCUUID person
$person_getCUUID person1
$person_getCUUID person3

Plumbum は、Pythonに似たシェル言語です。シェルのような構文をPython=でパッケージ化して、エクスペリエンスをオブジェクト指向にします。

0
joshlk

さて、ほとんどの場合、シェルでどのオブジェクトを扱っていますか?それはファイル/ディレクトリ、プロセス、およびそれらの相互作用です。したがって、それは_f1.edit_または_currentFile=f1.c ; .edit ; .compile ; .run_のようになります。またはd1.search(filename='*.c' string='int \*')。または_p1.stop_、_p1.bg_。それが私のooshellの理解です。

0
ott--

短い返信で申し訳ありませんが、ここに行きます。

hipersayanxが記事 Bashのオブジェクト指向プログラミング を作成しました。基本的に彼はハイジャックしました$FUNCNAMEfunctioncompgen、およびexportは、OOPに近いものを作成するために、bashで取得できます。

クールな部分は、それがうまく機能し、クラスを構築するためにたった数行のboilerplatingを必要とすることです。

必要な基本パーツは次のとおりです。

ClassName() {
# A pointer to this Class. (2)
base=$FUNCNAME
this=$1

# Inherited classes (optional).
export ${this}_inherits="Class1 Class2 Class3" # (3.1)
 for class in $(eval "echo \$${this}_inherits")
do
    for property in $(compgen -A variable ${class}_)
    do
        export ${property/#$class\_/$this\_}="${property}" # (3.2)
    done

    for method in $(compgen -A function ${class}_)
    do
        export ${method/#$class\_/$this\_}="${method} ${this}"
    done
done

# Declare Properties.
export ${this}_x=$2
export ${this}_y=$3
export ${this}_z=$4

# Declare methods.
for method in $(compgen -A function); do
    export ${method/#$base\_/$this\_}="${method} ${this}"
done
}

function ClassName_MethodName()
{
#base is where the magic happens, its what holds the class name
base=$(expr "$FUNCNAME" : '\([a-zA-Z][a-zA-Z0-9]*\)')
this=$1

x=$(eval "echo \$${this}_x")

echo "$this ($x)"
}

使用法:

# Create a new Class Instance
ClassName 'instanceName' $param1 $param2

$instanceName_method

今、私はこれを自分のAuditOpsプロジェクトで使用しており、hipersayanxはこれが彼のサイトで実際にどのように機能するかについての詳細を持っています。これは非常にバシズムですが、運賃の警告はbash 4.0より古いものでは機能せず、デバッグで頭痛の種になる可能性があります。個人的には、ボイラーメッキの大部分をクラス自体としてやり直してほしいと思います。

真剣なOOP Perl、Rubyなどのスクリプト言語、およびプロジェクトに適している場合はpythonを使用するほうが賢明です。しかし、正直なオプションでは、 OOPのこの方法をbashで使用するためにモジュラーbashスクリプトを保守するときの時間と労力。

0
Dwight Spencer

HashMap ObjectShell_map のように機能する関数をGitHubで開発しています。

HashMapインスタンス」を作成するために、この関数は異なる名前で自身のコピーを作成できます。新しい関数のコピーごとに、異なる$ FUNCNAME変数があります。次に$ FUNCNAMEを使用して、各Mapインスタンスの名前空間を作成します。

マップキーは$ FUNCNAME_DATA_ $ KEYの形式のグローバル変数です。$ KEYはマップに追加されたキーです。これらの変数は 動的変数 です。

例として使用できるように、簡略化したバージョンを配置します。

#!/bin/bash

Shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads Shell_map function declaration
        test -n "$(declare -f Shell_map)" || return

        # declares in the Global Scope a copy of Shell_map, under a new name.
        eval "${_/Shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

使用法:

Shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains "Mary" && echo "Mary has credit!"
0