printf "%*s\n" $(((${#fname}+$COLUMNS)/2)) "$fname"
Ich erhalte diesen Fehler:
line 9: (7+)/2: syntax error: operand expected (error token is ")/2")
Das funktioniert im Terminal, aber nicht in meinem Skript. Habt ihr Ideen?
Antwort1
Nicht alle Shells setzen die $COLUMNS
Variable auf die Breite des Terminals.
bash
Versionen vor 5.0 setzen es nur im interaktiven Modus, nicht in Skripten. Seit Version 4.3 können Sie es jedoch weiterhin in nicht interaktiven Shells mit aktivieren shopt -s checkwinsize
.
In beiden Fällen gibt es jedoch einen Haken: Wenn diese Option in nicht-interaktiven Shells aktiviert ist (standardmäßig aktiviert seit 5.0), werden $COLUMNS
/ $LINES
erst gesetzt, wenn auf einen Kindprozess gewartet und dieser beendet wurde (der NEWS
Eintrag in der Quelle erwähntnachdem ein Vordergrundjob beendet wurde, was etwas irreführend ist, da in nicht-interaktiven Shells standardmäßig keine Jobsteuerung vorhanden ist). Sie müssen also sicherstellen, dass ein externer Befehl oder eine Subshell synchron ausgeführt wurde, bevor Sie diese Variablen verwenden:
#! /bin/bash -
shopt -s checkwinsize # for versions 4.3 and 4.4
(:) # start a synchronous subshell that runs the null command
echo "$COLUMNS $LINE"
Beachten Sie auch, dass dies nur geschieht, wenn stderr zum Terminal geht (und wenn nicht, $COLUMNS
bleibt es nicht festgelegt). Sie möchten also möglicherweise etwas verwenden, ${COLUMNS:-80}
um einen vernünftigeren Standardwert zu verwenden, wenn bash
die Bildschirmbreite nicht bestimmt werden kann.
Alternativ können Sie zu wechseln, zsh
das immer gesetzt wird, $COLUMNS
auch wenn es nicht interaktiv ist, solange es in einem Terminal ausgeführt wird (und $COLUMNS
andernfalls standardmäßig auf 80 gesetzt ist), oder in jeder Bourne-ähnlichen Shell ${COLUMNS:=$(tput cols)}
anstelle von $COLUMNS
for verwenden $COLUMNS
, um es aus der Ausgabe von zu setzen, tput cols
wenn es zuvor nicht gesetzt oder leer war.
Wenn tput cols
dies auf Ihrem System nicht funktioniert, können Sie versuchen </dev/tty stty size | awk '{print $2}'
, oderzsh -c 'print $COLUMNS'
Beachten Sie jedoch, dass die Einstellung, wenn sie einmal $COLUMNS
auf diese Weise vorgenommen wurde, nicht bei jeder Größenänderung des Terminals aktualisiert wird¹. Verwenden Sie daher $(tput cols)
stattdessen möglicherweise „always“, damit die Terminalgröße jedes Mal abgefragt wird, wenn Sie in Ihrem Skript zentrierten Text drucken.
Beachten Sie auch, dassprintf '%*s'
in anderen Shells als zsh
und fish
füllt den Text auf die angegebene Anzahl vonBytesnichtFiguren, daher kann dieser Ansatz nur verwendet werden, um Text mit Einzelbyte-Zeichen und einfacher Breite aufzufüllen, was in Gebietsschemas, die UTF-8 verwenden, auf die US-ASCII-Zeichen (0,011 % aller möglichen Zeichen) beschränkt ist.
Wenn Sie zsh
anstelle von verwenden, können Sie stattdessen dessen Erweiterungsflags für die Füllparameter eft und ight bash
verwenden (mit dem Flag können sogar Zeichen mit der Breite null oder doppelter Breite verarbeitet werden):l
r
m
print -r -- ${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}
Beachten Sie, dass es sowohl nach links als auch nach rechts (also bis zum rechten Bildschirmrand) auffüllt. Sie können die rechte Auffüllung (zusammen mit allen nachfolgenden Leerzeichen) wie folgt entfernen:
set -o extendedglob
print -r -- ${${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}%%[[:space:]]#}
Das Zentrieren von Text, der Farb-, Fett-, Hervorhebungs- usw. Escape-Sequenzen enthält, wäre komplizierter. Am einfachsten wäre es wahrscheinlich, sie zu entfernen, bevor man die Breite der Zeichenfolge ermittelt. Beispielsweise mit , unter zsh
Verwendung vondieser Ansatzum die Zeichenfolgenbreite zu bestimmen (und Zeichen mit der Breite 0 oder doppelter Breite zu verarbeiten).
varwidth() (( ${(P)#1} * 3 - ${#${(ml[${(P)#1} * 2])${(P)1}}} ))
functions -Ms varwidth
varwidth_without_formatting() {
set -o localoptions -o extendedglob
local without_formatting=${(P)1//$'\e'\[[0-9;]#m}
(( varwidth(without_formatting) ))
}
functions -Ms varwidth_without_formatting
center() {
local text
for text do
print -r -- ${(l[(COLUMNS-varwidth_without_formatting(text))/2])}$text
done
}
center $'\e[31mred\e[1;39mbold\e[m' \
${(%):-%F{green}Blah%F{yellow}blah%F{magenta}blah%f}
¹ Allerdings könnten Sie auf den meisten Systemen einen Handler für das SIGWINCH-Signal installieren, wie @zevzek in Kommentaren gezeigt hat, was in den meisten Fällen hilfreich wäre.
Antwort2
Anstelle der Variable COLUMNS können Sie versuchen, den Wert aus einer externen Quelle zu beziehen:
tput cols
stty size | cut '-d ' -f1
Antwort3
Versuche Folgendes:
read WindowHeight WindowWidth<<<$(stty size)
printf "%$(((${#fname}+${WindowWidth})/2))s" "$fname"
COLUMNS wird im Skript nicht automatisch festgelegt, daher ist es eine gute Wahl, stty zu verwenden, um die aktuelle Fenstergröße abzurufen. Dies funktioniert in mehreren Shells (einschließlich Bash, Ksh, Zsh).