Shell orientata agli oggetti per * nix


38

Prefazione: amo bash e non ho intenzione di iniziare alcun tipo di argomento o guerra santa, e speriamo che questa non sia una domanda estremamente ingenua.

Questa domanda è in qualche modo correlata a questo post su superutente, ma non credo che l'OP sapesse davvero cosa stava chiedendo. Uso bash su FreeBSD, Linux, OS X e Cygwin su Windows. Di recente ho anche avuto una vasta esperienza con PowerShell su Windows.

Esiste una shell per * nix, già disponibile o disponibile, compatibile con bash ma che aggiunge un mix di script orientati agli oggetti nel mix? L'unica cosa che so che si avvicina è la console Python, ma per quanto ne so non fornisce l'accesso all'ambiente shell standard. Ad esempio, non posso semplicemente cd ~e ls, quindi, chmod +x fileall'interno della console di Python. Dovrei usare Python per eseguire quelle attività piuttosto che i binari unix standard, o chiamare i binari usando il codice Python.

Esiste una shell del genere?


3
C'è Pash ma è molto più simile a Powershell che a Bash.
effimero

1
@ephemient forse dovresti scrivere una risposta per pash ... anche se non so nulla, iirc, powershell è una shell OO.
xenoterracide,

4
Ehi, dovresti dare un'occhiata a ipython . Se inserisci un'espressione che non ha senso come Python, proverà a mapparla su un comando shell. Ad esempio, cose come cd ~seguite da lsopere come in Bash. Puoi anche assegnare l'output alle variabili Python (elenchi di righe.. Sorta di) con comandi simili listing = !ls.
intuito il

@intuited: fantastico, lo controllerò
Robert S Ciaccio il

1
@intuited: iPython è stato abbastanza buono per le cose che voglio fare, grazie!
Robert S Ciaccio,

Risposte:


43

Posso pensare a tre caratteristiche desiderabili in una shell:

  • Usabilità interattiva: i comandi comuni dovrebbero essere veloci da digitare; completamento; ...
  • Programmazione: strutture di dati; concorrenza (lavori, pipe, ...); ...
  • Accesso al sistema: lavorare con file, processi, finestre, database, configurazione del sistema, ...

Le shell Unix tendono a concentrarsi sull'aspetto interattivo e subappaltano la maggior parte dell'accesso al sistema e parte della programmazione su strumenti esterni, come:

  • bc per la matematica semplice
  • openssl per la crittografia
  • sed , awk e altri per l'elaborazione del testo
  • nc per reti TCP / IP di base
  • ftp per FTP
  • mail, Mail, mailx, Ecc per la posta elettronica di base
  • cron per attività programmate
  • wmctrl per la manipolazione di base della finestra X.
  • dcop per librerie KDE ≤3.x.
  • strumenti dbus ( dbus-*o qdbus ) per varie informazioni di sistema e attività di configurazione (inclusi ambienti desktop moderni come KDE ≥4)

Molte, molte cose possono essere fatte invocando un comando con gli argomenti giusti o l'input di piping. Questo è un approccio molto potente - meglio avere uno strumento per compito che lo fa bene, che un singolo programma che fa tutto tranne che male - ma ha i suoi limiti.

Una grande limitazione delle shell unix, e sospetto che questo sia ciò che stai cercando con il tuo requisito di "scripting orientato agli oggetti", è che non sono bravi a conservare le informazioni da un comando all'altro, o combinare i comandi in modi più fantasiosi di una conduttura. In particolare, la comunicazione tra programmi è basata su testo, quindi le applicazioni possono essere combinate solo se serializzano i loro dati in modo compatibile. Questa è sia una benedizione che una maledizione: l'approccio tutto-è-testo semplifica l'esecuzione rapida di compiti semplici, ma alza la barriera per compiti più complessi.

L'usabilità interattiva funziona anche piuttosto contro la manutenibilità del programma. I programmi interattivi dovrebbero essere brevi, richiedere poche citazioni, non disturbarti con dichiarazioni o digitazioni variabili, ecc. I programmi gestibili dovrebbero essere leggibili (quindi non avere molte abbreviazioni), dovrebbero essere leggibili (quindi non devi chiederti se una parola nuda è una stringa, un nome di funzione, un nome di variabile, ecc.), dovrebbe avere controlli di coerenza come dichiarazioni di variabili e digitazione, ecc.

In sintesi, una shell è un compromesso difficile da raggiungere. Ok, questo termina la sezione di rant, sugli esempi.


  • La shell Perl (psh) "combina la natura interattiva di una shell Unix con la potenza di Perl". Comandi semplici (anche pipeline) possono essere inseriti nella sintassi della shell; tutto il resto è Perl. Il progetto non è in sviluppo da molto tempo. È utilizzabile, ma non ha raggiunto il punto in cui considererei di usarlo su puro Perl (per lo scripting) o pure shell (interattivamente o per lo scripting).

  • IPython è una console Python interattiva migliorata, particolarmente mirata al calcolo numerico e parallelo. Questo è un progetto relativamente giovane.

  • irb (ruby interattivo) è l'equivalente Ruby della console Python.

  • scsh è un'implementazione di schema (cioè un linguaggio di programmazione decente) con il tipo di associazioni di sistema che si trovano tradizionalmente nelle shell unix (stringhe, processi, file). Tuttavia, non intende essere utilizzabile come shell interattiva.

  • zsh è una shell interattiva migliorata. Il suo punto di forza è l'interattività (edizione della riga di comando, completamento, attività comuni eseguite con sintassi concisa ma criptica). Le sue funzionalità di programmazione non sono eccezionali (alla pari di Ksh), ma viene fornito con una serie di librerie per il controllo dei terminali, regexps, networking, ecc.

  • il pesce è un inizio pulito in un guscio stile unix. Non ha migliori funzioni di programmazione o di accesso al sistema. Poiché interrompe la compatibilità con sh, ha più spazio per sviluppare funzionalità migliori, ma ciò non è avvenuto.


Addendum: un'altra parte della toolbox di unix tratta molte cose come file:

  • La maggior parte dei dispositivi hardware sono accessibili come file.
  • Sotto Linux, /sys fornisce più controllo hardware e di sistema.
  • Su molte varianti unix, il controllo del processo può essere eseguito tramite /proc filesystem.
  • FUSE semplifica la scrittura di nuovi filesystem. Esistono già filesystem esistenti per convertire i formati di file al volo, accedere ai file tramite vari protocolli di rete, guardare all'interno degli archivi, ecc.

Forse il futuro delle shell unix non è un migliore accesso al sistema attraverso i comandi (e migliori strutture di controllo per combinare i comandi) ma un migliore accesso al sistema attraverso i filesystem (che si combinano in modo leggermente diverso - Non penso che abbiamo capito quali sono i modi di dire chiave (come il tubo shell) sono ancora).


1
Hai colpito l'unghia sulla testa subito dopo aver detto "Ho il sospetto che questo sia ciò che stai cercando". Il motivo principale per cui sto ponendo questa domanda è che adoro avere il potere degli strumenti unix, ma l'interazione testuale tra i programmi "solleva sicuramente una barriera per compiti più complessi". Ho trascorso abbastanza dei miei giorni di programmazione scrivendo parser di testo :) Penso che questa sia una risposta molto ben ponderata. Arriva al cuore della questione e della complessità della materia. Vorrei poterlo votare due volte: P
Robert S Ciaccio,

1
+1 per ipython, anche se non ho idea di cosa voglia fare l'OP.
Falmarri,

1
Questa è un'ottima risposta: onestamente penso che i semi di un'interessante tesi di dottorato siano qui.
Ziggy,

1
@RobertSCiaccio Questa risposta è stata appena collegata in un post recente e il tuo commento sugli analisi del testo mi ha fatto pensare ... se l'analisi del testo è sufficiente per realizzare i tuoi "compiti complessi", allora non potresti avere un piccolo script o programma che implementa e usarlo come una sorta di funzione nei tuoi script bash? Solo un pensiero, non ho molta esperienza di scripting bash di cui parlare.
Oxwivi,

1
@onlyanegg In che modo si può dire che i pesci siano "orientati agli oggetti"? Il pesce mira principalmente ad essere più semplice. C'è un modo in cui è più potente delle alternative?
Gilles 'SO- smetti di essere malvagio' il

13

Non è necessario molto codice bash per implementare classi o oggetti in bash.

Di '100 righe.

Bash ha array associativi che possono essere utilizzati per implementare un semplice sistema a oggetti con ereditarietà, metodi e proprietà.

Quindi, potresti definire una classe come questa:

class Queue N=10 add=q_add remove=q_remove

La creazione di un'istanza di questa coda potrebbe essere eseguita in questo modo:

class Q:Queue N=100

o

inst Q:Queue N=100

Poiché le classi sono implementate con un array, class e inst sono in realtà sinonimi - una sorta di like in javascript.

L'aggiunta di elementi in questa coda potrebbe essere eseguita in questo modo:

$Q add 1 2 aaa bbb "a string"

La rimozione di elementi in una variabile X potrebbe essere eseguita in questo modo:

$Q remove X

E la struttura di dumping di un oggetto potrebbe essere eseguita in questo modo:

$Q dump

Che restituirebbe qualcosa del genere:

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

Le classi vengono create utilizzando una funzione di classe come questa:

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: quando si definisce una nuova classe o istanza, è possibile sovrascrivere qualsiasi valore o funzione del membro.

Le matrici associative di Bash hanno una stranezza che fa sì che questo funzioni perfettamente: $ Q [0]} è identico a $ Q. Ciò significa che possiamo usare il nome dell'array per chiamare una funzione di invio del metodo:

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

Un lato negativo è che non posso usare [0] per i dati, quindi le mie code (in questo caso) iniziano da index = 1. In alternativa avrei potuto usare indici associativi come "q + 0".

Per ottenere e impostare membri potresti fare qualcosa del genere:

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

E scaricare una struttura a oggetti, ho fatto questo:

NOTA: questo non è necessario per OOP in bash, ma è bello vedere come sono fatti gli oggetti.

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

Il mio design OOP non ha considerato gli oggetti all'interno degli oggetti, ad eccezione della classe ereditata. Potresti crearli separatamente o creare un costruttore speciale come class (). * obj_dump * dovrebbe essere modificato per rilevare le classi interne per stamparle in modo ricorsivo.

Oh! e definisco manualmente una classe ROOT per semplificare la funzione di classe :

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

Con alcune funzioni di coda ho definito alcune classi come questa:

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

Creato alcune istanze di coda e le ha fatte funzionare:

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

Potrebbe funzionare ma è brutto . E non bashè assolutamente quello per cui. Mi ricorda la risposta di Stephane sul perché non usare i loop di shell per elaborare il testo, in particolare la sezione intitolata "concettualmente" che dettaglia la differenza di scopo tra linguaggi come C e bash. unix.stackexchange.com/a/169765/135943
Wildcard

1
Potrebbe funzionare? Funziona, ma il punto è che, sebbene non sia sbagliato, semplicemente non è fatto. Inoltre non ho risposto alla domanda OP, ma se bash è TC allora ho pensato che dovrebbe essere in grado di elaborare gli oggetti. E molti lo hanno dimostrato.
philcolbourn,


5

IPython è sorprendentemente comodo da usare.

Funzionalità della shell standard: controllo del lavoro, modifica e cronologia readline, alias cat ls cde pwdintegrazione pager, esecuzione di qualsiasi comando di sistema con prefisso !o%rehashx , output dei comandi assegnabile a una variabile Python, valori Python disponibili come variabili Shell.

Specifico per Python: riutilizzo dei risultati degli ultimi comandi, accesso rapido alla documentazione e alla fonte, ricarica del modulo, debugger. Qualche supporto per i cluster se ti interessa.

Detto questo, l'esecuzione di pipe complesse non viene eseguita in Python; utilizzerai anche la shell posix, solo con un po 'di colla per passare i valori avanti e indietro.



2

jq funziona abbastanza bene come una sorta di livello orientato agli oggetti.


2

Questo è un po 'più semplice da usare e da configurare, ha chiamato args, ecc. Https://github.com/uudruid74/bashTheObjects

Sto aggiornando la mia risposta con un esempio, che segue uno degli esempi di base forniti per un'altra risposta, ma con questa sintassi. Il programma di esempio è simile, ma non è necessario aggiungere il prefisso a tutte le variabili con il nome classe (lo sa come mostra il metodo kindof ) e penso che la sintassi sia molto più semplice!

Innanzitutto, un file di classe. I valori predefiniti per le variabili di istanza sono facoltativi e vengono utilizzati solo se non si passano questi valori al costruttore.

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
}

Ora ad esempio l'utilizzo:

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

GLI APPUNTI:

  1. L'ultima affermazione fallirà. A differenza dell'esempio precedente, la libreria non supporta ancora l'assegnazione di oggetti, ma non sarebbe troppo difficile da aggiungere. Lo inserirò nel mio TO-DO insieme al supporto contenitore / iteratore in arrivo.

L'istruzione import non è tecnicamente necessaria, ma impone il caricamento della classe in un determinato punto invece di attendere il primo nuovo , il che può aiutare a inizializzare le cose nell'ordine corretto. Nota la facilità con cui puoi impostare più variabili di istanza contemporaneamente.

Ci sono anche livelli di debug, costruttori, distruttori, sottoclassi e un sistema di riflessione di base , e lo è mostrato print / println per sostituire l'eco (hai mai provato a stampare una variabile che inizia con un trattino?). L'esempio su github lo mostra in esecuzione come CGI che genera HTML da classi.

La libreria stessa (oop.lib.sh) non è così semplice (400+ righe, 11K), ma la includi e la dimentichi.



1

Se qualcuno vuole solo le basi della programmazione orientata agli oggetti (proprietà e metodi) di un framework davvero semplice farebbe il trucco.

Supponiamo che tu voglia visualizzare il testo "Hello World" usando oggetti. Innanzitutto si crea una classe di oggetti che ha una proprietà per il testo da visualizzare e ha alcuni metodi per impostare questo testo e visualizzarlo. Per mostrare come più istanze di una classe possono lavorare insieme, ho aggiunto due metodi per visualizzare il testo: uno con NewLine alla fine e uno senza quello.

File di definizione della 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"
}

Nota la parola "<<Nome_istanza>>". Questo verrà sostituito in seguito per creare più istanze di un oggetto classe. Prima di poter utilizzare un'istanza di un oggetto è necessaria una funzione che lo crei effettivamente. Per semplificare le cose, sarà uno script separato chiamato: 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"
}

Quindi ora abbiamo un file di definizione di classe e una funzione CreateObject che crea una copia di questo file con il testo "<<NomeIstanza>>" sostituito con qualsiasi nome desideriamo.

Usiamo il nostro nuovo oggetto in uno script chiamato: HelloWorld.sh (si noti che HelloWorld.sh dovrebbe essere eseguibile. Gli altri due file non devono essere)

# 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

Eseguendo lo script HelloWorld.sh viene visualizzato il testo "Hello World" (e aggiunge un NewLine). Nessuno sarà molto colpito da questo risultato, tuttavia sapremo che non è così semplice come sembra :)

Buona programmazione!


È meglio rendere le cose complesse semplici piuttosto che rendere le cose semplici complesse.
Wildcard

1

Questa è una shell orientata agli oggetti basata su Python, ma ha una chiusura della sintassi di Golang: https://github.com/alexst07/shell-plus-plus

Ad esempio, prova a catturare:

try {
  git clone git@github.com:alexst07/shell-plus-plus.git
} catch InvalidCmdException as ex {
  print("git not installed [msg: ", ex, "]")
}

sovraccarico classe e operatore:

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 puoi usare i comandi simili 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

0

Ora, con quali oggetti hai a che fare in una shell per la maggior parte del tempo? Sono file / directory, processi e loro interazione. Quindi dovrebbe piaceref1.edit o qualcosa del genere currentFile=f1.c ; .edit ; .compile ; .run. Or d1.search(filename='*.c' string='int \*'). O p1.stop, p1.bg. Questa è la mia comprensione di un ooshell.


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


}

ho appena provato a introdurre concetti oo per bash in base al riferimento 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

Ci scusiamo per la breve risposta, ma qui va.

hipersayanx ha creato un articolo Programmazione orientata agli oggetti in Bash . Fondamentalmente ha dirottato $FUNCNAME, function, compgen, eexport di creare il più vicino alla OOP si può arrivare in bash.

La parte interessante è che funziona bene e per costruire una classe sono necessarie solo poche linee di caldaia.

Le parti di base necessarie sono:

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

Ora, l'ho usato io stesso nel mio progetto AuditOps e hipersayanx ha ulteriori dettagli su come funziona sul suo sito. L'avvertimento tariffario, sebbene sia molto bashism, non funzionerà con qualcosa di più vecchio di bash 4.0 e può portare a mal di testa durante il debug. Mentre personalmente vorrei vedere la maggior parte della placcatura della caldaia rifatta come una classe stessa.

È sempre più saggio usare un serio linguaggio di scripting OOP come perl, ruby ​​e python quando si adatta meglio al tuo progetto. Tuttavia, nella mia onesta opzione, vale la pena dedicare tempo e fatica a mantenere script bash modulari per utilizzare questo metodo di OOP in bash.


0

Sto sviluppando su GitHub una funzione che funziona proprio come un oggetto HashMap , shell_map .

Per creare " istanze di HashMap " questa funzione è in grado di creare copie di se stesso con nomi diversi. Ogni nuova copia della funzione avrà una variabile $ FUNCNAME diversa. $ FUNCNAME viene quindi utilizzato per creare uno spazio dei nomi per ogni istanza di Map.

Le chiavi della mappa sono variabili globali, nella forma $ FUNCNAME_DATA_ $ KEY, dove $ KEY è la chiave aggiunta alla mappa. Queste variabili sono variabili dinamiche .

Di seguito ne fornirò una versione semplificata in modo da poterla usare come esempio.

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

Sembra che tu abbia frainteso il funzionamento della sostituzione dei comandi. Viene sostituito con l' output del comando contenuto nei backtick, non sostituito con lo stato di ritorno . In altre parole, il tuo codice non fa quello che pensi che faccia.
Wildcard

Aha. Hai ragione. Mie scuse. Tuttavia, sarebbe molto più chiaro da usare carp "Some error message"; returninvece.
Wildcard l'

Oppure [ -z "$KEY" ] && { carp "some message"; return;} Non è necessario un subshell. Ma in realtà sembra un vero candidato per un if ... elif ... elif ... else ... ficostrutto, che spesso non è la scelta migliore, ma probabilmente lo è, qui. :) (O forse un cambio di case.)
Wildcard l'

@Wildcard Ho modificato la risposta. Ora la nostra discussione su carpe e bastoncini non ha senso. cancelliamo questa conversazione?
Bruno Negrão Zica,

0

Plumbum è un linguaggio shell simile a Python. Comprime shell come sintassi con Python rendendo l'esperienza orientata agli oggetti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.