Shell orientado a objetos para * nix

38

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 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 que agregue una capa de secuencias de comandos orientadas a objetos en la mezcla? Lo único que sé acerca de eso es la consola de Python, pero por lo que puedo decir, no proporciona acceso al entorno de shell estándar. Por ejemplo, no puedo simplemente cd ~y lsluego chmod +x filedentro de la consola de 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 el código Python.

¿Existe tal caparazón?

Robert S Ciaccio
fuente
3
Hay Pash pero es mucho más parecido a Powershell que a Bash.
Ephemient
1
@ephemient tal vez deberías escribir una respuesta para pash ... aunque no sé nada sobre eso, iirc, powershell es un shell OO.
xenoterracida
44
Oye, deberías revisar ipython . Si ingresa una expresión que no tiene sentido como python, intentará asignarla a un comando de shell. Por ejemplo, cosas como cd ~seguidas de lstrabajos como en Bash. También puede asignar resultados a variables de Python (listas de líneas ... más o menos) con comandos como listing = !ls.
intuido el
@intuited: impresionante, lo comprobaré
Robert S Ciaccio
1
@intuited: iPython ha sido bastante bueno para las cosas que quiero hacer, ¡gracias!
Robert S Ciaccio

Respuestas:

43

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:

  • aC para matemáticas simples
  • 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 el correo electrónico básico
  • cron para tareas programadas
  • wmctrl para la manipulación básica de ventanas X
  • dcop para bibliotecas KDE ≤3.x
  • Herramientas dbus ( 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 busca 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 funciona en contra de la mantenibilidad del programa. Los programas interactivos deben ser breves, requieren pocas comillas, no molestarlo con declaraciones o tipeos variables, 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 caparazón es un compromiso difícil de alcanzar. Ok, esto termina la sección de despotricar, a los ejemplos.


  • El Shell Perl (PSH) “combina la naturaleza interactiva de una cáscara de Unix con el poder de Perl”. Se pueden ingresar comandos simples (incluso tuberías) en 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 que consideraría usarlo sobre Perl puro (para secuencias de comandos) o shell puro (interactivamente o para secuencias de comandos).

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

  • irb (ruby interactivo) es el equivalente de Ruby de la consola de Python.

  • 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 varias bibliotecas para control de terminales, expresiones regulares, redes, etc.

  • fish es un comienzo limpio en un caparazón 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, /sysproporciona más control de hardware y sistema.
  • En muchas variantes de Unix, el control del proceso se puede realizar a través del /procsistema de archivos.
  • FUSE 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.

Quizás 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 el tubo de la carcasa) todavía).

Gilles 'SO- deja de ser malvado'
fuente
1
Golpea el clavo en la cabeza justo después de decir "Sospecho que esto es lo que buscas". La razón principal por la que hago esta pregunta es que me encanta tener el poder de las herramientas de Unix, pero la interacción basada en texto entre programas definitivamente 'levanta una barrera para tareas más complejas'. He pasado suficientes días de programación escribiendo analizadores de texto :) Creo que esta es una respuesta muy bien pensada. Llega al corazón del problema y la complejidad del tema. Desearía poder votarlo dos veces: P
Robert S Ciaccio
1
+1 para ipython, aunque no tengo idea de lo que quiere hacer el OP.
Falmarri
1
Esta es una gran respuesta: honestamente creo que las semillas de una tesis doctoral interesante están aquí.
Ziggy
1
@RobertSCiaccio Esta respuesta se acaba de vincular en una publicación reciente, y su comentario sobre los análisis de texto me hizo pensar ... si el análisis de texto es suficiente para realizar sus "tareas complejas", ¿no podría tener un pequeño script o programa que implemente y lo usas como algún tipo de función en tus scripts de bash? Solo un pensamiento, no tengo mucha experiencia en scripting bash en mi haber para hablar.
Oxwivi
1
@onlyanegg ¿De qué manera se podría decir que los peces están "orientados a objetos"? El pescado apunta principalmente a ser más simple. ¿Hay alguna forma en que sea más poderoso que las alternativas?
Gilles 'SO- deja de ser malvado'
13

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

Como las clases se implementan con una matriz, class e inst son realmente sinónimos, algo así como en 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 "$@" "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 "$@"
}

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 "$@"; 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
}

Y para volcar 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 diseño OOP no ha considerado objetos dentro de 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 imprimirlas recursivamente.

Oh! y defino manualmente una clase ROOT para simplificar la función de clase :

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
philcolbourn
fuente
Podría funcionar pero es feo . Y totalmente no bashes para qué sirve. Me recuerda la respuesta de Stephane sobre por qué no usar bucles de shell para procesar texto, particularmente la sección titulada "conceptualmente" que detalla la diferencia de propósito entre lenguajes como C y bash. unix.stackexchange.com/a/169765/135943
Comodín
1
¿Podría funcionar? Funciona, pero su punto es que, aunque no está mal, simplemente no está hecho. Tampoco respondí la pregunta OP, pero si bash es TC, entonces pensé que debería ser capaz de procesar objetos. Y muchos han demostrado esto.
philcolbourn
5

IPython es sorprendentemente conveniente de usar.

Características estándar de la shell: control de trabajos, edición e historial de líneas de lectura, alias cat ls cde pwdintegración de buscapersonas, ejecutar cualquier comando del sistema con un prefijo !o habilitación %rehashx, salida de comando asignable a una variable de Python, valores de Python 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á.

Tobu
fuente
2

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

Abbafei
fuente
2

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 lo muestra 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.

La declaración de importación no es técnicamente necesaria, pero obliga a cargar la clase en el punto dado en lugar de esperar la primera noticia nueva , lo 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 de reflexión básico , y se muestra print / println para reemplazar echo (¿alguna vez intentas 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 solo la incluye y la olvida.

Evan Langlois
fuente
2

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

Trevor Sullivan
fuente
1

Si alguien solo quiere lo básico de la programación orientada a objetos (propiedades y métodos), 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 debería 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!

vandor76
fuente
Es mejor simplificar las cosas complejas que complicar las cosas simples.
Comodín el
1

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 git@github.com: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} | $@{cgrep} # pass an array to command
Alex
fuente
0

Ahora, ¿con qué objetos trabajas en un caparazón la mayor parte del tiempo? Son archivos / directorios, procesos y su interacción. Entonces debería gustar f1.edito algo así 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.

ott--
fuente
0
## 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 en base a 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
Aravamudhan saranyan
fuente
0

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

hipersayanx ha creado un artículo Programación orientada a objetos en Bash . Básicamente alta secuestrada $FUNCNAME, function, compgen, y exportpara crear lo más cercano a la POO 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 anterior a 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 secuencia de comandos OOP serio como perl, ruby ​​y python cuando se adapta mejor a su proyecto. Sin embargo, en mi opción honesta, vale la pena el tiempo y el esfuerzo al mantener scripts bash modulares para utilizar este método de OOP en bash.

Dwight Spencer
fuente
0

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!"
Bruno Negrão Zica
fuente
Parece que has entendido mal cómo funciona la sustitución de comandos. Se reemplaza con la salida del comando contenido dentro de los backticks, no se reemplaza con el estado de retorno . En otras palabras, su código no hace lo que cree que hace.
Comodín el
Ajá. Estás en lo correcto. Mis disculpas. Sin embargo, sería mucho más claro usarlo carp "Some error message"; returnen su lugar.
Comodín
O [ -z "$KEY" ] && { carp "some message"; return;} no es necesario un subshell. Pero en realidad eso parece un candidato real para una if ... elif ... elif ... else ... ficonstrucción, que a menudo no es la mejor opción, pero probablemente lo sea, aquí. :) (O tal vez un cambio de mayúsculas y minúsculas.)
Comodín
@Wildcard Edité la respuesta. Ahora nuestra discusión sobre la carpa y los backticks no tiene sentido. vamos a eliminar esta conversación?
Bruno Negrão Zica
0

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

joshlk
fuente