Umgebungsvariablen LINES und COLUMNS gehen in einem Skript verloren

72

Folgendes berücksichtigen:

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ 

Die Variablen $LINESund $COLUMNSsind Shell-Variablen, keine Umgebungsvariablen, und werden daher nicht in den untergeordneten Prozess exportiert (sie werden jedoch automatisch aktualisiert, wenn ich die Größe des xterm-Fensters ändere, selbst wenn ich über ssh von einem entfernten Standort aus angemeldet bin). Gibt es eine Möglichkeit, meinem Skript die aktuelle Terminalgröße mitzuteilen?

BEARBEITEN: Ich brauche dies als Problemumgehung, um dieses Problem zu lösen : vi (sowie vim, less und ähnliche Befehle) bringt den Bildschirm jedes Mal durcheinander, wenn ich ihn benutze. Das Ändern des Terminals ist keine Option, und daher suche ich nach Problemumgehungen (das Scrollen nach unten $LINESist sicherlich nicht die perfekte Lösung, aber zumindest besser, als den vorherigen Bildschirm zu verlieren).

Davide
quelle
Ich denke, Sie können Ihr ursprüngliches Problem mit einem "Strg-L" -Befehl an vi lösen.
ndim
@ndim: Danke für den Vorschlag, aber du solltest ihn auf die andere Frage schreiben (wo ich dir antworten würde, dass es nicht funktioniert)
Davide
Davide, aus einer Laune heraus, habe ich ganz nach unten gescrollt und Cys Antwort gefunden, und ich bin wirklich froh, dass ich das getan habe. Möglicherweise möchten Sie die von Ihnen akzeptierte Antwort ändern. Es wird vielen Menschen helfen, die diese Frage finden.
mr_carrera

Antworten:

85

Sie können die Zeilen und Spalten erhalten von tput:

#!/bin/bash

lines=$(tput lines)
columns=$(tput cols)

echo "Lines: " $lines
echo "Columns: " $columns
Puppe
quelle
5
Für Cygwin 1.7 oder höher müssen Sie das Paket ncurses installieren, um tput zu erhalten.
Aleksander Blomskøld
2
Dies sollte geschützt werden mit: if [-n "$ {TERM}"]; dann Zeilen = $ (tput Zeilen); sonst ...
MarcH
2
@Aleksander Der sttyBefehl ist in coreutils auf Cygwin enthalten, das wir anstelle von verwenden können, tputwenn wir ncurses nicht installieren müssen. Ich habe eine Antwort mit einem Beispiel hinzugefügt.
Cy Rossignol
auf Alpine brauchst du ncurses.
Webjay
1
Mein Terminal hat 197 Spalten, aber tput gibt mir immer 80
datdinhquoc
37

Da diese Frage sehr beliebt ist, möchte ich eine neuere Antwort mit einigen zusätzlichen Informationen hinzufügen.

In modernen Systemen sind die Variablen $COLUMNSund häufig keine Umgebungsvariablen. Die Shell setzt diese Werte nach jedem Befehl dynamisch und wir können normalerweise nicht über nicht interaktive Skripte darauf zugreifen. Einige Programme respektieren diese Werte, wenn wir sie exportieren , aber dieses Verhalten ist nicht standardisiert oder wird nicht allgemein unterstützt.$LINES

Bash setzt diese Variablen im Rahmen des Prozesses (nicht der Umgebung), wenn wir die checkwinsizeOption aktivieren, indem wir :

shopt -s checkwinsize 

Viele Systeme aktivieren diese Option für uns in einer Standard- oder systemweiten Startdatei ( / etc / bashrc oder ähnlichem). Daher müssen wir uns daran erinnern, dass diese Variablen möglicherweise nicht immer verfügbar sind. Auf einigen Systemen, wie z. B. Cygwin, ist diese Option für uns nicht aktiviert, sodass Bash nicht festgelegt wird $COLUMNSund es $LINESsei denn, wir führen die obige Zeile aus oder fügen sie unserem ~ / .bashrc hinzu .

Tragbare Ansätze

Beim Schreiben nicht interaktiver Skripte möchten wir uns normalerweise nicht auf $LINESund $COLUMNSstandardmäßig verlassen (wir können diese jedoch überprüfen, damit ein Benutzer die Terminalgröße bei Bedarf manuell überschreiben kann).

Stattdessen wird die sttyund tputbieten Dienstprogramme tragbare Mittel , um die Klemmengröße von einem Skript , um zu bestimmen (die Befehle sind nachstehend beschrieben derzeit Standardisierung für POSIX unterziehen ).

Wie in der akzeptierten Antwort von Puppe gezeigt , können wir tputdie Terminalgröße auf ziemlich einfache Weise erfassen:

lines=$(tput lines)
columns=$(tput cols)

Alternativ gibt die sizeAbfrage für sttydie Anzahl der Terminalzeilen und -spalten in einem Schritt an (Ausgabe als Anzahl der Zeilen, gefolgt von zwei Leerzeichen, gefolgt von der Anzahl der Spalten):

size=$(stty size)  # "40  80" for example 

Das sttyProgramm wird normalerweise mit GNU Coreutils ausgeliefert , sodass wir es häufig auf Systemen ohne finden können tput. Ich bevorzuge manchmal den sttyAnsatz, weil wir einen Befehl und eine Unterschale weniger aufrufen (teuer bei Cygwin), aber es erfordert, dass wir die Ausgabe in Zeilen und Spalten analysieren, die möglicherweise weniger lesbar sind:

lines=${size% *}
columns=${size#* }

Beide oben beschriebenen Ansätze funktionieren in jeder POSIX-Shell.

Nicht tragbare Ansätze

Wenn uns die Portabilität egal ist, unterstützt Bash die Prozessersetzung , um das vorherige Beispiel zu vereinfachen:

read lines columns < <(stty size) 

... was schneller läuft als das tput Beispiel, aber immer noch langsamer als die erste sttyImplementierung, zumindest auf meinem Computer. In der Praxis ist die Auswirkung auf die Leistung wahrscheinlich vernachlässigbar. Wählen Sie den Ansatz, der für das Programm am besten geeignet ist (oder basierend darauf, welcher Befehl auf dem Zielsystem verfügbar ist).

Für Bash- Versionen 4.3 und höher können wir die checkwinsizeOption nutzen, um eine Abhängigkeit von einem anderen Programm zu vermeiden. Wenn wir diese Option in einem Skript zu aktivieren, wird eingestellt Bash $LINESund $COLUMNSwie es funktioniert für eine interaktive Eingabeaufforderung nach einem Kind Prozess beendet:

#!/bin/bash
shopt -s checkwinsize
cat /dev/null # Refresh LINES and COLUMNS

... wie wenn eine Unterschale beendet wird:

shopt -s checkwinsize
(: Refresh LINES and COLUMNS)

Bash ruft die Terminalgröße nach jedem Aufruf eines externen Befehls ab, wenn wir diese Option aktivieren. Daher möchten wir sie möglicherweise nach dem Initialisieren der Variablen wieder deaktivieren:

shopt -u checkwinsize

Wenn wir aus irgendeinem Grund weiterhin $LINESund $COLUMNSaus der Umgebung in unseren Skripten verwenden möchten , können wir Bash so konfigurieren, dass diese Variablen in die Umgebung exportiert werden:

trap 'export LINES COLUMNS' DEBUG

Der Bash- DEBUGTrap wird vor jedem an der Eingabeaufforderung eingegebenen Befehl ausgeführt, sodass wir ihn zum Exportieren dieser Variablen verwenden können. Durch erneutes Exportieren mit jedem Befehl stellen wir sicher, dass die Umgebungsvariablen auf dem neuesten Stand bleiben, wenn sich die Terminalgröße ändert. Fügen Sie diese Zeile zusammen mit der oben gezeigten Option zu .bashrc hinzucheckwinsize . Es funktioniert gut für persönliche Skripte, aber ich empfehle nicht, diese Variablen in Skripten zu verwenden, die gemeinsam genutzt werden.

Cy Rossignol
quelle
1
Fantastische Antwort. Endlich verstanden, warum sich Systeme so unterschiedlich verhalten ... Nur angemeldet, um dies zu verbessern - und um auf einen kleinen Fehler hinzuweisen: Sie haben Zeilen und Spalten beim Abgleichen der sizeAusgabe zurückgesetzt. Sollte .eg sein lines=${size#* }. sizegibt zuerst Zeilen und dann Spalten aus.
Rote Pille
@ RedPill Ich bin ein Dummkopf :) habe dies auf einem tatsächlichen Computer anstelle meines Telefons überprüft, und ich habe nur die Werte falsch gelesen. Tatsächlich definiert der POSIX-Vorschlag das Format als "%1dΔ%1d\n", <rows>, <columns>, sodass wir uns auf die Annahme verlassen können, dass stty zuerst Zeilen und dann Spalten ausgibt. Ich werde die Antwort aktualisieren ... danke!
Cy Rossignol
8
eval $( resize )

erledigt diesen Job ... (auf einem xterm-basierten Terminal)

Anthony
quelle
6
kill -s WINCH $$

setzt die Variablen.

elo
quelle
Ich glaube nicht, dass er danach sucht
Nikana Reklawyks
2
Trotzdem interessant.
Alfe
6

Lassen Sie mich zum Abschluss erwähnen, dass das Festlegen der Option 'checkwinsize' genau das ist, wonach das OP sucht, aber es gibt einen Haken. In nicht interaktiven Skripten ist sie standardmäßig deaktiviert. Sie können jedoch die folgende Zeile am Anfang eines Skripts hinzufügen, um es zu aktivieren:

shopt -s checkwinsize

Leider werden die Variablen LINES und COLUMNS nicht sofort nach dem Festlegen der Option festgelegt (zumindest beim letzten Versuch). Stattdessen müssen Sie Bash zwingen, auf den Abschluss einer Subshell zu warten. An diesem Punkt werden diese Variablen festgelegt. Die vollständige Nur-Bash-Lösung für dieses Problem besteht darin, das Skript mit der folgenden Zeile zu starten:

shopt -s checkwinsize; (:;:)

Sie können dann die Variablen LINES und COLUMNS nach Herzenslust verwenden. Bei jeder Größenänderung des Terminals werden sie auf die richtigen Werte zurückgesetzt, ohne dass externe Dienstprogramme aufgerufen werden müssen.

Marc Coiffier
quelle
Dies scheint nur mit neuen Versionen von Bash zu funktionieren. Es funktioniert nicht mit älteren Versionen von Bash (weder 4.2.46 auf Centos7 noch 4.2.37 auf Debian7). Die tput-Methode (siehe Puppes Antwort) scheint jedoch überall zu funktionieren.
Ján Lalinský
4

Haben Sie versucht, Ihren Schebang dazu zu bringen, zu sagen:

#!/bin/bash -i
Dennis Williamson
quelle
Siehe auch meine Antwort auf die Frage, auf die Sie verwiesen haben. Das Setzen der t_tiVariablen auf null kann hilfreich sein vim. stackoverflow.com/questions/630519/…
Dennis Williamson
Leider #!/bin/bash -imacht weder in AIX noch in Linux einen Unterschied
Davide
1
Ich erhalte eine leere Ausgabe von Ihrem Skript ohne die -iund korrekten Zahlen. Dies ist unter Ubuntu (LINES und COLUMNS werden nicht exportiert). Ich fand, dass ich auf Cygwin die beiden Variablen exportieren musste (Sie können dies in ~ / .bashrc tun), damit es funktioniert und das -inicht benötigt wurde. Ich musste jedoch kill -SIGWINCH $$an der Bash-Eingabeaufforderung die zu aktualisierenden Werte abrufen, wenn ich die Fenstergröße änderte (für Cygwin).
Dennis Williamson
Ubuntu was? Auf Hardy Haron -imacht das keinen Unterschied: leere Ausgabe mit oder ohne (sowieso brauche ich diese auf AIX, nicht Ubuntu)
Davide
1
Schlechte Idee, da für bash die Option -i bedeutet, dass die Shell interaktiv ist. Dies hat also Nebenwirkungen wie das Ausführen Ihrer ~ / .bashrc-Datei.
MarkHu
3

Laufen help exportkönnte helfen?

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ export LINES COLUMNS
me@mine:~$ ./a.sh 
Lines:  52
Columns:  157
me@mine:~$ 
Schwertfisch
quelle
1
Ich bin mir nicht sicher, warum dies abgelehnt wurde. Ich habe export LINES=$LINESund export COLUMNS=$COLUMNSin meinem Bashrc, und das funktioniert richtig für mich. Sie müssen nicht haben mit tput zu verwirren.
Tandrewnichols
2

$LINESund $COLUMNSin bash ist nur ein Shell-y-Wrapper um die TTY-Ioctls, der Ihnen die Größe des TTY und die vom Terminal jedes Mal gesendeten Signale gibt, wenn sich diese Größe ändert.

Sie könnten ein Programm in einer anderen Sprache schreiben, das diese ioctls direkt aufruft, um zu den TTY-Dimensionen zu gelangen, und dann dieses Programm verwenden.

EDIT: Nun, es stellt sich heraus, dass das Programm bereits existiert und aufgerufen wird tput. Stimmen Sie über Puppes tputAntwort ab .

ndim
quelle
2
#!/bin/bash -i

-ifunktioniert jetzt mit bash 4.2.10 (1) -release unter Ubuntu 11.10 .

$ cat show_dimensions.sh 
#!/bin/bash -i
printf "COLUMNS = %d\n" $COLUMNS
printf "LINES = %d\n" $LINES

$ ./show_dimensions.sh 
COLUMNS = 150
LINES = 101

$ bash --version
GNU bash, version 4.2.10(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Die Zahlen ändern sich mit der Größe des Fensters. Eine Falle zeigt, dass das Skript einen SIGWINCH erhält.

Seganku
quelle
Unter Cygwin (mintty) und 4.1.10 (4) -release sind LINES / COLUMNS noch leer, aber tput funktioniert.
Andreas Spindler
1

Warum nicht Umgebungsvariablen für den Befehl exec wie folgt verwenden:

docker exec -ti -e LINES=$LINES -e COLUMNS=$COLUMNS  container /bin/bash
Klassisch
quelle
-1

Meine Erfahrung, dass Sie das Skript durch die 'starten sollten. script_to_run 'anstelle des' scritp_to_run '. Eine einfache Überprüfung wie folgt:

'(( ${#COLUMNS} )) || { echo "Try start like '. name'" ; return 1 ; }
Apexik
quelle
2
Diese Art der Ausführung wird als Sourcing bezeichnet, und der .Befehl wird aufgerufen source. Nicht alle Skripte unterstützen diese Art der Ausführung. ZB wenn das Skript aufruftexit , wird die gesamte Shell, aus der das Skript stammt, beendet, nicht nur das Skript. Siehe Was ist der Unterschied zwischen shund source? .
Palec