Shell输入输出详解:read、echo、printf、颜色输出

Shell 脚本跟用户打交道,靠的就是输入输出这几件事:read 从键盘读变量,echo、printf 往外打印。看着都是最基础的命令,真用起来一堆细节------read 怎么隐藏密码、echo 为什么要加 -e、printf 怎么对齐列、日志里怎么打出一行红字。这一篇把它们逐个拆开。

一、read:从标准输入读变量

语法格式

bash 复制代码
read [选项] 变量名...

不带变量名时,读进来的内容默认存进 REPLY,这是 bash 的约定,不是 bug。

常用参数

参数 说明
-p 提示串 把提示打到标准错误,不换行,紧跟在输入位置等用户敲
-s 静默模式,输入不回显,输密码用
-t 秒数 超时返回非零,脚本里用来做"默认继续"
-n 字符数 读满 N 个字符就返回,不用按回车
-a 数组名 把输入按 IFS 拆成数组
-d 分隔符 用指定字符代替换行作为结束符

示例与预期输出

最基础的交互:

bash 复制代码
read -p "请输入你的名字: " name
echo "你好, $name"

预期输出(用户敲 Ben 后回车):

text 复制代码
请输入你的名字: Ben
你好, Ben

静默读密码:

bash 复制代码
read -s -p "请输入密码: " pw
echo
echo "长度是 ${#pw}"

敲密码时屏幕一个字符都不显示,最后一行打长度。

超时给默认值:

bash 复制代码
read -t 5 -p "5 秒内按 Y 继续,其它任意键退出: " ans
ans=${ans:-N}
[ "$ans" = "Y" ] && echo "继续" || echo "退出"

5 秒不敲,ans 是空串,${ans:-N} 落回默认值 N。

一行拆进数组:

bash 复制代码
read -a parts <<< "apple banana cherry"
echo "第一个: ${parts[0]}, 共 ${#parts[@]} 个"

预期输出:

text 复制代码
第一个: apple, 共 3 个

⚠️ 常见坑

  1. -p 后面的提示串含空格或中文,必须加引号,否则 shell 会把空格后的部分当成另一个参数。
  2. 从管道或文件喂 read 时,循环里的 read 会把后面所有输入一次性吃掉(while read 那一篇专门讲)。
  3. read -s 在没有 tty 的环境(cron、管道)里不生效,因为它要改终端属性。

一行一次读多个变量也很常见,按 IFS 拆:

bash 复制代码
read -p "输入姓名和年龄: " name age
echo "$name 今年 $age 岁"

预期输出(输入 Ben 25):

text 复制代码
输入姓名和年龄: Ben 25
Ben 今年 25 岁

输入段数比变量多,最后一个变量吃掉剩余全部。比如只读 name age 两个变量,敲 Ben 25 Beijing,age 会拿到 25 Beijing。

二、echo:最常用的打印

常用参数

参数 说明
-n 行尾不加换行
-e 开启转义解释,识别 \n \t \\
-E 关闭转义(bash 默认行为)

示例与预期输出

bash 复制代码
echo "hello"
echo -n "不换行"
echo " 接着上面"

预期输出:

text 复制代码
hello
不换行 接着上面

用 -e 打制表和换行:

bash 复制代码
echo -e "姓名\tBen\n年龄\t25"
text 复制代码
姓名    Ben
年龄    25

常见转义符:

写法 含义
\n 换行
\t 制表符
\\ 反斜杠本身
\a 响铃(有些终端会"嘀"一声)
\033 / \e ESC 字符,颜色码的开头

注意:echo 不带 -e 时,\n 原样打印成两个字符。这是新手最常遇到的"为什么我的 \n 不换行"。

⚠️ 注意

echo 在 bash 和 dash 下对 -e 的默认处理不一样。脚本要跨发行版跑(比如 Debian 默认 sh 是 dash),格式化输出交给 printf,echo 只用来打一行简单文本。另外 echo $var 不加引号,变量里带空格会被 IFS 拆开,还会把 *.log 这种通配符展开,这个坑在下面 printf 一节一起说。

三、printf:格式化输出,跨 shell 一致

printf 模仿 C 语言的同名函数,格式串后面跟参数。它不自动加换行,这是和 echo 最大的区别。

常用格式符

格式符 含义
%s 字符串
%d 十进制整数
%f 浮点数
%x 十六进制
%% 百分号本身

宽度和对齐写在 % 和字母之间:%10s 右对齐占 10 位,%-10s 左对齐,%.2f 保留两位小数。

示例与预期输出

bash 复制代码
printf "%-10s %6s\n" 姓名 年龄
printf "%-10s %6d\n" Ben 25
printf "%-10s %6d\n" Alice 30

预期输出:

text 复制代码
姓名          年龄
Ben            25
Alice          30

%-10s 左对齐占 10 个字符宽,%6d 右对齐占 6 位,两列自动对齐。这就是 printf 比 echo 强的地方------做表格、对齐数字,echo 做不到。

保留两位小数:

bash 复制代码
printf "Pi = %.2f\n" 3.14159
text 复制代码
Pi = 3.14

循环里用 printf 打对齐的表:

bash 复制代码
while read -r user uid; do
  printf "%-15s %6d\n" "$user" "$uid"
done <<'EOF'
root 0
zhangsan 1000
lisi 1001
EOF

预期输出:

text 复制代码
root                 0
zhangsan          1000
lisi              1001

%-15s 左对齐占 15 位,短名字自动补空格,长名字顶出来也不影响下一列。这种表用 echo 拼不出来。

⚠️ 常见错误

  1. 忘记加 \n,所有输出挤在一行。
  2. 格式符数量多于参数,剩下的 %s 填空串、%d 填 0;参数多于格式符,格式串会从头再用一遍。写循环打印时这个"从头再用"经常导致意外重复。

四、颜色输出:ANSI 转义码

终端里打红字,本质是往输出里塞一段特殊字符序列,终端模拟器识别后改颜色。这段序列以 \033[ 开头、以 m 结束。

常用颜色码

编码 含义
0 恢复默认
1 加粗
31 前景红
32 前景绿
33 前景黄
34 前景蓝
37 前景白
41 背景红
42 背景绿

封装成变量用

bash 复制代码
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
NC='\033[0m'

echo -e "${RED}错误: 备份失败${NC}"
echo -e "${GREEN}成功: 备份完成${NC}"
echo -e "${YELLOW}警告: 磁盘剩余不足 10%${NC}"

屏幕上三行分别是红、绿、黄。每次打完必须用 ${NC} 复位,否则后面所有输出都带着这个颜色,连下一个命令提示符都会变色。

用 tput 更可读,也不依赖硬编码码值:

bash 复制代码
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
NC=$(tput sgr0)
echo "${RED}错误${NC}"

⚠️ 注意

重定向到文件时,颜色码会变成一堆 ^[[31m 乱字符写进日志。判断一下输出是不是连着终端,是才上色:

bash 复制代码
if [ -t 1 ]; then
  RED=$(tput setaf 1)
  GREEN=$(tput setaf 2)
  NC=$(tput sgr0)
else
  RED=""; GREEN=""; NC=""
fi

-t 1 测试标准输出是否连着终端,是写彩色日志的标配。

五、知识扩展:read 为什么非得按回车

read 拿到一个字符就返回吗?默认不是。这跟终端工作在 canonical(规范)模式有关:内核的终端驱动会自己把一行字符攒起来、处理退格和 Ctrl+U,攒够一个换行才把整行交给 read。所以你敲一半按退格改字,read 根本看不到中间过程,它只收到最终那一行。

-n N 和 -s 其实是偷偷把终端切到非规范模式(或关掉回显),读完再切回来。这也是为什么在管道里 read -s 读不到密码------没有 tty 可切。写交互式脚本遇到"read 怎么不响应按键",先检查是不是被 cron 或管道包着,那种环境下根本没有终端。

ANSI 转义码同理:它不是 shell 干的,是终端模拟器在解释。把带颜色的输出重定向到文件、再 cat 出来,文件里存的就是那串 \033[31m;放到不支持转义的终端(某些串口控制台、老的网络设备 CLI),它就老老实实显示成乱码。颜色是终端的能力,不是文本的一部分。

相关推荐
和裕41 分钟前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
FPGA小徐1 小时前
从 NEMU 到 AbstractMachine:程序、运行时环境与 AM 完全解析
linux·运维·服务器
呉師傅2 小时前
得力P2500、M2500系列打印机硒鼓加粉及清零方法【纯享版】
运维·网络·windows·电脑
Sayai2 小时前
Elasticsearch 日志检索 DSL 实战:时间范围查询、字段去重、分钟级统计与最新日志获取
大数据·运维·elasticsearch·搜索引擎·日志分析
qetfw2 小时前
Debian Chrony 时间同步、RAID5 与 LUKS 磁盘加密配置
linux·debian
fb_123454 小时前
MySQL运维实战:备份恢复+主从复制+读写分离+MHA高可用(超详细手把手教程)
运维·mysql·oracle
东城居士5 小时前
Linux驱动阻塞与非阻塞访问的理解
linux·嵌入式系统
..Dauntless..5 小时前
【Linux】进程地址空间初步理解
linux·运维·服务器
Julien20045 小时前
Docker 网络(一)
linux·运维·服务器·ssh·学习方法