Verarbeiten Sie die `zsh`-Funktion mit einem Dienstprogramm, das nur Befehle erkennt

Verarbeiten Sie die `zsh`-Funktion mit einem Dienstprogramm, das nur Befehle erkennt

(Diese Frage verwendet das Mac- caffeinateTool als Beispiel, aber das Konzept gilt für alle Tools (d. h. xargs), die ein Dienstprogramm als Argument akzeptieren.)

Das Tool von Mac caffeinateakzeptiert den Namen eines Dienstprogramms: caffeinate sleep 1, zum Beispiel (wo sleepist das Dienstprogramm). Gibt es eine Möglichkeit, dass es zshauch eine Funktion akzeptiert, ohne das Tool selbst zu ändern? Zum Beispiel:

function mysleep {
  sleep 2
}

caffeinate mysleep    # mysleep: No such file or directory

export -fBearbeiten: Diese Frage ist tatsächlich ein Duplikat für Bash – danke, dass Sie mich auf die Antwort hingewiesen haben. Für zsh funktioniert es jedoch nicht. Gibt es eine Möglichkeit, dies in zsh zu tun? (Ich entferne das bashTag, um Verwirrung zu vermeiden.)

Antwort1

caffeinateerwartet die Ausführung eines Befehls in einem neuen Prozess.

Um eine zshFunktion zu interpretieren, benötigen Sie einen zshBefehl.

Und Sie müssen ihr die Definition dieser Funktion (sowie anderer Funktionen, die sie möglicherweise benötigt) übergeben, beispielsweise mit:

mysleep() {
  sleep 2
}
caffeinate zsh -c "$(functions mysleep);mysleep"

functions mysleepgibt die Definition der mysleepFunktion aus, die wir zshvor dem Aufruf der Funktion zur Interpretation an das neue übergeben, sodass „ zshinvoved by“ am caffeinateEnde Folgendes interpretiert:

mysleep() {
  sleep 2
};mysleep

Wenn wir mit vergleichen bash:

mysleep() {
  sleep 2
}
export -f mysleep
caffeinate bash -c "mysleep"

(das ist 2 Zeichen kürzer zum Eintippen) bashreicht aus:

execve("/path/to/caffeinate",
  ["caffeinate", "bash", "-c", "mysleep"],
  ["BASH_FUNC_mysleep%%=() {  sleep 2\n}", rest-of-environment])

Während zshwir mit erhalten:

execve("/path/to/caffeinate",
  ["caffeinate", "zsh", "-c", "mysleep () {\n\tsleep 2\n};mysleep"],
  [rest-of-environment])

Ich sehe mehrere Vorteile dieses letzteren Ansatzes:

  • wir haben die volle Kontrolle: wir wissen, wie wir die Funktionsdefinition weitergeben und wie sie verwendet wird. Hier gibt es weniger Spielraum für unangenehme Dinge wie die Art von Shellshock.
  • Da der Name der Bash-Umgebungsvariablen, die die Funktionsdefinition enthält, %Zeichen enthält (und selbst wenn dies nicht der Fall wäre, denken Sie sudobeispielsweise an), können wir nicht garantieren, dass caffeinatesie an den Befehl weitergegeben wird, bashden sie ausführt.
  • Wenn es propagiert wird, weil die Funktionsdefinition in envp[] statt in argv[] gespeichert ist, bedeutet dies, dass es die Umgebung jedes anderen Befehls verunreinigt, der in dieser Umgebung ausgeführt wird (einschließlich sleepbeispielsweise in diesem Beispiel).
  • (geringfügig) Auch wenn der bashShell-Code kürzer ist, werden mehr Daten übergeben und execve()tragen daher stärker zum E2BIG-Limit dieses Systemaufrufs bei.

Wenn Sie die Umgebung nutzen möchten, können Sie weiterhin Folgendes tun:

FUNCS=$(functions mysleep) caffeinate zsh -c '
  eval "$FUNCS";mysleep'

In diesem Fall caffeinatemuss es nur ausgeführt werden, caffeinatewährend die Funktion ausgeführt wird, und nicht unbedingt, damit die Funktion ausgeführt wird. Wir können andere Ansätze verwenden wie:

mysleep | caffeinate cat

catwird so lange ausgeführt, wie mysleepausgeführt wird. mysleepwürde jedoch weiterhin in einem separaten Prozess ausgeführt werden und dies wirkt sich auf die Standardausgabe aus mysleep.

mysleep 3> {fd}>(caffeinate cat)

würde beide Probleme lösen.

Wie oben wird dadurch eine Pipe zwischen mysleepund erstellt cat. Das schreibende Ende der Pipe befindet sich jetzt jedoch auf einem neu zugewiesenen Dateideskriptor über 10 (gespeichert in $fd), in den mysleepnormalerweise nicht geschrieben wird. catliest daher nichts, sondern wartet bis zum Dateiende der Pipe, was nur geschieht, wenn mysleep(und alle untergeordneten Prozesse, die diesen Dateideskriptor erben) beendet wird.

Antwort2

Am besten geht das, indem Sie Ihre Funktion einfach in eine Skriptdatei einfügen und das Skript folgendermaßen aufrufen:

caffeinate myfunction.sh

Inhalt von myfunction.sh:

#!/bin/bash
sleep 2

Stellen Sie sicher, dass die Skriptdatei ausführbar ist. Andernfalls wird ein Berechtigungsfehler angezeigt:

chmod +x myfunction.sh

verwandte Informationen