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
und COLUMNS
umgebungsvariablen verlassen können less
. Zuverlässigere Methoden sind:
- Verwenden Sie
rows="${LINES:=$(tput lines)}"
und cols="${COLUMNS:=$(tput cols)}"
, wie von AP vorgeschlagen , oder
- Schauen Sie sich die Ausgabe von an
stty 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 Sie stty size <&1
(in Bash) oder sagen stty 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.
#!/bin/sh
buffer=$(mktemp)
rows="$LINES"
cols="$COLUMNS"
while true
do
IFS= read -r some_data
e=$? # 1 if EOF, 0 if normal, successful read.
printf "%s" "$some_data" >> "$buffer"
if [ "$e" = 0 ]
then
printf "\n" >> "$buffer"
fi
if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ]
then
if [ "$e" != 0 ]
then
cat "$buffer"
else
continue
fi
else
if [ "$e" != 0 ]
then
"${PAGER:="less"}" < "$buffer"
# The above is equivalent to
# cat "$buffer" | "${PAGER:="less"}"
# … but that’s a UUOC.
else
cat "$buffer" - | "${PAGER:="less"}"
fi
fi
break
done
rm "$buffer"
So verwenden Sie:
- Legen Sie das oben genannte in eine Datei; Nehmen wir an, Sie nennen es
mypager
.
- (Optional) Legen Sie es in einem Verzeichnis ab, das Ihr Suchpfad ist. zB ,
$HOME/bin
.
- Machen Sie es durch Eingabe ausführbar
chmod +x mypager
.
- Verwenden Sie es in Befehlen wie
ps ax | mypager
oder ls -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:
- Die Shell ist wie in
Well das Ausführen von benutzerdefinierten Befehlen die Aufgabe der Shell . Es ist das Eine , was die Shell tut. (Natürlich sage ich nicht, dass die Shell ein einfaches Programm ist.)
sh -c "command"
env
, nice
, nohup
, setsid
, su
, Und sudo
. 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 dann fork
und / oder exec
.
_______
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.
- Programme, die ein "Shell-Escape" ermöglichen. Das einzige Beispiel, das mir in den Sinn kommt , ist
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ühren sort
. (Zum Beispiel gibt es Geschichten, die in früheren Versionen spell
verwendet wurden sort -u
, um eine Liste der in einem Dokument verwendeten Wörter abzurufen und diese Liste dann diff
- oder vielleicht comm
- 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)
ps ax | mypager
Sie können dann eingeben
!:1 > myfile
oder drücken ↑und bearbeiten Sie die Zeile entsprechend. Nun, wenn Sie getippt hätten
mypager "ps ax"
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ätten ps ax | mypager
, könnten Sie tun
!:0 u!:*
Auch hier ist mypager "ps ax"
es immer noch machbar, aber wohl schwieriger.
Schauen Sie sich auch die beiden Befehle an: ps ax | mypager
und mypager "ps ax"
. Angenommen, Sie führen eine history
Stunde später eine Liste aus. ISTM, das Sie sich mypager "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, wie ps ax | grep oracle
? Wenn Sie tippen
mypager ps ax | grep oracle
es läuft mypager ps ax
und leitet die Ausgabe von diesem durch grep oracle
. Wenn also die Ausgabe von ps ax
30 Zeilen lang ist,
mypager
wird sie aufgerufen less
, auch wenn die Ausgabe von ps 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:
mypager "ps ax | grep oracle"
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,
Befehl 1 " arg 1 " | Befehl 2 ' arg 2 ' $ ' arg 3 '
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 weiterzugeben mypager
.
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?