Сценарий Bash для центрирования состояния / текста из stdin / pipe

Если вы документируете свои коды выхода, чтобы помнить их через год, когда вам придется вернуться и настроить сценарий, все будет в порядке . Идея «зарезервированных кодов выхода» на самом деле больше не применима, кроме как сказать, что принято использовать 0 в качестве кода успеха и что-либо еще в качестве кода ошибки.

5
08.01.2017, 17:50
4 ответа

Вот мой сценарий 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)
  • вторая команда считывает ширину окна из параметра №1 и помещает ее в переменную width . Если параметр не указан, используется фактическая ширина клеммы.
  • третья команда отправляет все сообщение на awk , чтобы создать левое поле в виде строки пробелов, которая помещается в переменную margin . { {1}}
    • первая строка awk выполняется для каждой строки ввода. Он вычисляет max_len , длину самой длинной строки ввода (ограниченную до width )
    • вторая строка awk выполняется, когда все строки ввода были обработаны.Он печатает строку (ширина - 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.
     ──────────────────────────────────────────────────
3
27.01.2020, 20:33

Давайте по частям переведем с 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. Длина символа табуляции равна 1. Иногда из-за этого разделительная линия будет выглядеть короче самой длинной строки, например:

      Q : Почему тахион перешел дорогу? 
    A: Потому что он был на другой стороне. 
     ────────────────────── ───────────────── 
     
  2. Если некоторые строки длиннее, чем столбцов , для разделительной линии, вероятно, лучше использовать длину столбцы вместо самой длинной строки.

7
27.01.2020, 20:33
#!/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 ).
2
27.01.2020, 20:33

Я лично не стремлюсь к чистому решению 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 будет более важной, чем рекомендуемый стиль кода.

2
27.01.2020, 20:33

Теги

Похожие вопросы