我在 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 中的除法会被截断。因此offset
、perfect_center
和的值padsize
可能略有不同。
原始Python代码也存在一些问题:
制表符的长度为 1。这有时会导致分隔线看起来比最长的线短,如下所示:
Q: Why did the tachyon cross the road? A: Because it was on the other side. ──────────────────────────────────────
如果某些行比 长
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
。如果没有给出参数,则使用实际的终端宽度。 - 第三个命令将整体发送到
message
,awk
以便将左边距作为空格字符串生成,并将其放入变量中margin
。- 对每个输入行执行第一行 awk 。它计算
max_len
,最长输入行的长度(上限为width
) - 当所有输入行都已处理完毕后,执行第二个 awk 行。它打印一串
(width - max_len) / 2
空白字符
- 对每个输入行执行第一行 awk 。它计算
- 最后一个命令打印添加到它们
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 解决方案,而是利用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 不知道终端的宽度,它将优雅地失败(无缩进)。
我使用旧式条件运算符 ( [ ... ]
) 和 shell 算术 ( $[..]
) 只是因为我希望在旧版本的 Bash(以及自定义编译的最小 Bashes,其中新式运算符在编译时被禁用)之间获得最大兼容性。我通常也不建议这样做,但在这种情况下,由于我们正在努力寻求纯 Bash 解决方案,我认为跨 Bash 编译选项的最大兼容性比推荐的代码风格更重要。