Wie kann ich Lese- und Schreibvorgänge in einem einzelnen Dateideskriptor an verschiedene Stellen senden?

7

Ich habe eine ausführbare Linux-Datei foo, die Eingaben von fd 0 liest und Ausgaben in fd 0 schreibt ( nicht fd 1). Dies funktioniert gut für die interaktive Verwendung im Terminal.

Wie kann ich dieses Programm über die Shell-Befehlszeile so ausführen, dass Lesevorgänge von fd 0 aus einer Datei stammen, Schreibvorgänge von fd 0 jedoch in eine andere Datei verschoben werden?

Das Beste , was ich mir einfallen lassen kann, ist, $ goo input.txt output.txt | foowo goosich ein Hilfsprogramm befindet, aber ich kenne kein vorhandenes Hilfsprogramm, und ich weiß nicht einmal, ob die von eingerichtete Pipe |bidirektional ist.

Jay
quelle
1
Rohre sind unidirektional. Die einzige Möglichkeit, dies zu tun, besteht darin, FD 0 durch einen Socket zu ersetzen, der mit einem Programm verbunden ist, das das Lesen / Schreiben übernimmt. Wer das Programm geschrieben hat, das in FD 0 schreibt, muss ein bisschen herumgeschlagen werden.
Patrick
Vielen Dank. foo <> /dev/tcp/localhost/1234in einem Fenster und nc -l 1234 < input.txt > output.txtin einem anderen scheint zu funktionieren!
Jay
1
Eine andere Lösung wäre, eine LD_PRELOADBibliothek zum Überschreiben zu verwenden, write()so dass write()Aufrufe von FD 0 in FD 1 geändert werden.
Patrick
@Patrick, die LD_PRELOAD-Idee ist klug. Angenommen, es kann verwendet werden (foo ist nicht statisch verknüpft, nicht setuid oder setgid usw.), und wenn angenommen wird, dass es nicht einfach auf Quellcodeebene repariert und neu erstellt werden kann, scheint es langfristig ziemlich gut zu sein Lösung.
Godlygeek
... insofern, als es in einem Wrapper-Skript eingerichtet werden könnte, wobei es fooausgezogen $PATHund der Wrapper ersetzt wird, wodurch es einen fooin dem gibt $PATH, der so funktioniert, wie er eigentlich hätte geschrieben werden sollen.
Godlygeek

Antworten:

6

Mit socat(Version 2 oder höher):

socat 'system:cat input.txt & cat > output.txt,commtype=socketpair' \
      'system:foo,nofork'

Oder noch besser:

socat 'CREATE:output.txt%OPEN:input.txt' 'system:foo,commtype=socketpair'

Wir verwenden eine socketpairbidirektionale (Pipes sind unter Linux nicht bidirektional). Eine andere Möglichkeit besteht darin, ein Pseudo-Terminal (auch bidirektional) zu verwenden, indem Sie commtype=ptyoben verwenden. Dies ist möglicherweise ein besserer Weg, wenn Sie foomit einem Terminal sprechen möchten.

Oder Sie implementieren den socketpairAnsatz perlwie folgt:

perl -MSocket -e '
  socketpair(A, B, AF_UNIX, SOCK_STREAM, PF_UNSPEC);
  if (fork) {
    close A;
    if (fork) {
      open STDOUT, ">&B";
      exec("cat", "input.txt")
    }
    open STDIN, "<&B";
    open STDOUT, ">output.txt";
    exec("cat")
  }
  close B;
  open STDIN, "<&A";
  exec "foo"'

(Als schnellen Proof-of-Concept möchten Sie möglicherweise die Fehlerprüfung hinzufügen und effizienter gestalten, indem Sie nicht so viele Prozesse verwenden und die Ausführung vermeiden. cat)

Stéphane Chazelas
quelle
mit socat 1.7.2.3 gibt mir der erste E parseopts(): unknown option "commtype"und der zweite gibt mir E CREATE: wrong number of parameters (3 instead of 1).
Jay
@ Jay, ja, es gab eine große Anzahl neuer Funktionen in Version 2. Diese Befehle basieren auf einigen von ihnen.
Stéphane Chazelas