Eigene Befehle
Die Fähigkeit von Rsh, lange Pipelines zu verarbeiten, erlauben es große Kontrolle über Daten und das System zu haben. Das Ganze kommt allerdings zum Preis von viel Tipparbeit. Idealerweise sollte es eine Möglichkeit geben, mühsam gebaute Pipelines zu speichern und wieder und wieder auszuführen.
Hier kommen eigene Befehle ins Spiel.
Eine beispielhafte Definition eines eigenen Befehls sieht wie folgt aus:
def greet [name] {
echo "hello" $name
}
In dieser Definition, wird ein Befehl
greet
beschrieben, der einen Parameter
name
konsumiert. Nach diesem Parameter erfolgt die
Beschreibung was passiert, wenn der Befehl ausgeführt wird. Wenn
der Befehl aufgerufen wird, wird der Wert, der als Parameter
name
übergeben wurde, in die Variable
$name
geschrieben, die im Codeblock verfügbar ist.
Um den obigen Befehl auszuführen wird er wie ein eingebauter Befehl aufgerufen:
> greet "world"
Wenn das getan wird, wird eine Ausgabe erzeugt, die wie die der eingebauten Befehle aussieht:
───┬───────
0 │ hello
1 │ world
───┴───────
Namen von Befehlen
In rsh ist ein valider Name eines Befehls ein String aus Zeichen
oder ein String in Anführungszeichen. Beispiele hierfür sind:
greet
, get-size
,
mycommand123
, "mycommand"
,
😊
und 123
.
Hinweis: Es wird empfohlen Worte in Befehlen mit
-
zur besseren Lesbarkeit zu trennen.
Beispiele: get-size
anstatt
getsize
oder get_size
.
Unterbefehle
Es ist auch möglich Unterbefehle zu definieren. Dazu wird der
Unterbefehl vom Superbefehl durch ein Leerzeichen getrennt. Wenn
beispielsweise der Befehl str
durch einen
Unterbefehl mycommand
erweitert werden soll,
funktioniert das wie folgt:
def "str mycommand" [] {
echo hello
}
Jetzt kann der eigene Unterbefehl aufgerufen werden, als ob er
ein eingebauter Befehl von str
wäre:
> str mycommand
Typen von Parametern
Wenn eigene Befehle definiert werden, kann optional auch der Typ jedes Parameters angegeben werden. Das obige Beispiel kann beispielsweise wie folgt abgeändert werden:
def greet [name: string] {
echo "hello" $name
}
Die Typen der Parameter anzugeben ist optional. rsh erlaubt es
diese wegzulassen und behandelt diese dann als Typ
any
. Es kann also jede Art von Typ verarbeitet
werden. Wenn ein Typ angegeben wurde, überprüft rsh den Typ,
wenn die Funktion aufgerufen wird.
Beispielhaft soll nur noch ein int
als Typ erlaubt
sein:
def greet [name: int] {
echo "hello" $name
}
greet world
Wenn versucht wird, den oberen Code auszuführen, wird Rsh darauf aufmerksam machen, dass die Typen nicht passen und die Ausführung stoppen:
error: Type Error
┌─ shell:6:7
│
5 │ greet world
│ ^^^^^ Expected int, found world
Dies kann dabei helfen Nutzer darauf aufmerksam zu machen, welche Art von Typ erlaubt ist.
Die aktuell erlaubten Typen sind (mit Version 0.65.0 und neuer):
any
block
cell-path
duration
path
expr
filesize
glob
int
math
number
operator
range
cond
bool
signature
string
variable
record
list
table
error
Flags
Zusätzlich zu den obigen Parametern, können auch namenabhängige Parameter verwendet werden, indem Flags für eigene Befehle definiert werden.
Zum Beispiel:
def greet [
name: string
--age: int
] {
echo $name $age
}
In der obigen Definition von greet
, werden ein
fester Parameter name
und eine Flag
age
definiert. Damit ist es möglich, dem Befehl
greet
optional den Parameter age
zu
übergeben.
Das obige Beispiel kann wie folgt aufgerufen werden:
> greet world --age 10
Oder:
> greet --age 10 world
Oder gleich ganz ohne Flag:
> greet world
Flags können auch so definiert werden, dass es eine Kurzform gibt. Das erlaubt es sowohl eine kurze als auch eine einfach lesbare lange Flag für die selbe Aufgabe zu haben.
Das Beispiel wird hier, um eine Kurzform für die Flag
age
erweitert:
def greet [
name: string
--age (-a): int
] {
echo $name $age
}
Hinweis: Flags sind benannt nach der langen Form des
Namens. Im obigen Beispiel erfolgt der Zugriff immer über
$age
und nicht über $a
.
Nun kann diese neue Version von greet
wie folgt
aufgerufen werden:
> greet -a 10 hello
Dokumentation für den eigenen Befehl
Um Nutzern eines eigenen Befehls zu helfen, können diese und ihre Parameter mit zusätzlichen Beschreibungen versehen werden.
Es wird weiterhin das obige Beispiel verwendet:
def greet [
name: string
--age (-a): int
] {
echo $name $age
}
Wenn der Befehl definiert ist kann
help greet
aufgerufen werden, um Informationen zum
Befehl zu erhalten:
Usage:
> greet <name> {flags}
Parameters:
<name>
Flags:
-h, --help: Display this help message
-a, --age <integer>
Wie zu sehen ist, werden der Parameter und die Flag, die
definiert wurden, aufgelistet. Zusätzlich gibt es noch die Flag
-h
, die jeder Befehl hat.
Um diese Hilfe zu verbessern, können Beschreibungen zur Definition hinzugefügt werden:
# A greeting command that can greet the caller
def greet [
name: string # The name of the person to greet
--age (-a): int # The age of the person
] {
echo $name $age
}
Diese Kommentare, die zur Definition und den Parametern hinzugefügt wurden, werden sichtbar, wenn die Hilfe zum Befehl aufgerufen wird.
Wenn jetzt help greet
ausgeführt wird, wird ein
hilfreicherer Text angezeigt:
A greeting command that can greet the caller
Usage:
> greet <name> {flags}
Parameters:
<name> The name of the person to greet
Flags:
-h, --help: Display this help message
-a, --age <integer>: The age of the person
Ausgabe
Eigene Befehle streamen ihre Ausgabe gleich wie eingebaute Befehle. Beispielsweise soll die folgende Pipeline umgebaut werden:
> ls | get name
ls
soll jetzt in einen neuen, eigenen Befehl
verschoben werden:
def my-ls [] { ls }
Die Ausgabe dieses Befehls, kann identisch zur Ausgabe von
ls
verwendet werden.
> my-ls | get name
───┬───────────────────────
0 │ myscript.rsh
1 │ myscript2.rsh
2 │ welcome_to_rsh.md
───┴───────────────────────
Das erlaubt es sehr einfach eigene Befehle zu definieren und deren Ausgabe zu verwenden. Ein Hinweis: Es werden keine return Statements wie in anderen Sprachen verwendet. Stattdessen werden in rsh Pipelines gebaut, die ihre Ausgabe zur verbundenen Pipeline streamen.
Eingabe
Eigene Befehle können, wie andere Befehle, auch Eingaben verarbeiten. Diese Eingabe wird durch die Pipeline an den Codeblock des eigenen Befehls übergeben.
Hier soll nun beispielhaft ein eigener echo-Befehl definiert werden, der eine weitere Zeile nach jeder Zeile der Eingabe ausgibt:
def my-echo [] {
each {
echo $it "--"
}
}
Wenn dieser neue Befehl nun in einer Pipeline aufgerufen wird, sieht die Ausgabe wie folgt aus:
> echo foo bar | my-echo
───┬─────
0 │ foo
1 │ --
2 │ bar
3 │ --
───┴─────
Persistenz
Um Informationen darüber zu erhalten, wie eigene Befehle bei jedem Start von rsh verfügbar bleiben, sei auf das Konfigurationskapitel verwiesen.