printf でテキストを中央揃えにする

printf でテキストを中央揃えにする
printf "%*s\n" $(((${#fname}+$COLUMNS)/2)) "$fname"

次のエラーが発生します:

line 9: (7+)/2: syntax error: operand expected (error token is ")/2")

これはターミナルでは機能しますが、私のスクリプトでは機能しません。何かアイデアはありますか?

答え1

すべてのシェルが$COLUMNS変数を端末の幅に設定するわけではありません。

bash5.0 より前のバージョンでは、スクリプト内ではなく対話型の場合にのみ設定されていました。ただし、バージョン 4.3 以降では、 を使用すると非対話型シェルでも有効にすることができますshopt -s checkwinsize

どちらの場合も、ひねりがあります。非対話型シェルでこのオプションを有効にすると(5.0以降はデフォルトで有効)、子プロセスが待機されて終了するまで$COLUMNS/は設定されません(ソースのエントリには次のように記載されています)。$LINESNEWSフォアグラウンドジョブが終了した後(非対話型シェルではデフォルトでジョブ制御がないため、これは少し誤解を招く可能性があります)。したがって、これらの変数を使用する前に、外部コマンドまたはサブシェルが同期的に実行されていることを確認する必要があります。

#! /bin/bash -
shopt -s checkwinsize # for versions 4.3 and 4.4
(:) # start a synchronous subshell that runs the null command
echo "$COLUMNS $LINE"

また、これは stderr が端末に送信される場合にのみ発生することに注意してください (送信されない場合は未設定のままです)。そのため、画面の幅を決定できない場合は、より適切なデフォルトを使用するために$COLUMNS、 のようなものを使用することをお勧めします。${COLUMNS:-80}bash

あるいは、 に切り替えることもできます。これは、ターミナルで実行されている限り、非対話型の場合でもzsh常に設定されます(それ以外の場合はデフォルトで 80 に設定されます)。または、Bourne のようなシェルでは、 の代わりに を使用すると、が以前に設定されていないか空だった場合にの出力から設定されます。$COLUMNS$COLUMNS${COLUMNS:=$(tput cols)}$COLUMNS$COLUMNStput cols

tput colsがシステム上で動作しない場合は、</dev/tty stty size | awk '{print $2}'またはzsh -c 'print $COLUMNS'

ただし、一度$COLUMNSそのように設定すると、ターミナルのサイズが変更されても更新されなくなることに注意してください¹。そのため、$(tput cols)スクリプトで中央揃えのテキストを印刷するたびにターミナルのサイズが照会されるように、代わりに always を使用することをお勧めします。

また、注意してくださいprintf '%*s'および以外のシェルではzshfishテキストを指定された数だけ埋めます。バイトない文字そのため、この方法は、UTF-8 を使用するロケールでは US-ASCII 文字 (可能な文字全体の 0.011%) に制限される 1 バイト、単一幅の文字を含むテキストを埋め込むためにのみ使用できます。

zshの代わりに を使用する場合は、代わりにeft およびight パディング パラメータ拡張フラグbashを使用できます(フラグを使用すると、ゼロ幅または全角文字も処理できます)。lrm

print -r -- ${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}

左と右の両方にパディングされることに注意してください (つまり、画面の右端にパディングされます)。 次のようにして、右側のパディング (およびすべての末尾の空白) を削除できます。

set -o extendedglob
print -r -- ${${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}%%[[:space:]]#}

色付け/太字/目立つなどのエスケープシーケンスを含むテキストを中央に配置するのは、より複雑になります。最も簡単なのは、文字列の幅を取得する前にそれらを削除することです。たとえばzshそのアプローチ文字列の幅を決定します (および 0 幅または全角文字を処理します)。

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}

¹ ただし、ほとんどのシステムでは、@zevzek がコメントで示したように、SIGWINCH シグナルのハンドラーをインストールすることができ、これは最も一般的なケースで役立ちます。

答え2

変数 COLUMNS の代わりに、外部ソースから値を取得することもできます。

tput cols 

stty size | cut '-d ' -f1

答え3

次のことを試してください。

read WindowHeight WindowWidth<<<$(stty size)
printf "%$(((${#fname}+${WindowWidth})/2))s" "$fname"

COLUMNS はスクリプト内で自動的に設定されないため、現在のウィンドウ サイズを取得するには stty を使用するのがよいでしょう。これは複数のシェル (bash、ksh、zsh を含む) で機能します。

関連情報