Если вы документируете свои коды выхода, чтобы помнить их через год, когда вам придется вернуться и настроить сценарий, все будет в порядке . Идея «зарезервированных кодов выхода» на самом деле больше не применима, кроме как сказать, что принято использовать 0
в качестве кода успеха и что-либо еще в качестве кода ошибки.
Вот мой сценарий center.sh
:
#!/bin/bash
readarray message < <(expand)
width="${1:-$(tput cols)}"
margin=$(awk -v "width=$width" '
{ max_len = length > width ? width : length > max_len ? length : max_len }
END { printf "%" int((width - max_len + 1) / 2) "s", "" }
' <<< "${message[@]}")
printf "%s" "${message[@]/#/$margin}"
Как это работает:
stdin
в массиве сообщение
после преобразования таблиц в пробелы (благодаря @NominalAnimal) width
. Если параметр не указан, используется фактическая ширина клеммы.
на awk
, чтобы создать левое поле в виде строки пробелов, которая помещается в переменную margin
. { {1}}
max_len
, длину самой длинной строки ввода (ограниченную до width
) (ширина - max_len) / 2
символов пробела сообщения
после добавления к ним поля
Тест:
$ fortune | cowthink | center.sh
_______________________________________
( English literature's performing flea. )
( )
( -- Sean O'Casey on P. G. Wodehouse )
---------------------------------------
o ^__^
o (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ echo $'|\tTAB\t|' | center.sh 20
| TAB |
$ echo "A line exceeding the maximum width" | center.sh 10
A line exceeding the maximum width
Наконец, если вы хотите завершить отображение разделительной линией, как в вашем скрипте Python, добавьте эту строку перед последней командой printf
:
message+=( $(IFS=''; sed s/./─/g <<< "${message[*]}" | sort | tail -n1)$'\n' )
Она заменяет каждый символ в каждой строке с ─
, выберите самый длинный с помощью sort | tail -n1
и добавьте его в конец сообщения.
Тест:
$ fortune | center.sh 60
Tuesday is the Wednesday of the rest of your life.
──────────────────────────────────────────────────
Давайте по частям переведем с Python на Bash.
Python:
#! / Usr / bin / env python3 import sys, os linelist = list (sys.stdin) { {1}}
Bash:
#!/usr/bin/env bash
linelist=()
while IFS= read -r line; do
linelist+=("$line")
done
Python:
# получает самую большую строку big_line_size = 0 для строки в списке строк: line_lenght = len (line ) если длина_линии> размер_большей_линии: размер_большей_линии = длина_строки
Bash:
biggest_line_size=0
for line in "${linelist[@]}"; do
# caveat alert: the length of a tab character is 1
line_length=${#line}
if ((line_length > biggest_line_size)); then
biggest_line_size=$line_length
fi
done
Python:
столбцы = int (os.popen ('tput cols', 'r'). read ()) смещение = размер самой большой_линии / 2 perfect_center = columns / 2 padsize = int (perfect_center - offset) spacing = ' '* padsize # space char
Bash:
columns=$(tput cols)
# caveat alert: division truncates to integer value in Bash
((offset = biggest_line_size / 2))
((perfect_center = columns / 2))
((padsize = perfect_center - offset))
if ((padsize > 0)); then
spacing=$(printf "%*s" $padsize "")
else
spacing=
fi
Python:
text = str () для строки в списке строк: text + = (spacing + строка) divider = spacing + ('─' * int (most_line_size)) # unicode 0x2500 text + = divider print ( text, end = "\ n" * 2)
Bash:
for line in "${linelist[@]}"; do
echo "$spacing$line"
done
printf $spacing
for ((i = 0; i < biggest_line_size; i++)); do
printf -- -
done
echo
Полный сценарий для упрощения копирования и вставки:
#!/usr/bin/env bash
linelist=()
while IFS= read -r line; do
linelist+=("$line")
done
biggest_line_size=0
for line in "${linelist[@]}"; do
line_length=${#line}
if ((line_length > biggest_line_size)); then
biggest_line_size=$line_length
fi
done
columns=$(tput cols)
((offset = biggest_line_size / 2))
((perfect_center = columns / 2))
((padsize = perfect_center - offset))
spacing=$(printf "%*s" $padsize "")
for line in "${linelist[@]}"; do
echo "$spacing$line"
done
printf "$spacing"
for ((i = 0; i < biggest_line_size; i++)); do
printf ─ # unicode 0x2500
done
echo
Деление в Bash усекается. Таким образом, значения смещения
, perfect_center
и padsize
могут немного отличаться.
Есть некоторые проблемы, которые существуют и в исходном коде Python:
Длина символа табуляции равна 1. Иногда из-за этого разделительная линия будет выглядеть короче самой длинной строки, например:
Q : Почему тахион перешел дорогу?
A: Потому что он был на другой стороне.
────────────────────── ─────────────────
Если некоторые строки длиннее, чем столбцов
, для разделительной линии, вероятно, лучше использовать длину столбцы
вместо самой длинной строки.
#!/usr/bin/env bash
# Reads stdin and writes it to stdout centred.
#
# 1. Send stdin to a temporary file while keeping track of the maximum
# line length that occurs in the input. Tabs are expanded to eight
# spaces.
# 2. When stdin is fully consumed, display the contents of the temporary
# file, padded on the left with the apropriate number of spaces to
# make the whole contents centred.
#
# Usage:
#
# center [-c N] [-t dir] <data
#
# Options:
#
# -c N Assume a window width of N columns.
# Defaults to the value of COLUMNS, or 80 if COLUMNS is not set.
#
# -t dir Use dir for temporary storage.
# Defaults to the value of TMPDIR, or "/tmp" if TMPDIR is not set.
tmpdir="${TMPDIR:-/tmp}"
cols="${COLUMNS:-80}"
while getopts 'c:t:' opt; do
case "$opt" in
c) cols="$OPTARG" ;;
t) tmpdir="$OPTARG" ;;
esac
done
tmpfile="$tmpdir/center-$$.tmp"
trap 'rm -f "$tmpfile"' EXIT
while IFS= read -r line
do
line="${line//$'\t'/ }"
len="${#line}"
maxlen="$(( maxlen < len ? len : maxlen ))"
printf '%s\n' "$line"
done >"$tmpfile"
padlen="$(( maxlen < cols ? (cols - maxlen) / 2 : 0 ))"
padding="$( printf '%*s' "$padlen" "" )"
while IFS= read -r line
do
printf '%s%s\n' "$padding" "$line"
done <"$tmpfile"
Тестирование:
$ fortune | cowsay | ./center
________________________________________
/ "There are two ways of constructing a \
| software design: One way is to make it |
| so simple that there are obviously no |
| deficiencies, and the other way is to |
| make it so complicated that there are |
| no obvious deficiencies." |
| |
\ -- C. A. R. Hoare /
----------------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ fortune | cowsay -f bunny -W 15 | ./center -c 100
_______________
/ It has just \
| been |
| discovered |
| that research |
| causes cancer |
\ in rats. /
---------------
\
\ \
\ /\
( )
.( o ).
Я лично не стремлюсь к чистому решению Bash, а буду использовать tput
и expand
. Однако решение на чистом Bash вполне осуществимо:
#!/bin/bash
# Bash should populate LINES and COLUMNS
shopt -s checkwinsize
# LINES and COLUMNS are updated after each external command is executed.
# To ensure they are populated right now, we run an external command here.
# Because we don't want any other dependencies other than bash,
# we run bash. (In that child shell, run the 'true' built-in.)
bash -c true
# Tab character.
tab=$'\t'
# Timeout in seconds, for reading each input line.
timeout=5.0
# Read input lines into lines array:
lines=()
maxlen=0
while read -t $timeout LINE ; do
# Expand each tab in LINE:
while [ "${LINE#*$tab}" != "$LINE" ]; do
# Beginning of LINE, replacing the tab with eight spaces
prefix="${LINE%%$tab*} "
# Length of prefix
length=${#prefix}
# Round length down to nearest multiple of 8
length=$[$length - ($length & 7)]
# Combine prefix and the rest of the line
LINE="${prefix:0:$length}${LINE#*$tab}"
done
# If LINE is longest thus far, update maxlen
[ ${#LINE} -gt $maxlen ] && maxlen=${#LINE}
# Add LINE to lines array.
lines+=("$LINE")
done
# If the output is redirected to a file, COLUMNS will be undefined.
# So, use the following idiom to ensure we have an integer 'cols'.
cols=$[ $COLUMNS -0 ]
# Indentation needed to center the block
if [ $maxlen -lt $cols ]; then
indent=$(printf '%*s' $[($cols-$maxlen)/2] '')
else
indent=""
fi
# Display
for LINE in "${lines[@]}"; do
printf '%s%s\n' "$indent" "$LINE"
done
Вышеупомянутый скрипт считывает строки из стандартного ввода и делает отступ для вывода так, чтобы самая длинная строка находилась в центре терминала. Он завершится неудачно (без отступа), если ширина терминала не известна Bash.
Я использовал условные операторы старого стиля ( [...]
) и арифметику оболочки ( $ [..]
) только потому, что мне нужна была максимальная совместимость со старыми версиями. Bash (и скомпилированные на заказ минимальные Bashes, в которых операторы нового стиля отключены во время компиляции). Обычно я тоже не рекомендую этого делать, но в данном случае, поскольку мы стремимся к решению на чистом Bash, я подумал, что максимальная совместимость между параметрами компиляции Bash будет более важной, чем рекомендуемый стиль кода.