Bash 脚本将来自标准输入/管道的财富/文本置于中心

Bash 脚本将来自标准输入/管道的财富/文本置于中心

我在 python3 中使用一个小脚本在控制台中显示集中的财富,你能建议我如何在纯 bash 中做到这一点吗?

文件:center.python3

#!/usr/bin/env python3

import sys, os

linelist = list(sys.stdin)

# gets the biggest line
biggest_line_size = 0
for line in linelist:
    line_lenght = len(line.expandtabs())
    if line_lenght > biggest_line_size:
        biggest_line_size = line_lenght

columns = int(os.popen('tput cols', 'r').read())
offset = biggest_line_size / 2
perfect_center = columns / 2
padsize =  int(perfect_center - offset)
spacing = ' ' * padsize # space char

text = str()
for line in linelist:
    text += (spacing + line)

divider = spacing + ('─' * int(biggest_line_size)) # unicode 0x2500
text += divider

print(text, end="\n"*2)

然后在.bashrc

使其可执行后chmod +x ~/center.python3

fortune | ~/center.python3

编辑:稍后我会尝试根据我的评论回复这个OP,但现在我让它更加有文化。

编辑2:更新 python 脚本以解决 @janos 指出的有关选项卡扩展的错误。

在此输入图像描述

答案1

让我们逐块地从 Python 转换为 Bash。

Python:

#!/usr/bin/env python3

import sys, os

linelist = list(sys.stdin)

重击:

#!/usr/bin/env bash

linelist=()
while IFS= read -r line; do
    linelist+=("$line")
done

Python:

# gets the biggest line
biggest_line_size = 0
for line in linelist:
    line_lenght = len(line)
    if line_lenght > biggest_line_size:
        biggest_line_size = line_lenght

重击:

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:

columns = int(os.popen('tput cols', 'r').read())
offset = biggest_line_size / 2
perfect_center = columns / 2
padsize =  int(perfect_center - offset)
spacing = ' ' * padsize # space char

重击:

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()
for line in linelist:
    text += (spacing + line)

divider = spacing + ('─' * int(biggest_line_size)) # unicode 0x2500
text += divider

print(text, end="\n"*2)

重击:

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 中的除法会被截断。因此offsetperfect_center和的值padsize可能略有不同。

原始Python代码也存在一些问题:

  1. 制表符的长度为 1。这有时会导致分隔线看起来比最长的线短,如下所示:

                      Q:    Why did the tachyon cross the road?
                      A:    Because it was on the other side.
                      ──────────────────────────────────────
    
  2. 如果某些行比 长columns,则分隔线可能会更好地使用 的长度columns而不是最长的行。

答案2

这是我的脚本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数组中message(感谢@NominalAnimal)
  • 第二个命令从参数 #1 读取窗口宽度并将其放入变量中width。如果没有给出参数,则使用实际的终端宽度。
  • 第三个命令将整体发送到messageawk以便将左边距作为空格字符串生成,并将其放入变量中margin
    • 对每个输入行执行第一行 awk 。它计算max_len,最长输入行的长度(上限为width
    • 当所有输入行都已处理完毕后,执行第二个 awk 行。它打印一串(width - max_len) / 2空白字符
  • 最后一个命令打印添加到它们message之后的每一行margin

测试 :

$ 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

#!/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 ).

答案4

我个人不会追求纯粹的 Bash 解决方案,而是利用tputexpand。然而,纯粹的 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 不知道终端的宽度,它将优雅地失败(无缩进)。

我使用旧式条件运算符 ( [ ... ]) 和 shell 算术 ( $[..]) 只是因为我希望在旧版本的 Bash(以及自定义编译的最小 Bashes,其中新式运算符在编译时被禁用)之间获得最大兼容性。我通常也不建议这样做,但在这种情况下,由于我们正在努力寻求纯 Bash 解决方案,我认为跨 Bash 编译选项的最大兼容性比推荐的代码风格更重要。

相关内容