Ich möchte in der Lage sein, einen Befehl so zu verpacken, dass er automatisch durch einen Pager geleitet wird, wenn seine Ausgabe nicht in ein Terminal passt.
Im Moment verwende ich die folgende Shell-Funktion (in zsh unter Arch Linux):
export LESS="-R"
RET="$($@)"
RET_LINES="$(echo "${RET}" | wc -l)"
if [[ $RET_LINES -ge $LINES ]]; then
echo "${RET}" | ${PAGER:="less"}
else
echo "${RET}"
fi
aber das überzeugt mich nicht wirklich. Gibt es einen besseren Weg (in Bezug auf Robustheit und Overhead), um das zu erreichen, was ich will? Ich bin auch offen für zsh-spezifischen Code, wenn er den Job gut macht.
Update: Seit ich diese Frage gestellt habe, habe ich eine Antwort gefunden , die eine etwas bessere - wenn auch kompliziertere - Lösung bietet, bei der die meisten $LINES
Zeilen gepuffert werden, bevor die Ausgabe weitergeleitet wird, less
anstatt alles zwischenzuspeichern. Leider ist das auch nicht wirklich befriedigend, da keine der beiden Lösungen lange, umbrochene Zeilen berücksichtigt. Wenn der obige Code beispielsweise in einer aufgerufenen Funktion gespeichert ist pager_wrap
, dann
pager_wrap echo {1..10000}
druckt eine sehr lange Zeile nach stdout, anstatt durch einen Pager zu leiten.
wc -l
Zeilenumbrüche gezählt, aber was ist, wenn der Inhalt nur aus einer Zeile besteht, aber genug Wraps enthält, um das Terminal zu verlassen?$LINES
Zeilen zu puffern , aber ich weiß auch nicht, wie ich das beheben soll ...less
wenn sie automatisch beendet werden, wenn der Text auf einem Bildschirm angezeigt wird und kein Paging erforderlich ist und der Bildschirm beim Beenden nicht gelöscht wird? Sie können das tun - superuser.com/a/106644/67909less -F
, aber ich habe es verworfen, weil es die Piping- Ausgabe nicht zurückdruckt. Hast du eine bessere Idee? Jedenfalls habe ich derzeit zwei Anwendungsfälle: Umwickelnls -l
und Umwickelnpacman -Ss [foo]
.Antworten:
Ich habe eine Lösung, die für die POSIX-Shell-Konformität geschrieben wurde, aber ich habe sie nur in Bash getestet, daher weiß ich nicht genau, ob sie portabel ist. Und ich kenne zsh nicht, also habe ich keinen Versuch unternommen, es zsh-freundlich zu machen. Sie leiten Ihren Befehl hinein; Das Übergeben eines Befehls als Argument (e) an einen anderen Befehl ist ein schlechtes Design * .
Natürlich muss jede Lösung für dieses Problem wissen, wie viele Zeilen und Spalten das Terminal hat. Im folgenden Code habe ich angenommen, dass Sie sich auf die Variablen
LINES
undCOLUMNS
umgebungsvariablen verlassen könnenless
. Zuverlässigere Methoden sind:rows="${LINES:=$(tput lines)}"
undcols="${COLUMNS:=$(tput cols)}"
, wie von AP vorgeschlagen , oderstty size
. Beachten Sie, dass dieser Befehl das Terminal als Standardeingabe haben muss. Wenn es sich also in einem Skript befindet und Sie in das Skript einleiten, müssen Siestty size <&1
(in Bash) oder sagenstty size < /dev/tty
. Die Erfassung der Ausgabe ist noch komplizierter.Die geheime Zutat: Der
fold
Befehl unterbricht lange Zeilen wie der Bildschirm, sodass das Skript lange Zeilen korrekt verarbeiten kann.So verwenden Sie:
mypager
.$HOME/bin
.chmod +x mypager
.ps ax | mypager
oderls -la | mypager
.Wenn Sie den zweiten Schritt übersprungen haben (das Skript in ein Verzeichnis stellen, das Ihr Suchpfad ist), müssen Sie Folgendes tun , wobei ein relativer Pfad wie " " sein kann.
ps ax | path_to_mypager/mypager
path_to_mypager
.
* Warum ist die Übergabe eines Befehls als Argument (e) an einen anderen Befehl ein schlechtes Design?
I. Ästhetik / Konformität mit Traditionen / Unix-Philosophie
Unix hat eine Philosophie von Do eine Sache und tun es auch . Wenn ein Programm beispielsweise Daten auf eine bestimmte Weise anzeigt (wie es Pager tun), sollte es nicht auch den Mechanismus aufrufen, der die Daten erzeugt. Dafür sind Pfeifen da.
Nicht viele Unix-Programme führen benutzerdefinierte Befehle oder Programme aus. Schauen wir uns einige an, die dies tun:
sh -c "command"
env
,nice
,nohup
,setsid
,su
, Undsudo
. Diese Programme haben etwas gemeinsam - sie sind alle vorhanden, um ein Programm mit einer geänderten Ausführungsumgebung 1 auszuführen . Sie müssen so arbeiten, wie sie es tun, da Sie mit Unix im Allgemeinen nicht die Ausführungsumgebung eines anderen Prozesses ändern können. Sie müssen Ihren eigenen Prozess ändern und dannfork
und / oderexec
._______
1 Ich verwende den Begriff Ausführungsumgebung im weiteren Sinne, die sich auf Umgebungsvariablen nicht nur, aber auch Prozessattribute wie „
nice
“ Wert, UID und GIDs, Prozessgruppe, Sitzungs - ID, Steueranschluss, offene Dateien, Arbeitsverzeichnis ,umask
Wert,ulimit
s, Signal Dispositionen,alarm
Timer usw.vi
/vim
, obwohl ich mir ziemlich sicher bin, dass es noch andere gibt. Dies sind historische Artefakte. Sie sind älter als Fenstersysteme und sogar die Jobkontrolle. Wenn Sie eine Datei bearbeitet haben und etwas anderes tun möchten (z. B. eine Verzeichnisliste anzeigen), müssten Sie Ihre Datei speichern und den Editor verlassen, um zu Ihrer Shell zurückzukehren. Heutzutage können Sie zu einem anderen Fenster wechseln oder Ctrl+ Z(oder Typ:suspend
) verwenden, um zu Ihrer Shell zurückzukehren, während Ihr Editor am Leben bleibt, sodass Shell-Escapezeichen möglicherweise veraltet sind.Ich zähle keine Programme, die andere (fest codierte) Programme ausführen, um ihre Fähigkeiten zu nutzen, anstatt sie zu duplizieren. Beispielsweise können einige Programme
diff
oder ausführensort
. (Zum Beispiel gibt es Geschichten, die in früheren Versionenspell
verwendet wurdensort -u
, um eine Liste der in einem Dokument verwendeten Wörter abzurufen und diese Liste danndiff
- oder vielleichtcomm
- mit der Wörterbuch-Wörterbuchliste zu vergleichen und festzustellen, welche Wörter aus dem Dokument nicht enthalten waren das Wörterbuch.)II. Zeitprobleme
So wie Ihr Skript geschrieben ist, wird die
RET="$($@)"
Zeile erst abgeschlossen, wenn der aufgerufene Befehl abgeschlossen ist. Daher kann Ihr Skript erst dann mit der Anzeige von Daten beginnen, wenn der Befehl, der sie generiert, abgeschlossen wurde. Der wahrscheinlich einfachste Weg, dies zu beheben, besteht darin, den Befehl zur Datengenerierung vom Datenanzeigeprogramm zu trennen (obwohl es andere Möglichkeiten gibt).III. Befehlsverlauf
Angenommen, Sie führen einen Befehl mit einer Ausgabe aus, die von Ihrem Anzeigefilter verarbeitet wird, und Sie sehen sich die Ausgabe an und entscheiden, dass Sie diese Ausgabe in einer Datei speichern möchten. Wenn Sie getippt hatten (als hypothetisches Beispiel)
Sie können dann eingeben
oder drücken ↑und bearbeiten Sie die Zeile entsprechend. Nun, wenn Sie getippt hätten
Sie können immer noch zurückgehen und diesen Befehl bearbeiten
ps ax > myfile
, aber es ist nicht so einfach.Oder nehmen Sie an, Sie möchten als
ps uax
Nächstes ausgeführt werden. Wenn Sie getippt hättenps ax | mypager
, könnten Sie tunAuch hier ist
mypager "ps ax"
es immer noch machbar, aber wohl schwieriger.Schauen Sie sich auch die beiden Befehle an:
ps ax | mypager
undmypager "ps ax"
. Angenommen, Sie führen einehistory
Stunde später eine Liste aus. ISTM, das Sie sichmypager "ps ax"
etwas genauer ansehen müssen, um zu sehen, welcher Befehl ausgeführt wird.IV. Komplexe Befehle / Anführungszeichen
echo {1..10000}
ist offensichtlich nur ein Beispielbefehl;ps ax
ist nicht viel besser. Was ist, wenn Sie wollen einfach nur ein etwas tun , wenig etwas realistischer, wieps ax | grep oracle
? Wenn Sie tippenes läuft
mypager ps ax
und leitet die Ausgabe von diesem durchgrep oracle
. Wenn also die Ausgabe vonps ax
30 Zeilen lang ist,mypager
wird sie aufgerufenless
, auch wenn die Ausgabe vonps ax | grep oracle
nur 3 Zeilen umfasst. Es gibt wahrscheinlich Beispiele, die dramatischer scheitern werden.Sie müssen also das tun, was ich zuvor gezeigt habe:
Aber damit
RET="$($@)"
kann ich nicht umgehen. Es gibt natürlich Möglichkeiten, mit solchen Dingen umzugehen, aber sie werden entmutigt.Was ist, wenn die Befehlszeile, deren Ausgabe Sie erfassen möchten, noch komplizierter ist? z.B,
wobei die Argumente enthalten unordentlich Kombinationen von Raum, Registerkarte
$
,|
,\
,<
,>
,*
,;
,&
,[
,]
,(
,)
,`
, und vielleicht sogar'
und"
. Ein solcher Befehl kann schwierig genug sein, um direkt in die Shell zu tippen. Stellen Sie sich nun den Albtraum vor, ihn zitieren zu müssen, um ihn als Argument weiterzugebenmypager
.quelle
rows="${LINES:=$(tput lines)}"
und verwendencols="${COLUMNS:=$(tput cols)}"
, da das Skript auf keine der Umgebungsvariablen zugreifen kann, obwohl es beispielsweiseecho $LINES
wie von der CLI erwartet funktioniert. Ich habe auch dieprintf "%s" "$e"
Zeile auskommentiert. Ich habe nur noch ein paar Fragen: 1. Können Sie erklären, warum das Übergeben eines Befehls als Argument (e) an einen anderen Befehl ein schlechtes Design ist? Ich verstehe, dass es besser ist, eine Gabel zu vermeiden, aber gibt es andere Gründe?fold -w"$cols" "$buffer" | wc -l
jedes Mal auf, anstatt beispielsweise eine laufende Zählung von zu führenfold -w"$cols" "$some_data" | wc -l
?printf "%s" "$e"
. Das war nur eine Debug-Anweisung, die ich nicht in meiner Antwort veröffentlichen wollte. (1) Ich habe Nummer 1 in der Antwort angesprochen. (2) Nun,fold -w"$cols" "$some_data" | wc -l
würde nicht funktionieren, aber ich denke, ich hätte es tun könnenprintf "%s" "$some_data" | fold -w"$cols" | wc -l
, und ehrlich gesagt habe ich nicht daran gedacht. Ich glaube, dass mein Ansatz etwas einfacher ist, aber Ihre Idee wäre effizienter, besonders wenn sie$rows
groß ist. (3) Entschuldigen Sie, dass Sie so lange gebraucht haben, um zu antworten. Ein Truthahn überquerte die Straße, und ich musste herausfinden, warum. :-) Dafür ist die
-F
Option vorgesehenless
, obwohl Sie diese-X
Option auch verwenden müssen. Andernfalls wird der Text auf Terminals mit einem Terminal auf dem alternativen Bildschirm gedruckt (was bedeutet, dass er nach dem Beenden nicht sofort verfügbar istless
). Dies könnte sich in Zukunft ändern, da derzeit eine Erweiterungsanforderung für -X impliziert wird, wenn der Text mit-F
(303) auf einen Bildschirm passt, und RedHat-Systeme offenbar seit 2008 einen Patch dafür haben (obwohl er noch nicht vorgelagert wurde ( Ab dem 14.09.2017 habe ich gerade eine E-Mail an [email protected] gesendet.Damit:
Wenn Sie den alternativen Bildschirm immer noch verwenden möchten, wenn die Ausgabe zu lang ist, müssen Sie sich etwas einfallen lassen (auf Systemen, auf denen der oben erwähnte RedHat-Patch nicht vorhanden ist):
Zu verwenden als:
oder
(nicht
page file
, es ist nur dazu gedacht, stdin zu paginieren).Wir versuchen, die Länge der Ausgabe zu erraten, indem wir die Farb-Escape-Sequenzen entfernen, die Registerkarten erweitern und die Breite berechnen, damit wir die Anzahl der Terminalzeilen bestimmen können, die eine bestimmte Textzeile anzeigen sollen.
Das sollte funktionieren, solange die Ausgabe keine anderen Escape-Sequenzen oder Steuer- / schlecht codierten Zeichen enthält.
Beachten Sie auch einen signifikanten Unterschied zum RedHat-Patch: Bei der Ausgabe auf einem Bildschirm wird die Ausgabe nicht
less
nachbearbeitet (wie das Rendern von Steuerzeichen wie^X
im umgekehrten Video, das Zusammendrücken von Leerzeilen mit-s
...). Das ist zwar näher an dem, was hier gefragt wird, aber in der Praxis ist dies möglicherweise weniger wünschenswert.Möglicherweise müssen Sie das Text :: CharWidth-Modul installieren, das nicht zu den Standardmodulen gehört (
libtext-charwidth-perl
Paket unter Debian).quelle
ls | less -F
automatisch der alternative Bildschirm verwendet, wenn die Ausgabe zu lang ist, und nicht, wenn dies nicht der Fall ist. Vermisse ich etwas$LESS
Variable mit zusätzlichen Optionen?