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.sh
und Weiterleiten der Ausgabe pipe.sh
erhalte 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: 321
von 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.sh
in codieren script1.sh
. Ich könnte script2.sh
als Argument an übergeben script1.sh
, aber ich dachte zunächst, eine Pipe wäre eine bessere Lösung?
Antwort1
Die read -p
Aufrufe in beiden Fällen script.sh
werden pipe.sh
vom 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 -p
Absender script.sh
gibt möglicherweise seine Eingabeaufforderung aus, aber die vom Benutzer eingegebene Zeichenfolge wird möglicherweise vom read -p
Absender gelesen pipe.sh
und umgekehrt.
In einer Pipeline wie a | b
könnte b
man 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, a
müsste eine Menge Daten geschrieben werden, bevor bemerkt wird, dass b
keine davon gelesen werden.
Ein Ausweg besteht darin, den stdout von b
mit dem stdin von a
in 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$
Dercircpipe
Das 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 -command
muss 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