bash: Lesen vom TTY in einem Pipe-Skript

bash: Lesen vom TTY in einem Pipe-Skript

Ich habe ziemliche Probleme mit zwei Bash-Skripten, an denen ich arbeite:

Skript.sh

#!/bin/bash
while true; do
    read -p "script: read: var: " var
    echo "script: write: var: $var"
done

pipe.sh

#!/bin/bash
while read line; do
        echo "pipe: read: line: (( $line ))"
        read -p "pipe: read var: " var < /dev/tty
        echo "pipe: write: var: $var"
done< <(cat) 

Beim Ausführen script.shund Weiterleiten der Ausgabe pipe.sherhalte ich die folgende Ausgabe:

$ ./script.sh | ./pipe.sh
1: script: read: var: 123   # user entering '123'
2: script: read: var: pipe: read: line: (( script: write: var: 123 ))
3: pipe: read var: 321      # user entering '321'
4: script: read: var: 456   # user entering '456'
5: pipe: write: var: 456
6: pipe: read: line: (( script: write: var: 321 ))
7: pipe: read var: 

Wie Sie sehen, scheint bis zur vierten Zeile alles zu funktionieren. Ich hatte erwartet, dass Zeile 4 pipe: write: var: 321von ist pipe.sh. Stattdessen erhalte ich die Eingabeaufforderung von script.sh.

Bei Eingabe des Strings "456" wird die zuvor erwartete Zeile ausgeführt, allerdings mit dem falschen String (erwartet: "321", erhalten: "456"). Zudem gibt Zeile 6 nicht "456", sondern "321" aus.

Hier stimmt etwas ganz und gar nicht. Irgendwelche Vorschläge, wie man das beheben kann und warum das passiert?

Aktualisieren:

Im Wesentlichen möchte ich, dass die Pipe genauso funktioniert wie der Code unten.

script1.sh

#!/bin/bash
while true; do
  read -p "val1: " val1
  script2.sh "${val1}"
done

script2.sh

#!/bin/bash
val1="${1}"
read -p "val2: " val2
echo "${val1} ${val2}"

Ich möchte jedoch nicht fest script2.shin codieren script1.sh. Ich könnte script2.shals Argument an übergeben script1.sh, aber ich dachte zunächst, eine Pipe wäre eine bessere Lösung?

Antwort1

Die read -pAufrufe in beiden Fällen script.shwerden pipe.shvom aktuellen Terminal gelesen, und da die Befehle in einer Pipeline parallel ausgeführt werden, können Sie keine Annahmen darüber treffen, welcher von ihnen zuerst die vom Benutzer eingegebenen Daten abgreift.

Der read -pAbsender script.shgibt möglicherweise seine Eingabeaufforderung aus, aber die vom Benutzer eingegebene Zeichenfolge wird möglicherweise vom read -pAbsender gelesen pipe.shund umgekehrt.

In einer Pipeline wie a | bkönnte bman leicht dafür sorgen, dass auf die Eingabe von gewartet wird a, bevor fortgefahren wird. Der Umgekehrte Fall ist jedoch nicht zutreffend: Da Pipes puffern, amüsste eine Menge Daten geschrieben werden, bevor bemerkt wird, dass bkeine davon gelesen werden.

Ein Ausweg besteht darin, den stdout von bmit dem stdin von ain einer Art „Ringleitung“ zu verbinden und a( script.sh) so zu ändern, dass auch auf Eingaben von stdin gewartet wird, genau wie b( pipe.sh) es tut.

Aufgrund der Einschränkungen der Shell-Sprache sollten Sie eineBenannte Pipedafür. Einfaches Beispiel:

cat > circpipe <<'EOT'; chmod 755 circpipe
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" <"$fifo"
rm "$fifo"
echo trigger
"$1" | "$2"
EOT

cat > pipe-head <<'EOT'; chmod 755 pipe-head
while read next; do
        read -p "HEAD's prompt>> " var </dev/tty || exit
        echo "$var"
done
EOT

cat > pipe-tail <<'EOT'; chmod 755 pipe-tail
while read input; do
        echo >&2 "  TAIL'input: $input"
        read -p "  TAIL's prompt>> " var </dev/tty
        echo >&2 "  TAIL processing <$var>"
        echo next       # trigger the head of the pipeline
done
EOT
./circpipe ./pipe-head ./pipe-tail
HEAD's prompt>> foo
  TAIL'input: foo
  TAIL's prompt>> bar
  TAIL processing <bar>
HEAD's prompt>> baz
  TAIL'input: baz
  TAIL's prompt>> quux
  TAIL processing <quux>
HEAD's prompt>> ^D$

DercircpipeDas Skript könnte in ein allgemeineres Tool umgewandelt werden, das normale Shell-Befehle akzeptiert und dessen „Ende“ auch aus der Schleife ausbrechen könnte.

Anders als im obigen Beispiel wird die Schleife hierdurch nicht standardmäßig „gestartet“. Hierzu -commandmuss ein Argument verwendet werden. Anwendungsbeispiel:

./circpipe -echo './pipe-head | stdbuf -oL sed s/e/o/g | ./pipe-tail'
HEAD's prompt>> pee
  TAIL'input: poo
  TAIL's prompt>> lol
  TAIL processing <lol>
HEAD's prompt>>^D

Umlenkrohr

#! /bin/sh
# usage: circpipe [-start_command] shell_command
# run 'shell_command' with its stdin connected to its stdout
# via a FIFO
# if 'start_command' is given, run it before `shell_command`
# with its stdout redirected to the same FIFO
[ "$#" -gt 0 ] || exit 0
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" 3<"$fifo"
rm "$fifo"
case $1 in -*)  eval "${1#-}"; shift; esac
IFS='|'; eval "<&3 $* &"
exec >&-
wait

verwandte Informationen