Allgemeines Problem
Ich möchte ein Skript schreiben, das mit dem Benutzer interagiert, obwohl es sich mitten in einer Rohrkette befindet.
Konkretes Beispiel
Konkret dauert es ein file
oder stdin
, zeigt Zeilen (mit Zeilennummern) an, fordert den Benutzer auf, eine Auswahl oder Zeilennummern einzugeben, und druckt dann die entsprechenden Zeilen aus stdout
. Nennen wir dieses Skript selector
. Dann möchte ich im Grunde in der Lage sein zu tun
grep abc foo | selector > myfile.tmp
Wenn foo
enthält
blabcbla
foo abc bar
quux
xyzzy abc
dann selector
präsentiert mir (auf dem terminal, nicht in myfile.tmp
!) optionen
1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:
Danach tippe ich ein
2-3
und am Ende mit
foo abc bar
xyzzy abc
als Inhalt von myfile.tmp
.
Ich habe ein Auswahlskript eingerichtet, und im Grunde funktioniert es perfekt, wenn ich Ein- und Ausgabe nicht umleitung. So
selector foo
benimmt sich wie ich will. Wenn Sie jedoch Dinge wie im obigen Beispiel zusammenfügen, werden selector
die dargestellten Optionen gedruckt myfile.tmp
und versucht, eine Auswahl aus der Grepped-Eingabe zu lesen.
Mein Ansatz
Ich habe versucht, die -u
Flagge von zu verwenden read
, wie in
exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "
aber das macht nicht das, was ich mir erhofft hatte.
F: Wie erhalte ich die tatsächliche Benutzerinteraktion?
cmd | { some processing; read var </dev/tty; } | cmd
alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'
was ziemlich gut funktioniert. Allerdingsgrep b foo | selector | wc -l
bricht hier rüber. Irgendwelche Ideen, wie man das behebt? Das,rangeselect
was ich verwendet habe, finden Sie übrigens unter pastebin.com/VAxTSSHs . Es ist ein einfaches AWK-Skript, das die Zeilen einer Datei druckt, die einem bestimmten Bereich von Leinenzahlen entsprechen. (Bereiche können Dinge wie "3-10, 12,14,16-20" sein.)alias
das, sondernselector() { all of that stuff...; }
in eine Funktion.alias
Es benennt einfache Befehle um, während Funktionen einen zusammengesetzten Befehl in einen einzigen einfachen Befehl packen .Antworten:
Die Verwendung
/proc/$PPID/fd/0
ist unzuverlässig: Das übergeordnete Element desselector
Prozesses verfügt möglicherweise nicht über das Terminal als Eingabe.Es gibt einen Standardpfad , der sich immer auf das Terminal des aktuellen Prozesses bezieht :
/dev/tty
.oder
quelle
Ich habe eine kleine Funktion geschrieben: Sie antwortet nicht auf das, was Sie um Verkettung von Rohren gebeten haben, sondern löst Ihr Problem.
Die Funktion übergibt alle Argumente, denen Sie sie sofort geben
grep
. Wenn Sie einen Shell-Glob verwenden, um die Dateien anzugeben, aus denen er gelesen werden soll, werden alle Übereinstimmungen in allen Dateien zurückgegeben, beginnend mit der ersten in der Glob-Reihenfolge und endend mit der letzten Übereinstimmung.grep
Übergibt seine Ausgabe annl
welche Nummern jede Zeile und welche übergibt seine Ausgabe antee
welche dupliziert seine Ausgabe sowohl anstdout
als auch an/dev/tty
. Dies bedeutet, dass die Ausgabe aus der Pipeline gleichzeitig sowohl in das Argumentarray der Funktion, wo sie in\n
Ewlines aufgeteilt wird, als auch in das Terminal gedruckt wird, während es funktioniert.Als nächstes
_in()
versucht die Funktionread
in einer Auswahl, ob mindestens ein Ergebnis der vorherigen Aktion maximal fünf Mal vorliegt. Die Auswahl kann nur aus durch Leerzeichen getrennten Zahlen oder durch durch Leerzeichen getrennten Zahlenbereichen bestehen-
. Wenn etwas anderes vorhanden istread
(einschließlich einer Leerzeile) , wird es erneut versucht - jedoch nur nach wie vor maximal fünf Mal.Zuletzt
_out()
analysiert die Funktion die Auswahl des Benutzers und erweitert alle darin enthaltenen Bereiche. Es druckt seine Ergebnisse in der"${[num]}"
jeweiligen Form aus und stimmt damit mit dem Wert der iminf()
arg-Array gespeicherten Zeilen überein. Diese Ausgabe wirdeval
als Argumente ausgegeben, aufprintf
die daher nur die vom Benutzer ausgewählten Zeilen gedruckt werden.Es kommt explizit
read
vom Terminal und druckt nur dasSelect:
Menü ausstderr
und ist daher reichlich Pipeline-freundlich. Zum Beispiel funktioniert Folgendes:Sie können jedoch alle Optionen verwenden, die Sie angeben würden,
grep
und eine beliebige Anzahl von Dateinamen, die Sie möglicherweise auch übergeben. Das heißt, Sie können jede andere als eine Art verwenden - als Nebeneffekt der Parsing-Eingabe$IFS
funktioniert dies nicht, wenn Sie nach Leerzeilen suchen. Aber wer möchte aus einer nummerierten Liste von Leerzeilen auswählen?Letzter Hinweis: Da dies funktioniert, indem numerische Benutzereingaben direkt in die im Argumentarray der Funktion gespeicherten numerischen Positionsparameter übersetzt werden, erfolgt die Ausgabe unabhängig von der Auswahl des Benutzers, so oft der Benutzer sie auswählt und in der vom Benutzer ausgewählten Reihenfolge es.
Zum Beispiel:
quelle