ti-enxame.com

Shell orientado a objetos para * nix

Prefácio: Eu amo bash e não tenho intenção de iniciar qualquer tipo de argumento ou guerra santa, e espero que essa não seja uma pergunta extremamente ingênua.

Esta pergunta está um pouco relacionada a este post no superusuário, mas não acho que o OP realmente sabia o que estava pedindo. Eu uso o bash no FreeBSD, linux, OS X e cygwin no Windows. Também tive uma vasta experiência recentemente com o PowerShell no Windows.

Existe um Shell para * nix, já disponível ou em andamento, compatível com o bash, mas que adiciona uma camada de script orientado a objetos ao mix? A única coisa que sei disso se aproxima é o console python $, mas, tanto quanto posso dizer, não fornece acesso ao ambiente padrão do Shell. Por exemplo, não posso simplesmente cd ~ e ls, então chmod +x file dentro do console python console. Eu precisaria usar python para executar essas tarefas, em vez dos binários unix padrão, ou chame os binários usando python.

Existe tal Shell?

42
Robert S Ciaccio

Posso pensar em três recursos desejáveis ​​em um Shell:

  • Usabilidade interativa: comandos comuns devem ser rápidos para digitar; conclusão; ...
  • Programação: estruturas de dados; concorrência (empregos, canalização, ...); ...
  • Acesso ao sistema: trabalhando com arquivos, processos, janelas, bancos de dados, configuração do sistema, ...

Os shells Unix tendem a se concentrar no aspecto interativo e subcontratam a maior parte do acesso ao sistema e parte da programação para ferramentas externas, como:

  • bc para matemática simples
  • openssl para criptografia
  • sed , awk e outros para processamento de texto
  • nc para redes TCP/IP básicas
  • ftp para FTP
  • mail, Mail, mailx, etc. para email básico
  • cron para tarefas agendadas
  • wmctrl para manipulação básica de janelas X
  • dcop para bibliotecas KDE ≤3.x
  • dbus ferramentas (dbus-* ou qdbus ) para várias tarefas de informações e configuração do sistema (incluindo ambientes de desktop modernos, como o KDE ≥4)

Muitas coisas podem ser feitas invocando um comando com os argumentos corretos ou com a entrada canalizada. Essa é uma abordagem muito poderosa - é melhor ter uma ferramenta por tarefa que faça isso bem do que um único programa que faz tudo menos que mal - mas tem suas limitações.

Uma grande limitação dos shells unix, e suspeito que é isso que você procura com seu requisito de "script orientado a objetos", é que eles não são bons em reter informações de um comando para o próximo ou combinar comandos de maneiras mais sofisticadas do que um gasoduto. Em particular, a comunicação entre programas é baseada em texto; portanto, os aplicativos só podem ser combinados se eles serializarem seus dados de maneira compatível. Isso é uma bênção e uma maldição: a abordagem "tudo é texto" facilita a execução de tarefas simples rapidamente, mas aumenta a barreira para tarefas mais complexas.

A usabilidade interativa também funciona bastante contra a manutenção do programa. Os programas interativos devem ser curtos, exigir poucas citações, não incomodá-lo com declarações variáveis ​​ou digitação, etc. Programas que podem ser mantidos devem ser legíveis (para que não haja muitas abreviações), devem ser legíveis (para que você não precise se perguntar se um Word vazio é uma string, um nome de função, um nome de variável etc.), deve ter verificações de consistência, como declarações de variáveis ​​e digitação, etc.

Em resumo, um Shell é um compromisso difícil de alcançar. Ok, isso termina a seção de retórica, para os exemplos.


  • O Shell Perl (psh) "combina a natureza interativa de um Shell Unix com o poder do Perl". Comandos simples (até pipelines) podem ser inseridos na sintaxe do Shell; tudo o resto é Perl. O projeto não está em desenvolvimento há muito tempo. É utilizável, mas não chegou ao ponto em que eu consideraria usá-lo sobre o Perl puro (para script) ou o Shell puro (interativamente ou para script).

  • IPython é um console interativo aprimorado Python console, especialmente direcionado à computação numérica e paralela). Este é um projeto relativamente jovem.

  • irb (Ruby interativo) é o Ruby equivalente ao Python.

  • scsh é uma implementação de esquema (ou seja, uma linguagem de programação decente) com o tipo de ligações do sistema tradicionalmente encontradas em shells unix (strings , processos, arquivos). No entanto, não pretende ser utilizável como um shell interativo.

  • zsh é um Shell interativo aprimorado. Seu ponto forte é a interatividade (edição de linha de comando, conclusão, tarefas comuns realizadas com sintaxe concisa mas enigmática). Seus recursos de programação não são tão bons (a par do ksh), mas vêm com várias bibliotecas para controle de terminal, regexps, rede, etc.

  • fish é um começo limpo em um Shell estilo unix. Não possui recursos melhores de programação ou acesso ao sistema. Por quebrar a compatibilidade com o sh, ele tem mais espaço para desenvolver recursos melhores, mas isso não aconteceu.


Adendo: outra parte da caixa de ferramentas unix está tratando muitas coisas como arquivos:

  • A maioria dos dispositivos de hardware é acessível como arquivos.
  • No Linux, /sys fornece mais controle de hardware e sistema.
  • Em muitas variantes do unix, o controle do processo pode ser feito através do /proc sistema de arquivo.
  • Fusível facilita a gravação de novos sistemas de arquivos. Já existem sistemas de arquivos para converter formatos de arquivos em tempo real, acessar arquivos por vários protocolos de rede, procurar dentro de arquivos etc.

Talvez o futuro dos shells unix não seja um melhor acesso ao sistema por meio de comandos (e melhores estruturas de controle para combinar comandos), mas um melhor acesso ao sistema por meio de sistemas de arquivos (que se combinam de maneira um pouco diferente - não acho que tenhamos descoberto quais são os principais idiomas (como o tubo Shell) ainda estão).

45

Você não precisa de muito código bash para implementar classes ou objetos no bash.

Digamos, 100 linhas.

O Bash possui matrizes associativas que podem ser usadas para implementar um sistema de objetos simples com herança, métodos e propriedades.

Portanto, você pode definir uma classe como esta:

class Queue N=10 add=q_add remove=q_remove

A criação de uma instância desta fila pode ser feita assim:

class Q:Queue N=100

ou

inst Q:Queue N=100

Como as classes são implementadas com uma matriz, class e inst são realmente sinônimos - como em javascript.

Adicionar itens a essa fila pode ser feito da seguinte maneira:

$Q add 1 2 aaa bbb "a string"

A remoção de itens em uma variável X pode ser feita assim:

$Q remove X

E a estrutura de dumping de um objeto pode ser feita assim:

$Q dump

O que retornaria algo como isto:

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=
    }

As classes são criadas usando uma função de classe como esta:

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 "[email protected]" "0=dispatch $_this" "parent=${_parent:-ROOT}" "class=$_this"
}

NOTA: Ao definir uma nova classe ou instância, você pode substituir qualquer valor ou função de membro.

As matrizes associativas Bash têm uma peculiaridade que faz com que isso funcione perfeitamente: $ Q [0]} é idêntico a $ Q. Isso significa que podemos usar o nome do array para chamar uma função de despacho de método:

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

Um lado negativo é que eu não posso usar [0] para dados, então minhas filas (neste caso) começam no índice = 1. Alternativamente, eu poderia ter usado índices associativos como "q + 0".

Para obter e definir membros, você pode fazer algo assim:

# basic set and get for key-value members
ROOT_set(){                                       # $QOBJ set key=value
    local _this=$1 _exp _key _val
    shift
    for _exp in "[email protected]"; 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 "[email protected]"; do
        _var=${_exp%%=*}
        _key=${_exp#*=}
        eval $_var=\"\${$_this[$_key]}\"
    done
}

E para despejo uma estrutura de objeto, eu fiz isso:

NOTA: Isso não é necessário para OOP no bash, mas é bom ver como os objetos são criados.

# 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
}

Meu design OOP não considerou objetos dentro de objetos - exceto para a classe herdada. Você pode criá-los separadamente ou criar um construtor especial como class (). * Obj_dump * precisaria ser modificado para detectar classes internas para imprimi-las recursivamente.

Oh! e eu defino manualmente uma classe ROOT para simplificar a função class:

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

Com algumas funções de fila, defini algumas classes como esta:

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

Criou algumas instâncias da fila e as fez funcionar:

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

o ksh93t + está introduzindo alguns conceitos OO enquanto mantém a sintaxe do shell bourne/posix: http://blog.fpmurphy.com/2010/05/ksh93-using-types-to- create-object-orientated-scripts.html

7
jlliagre

IPython é surpreendentemente conveniente de usar.

Recursos padrão do Shell: controle de tarefas, edição e histórico de linhas de leitura, aliases, catlscd e pwd, integração de pager, executando qualquer comando do sistema, prefixando-o com um ! ou habilitando %rehashx, saída do comando atribuível a uma variável python python, python disponíveis como variáveis ​​do Shell.

Específico para Python: reutilizando resultados dos últimos comandos, acesso rápido à documentação e fonte, recarregamento de módulos, depurador. Algum suporte de cluster, se você gosta disso.

Dito isto, a execução de pipes complexos não é feita no Python; você também usará o posix Shell, apenas com cola para passar valores de um lado para o outro.

5
Tobu

Este é um pouco mais simples de usar e configurar, nomeou args, etc. https://github.com/uudruid74/bashTheObjects

Estou atualizando minha resposta com um exemplo, que segue um dos exemplos básicos dados para outra resposta, mas com esta sintaxe. O programa de exemplo é semelhante, mas você não precisa prefixar todas as variáveis ​​com o nome da classe (ele sabe disso como mostra o método kindof ) e I pense que a sintaxe é muito mais simples!

Primeiro, um arquivo de classe. Os padrões para as variáveis ​​de instância são opcionais e usados ​​apenas se você não passar esses valores para o construtor.

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
}

Agora, por exemplo, uso:

#!/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 ]'

NOTAS:

  1. Essa última afirmação falhará. Diferente do exemplo acima, a biblioteca ainda não suporta a atribuição de objetos, mas isso não seria muito difícil de adicionar. Vou colocá-lo na minha lista de tarefas juntamente com o próximo suporte a contêiner/iterador.

Tecnicamente, a instrução de importação não é necessária, mas força o carregamento da classe no ponto especificado, em vez de aguardar o primeiro novo , o que pode ajudar a inicializar as coisas na ordem correta. Observe a facilidade com que você pode definir várias variáveis ​​de instância de uma só vez.

Também existem níveis de depuração, construtores, destruidores, subclassing e um sistema básico reflection, e é mostrado print/println = para substituir o eco (tente imprimir uma variável que comece com um traço?). O exemplo no github mostra como sendo executado como CGI gerando HTML a partir de classes.

A biblioteca em si (oop.lib.sh) não é tão simples (mais de 400 linhas, 11K), mas você apenas a inclui e esquece.

2
Evan Langlois

Tem Rush que usa Ruby e Psh que é baseado em Perl.

2
OneOfOne

Você pode instalar PowerShell Core Edition no Linux agora. Ele é executado na estrutura .NET Core de plataforma cruzada, que está sendo ativamente desenvolvida pela Microsoft.

2
Trevor Sullivan

jq funciona muito bem como uma espécie de camada orientada a objetos.

2
Abbafei

Este é um Shell orientado a objetos baseado em Python, mas possui uma sintaxe próxima ao Golang: https://github.com/alexst07/Shell-plus-plus

Por exemplo, tente pegar:

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

sobrecarga de classe e operador:

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)

e você pode usar os comandos bash semelhantes:

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

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

Se alguém quiser apenas o básico da programação orientada a objetos (propriedades e métodos), uma estrutura realmente simples seria suficiente.

Digamos que você queira exibir o texto "Hello World" usando objetos. Primeiro, você cria uma classe de objeto que possui uma propriedade para o texto a ser exibido e possui alguns métodos para definir e exibi-lo. Para mostrar como várias instâncias de uma classe podem funcionar juntas, adicionei dois métodos para exibir o texto: um com NewLine no final e outro sem isso.

Arquivo de definição de classe: 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"
}

Observe a palavra "<<InstanceName>>". Isso será substituído posteriormente para criar várias instâncias de um objeto de classe. Antes de poder usar uma instância de um objeto, você precisa de uma função que realmente o crie. Para simplificar, será um script separado chamado: 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"
}

Portanto, agora temos um arquivo de definição de classe e uma função CreateObject que cria uma cópia desse arquivo com o texto "<<InstanceName>>" substituído pelo nome que desejarmos.

Vamos usar nosso novo objeto em um script chamado: HelloWorld.sh (observe que HelloWorld.sh deve ser executável. Os outros dois arquivos não precisam)

# 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

Ao executar o script HelloWorld.sh, ele exibe o texto "Hello World" (e adiciona um NewLine). Ninguém ficará impressionado com este resultado, no entanto, saberemos que isso não é tão simples quanto parece :)

Feliz codificação!

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


}

apenas tentei introduzir oo conceitos para bash com base em referência 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
0
Aravamudhan saranyan

Plumbum é uma linguagem Shell semelhante ao Python. Ele empacota a sintaxe do Shell com Python tornando a experiência orientada a objetos.

0
joshlk

Agora, com quais objetos você lida com um Shell na maioria das vezes? São arquivos/diretórios, processos e sua interação. Portanto, deve gostar de f1.edit Ou algo como currentFile=f1.c ; .edit ; .compile ; .run. Ou d1.search(filename='*.c' string='int \*'). Ou p1.stop, p1.bg. Essa é a minha compreensão de um Ooshell.

0
ott--

Desculpe pela resposta curta, mas aqui vai.

hipersayanx criou um artigo Programação Orientada a Objetos no Bash . Basicamente, ele chocou $FUNCNAME, function, compgen e export para criar o mais próximo de OOP pode-se entrar no bash.

Parte legal é que funciona bem e é preciso apenas algumas linhas de caldeira para construir uma classe.

As peças básicas necessárias são:

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)"
}

Uso:

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

$instanceName_method

Agora, eu mesmo usei isso no meu projeto AuditOps e o hipersayanx tem mais detalhes sobre como isso realmente funciona em seu site. O aviso de tarifa, embora isso seja muito básico, não funcionará com nada mais antigo que o bash 4.0 e pode causar dor de cabeça na depuração. Enquanto pessoalmente, eu gostaria de ver a maior parte do revestimento da caldeira refeito como uma classe em si.

É sempre mais sensato usar uma linguagem de script séria OOP como Perl, Ruby e python quando for mais adequada ao seu projeto. No entanto, na minha opção honesta, vale a pena tempo e esforço ao manter scripts bash modulares para utilizar este método de OOP no bash.

0
Dwight Spencer

Estou desenvolvendo no GitHub uma função que funciona como um Objeto HashMap , Shell_map .

Para criar " instâncias do HashMap ", esta função pode criar cópias de si mesma com nomes diferentes. Cada nova cópia de função terá uma variável $ FUNCNAME diferente. $ FUNCNAME é usado para criar um espaço para nome para cada instância do Mapa.

As chaves do mapa são variáveis ​​globais, no formato $ FUNCNAME_DATA_ $ KEY, onde $ KEY é a chave adicionada ao mapa. Essas variáveis ​​são variáveis ​​dinâmicas .

Abaixo vou colocar uma versão simplificada para que você possa usar como exemplo.

#!/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
}

Uso:

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
Bruno Negrão Zica