You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用纯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------------------------------------------------------------
-----------------------------------------------------------------
-----------------------------------------------

解决方案

核心思路是手动控制文本换行,避免终端自动换行:

  1. 获取终端实际列数,兼容不同终端大小
  2. 对每一段文本按单词拆分,确保每行长度不超过「终端列数 - 4」(预留左右| 和 |的占位)
  3. 基于拆分后的行计算框的宽度,再绘制边框

修改后的完整代码:

#!/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"
}

修改说明

  1. 新增wrap_text函数:按单词拆分超长文本,生成符合终端宽度的多行内容,保留原文本的换行符
  2. 动态获取终端列数:用tput cols获取实际终端宽度,避免硬编码
  3. 简化边框绘制逻辑:先绘制顶部边框,再逐行绘制带填充空格的文本行,最后绘制底部边框,避免光标定位带来的异常
  4. 自适应框宽度:基于拆分后的最长文本行计算框的总宽度,确保不会超出终端范围

测试效果

调用超长文本时,会自动拆分并生成完整的框:

~$ 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 14:33:11