it-swarm-es.tech

Shell orientado a objetos para * nix

Prefacio: Me encanta bash y no tengo intención de comenzar ningún tipo de argumento o guerra santa, y espero que esta no sea una pregunta extremadamente ingenua.

Esta pregunta está algo relacionada con esta publicación en el superusuario, pero no creo que el OP realmente supiera lo que estaba pidiendo. Yo uso bash en FreeBSD, linux, OS X y cygwin en Windows. También he tenido una amplia experiencia recientemente con PowerShell en Windows.

¿Hay un Shell para * nix, ya disponible o en proceso, que sea compatible con bash pero agregue una capa de secuencias de comandos orientadas a objetos en la mezcla? Lo único que sé acerca de eso es la consola python), pero por lo que puedo decir, no proporciona acceso al entorno estándar de Shell. Por ejemplo, no puedo simplemente cd ~ y ls, luego chmod +x file dentro de la consola python. Tendría que usar python para realizar esas tareas en lugar de los binarios estándar de Unix, o llamar a los binarios usando python.

¿Existe tal Shell?

42
Robert S Ciaccio

Puedo pensar en tres características deseables en un Shell:

  • Usabilidad interactiva: los comandos comunes deben ser rápidos de escribir; terminación; ...
  • Programación: estructuras de datos; concurrencia (trabajos, tubería, ...); ...
  • Acceso al sistema: trabajar con archivos, procesos, ventanas, bases de datos, configuración del sistema, ...

Los shells de Unix tienden a concentrarse en el aspecto interactivo y subcontratan la mayor parte del acceso al sistema y parte de la programación a herramientas externas, como:

  • bc para matemática simple
  • openssl para criptografía
  • sed , awk y otros para procesamiento de texto
  • nc para redes TCP/IP básicas
  • ftp para FTP
  • mail, Mail, mailx, etc. para correo electrónico básico
  • cron para tareas programadas
  • wmctrl para manipulación básica de ventanas X
  • dcop para bibliotecas KDE ≤3.x
  • dbus herramientas (dbus-* o qdbus ) para diversas tareas de información y configuración del sistema (incluidos entornos de escritorio modernos como KDE ≥4)

Se pueden hacer muchas, muchas cosas invocando un comando con los argumentos correctos o la entrada canalizada. Este es un enfoque muy poderoso: es mejor tener una herramienta por tarea que lo haga bien que un solo programa que haga todo menos mal, pero tiene sus limitaciones.

Una limitación importante de los shells de Unix, y sospecho que esto es lo que está buscando con su requisito de "script orientado a objetos", es que no son buenos para retener información de un comando al siguiente, o combinar comandos de maneras más elegantes que una tubería En particular, la comunicación entre programas se basa en texto, por lo que las aplicaciones solo se pueden combinar si serializan sus datos de manera compatible. Esto es tanto una bendición como una maldición: el enfoque de todo es texto hace que sea fácil realizar tareas simples rápidamente, pero levanta la barrera para tareas más complejas.

La usabilidad interactiva también corre en contra de la mantenibilidad del programa. Los programas interactivos deben ser cortos, requieren pocas comillas, no molestarlo con declaraciones variables o tipeo, etc. Los programas mantenibles deben ser legibles (por lo que no tienen muchas abreviaturas), deben ser legibles (para que no tenga que preguntarse si una palabra simple es una cadena, un nombre de función, un nombre de variable, etc.), debe tener comprobaciones de coherencia, como declaraciones de variables y escritura, etc.

En resumen, un Shell es un compromiso difícil de alcanzar. Ok, esto termina la sección despotricar, a los ejemplos.


  • El Perl Shell (psh) "combina la naturaleza interactiva de Unix Shell con el poder de Perl". Se pueden ingresar comandos simples (incluso tuberías) en la sintaxis de Shell; todo lo demás es Perl. El proyecto no ha estado en desarrollo durante mucho tiempo. Es utilizable, pero no ha llegado al punto en el que consideraría usarlo sobre Perl puro (para secuencias de comandos) o Shell puro (de forma interactiva o para secuencias de comandos).

  • IPython es una consola interactiva mejorada Python), especialmente dirigida a la computación numérica y paralela. Este es un proyecto relativamente joven.

  • irb (Ruby interactivo) es el Ruby equivalente del Python consola.

  • scsh es una implementación de esquema (es decir, un lenguaje de programación decente) con el tipo de enlaces de sistema que se encuentran tradicionalmente en shells de Unix (cadenas , procesos, archivos). Sin embargo, no pretende ser utilizable como un Shell interactivo.

  • zsh es un Shell interactivo mejorado. Su punto fuerte es la interactividad (edición de línea de comandos, finalización, tareas comunes realizadas con una sintaxis concisa pero críptica). Sus características de programación no son tan buenas (a la par con ksh), pero viene con una serie de bibliotecas para control de terminales, expresiones regulares, redes, etc.

  • fish es un comienzo limpio en un Shell de estilo unix. No tiene mejores funciones de programación o acceso al sistema. Debido a que rompe la compatibilidad con sh, tiene más espacio para desarrollar mejores características, pero eso no ha sucedido.


Anexo: otra parte de la caja de herramientas de Unix trata muchas cosas como archivos:

  • La mayoría de los dispositivos de hardware son accesibles como archivos.
  • Bajo Linux, /sys proporciona más hardware y control del sistema.
  • En muchas variantes de Unix, el control del proceso se puede realizar a través de /proc sistema de archivos.
  • Fusible facilita la escritura de nuevos sistemas de archivos. Ya existen sistemas de archivos para convertir formatos de archivos sobre la marcha, acceder a archivos a través de varios protocolos de red, buscar dentro de archivos, etc.

Tal vez el futuro de los shells de Unix no sea un mejor acceso al sistema a través de comandos (y mejores estructuras de control para combinar comandos) sino un mejor acceso al sistema a través de sistemas de archivos (que se combinan de manera algo diferente; no creo que hayamos resuelto cuáles son los modismos clave (como la tubería Shell) todavía).

No necesita mucho código bash para implementar clases u objetos en bash.

Digamos, 100 líneas.

Bash tiene matrices asociativas que se pueden usar para implementar un sistema de objetos simple con herencia, métodos y propiedades.

Entonces, podría definir una clase como esta:

class Queue N=10 add=q_add remove=q_remove

La creación de una instancia de esta Cola podría hacerse así:

class Q:Queue N=100

o

inst Q:Queue N=100

Dado que las clases se implementan con una matriz, class e inst son realmente sinónimos, algo así como javascript.

Agregar elementos a esta cola podría hacerse así:

$Q add 1 2 aaa bbb "a string"

La eliminación de elementos en una variable X podría hacerse así:

$Q remove X

Y la estructura de descarga de un objeto podría hacerse así:

$Q dump

Lo que devolvería algo como esto:

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

Las clases se crean usando una función de clase 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: Al definir una nueva clase o instancia, puede anular cualquier valor o función de miembro.

Las matrices asociativas Bash tienen una peculiaridad que hace que esto funcione perfectamente: $ Q [0]} es idéntico a $ Q. Esto significa que podemos usar el nombre de matriz para llamar a una función de envío de método:

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

Una desventaja es que no puedo usar [0] para los datos, por lo que mis colas (en este caso) comienzan desde index = 1. Alternativamente, podría haber usado índices asociativos como "q + 0".

Para obtener y establecer miembros, puede hacer algo como esto:

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

Y para volcado una estructura de objeto, hice esto:

NOTA: Esto no es necesario para OOP en bash, pero es bueno ver cómo se hacen los objetos.

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

Mi OOP diseño no ha considerado los objetos dentro de los objetos, excepto para la clase heredada. Puede crearlos por separado o crear un constructor especial como class (). * Obj_dump * necesitaría ser modificado para detectar clases internas para imprimirlos recursivamente.

Oh! y defino manualmente una clase ROOT para simplificar class function:

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

Con algunas funciones de cola definí algunas clases 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

Creé algunas instancias de cola y las hizo 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

ksh93t + está introduciendo algunos OO conceptos mientras conserva la sintaxis de bourne/posix Shell: http://blog.fpmurphy.com/2010/05/ksh93-using-types-to- create-object-orientated-scripts.html

7
jlliagre

IPython es sorprendentemente conveniente de usar.

Características estándar de Shell: control de trabajos, edición e historial de línea de lectura, alias, catlscd y pwd, integración de buscapersonas, ejecutar cualquier comando del sistema con el prefijo con un ! o habilitando %rehashx, salida de comando asignable a una python variable, python valores disponibles como variables de Shell.

Específico de Python: reutilización de resultados de los últimos comandos, acceso rápido a documentación y fuente, recarga de módulos, depurador. Un poco de soporte de clúster si te gusta eso.

Dicho esto, ejecutar tuberías complejas no se realiza en Python; también usará el shell posix, solo con un poco de pegamento para pasar valores de aquí para allá.

5
Tobu

Este es un poco más simple de usar y configurar, ha nombrado args, etc. https://github.com/uudruid74/bashTheObjects

Estoy actualizando mi respuesta con un ejemplo, que sigue uno de los ejemplos básicos dados para otra respuesta, pero con esta sintaxis. El programa de ejemplo es similar, pero no tiene que prefijar todas las variables con el nombre de clase (lo sabe como se muestra en el método kindof) y creo que la sintaxis es ¡mucho más simple!

Primero, un archivo de clase. Los valores predeterminados para las variables de instancia son opcionales y solo se usan si no pasa estos valores al constructor.

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
}

Ahora, por ejemplo, el 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. Esa última afirmación fallará. A diferencia del ejemplo anterior, la biblioteca aún no admite la asignación de objetos, pero eso no sería demasiado difícil de agregar. Lo colocaré en mi TO-DO junto con el próximo soporte de contenedor/iterador.

Técnicamente, la declaración de importación no es necesaria, pero obliga a cargar la clase en el punto dado en lugar de esperar el primer nuevo, que puede ayudar a inicializar las cosas en el orden correcto. Tenga en cuenta la facilidad con la que puede establecer múltiples variables de instancia de una vez.

También hay niveles de depuración, constructores, destructores, ¡subclases, y un sistema básico ¡reflexión, y se muestra es print/println = para reemplazar echo (¿alguna vez intentaste imprimir una variable que comienza con un guión?). El ejemplo en github lo muestra ejecutándose como un CGI que genera HTML a partir de clases.

La biblioteca en sí (oop.lib.sh) no es tan simple (más de 400 líneas, 11K), pero simplemente la incluye y la olvida.

2
Evan Langlois

Hay Rush que usa Ruby y Psh que se basa en Perl.

2
OneOfOne

Puede instalar PowerShell Core Edition en Linux ahora. Se ejecuta en el marco de .NET Core multiplataforma que Microsoft está desarrollando activamente.

2
Trevor Sullivan

jq funciona bastante bien como una especie de capa orientada a objetos.

2
Abbafei

Este es un Shell orientado a objetos basado en Python, pero tiene un cierre sintaxe de Golang: https://github.com/alexst07/Shell-plus-plus

Por ejemplo, intente atrapar:

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

sobrecarga de clase y 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)

y puedes usar los comandos bash similares:

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

Si alguien solo quiere lo básico de la programación orientada a objetos (propiedades y métodos), entonces un marco realmente simple sería suficiente.

Supongamos que desea mostrar el texto "Hola mundo" utilizando objetos. Primero, crea una clase de objeto que tiene una propiedad para mostrar el texto y tiene algunos métodos para establecer este texto y mostrarlo. Para mostrar cómo varias instancias de una clase pueden funcionar juntas, he agregado dos métodos para mostrar el texto: uno con NewLine al final y otro sin eso.

Archivo de definición de clase: 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"
}

Tenga en cuenta la palabra "<<InstanceName>>". Esto será reemplazado más tarde para crear múltiples instancias de un objeto de clase. Antes de poder usar una instancia de un objeto, necesita una función que realmente lo cree. Para simplificar las cosas, será un script separado llamado: 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"
}

Así que ahora tenemos un archivo de definición de clase y una función CreateObject que crea una copia de este archivo con el texto "<<InstanceName>>" reemplazado al nombre que queramos.

Usemos nuestro nuevo objeto en un script llamado: HelloWorld.sh (tenga en cuenta que HelloWorld.sh debe ser ejecutable. Los otros dos archivos no necesitan)

# 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

Al ejecutar el script HelloWorld.sh, muestra el texto "Hello World" (y agrega una nueva línea). Nadie quedará muy impresionado con este resultado, sin embargo, sabremos que esto no es tan simple como parece :)

¡Feliz codificación!

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


}

acabo de intentar introducir conceptos oo a bash basados ​​en la referencia 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

Plumbum es un lenguaje Shell similar a Python. Empaqueta Shell como sintaxis con Python haciendo que la experiencia esté orientada a objetos.

0
joshlk

Ahora, ¿con qué objetos trabajas en un Shell la mayor parte del tiempo? Son archivos/directorios, procesos y su interacción. Por lo tanto, debería indicar f1.edit O algo así como currentFile=f1.c ; .edit ; .compile ; .run. O d1.search(filename='*.c' string='int \*'). O p1.stop, p1.bg. Esa es mi comprensión de un ooshell.

0
ott--

Perdón por la breve respuesta, pero aquí va.

hipersayanx ha creado un artículo Programación orientada a objetos en Bash . Básicamente, él jodió $FUNCNAME, function, compgen y export para crear lo más cerca posible de OOP se puede obtener en bash.

Lo bueno es que funciona bien y solo se necesitan unas pocas líneas de calderería para construir una clase.

Las partes básicas necesarias son:

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

Ahora, lo he usado yo mismo en mi proyecto AuditOps y hipersayanx tiene más detalles sobre cómo funciona esto realmente en su sitio. La advertencia de tarifa, aunque esto es muy bashism, no funcionará con nada más antiguo que bash 4.0 y puede provocar un dolor de cabeza en la depuración. Si bien personalmente me gustaría ver la mayoría de las placas de calderas renovadas como una clase en sí misma.

Siempre es más sabio usar un lenguaje de script serio OOP como Perl, Ruby y python) cuando se adapta mejor a su proyecto. Sin embargo, en mi opción honesta, vale la pena tiempo y esfuerzo al mantener scripts de bash modulares para utilizar este método de OOP en bash.

0
Dwight Spencer

Estoy desarrollando en GitHub una función que funciona igual que un Objeto HashMap , Shell_map .

Para crear " instancias de HashMap " esta función puede crear copias de sí mismo con diferentes nombres. Cada nueva copia de función tendrá una variable $ FUNCNAME diferente. $ FUNCNAME se usa para crear un espacio de nombres para cada instancia de Map.

Las claves del mapa son variables globales, en la forma $ FUNCNAME_DATA_ $ KEY, donde $ KEY es la clave agregada al Mapa. Estas variables son variables dinámicas .

A continuación, pondré una versión simplificada para que puedas usarla como ejemplo.

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