Bash IPC: Wie Pipe richtig aufbauen, starten und aufrufen?

andy_m4 schrieb:
Dann hoffen wir mal, das da nicht versehentlich mal die Firmware vertauscht wird. :-)
Danke, ymmd. :evillol:

Das mit dem eigenen Verstand/Willen ist so eine Sache. Wenn dann auch noch die Replizierbarkeit hinzukommt, also wenn die Rheinmetall-Fabrik, die Robotor herstellt, auch noch von Robotern kontrolliert wird, sich also Robotor sozusagen selber herstellen und auch (planvoll) umprogrammieren können ... na dann, gute Nacht.
 
CyborgBeta schrieb:
und auch (planvoll) umprogrammieren können
Und wir haben ja jetzt schon das Problem, das wir unseren selbst geschriebenen Quelltext nicht wirklich beherrschen, was man regelmäßig an Bugs und/oder Sicherheitslücken bestaunen kann.
Das die Programmierung die da aus einem KI-System raus kommt durchschau- und beherrschbar ist, ist da natürlich völlig illusorisch.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: nutrix und CyborgBeta
Noch mal schnell zurück zur Eingangsfrage ...

execpipe.sh:

Bash:
#!/bin/bash
while true; do eval "$(cat /home/ich/pipe/mypipe)"; done

Das Script rufe ich einfach in Screen mit sh execpipe.sh auf und detache dann.

docker-compose.yml Part:

Code:
    volumes:
      - ./pipe/:/hostpipe/

Backend-Code Part:

Java:
    get(
        "/restart",
        (req, res) -> {
          try {
            Map<String, Object> model = new HashMap<>();
            ProcessBuilder builder =
                new ProcessBuilder(
                    "bash", "-c", "echo \"docker compose restart my-service\" > /hostpipe/mypipe");
            builder.redirectErrorStream(true);
            Process process = builder.start();
            Scanner s = new Scanner(process.getInputStream(), Charset.defaultCharset());
            List<String> lines = new LinkedList<>();
            while (s.hasNextLine()) {
              lines.add(s.nextLine().trim());
            }
            s.close();
            int result = process.waitFor();
            lines.add(0, String.valueOf(result));
            model.put("restart", lines);
            return new VelocityTemplateEngine().render(new ModelAndView(model, "index-view.html"));
          } catch (Exception e) {
            return "Error: " + e.getMessage();
          }
        });

Zurzeit hört die Pipe also auf alles ...

Wie bekomme ich es hin, dass sie nur auf 0, 1 oder 2 lauscht und dann die entsprechende Aktion ausführt? Also, wie kann ich noch eine zusätzliche Schicht da einziehen und sozusagen die API graduieren?

Dafür bräuchte ich doch ein cat (blockierend?), ein parse und eine Fallunterscheidung, oder nicht?
 
Hat geklappt. Ich glaube, das ist richtig, sicher und robust:

Bash:
#!/bin/bash
while true; do
    data="$(cat /home/ich/pipe/mypipe)";
    if [ -n "$data" ]; then
        if [ "$data" = "restart" ]; then
            docker compose restart my-service;
        fi
    fi
done

Wenn man sonst nie mit der Bash zu tun hat ... ist das halt kompliziert.
 
Endlosschleifen finde ich nicht gut. Da muss noch irgendeine Abbruchbedingung rein.

Außerdem würde ich noch eine Log-Funktion hinzufügen, die aufzeichnet, was für Befehle reinkommen und wie Docker das verdaut hat (Fehlermeldungen). Das macht es nachvollziehbar, was passiert oder nicht passiert.
 
  • Gefällt mir
Reaktionen: nutrix
In dem Link habe ich keine Endlosschleife gesehen. Kannst du genauer beschreiben, warum das so gut sein soll?

Mir als Programmierer rollen sich die Fußnägel hoch, wenn ich eine Endlosschleife sehe. Irgendeine Art Abbruchbedingung muss da rein oder willst du jedes Mal das Skript mit kill beenden, wenn was ist?

Dazu würde ich das Skript auch automatisch beenden lassen, wenn die Pipe warum auch immer nicht (mehr) verfügbar ist. Idealerweise hinterlässt das auch einen Log-Eintrag.

Es fehlt auch irgendwas, um die Schleife etwas einzubremsen. Wenn ich das richtig deute, macht sie aktives Warten, was eine Verschwendung von Ressourcen ist. Oder blockt data="$(cat /home/ich/pipe/mypipe)"; die Ausführung bis was über die Pipe reinkommt? Es reicht doch, wenn nur ein Mal in der Sekunde geschaut wird, ob Daten rein kommen (sleep 1).
Ideal wäre, wenn man einen Interrupt, Event Trigger oder so was setzen kann, sodass das Skript quasi nur dann aufwacht, wenn es was zu tun gibt. Ich weiß aber von Bash zu wenig, um dazu Tipps geben zu können.

Es ist auch vielleicht eine gute Idee, statt einer Endlosschleife systemd zu verwenden, das das Skript alle x Sekunden startet. Damit ist man ziemlich flexibel. Das könnte aber auch mit Kanonen auf Spatzen schießen sein.
 
  • Gefällt mir
Reaktionen: CyborgBeta
Krik schrieb:
Mir als Programmierer rollen sich die Fußnägel hoch, wenn ich eine Endlosschleife sehe. Irgendeine Art Abbruchbedingung muss da rein oder willst du jedes Mal das Skript mit kill beenden, wenn was ist?
Nein, Strg+C bzw. SIGHUP.

Und es tut mir leid, aber das ist nun mal Bash bzw. POSIX-Standard. Was du gut findest, ist dabei zweitrangig.
Ergänzung ()

Krik schrieb:
Oder blockt data="$(cat /home/ich/pipe/mypipe)"; die Ausführung bis was über die Pipe reinkommt?
Es blockiert bzw. ist kein aktives Warten ... (jedenfalls, wenn ich nach CPU/RAM gehe)
 
Der POSIX-Standard sagt nichts darüber aus, ob man Endlosschleifen verwenden soll oder nicht. Es ist aber normal und es gehört einfach dazu, dass man aus einer Schleife über einen wohldefinierten Mechanismus aussteigen kann.

SIGHUP definiert, dass das Terminal nicht (mehr) verfügbar ist, und das Programm nicht mehr davon überwacht wird. Nichts anderes. Wie ein Programm darauf reagiert, ist ihm überlassen. Beenden ist eine mögliche Option, aber das wird nicht gefordert und wird auch nicht überall so umgesetzt.
SIGINT wird von Strg+C ausgelöst und definiert eine vom Keyboard ausgelöste Unterbrechung.

Das korrekte Signal zum normalen Beenden eines Programms ist SIGTERM. Das Skript sollte das beachten.
Die Standard-Eskalationskette von harmlos bis Vorschlaghammer ist: SIGTERM, SIGINT, SIGQUIT, SIGABRT und schlussendlich SIGKILL als atomare Option.

Wenn man kill verwendet, wird standardmäßig SIGTERM gesendet, wenn man kein anderes Signal explizit angibt.
Das heißt für dich, dass die Endlosschleife so geändert werden muss, dass sie auf SIGTERM und optional auf SIGINT hört. Kommt ein derartiges Signal, dann sollte die Schleife verlassen werden, sodass das Skript in einer geordneten Art und Weise (die du selbst definierst) beendet werden kann.
 
  • Gefällt mir
Reaktionen: nutrix und CyborgBeta
Eigentlich fehlt ohnehin noch eine Rückmeldung an den Caller. Der müsste ja zumindest wissen, ob Erfolg/Misserfolg vorliegt.

Diesbezüglich kenne ich aber die Best Practices bzw. Dos and Don'ts und Pitfalls nicht.

Falls du dazu Ideen hast, gerne her damit.
 
Aus der manpage für mkfifo:
Opening a FIFO for reading normally blocks until some other process opens the same FIFO for writing, and vice versa.
Aus der manpage für pipe:
If a process attempts to read from an empty pipe, then read(2) will block until data is available. If a process attempts to write to a full pipe (see below), then write(2) blocks until sufficient data has been read from the pipe to allow the write to complete.
Das ist so blöd, da nur dann ein mögliches Beenden-Signal geprüft werden kann, wenn etwas über die Pipe kommt.

Es gibt zwar auch ein O_NONBLOCK-Flag, aber dann ob man das über Bash aufrufen kann? Ich habe nichts in der Richtung gefunden, außer man greift auf eine Programmiersprache zurück, die auf die C Standard Library zugreifen kann. Aber das ist dann kein Bash mehr.

Die Rückmeldung an den Caller kann man auch über die Pipe realisieren. Die geht ja in beide Richtungen. Hier wäre es aber gut, wenn man einen Marker mitsendet, der die Art der Daten markiert, also z. B. "command" und "response". Man schickt also bspw. "command restart" und erhält dann ein "response ok" oder "response error" zurück.
Wegen der blocking-Sache muss man aber sehr genau aufpassen, ob man die Pipe gerade lesen oder darüber senden will. Kommt das durcheinander, kann das zu einem Deadlock führen, wo Sender und Empfänger aufeinander warten und nie wieder aus diesem Zustand herauskommen.

Falls das eine Option für dich ist, wie wäre es, wenn du die Skripte in Python schreibst?
 
  • Gefällt mir
Reaktionen: nutrix und CyborgBeta
Krik schrieb:
Die Rückmeldung an den Caller kann man auch über die Pipe realisieren. Die geht ja in beide Richtungen. Hier wäre es aber gut, wenn man einen Marker mitsendet, der die Art der Daten markiert, also z. B. "command" und "response". Man schickt also bspw. "command restart" und erhält dann ein "response ok" oder "response error" zurück.
Wegen der blocking-Sache muss man aber sehr genau aufpassen, ob man die Pipe gerade lesen oder darüber senden will. Kommt das durcheinander, kann das zu einem Deadlock führen, wo Sender und Empfänger aufeinander warten und nie wieder aus diesem Zustand herauskommen.
Jein, dann muss man sich ein komplettes Protokoll überlegen, was dann auch wieder fehlerbehaftet sein kann. Ich glaube, das ist noch nicht das Gelbe vom Ei.

Krik schrieb:
Falls das eine Option für dich ist, wie wäre es, wenn du die Skripte in Python schreibst?
Geht in Java genauso gut. Oder in Bash ... lesen, Command ausführen, Ergebnis zurückschreiben, 1 Sekunde warten.

Was wäre mit einer zweiten "Ergebnis-Pipe"? Beide dann eben unidirektional.
 
CyborgBeta schrieb:
Was wäre mit einer zweiten "Ergebnis-Pipe"? Beide dann eben unidirektional.
Aber nicht in Bash, da dort die Lese- und Schreiboperationen blocken. Ein Skript kann also nicht gleichzeitig eine Pipe lesen und eine zweite beschreiben. Das ist ja die Krux an der Sache. Um diesen Umstand kommt man offenbar nur mit einer "richtigen" Programmiersprache drum herum.
 
  • Gefällt mir
Reaktionen: nutrix
Aber sequentiell wäre dies möglich. Der Caller muss dann nur eben zuerst die Ergebnis-Pipe öffnen, dann sein Command schreiben und von der Ergebnis-Pipe lesen.
 
Und der Receiver wartet während dessen auf seine Read-Funktion (bzw. cat), die genau dann unterbrochen wird, wenn der Sender die Pipe zum Beschreiben öffnet. Das ist echt suboptimal.

Krik schrieb:
Aus der manpage für mkfifo:
Opening a FIFO for reading normally blocks until some other process opens the same FIFO for writing, and vice versa.
Sobald also einer schreiben will, fliegen alle Mitleser raus.

Es macht eigentlich keinen Unterschied, ob die Skripte mit einer oder zwei Pipes arbeiten. Im Endeffekt wird man so oder so durch das Blocking limitiert.
Ergänzung ()

Es ist offensichtlich, dass die Standard-CLI-Programme nicht dafür gedacht/vorbereitet sind, eine Art bidirektionale Echtzeitkommunikation zwischen zwei Prozessen über Pipes zu ermöglichen.

Gibt es Alternativen für Pipes, die das vielleicht doch ermöglichen? Gibt es vielleicht Queues, Interrupte oder Events, die man füllen bzw. an die man sich hängen bzw. abonnieren kann?

Ich überblicke das leider auch nicht alles.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: nutrix und CyborgBeta
Zurück
Oben