如何用纯Bash脚本实现带框文本?解决长文本框变形问题
纯Bash实现带框文本消息:解决超长文本换行导致的框变形问题
问题说明
已用纯Bash结合ANSI转义序列实现带框文本消息函数,适配VT100类终端,但当文本行长度超过屏幕列数时,终端自动换行会导致文本框变形,需解决该问题。
现有代码
#!/bin/bash # 返回当前行号,超过255则返回255 function getCurPos() { local CURPOS local -i lineNumb echo -e "\E[6n"; read -sdR CURPOS; CURPOS=${CURPOS#*[} CURPOS=(${CURPOS//;/ }) lineNumb=${CURPOS[0]} if [ $lineNumb -gt 255 ]; then return 255 fi return $lineNumb } # $1: 支持echo -e语法的文本(\n表示换行) function messagetBox() { local oldIFS=IFS unset IFS if [ -z $1 ]; then exit 0; fi local text=$1 local -i maxLineLenght=0 local -i lineLenght=0 local -i lineNumb=0 local -i count local -i line echo while read i; do echo -e "| $i "; lineLenght=${#i} lineLenght+=4 if [ $lineLenght -gt $maxLineLenght ]; then maxLineLenght=$lineLenght fi lineNumb+=1 done < <(echo -e $text) count=$maxLineLenght while [ $count -gt 0 ]; do echo -en "-" count+=-1 done echo -en "\033[$((lineNumb+2))A\n" count=$maxLineLenght while [ $count -gt 0 ]; do echo -en "-" count+=-1 done echo -en "\033[1B" count=$lineNumb while [ $count -gt 0 ]; do getCurPos line=$? echo -e "\033[${line};${maxLineLenght}H|" count+=-1 done echo -en "\033[${lineNumb}B" echo -e "\033[0m" IFS=$localIFS }
正常调用效果
~$ source ./sample.sh ~$ messagetBox "Hello\nMy name is Lorenzo, I was born in Italy." -------------------------------------------- | Hello | | My name is Lorenzo, I was born in Italy. | -------------------------------------------- ~$
问题场景(超长文本导致框变形)
~$ messagetBox "Hello. My name is Lorenzo, I was born in Italy. Hello. My name is Lorenzo, I was born in Italy." | Hello. My name is Lorenzo, I was born in Italy. Hello. My name is Loren------------------------------------------------------------ ----------------------------------------------------------------- -----------------------------------------------
解决方案
核心思路是手动控制文本换行,避免终端自动换行:
- 获取终端实际列数,兼容不同终端大小
- 对每一段文本按单词拆分,确保每行长度不超过「终端列数 - 4」(预留左右
|和|的占位) - 基于拆分后的行计算框的宽度,再绘制边框
修改后的完整代码:
#!/bin/bash # 返回当前行号,超过255则返回255 function getCurPos() { local CURPOS local -i lineNumb echo -e "\E[6n"; read -sdR CURPOS; CURPOS=${CURPOS#*[} CURPOS=(${CURPOS//;/ }) lineNumb=${CURPOS[0]} if [ $lineNumb -gt 255 ]; then return 255 fi return $lineNumb } # 将文本拆分为指定宽度的行,$1为文本,$2为最大宽度 function wrap_text() { local text="$1" local max_width="$2" local current_line="" local word # 按空格拆分单词,保留原换行符 echo -e "$text" | while IFS= read -r line; do if [ -z "$line" ]; then echo "" continue fi for word in $line; do if [ $(( ${#current_line} + ${#word} + 1 )) -gt "$max_width" ]; then echo "$current_line" current_line="$word" else current_line=${current_line:+$current_line }$word fi done echo "$current_line" current_line="" done } # $1: 支持echo -e语法的文本(\n表示换行) function messagetBox() { local oldIFS="$IFS" unset IFS if [ -z "$1" ]; then exit 0; fi local text="$1" # 获取终端列数,默认80 local -i term_cols=$(tput cols 2>/dev/null || echo 80) # 每行文本最大宽度:终端列数 - 4(左右的| 和空格) local -i max_text_width=$(( term_cols - 4 )) local -i maxLineLength=0 local -i lineLength=0 local -i lineNumb=0 local -i count local wrapped_lines=() # 先对文本进行换行处理 while read -r wrapped_line; do wrapped_lines+=("$wrapped_line") lineLength=${#wrapped_line} [ "$lineLength" -gt "$maxLineLength" ] && maxLineLength="$lineLength" lineNumb=$((lineNumb + 1)) done < <(wrap_text "$text" "$max_text_width") # 框的总宽度:maxLineLength + 4 local -i box_width=$(( maxLineLength + 4 )) echo # 绘制顶部边框 count="$box_width" while [ "$count" -gt 0 ]; do echo -en "-" count=$((count - 1)) done echo # 绘制每行文本和左右边框 for wrapped_line in "${wrapped_lines[@]}"; do local -i pad_length=$(( maxLineLength - ${#wrapped_line} )) echo -en "| $wrapped_line" # 填充右侧空格 count="$pad_length" while [ "$count" -gt 0 ]; do echo -en " " count=$((count - 1)) done echo -e " |" done # 绘制底部边框 count="$box_width" while [ "$count" -gt 0 ]; do echo -en "-" count=$((count - 1)) done echo echo -e "\033[0m" IFS="$oldIFS" }
修改说明
- 新增
wrap_text函数:按单词拆分超长文本,生成符合终端宽度的多行内容,保留原文本的换行符 - 动态获取终端列数:用
tput cols获取实际终端宽度,避免硬编码 - 简化边框绘制逻辑:先绘制顶部边框,再逐行绘制带填充空格的文本行,最后绘制底部边框,避免光标定位带来的异常
- 自适应框宽度:基于拆分后的最长文本行计算框的总宽度,确保不会超出终端范围
测试效果
调用超长文本时,会自动拆分并生成完整的框:
~$ source ./sample.sh ~$ messagetBox "Hello. My name is Lorenzo, I was born in Italy. Hello. My name is Lorenzo, I was born in Italy." ---------------------------------------------------------------------- | Hello. My name is Lorenzo, I was born in Italy. Hello. My name is | | Lorenzo, I was born in Italy. | ----------------------------------------------------------------------
内容的提问来源于stack exchange,提问作者Lorenzo Sola
相关产品推荐
相关产品推荐

