printf "%*s\n" $(((${#fname}+$COLUMNS)/2)) "$fname"
Я получаю эту ошибку:
line 9: (7+)/2: syntax error: operand expected (error token is ")/2")
Это работает в терминале, но не в моем скрипте. Есть идеи?
решение1
Не все оболочки устанавливают $COLUMNS
переменную, равную ширине терминала.
bash
Версии до 5.0 устанавливают его только в интерактивном режиме, а не в скриптах. Однако, начиная с версии 4.3, вы все еще можете включить его в неинтерактивных оболочках с помощью shopt -s checkwinsize
.
В любом случае, однако, есть нюанс: при включении этой опции в неинтерактивных оболочках (включено по умолчанию с версии 5.0), $COLUMNS
/ $LINES
не устанавливаются до тех пор, пока не будет ожидан и завершен дочерний процесс ( NEWS
запись в исходном коде упоминаетпосле завершения приоритетного задания, что немного вводит в заблуждение, учитывая, что в неинтерактивных оболочках по умолчанию нет управления заданиями). Поэтому вам нужно убедиться, что внешняя команда или подоболочка была запущена синхронно, прежде чем использовать эти переменные:
#! /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
which always sets, $COLUMNS
даже если он неинтерактивен, пока он запущен в терминале (и $COLUMNS
по умолчанию 80 в противном случае) или в любой оболочке типа Bourne использовать ${COLUMNS:=$(tput cols)}
вместо $COLUMNS
for $COLUMNS
для установки из вывода , tput cols
если он ранее был не установлен или пуст.
Если tput cols
не работает на вашей системе, вы можете попробовать </dev/tty stty size | awk '{print $2}'
, илиzsh -c 'print $COLUMNS'
Однако следует помнить, что после $COLUMNS
такой настройки он не будет обновляться при изменении размера терминала¹, поэтому вы можете использовать $(tput cols)
значение always, чтобы размер терминала запрашивался каждый раз при печати центрированного текста в вашем скрипте.
Также имейте в виду, чтоprintf '%*s'
в оболочках, отличных от zsh
и fish
дополняет текст до указанного количествабайтынетперсонажи, поэтому этот подход можно использовать только для заполнения текста, содержащего однобайтовые символы одинарной ширины, которые в локалях, использующих UTF-8, ограничены символами US-ASCII (0,011% от всех возможных символов).
При использовании zsh
вместо bash
можно использовать флаги расширения параметров заполнения l
eft и r
ight (которые могут обрабатывать даже символы нулевой или двойной ширины с помощью флага m
):
print -r -- ${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}
Обратите внимание, что он отступает как слева, так и справа (то есть до правого края экрана). Вы можете удалить правый отступ (вместе со всеми конечными пробелами) с помощью:
set -o extendedglob
print -r -- ${${(ml[COLUMNS/2]r[COLUMNS-COLUMNS/2])fname}%%[[:space:]]#}
Центрирование текста, содержащего цветные / жирные / выделенные... escape-последовательности, будет более сложным. Проще всего, вероятно, будет удалить их перед получением ширины строки. Например, с помощью zsh
, используяэтот подходдля определения ширины строки (и обработки символов нулевой или двойной ширины).
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}
¹ хотя в большинстве систем можно установить обработчик сигнала SIGWINCH, как показал @zevzek в комментариях, что поможет в наиболее распространенных случаях.
решение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)