Text zentrieren mit printf

Text zentrieren mit printf
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 $COLUMNSVariable auf die Breite des Terminals.

bashVersionen 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/ $LINESerst gesetzt, wenn auf einen Kindprozess gewartet und dieser beendet wurde (der NEWSEintrag 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, $COLUMNSbleibt es nicht festgelegt). Sie möchten also möglicherweise etwas verwenden, ${COLUMNS:-80}um einen vernünftigeren Standardwert zu verwenden, wenn bashdie Bildschirmbreite nicht bestimmt werden kann.

Alternativ können Sie zu wechseln, zshdas immer gesetzt wird, $COLUMNSauch wenn es nicht interaktiv ist, solange es in einem Terminal ausgeführt wird (und $COLUMNSandernfalls standardmäßig auf 80 gesetzt ist), oder in jeder Bourne-ähnlichen Shell ${COLUMNS:=$(tput cols)}anstelle von $COLUMNSfor verwenden $COLUMNS, um es aus der Ausgabe von zu setzen, tput colswenn es zuvor nicht gesetzt oder leer war.

Wenn tput colsdies 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 $COLUMNSauf 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 zshund fishfü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 zshanstelle von verwenden, können Sie stattdessen dessen Erweiterungsflags für die Füllparameter eft und ight bashverwenden (mit dem Flag können sogar Zeichen mit der Breite null oder doppelter Breite verarbeitet werden):lrm

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 zshVerwendung 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).

verwandte Informationen