Objektorientierte Shell für * nix


38

Vorwort: Ich liebe Bash und habe nicht die Absicht, irgendwelche Auseinandersetzungen oder einen heiligen Krieg zu beginnen, und hoffentlich ist dies keine extrem naive Frage.

Diese Frage hat etwas mit diesem Beitrag zum Superuser zu tun, aber ich glaube nicht, dass der OP wirklich wusste, wonach er gefragt hat. Ich benutze Bash unter FreeBSD, Linux, OS X und Cygwin unter Windows. Ich habe in letzter Zeit auch umfangreiche Erfahrungen mit PowerShell unter Windows gesammelt.

Gibt es eine Shell für * nix, die bereits verfügbar ist oder in Arbeit ist, die mit bash kompatibel ist, der Mischung jedoch eine Ebene objektorientierten Skripts hinzufügt? Das einzige, von dem ich weiß, dass es nahe kommt, ist die Python-Konsole, aber soweit ich das beurteilen kann, bietet sie keinen Zugriff auf die Standard-Shell-Umgebung. Zum Beispiel kann ich nicht einfach cd ~und lsdann chmod +x filein der Python-Konsole. Ich müsste Python verwenden, um diese Aufgaben anstatt der Standard-Unix-Binärdateien auszuführen, oder die Binärdateien mit Python-Code aufrufen.

Existiert eine solche Hülle?


3
Es gibt Pash, aber das ist viel mehr Powershell-like als Bash-like.
ephemient

1
@ephemient vielleicht solltest du eine Antwort für pash schreiben ... obwohl ich nichts darüber weiß, iirc, ist Powershell eine OO-Shell.
Xenoterracide

4
Hey, du solltest ipython ausprobieren . Wenn Sie einen Ausdruck eingeben, der als Python nicht sinnvoll ist, wird versucht, ihn einem Shell-Befehl zuzuordnen. Zum Beispiel, cd ~gefolgt von lsArbeiten wie in Bash. Sie können die Ausgabe auch Python-Variablen (Listen von Zeilen ...) mit Befehlen wie zuweisen listing = !ls.
Intuited

@intuited: Super, ich werde es überprüfen
Robert S Ciaccio

1
@intuited: iPython war ziemlich gut für die Sachen, die ich machen möchte, danke!
Robert S Ciaccio

Antworten:


43

Ich kann mir drei wünschenswerte Funktionen in einer Shell vorstellen:

  • Interaktive Bedienbarkeit: Allgemeine Befehle sollten schnell eingegeben werden können. Fertigstellung; ...
  • Programmierung: Datenstrukturen; Parallelität (Jobs, Pipe, ...); ...
  • Systemzugriff: Arbeiten mit Dateien, Prozessen, Fenstern, Datenbanken, Systemkonfiguration, ...

Unix-Shells konzentrieren sich in der Regel auf den interaktiven Aspekt und vergeben den größten Teil des Systemzugriffs und einen Teil der Programmierung an externe Tools, wie z.

  • bc für einfache mathematik
  • openssl für die Kryptographie
  • sed , awk und andere für die Textverarbeitung
  • nc für grundlegende TCP / IP-Netzwerke
  • ftp für FTP
  • mail, Mail, mailxEtc. für grundlegende E-Mail
  • cron für geplante Aufgaben
  • wmctrl für die grundlegende Bearbeitung von X-Fenstern
  • dcop für KDE ≤3.x-Bibliotheken
  • dbus tools ( dbus-*oder qdbus ) für verschiedene Systeminformationen und Konfigurationsaufgaben (einschließlich moderner Desktop-Umgebungen wie KDE ≥4)

Viele, viele Dinge können durch Aufrufen eines Befehls mit den richtigen Argumenten oder über eine Pipeline eingegeben werden. Dies ist ein sehr leistungsfähiger Ansatz - es ist besser, ein Tool pro Task zu haben, das dies gut macht, als ein einzelnes Programm, das alles andere als schlecht macht - aber es hat seine Grenzen.

Eine wesentliche Einschränkung von Unix-Shells, und ich vermute, das ist, was Sie mit Ihrer "objektorientierten Skripterstellung" fordern, dass sie nicht gut darin sind, Informationen von einem Befehl zum nächsten zu speichern oder Befehle in einer Weise zu kombinieren, die ausgefallener ist als eine Pipeline. Insbesondere die programmübergreifende Kommunikation basiert auf Text, sodass Anwendungen nur kombiniert werden können, wenn sie ihre Daten auf kompatible Weise serialisieren. Dies ist sowohl ein Segen als auch ein Fluch: Der Alles-ist-Text-Ansatz erleichtert das schnelle Ausführen einfacher Aufgaben, erhöht jedoch die Barriere für komplexere Aufgaben.

Interaktive Bedienbarkeit steht auch eher der Programmwartbarkeit entgegen. Interaktive Programme sollten kurz sein, wenig Anführungszeichen erfordern, Sie nicht mit Variablendeklarationen oder Eingaben belästigen usw. Wartungsfähige Programme sollten lesbar sein (also nicht viele Abkürzungen haben), sollten lesbar sein (damit Sie sich nicht wundern müssen, ob ein bloßes Wort vorkommt) Ist eine Zeichenfolge, ein Funktionsname, ein Variablenname usw.), sollten Konsistenzprüfungen wie Variablendeklarationen und Typisierung usw. durchgeführt werden.

Zusammenfassend ist eine Shell ein schwer zu erreichender Kompromiss. Ok, hiermit ist der Randalierungsabschnitt zu den Beispielen beendet.


  • Die Perl-Shell (psh) kombiniert die Interaktivität einer Unix-Shell mit der Leistungsfähigkeit von Perl. Einfache Befehle (auch Pipelines) können in der Shell-Syntax eingegeben werden. alles andere ist Perl. Das Projekt ist schon lange nicht mehr in der Entwicklung. Es ist verwendbar, hat aber noch nicht den Punkt erreicht, an dem ich erwägen würde, es über reines Perl (für Skripte) oder reine Shell (interaktiv oder für Skripte) zu verwenden.

  • IPython ist eine verbesserte interaktive Python-Konsole, die speziell für numerisches und paralleles Computing entwickelt wurde. Dies ist ein relativ junges Projekt.

  • irb (Interactive Ruby) ist das Ruby-Äquivalent der Python-Konsole.

  • scsh ist eine Schema-Implementierung (dh eine anständige Programmiersprache) mit der Art von Systembindungen, die traditionell in Unix-Shells (Strings, Prozesse, Dateien) zu finden sind. Es ist jedoch nicht das Ziel, als interaktive Shell verwendet zu werden.

  • zsh ist eine verbesserte interaktive Shell. Seine Stärke ist die Interaktivität (Kommandozeilen-Edition, Vervollständigung, allgemeine Aufgaben, die mit knapper, aber kryptischer Syntax ausgeführt werden). Die Programmierfunktionen sind nicht besonders gut (vergleichbar mit ksh), es gibt jedoch eine Reihe von Bibliotheken für die Terminalsteuerung, reguläre Ausdrücke, Netzwerke usw.

  • Fisch ist ein sauberer Start in eine Unix-Shell. Es gibt keine besseren Programmier- oder Systemzugriffsfunktionen. Weil es die Kompatibilität mit sh bricht, hat es mehr Raum, um bessere Funktionen zu entwickeln, aber das ist nicht geschehen.


Nachtrag: Ein weiterer Teil der Unix-Toolbox behandelt viele Dinge als Dateien:

  • Auf die meisten Hardwaregeräte kann als Datei zugegriffen werden.
  • /sysBietet unter Linux mehr Hardware- und Systemsteuerung.
  • Bei vielen Unix-Varianten kann die Prozesssteuerung über das /procDateisystem erfolgen.
  • FUSE erleichtert das Schreiben neuer Dateisysteme. Es gibt bereits Dateisysteme, um Dateiformate im laufenden Betrieb zu konvertieren, über verschiedene Netzwerkprotokolle auf Dateien zuzugreifen, Archive zu durchsuchen usw.

Vielleicht ist die Zukunft von Unix-Shells nicht ein besserer Systemzugriff über Befehle (und bessere Kontrollstrukturen zum Kombinieren von Befehlen), sondern ein besserer Systemzugriff über Dateisysteme (die sich etwas anders kombinieren - ich glaube nicht, dass wir herausgefunden haben, wie die wichtigsten Redewendungen lauten (wie) das Mantelrohr) sind noch).


1
Sie haben den Nagel auf den Kopf getroffen, gleich nachdem Sie gesagt haben "Ich vermute, das ist, wonach Sie suchen". Der Hauptgrund, warum ich diese Frage stelle, ist, dass ich die Leistungsfähigkeit von Unix-Werkzeugen liebe, aber die textbasierte Interaktion zwischen Programmen auf jeden Fall ein Hindernis für komplexere Aufgaben aufwirft. Ich habe genug von meinen Programmiertagen damit verbracht, Textparser zu schreiben :) Ich denke, das ist eine sehr gut durchdachte Antwort. Es geht um den Kern des Problems und die Komplexität des Themas. Ich wünschte, ich könnte es zweimal verbessern: P
Robert S Ciaccio

1
+1 für Ipython, obwohl ich keine Ahnung habe, was das OP tun will.
Falmarri

1
Das ist eine großartige Antwort: Ich denke ehrlich, dass die Keime einer interessanten Doktorarbeit hier sind.
Ziggy

1
@RobertSCiaccio Diese Antwort wurde gerade in einem kürzlich veröffentlichten Beitrag verlinkt, und Ihr Kommentar zu Textparsing brachte mich zum Nachdenken ... Wenn Textparsing ausreicht, um Ihre "komplexen Aufgaben" zu lösen, dann könnten Sie kein kleines Skript oder Programm haben, das implementiert es und verwenden Sie es als eine Art Funktion in Ihren Bash-Skripten? Nur ein Gedanke, ich habe nicht viel Erfahrung mit Bash-Skripten.
Oxwivi

1
@onlyanegg Inwiefern kann Fisch als objektorientiert bezeichnet werden? Fisch zielt in erster Linie darauf ab, einfacher zu sein. Gibt es eine Art und Weise, in der es mächtiger ist als die Alternativen?
Gilles 'SO- hör auf böse zu sein'

13

Sie benötigen nicht viel Bash-Code, um Klassen oder Objekte in Bash zu implementieren.

Sagen wir 100 Zeilen.

Bash verfügt über assoziative Arrays, mit denen ein einfaches Objektsystem mit Vererbung, Methoden und Eigenschaften implementiert werden kann.

Sie könnten also eine Klasse wie diese definieren:

class Queue N=10 add=q_add remove=q_remove

Das Erstellen einer Instanz dieser Warteschlange kann folgendermaßen erfolgen:

class Q:Queue N=100

oder

inst Q:Queue N=100

Da Klassen mit einem Array implementiert werden, sind class und inst wirklich Synonyme - ähnlich wie in Javascript.

Das Hinzufügen von Elementen zu dieser Warteschlange könnte folgendermaßen erfolgen:

$Q add 1 2 aaa bbb "a string"

Das Entfernen von Elementen in eine Variable X kann folgendermaßen erfolgen:

$Q remove X

Die Dump-Struktur eines Objekts könnte folgendermaßen aussehen:

$Q dump

Welches würde so etwas zurückgeben:

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

Klassen werden mit einer Klassenfunktion wie der folgenden erstellt:

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

ANMERKUNG: Wenn Sie eine neue Klasse oder Instanz definieren, können Sie jeden Mitgliedswert oder jede Funktion überschreiben.

Assoziative Bash-Arrays haben eine Besonderheit, die diese Funktion sehr übersichtlich macht: $ Q [0]} ist identisch mit $ Q. Dies bedeutet, dass wir den Array-Namen verwenden können, um eine Methode-Dispatch-Funktion aufzurufen:

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

Ein Nachteil ist, dass ich [0] nicht für Daten verwenden kann, sodass meine Warteschlangen (in diesem Fall) bei index = 1 beginnen. Alternativ hätte ich assoziative Indizes wie "q + 0" verwenden können.

Um Mitglieder zu bekommen und zu setzen , könnten Sie Folgendes tun:

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

Und Dump eine Objektstruktur, habe ich dies:

HINWEIS: Dies ist für OOP in bash nicht erforderlich, aber es ist schön zu sehen, wie Objekte erstellt werden.

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

In meinem OOP-Design wurden keine Objekte innerhalb von Objekten berücksichtigt - mit Ausnahme der geerbten Klasse. Sie können sie separat erstellen oder einen speziellen Konstruktor wie class () erstellen. * obj_dump * müsste geändert werden, um interne Klassen zu erkennen und diese rekursiv zu drucken.

Oh! und ich definiere manuell eine ROOT-Klasse, um die Klassenfunktion zu vereinfachen :

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

Mit ein paar Queue-Funktionen habe ich einige Klassen wie folgt definiert:

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

Einige Warteschlangeninstanzen erstellt und funktionsfähig gemacht:

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

Könnte funktionieren, aber es ist hässlich . Und ganz und gar nicht wofür bash. Erinnert mich an Stephanes Antwort, warum man keine Shell-Schleifen zum Verarbeiten von Text verwendet, insbesondere den Abschnitt "Konzeptuell", in dem der Zweckunterschied zwischen Sprachen wie C und bash. unix.stackexchange.com/a/169765/135943
Wildcard

1
Könnte funktionieren? Es funktioniert, aber Ihr Punkt ist, dass, obwohl nicht falsch, es einfach nicht getan wird. Ich habe auch keine OP-Frage beantwortet, aber wenn bash TC ist, sollte es Objekte verarbeiten können. Und viele haben das bewiesen.
Philcolbourn


5

IPython ist überraschend bequem zu bedienen.

Standard-Shell-Funktionen: Jobsteuerung, Readline-Bearbeitung und -Verlauf, Aliase cat ls cdund pwdPager-Integration, Ausführen eines Systembefehls durch Präfixieren !oder Aktivieren einer %rehashxBefehlsausgabe, die einer Python-Variablen zugewiesen werden kann, Python-Werte, die als Shell-Variablen verfügbar sind.

Python-spezifisch: Wiederverwendung der Ergebnisse der letzten Befehle, schneller Zugriff auf Dokumentation und Quelle, erneutes Laden von Modulen, Debugger. Einige Cluster-Unterstützung, wenn Sie in das sind.

Das heißt, das Ausführen komplexer Pipes wird in Python nicht ausgeführt. Sie werden auch die Posix-Shell verwenden, nur mit etwas Kleber, um Werte hin und her zu übertragen.



2

jq funktioniert ganz gut als so eine art objektorientierte schicht.


2

Dieser ist etwas einfacher zu bedienen und einzurichten, hat den Namen args usw. https://github.com/uudruid74/bashTheObjects

Ich aktualisiere meine Antwort mit einem Beispiel, das einem der grundlegenden Beispiele für eine andere Antwort folgt, aber mit dieser Syntax. Das Beispielprogramm ist ähnlich, aber Sie müssen nicht allen Variablen den Klassennamen voranstellen (dies ist bekannt, wie die kindof- Methode zeigt), und ich denke, die Syntax ist viel einfacher!

Zuerst eine Klassendatei. Die Standardwerte für die Instanzvariablen sind optional und werden nur verwendet, wenn Sie diese Werte nicht an den Konstruktor übergeben.

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
}

Nun zum Beispiel Verwendung:

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

ANMERKUNGEN:

  1. Diese letzte Behauptung wird scheitern. Anders als im obigen Beispiel unterstützt die Bibliothek die Objektzuweisung noch nicht, aber das wäre nicht allzu schwer hinzuzufügen. Ich werde es zusammen mit dem bevorstehenden Container- / Iterator-Support auf meinem TO-DO platzieren.

Die import-Anweisung ist technisch nicht erforderlich, erzwingt jedoch das Laden der Klasse zum angegebenen Zeitpunkt, anstatt auf die erste neue zu warten. Dies kann dazu beitragen, die Dinge in der richtigen Reihenfolge zu initialisieren. Beachten Sie, wie einfach es ist, mehrere Instanzvariablen gleichzeitig festzulegen.

Es gibt auch Debug-Ebenen, Konstruktoren, Destruktoren, Unterklassen und ein grundlegendes Reflektionssystem , und es wird print / println angezeigt , um das Echo zu ersetzen (versuchen Sie jemals, eine Variable zu drucken, die mit einem Bindestrich beginnt?). Das Beispiel auf github zeigt, wie es als CGI ausgeführt wird, das HTML aus Klassen generiert.

Die Bibliothek selbst (oop.lib.sh) ist nicht so einfach (mehr als 400 Zeilen, 11 KB), aber Sie schließen sie einfach ein und vergessen sie.



1

Wenn jemand nur die Grundlagen der objektorientierten Programmierung (Eigenschaften und Methoden) möchte, reicht ein wirklich einfaches Framework aus.

Angenommen, Sie möchten den Text "Hello World" mithilfe von Objekten anzeigen. Zuerst erstellen Sie eine Objektklasse, die eine Eigenschaft für den anzuzeigenden Text und einige Methoden zum Festlegen und Anzeigen dieses Texts enthält. Um zu zeigen, wie mehrere Instanzen einer Klasse zusammenarbeiten können, habe ich zwei Methoden zum Anzeigen des Texts hinzugefügt: eine mit NewLine am Ende und eine ohne NewLine.

Klassendefinitionsdatei : 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"
}

Bitte beachten Sie das Wort "<<Instanzname>>". Dies wird später ersetzt, um mehrere Instanzen eines Klassenobjekts zu erstellen. Bevor Sie eine Instanz eines Objekts verwenden können, benötigen Sie eine Funktion, die es tatsächlich erstellt. Zur Vereinfachung wird es ein separates Skript mit dem Namen ObjectFramework.lib geben

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

Jetzt haben wir eine Klassendefinitionsdatei und eine CreateObject-Funktion, die eine Kopie dieser Datei erstellt, wobei der Text "<<InstanceName>>" durch einen beliebigen Namen ersetzt wird.

Lassen Sie uns unser neues Objekt in einem Skript namens HelloWorld.sh verwenden (bitte beachten Sie, dass HelloWorld.sh ausführbar sein sollte. Die anderen beiden Dateien müssen nicht).

# 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

Durch Ausführen des Skripts HelloWorld.sh wird der Text "Hello World" angezeigt (und eine neue Zeile hinzugefügt). Niemand wird von diesem Ergebnis beeindruckt sein, aber wir werden wissen, dass dies nicht so einfach ist, wie es aussieht :)

Viel Spaß beim Codieren!


Es ist besser, komplexe Dinge einfach zu machen, als einfache Dinge komplex zu machen.
Wildcard

1

Dies ist eine objektorientierte Shell, die auf Python basiert, aber eine ähnliche Sintaxe wie Golang hat: https://github.com/alexst07/shell-plus-plus

Versuchen Sie zum Beispiel, catch:

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

Klassen- und Operatorüberladung:

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)

und Sie können die Befehle ähnlich Bash verwenden:

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

Mit welchen Objekten beschäftigen Sie sich die meiste Zeit in einer Shell? Es sind Dateien / Verzeichnisse, Prozesse und deren Interaktion. So sollte es gefallen f1.editoder so ähnlich currentFile=f1.c ; .edit ; .compile ; .run. Oder d1.search(filename='*.c' string='int \*'). Oder p1.stop, p1.bg. So verstehe ich eine Muschel.


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


}

Ich habe gerade versucht, oo-Konzepte basierend auf der Referenz http://hipersayanx.blogspot.in/2012/12/object-oriented-programming-in-bash.html in bash einzuführen

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

Sorry für die kurze Antwort aber hier geht es weiter.

hipersayanx hat einen Artikel Objektorientierte Programmierung in Bash erstellt . Grundsätzlich er hallo-aufgebockt $FUNCNAME, function, compgen, und exportso nah zu schaffen , um OOP ein in bash zu bekommen.

Das coole daran ist, dass es gut funktioniert und man nur ein paar Zeilen Boilerplating braucht, um eine Klasse aufzubauen.

Grundlegende Teile benötigt werden:

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

Verwendung:

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

$instanceName_method

Jetzt habe ich dies selbst in meinem AuditOps-Projekt verwendet und hipersayanx hat weitere Details darüber, wie dies auf seiner Website tatsächlich funktioniert. Tarifwarnungen, obwohl dies sehr bashistisch ist, funktionieren nicht mit älteren Versionen als Bash 4.0 und können zu Kopfschmerzen beim Debuggen führen. Während ich persönlich möchte, dass die meisten Kesselbeschichtungen als Klasse selbst erneuert werden.

Es ist immer klüger, eine seriöse OOP-Skriptsprache wie Perl, Ruby und Python zu verwenden, wenn dies für Ihr Projekt besser geeignet ist. Meiner Meinung nach ist es jedoch die Zeit und Mühe wert, wenn modulare Bash-Skripte verwaltet werden, um diese OOP-Methode in Bash zu verwenden.


0

Ich entwickle auf GitHub eine Funktion, die genauso funktioniert wie ein HashMap-Objekt , shell_map .

Um " HashMap-Instanzen " zu erstellen, kann diese Funktion Kopien von sich selbst unter verschiedenen Namen erstellen. Jede neue Funktionskopie hat eine andere Variable $ FUNCNAME. Mit $ FUNCNAME wird dann für jede Map-Instanz ein Namespace erstellt.

Die Map-Schlüssel sind globale Variablen im Format $ FUNCNAME_DATA_ $ KEY, wobei $ KEY der der Map hinzugefügte Schlüssel ist. Diese Variablen sind dynamische Variablen .

Unten werde ich eine vereinfachte Version davon einfügen, damit Sie sie als Beispiel verwenden können.

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

Verwendung:

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

Sie scheinen falsch verstanden zu haben, wie die Befehlsersetzung funktioniert. Es wird durch die Ausgabe des Befehls ersetzt, der in Backticks enthalten ist, und nicht durch den Rückgabestatus . Mit anderen Worten, Ihr Code entspricht nicht Ihren Vorstellungen.
Wildcard

Aha. Du hast Recht. Entschuldigen Sie. Es wäre jedoch viel klarer, carp "Some error message"; returnstattdessen zu verwenden .
Wildcard

Oder [ -z "$KEY" ] && { carp "some message"; return;} Keine Notwendigkeit für eine Unterschale. Aber tatsächlich sieht das wie ein tatsächlicher Kandidat für ein if ... elif ... elif ... else ... fiKonstrukt aus - was hier nicht oft die beste Wahl ist, aber wahrscheinlich ist. :) (Oder vielleicht ein Fall wechseln.)
Wildcard

@Wildcard Ich habe die Antwort bearbeitet. Jetzt macht unsere Diskussion über Karpfen und Backticks keinen Sinn. lass uns diese Konversation löschen
Bruno Negrão Zica

0

Plumbum ist eine Python-ähnliche Shell-Sprache. Es enthält eine Shell-ähnliche Syntax mit Python, die die Erfahrung objektorientiert macht.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.