
Shell 脚本跟用户打交道靠的就是输入输出这几件事read从键盘读变量echo、printf往外打印。看着都是最基础的命令真用起来一堆细节——read怎么隐藏密码、echo为什么要加-e、printf怎么对齐列、日志里怎么打出一行红字。这一篇把它们逐个拆开。一、read从标准输入读变量语法格式read[选项]变量名...不带变量名时读进来的内容默认存进REPLY这是 bash 的约定不是 bug。常用参数参数说明-p 提示串把提示打到标准错误不换行紧跟在输入位置等用户敲-s静默模式输入不回显输密码用-t 秒数超时返回非零脚本里用来做默认继续-n 字符数读满 N 个字符就返回不用按回车-a 数组名把输入按 IFS 拆成数组-d 分隔符用指定字符代替换行作为结束符示例与预期输出最基础的交互read-p请输入你的名字: nameecho你好,$name预期输出用户敲Ben后回车请输入你的名字: Ben 你好, Ben静默读密码read-s-p请输入密码: pwechoecho长度是${#pw}敲密码时屏幕一个字符都不显示最后一行打长度。超时给默认值read-t5-p5 秒内按 Y 继续其它任意键退出: ansans${ans:-N}[$ansY]echo继续||echo退出5 秒不敲ans是空串${ans:-N}落回默认值 N。一行拆进数组read-apartsapple banana cherryecho第一个:${parts[0]}, 共${#parts[]}个预期输出第一个: apple, 共 3 个⚠️ 常见坑-p后面的提示串含空格或中文必须加引号否则 shell 会把空格后的部分当成另一个参数。从管道或文件喂read时循环里的read会把后面所有输入一次性吃掉while read那一篇专门讲。read -s在没有 tty 的环境cron、管道里不生效因为它要改终端属性。一行一次读多个变量也很常见按 IFS 拆read-p输入姓名和年龄: name ageecho$name今年$age岁预期输出输入Ben 25输入姓名和年龄: Ben 25 Ben 今年 25 岁输入段数比变量多最后一个变量吃掉剩余全部。比如只读name age两个变量敲Ben 25 Beijingage会拿到25 Beijing。二、echo最常用的打印常用参数参数说明-n行尾不加换行-e开启转义解释识别\n\t\\-E关闭转义bash 默认行为示例与预期输出echohelloecho-n不换行echo 接着上面预期输出hello 不换行 接着上面用-e打制表和换行echo-e姓名\tBen\n年龄\t25姓名 Ben 年龄 25常见转义符写法含义\n换行\t制表符\\反斜杠本身\a响铃有些终端会嘀一声\033/\eESC 字符颜色码的开头注意echo不带-e时\n原样打印成两个字符。这是新手最常遇到的为什么我的 \n 不换行。⚠️ 注意echo在 bash 和 dash 下对-e的默认处理不一样。脚本要跨发行版跑比如 Debian 默认sh是 dash格式化输出交给printfecho只用来打一行简单文本。另外echo $var不加引号变量里带空格会被 IFS 拆开还会把*.log这种通配符展开这个坑在下面 printf 一节一起说。三、printf格式化输出跨 shell 一致printf模仿 C 语言的同名函数格式串后面跟参数。它不自动加换行这是和echo最大的区别。常用格式符格式符含义%s字符串%d十进制整数%f浮点数%x十六进制%%百分号本身宽度和对齐写在%和字母之间%10s右对齐占 10 位%-10s左对齐%.2f保留两位小数。示例与预期输出printf%-10s %6s\n姓名 年龄printf%-10s %6d\nBen25printf%-10s %6d\nAlice30预期输出姓名 年龄 Ben 25 Alice 30%-10s左对齐占 10 个字符宽%6d右对齐占 6 位两列自动对齐。这就是printf比echo强的地方——做表格、对齐数字echo做不到。保留两位小数printfPi %.2f\n3.14159Pi 3.14循环里用printf打对齐的表whileread-ruser uid;doprintf%-15s %6d\n$user$uiddoneEOF root 0 zhangsan 1000 lisi 1001 EOF预期输出root 0 zhangsan 1000 lisi 1001%-15s左对齐占 15 位短名字自动补空格长名字顶出来也不影响下一列。这种表用echo拼不出来。⚠️ 常见错误忘记加\n所有输出挤在一行。格式符数量多于参数剩下的%s填空串、%d填 0参数多于格式符格式串会从头再用一遍。写循环打印时这个从头再用经常导致意外重复。四、颜色输出ANSI 转义码终端里打红字本质是往输出里塞一段特殊字符序列终端模拟器识别后改颜色。这段序列以\033[开头、以m结束。常用颜色码编码含义0恢复默认1加粗31前景红32前景绿33前景黄34前景蓝37前景白41背景红42背景绿封装成变量用RED\033[31mGREEN\033[32mYELLOW\033[33mNC\033[0mecho-e${RED}错误: 备份失败${NC}echo-e${GREEN}成功: 备份完成${NC}echo-e${YELLOW}警告: 磁盘剩余不足 10%${NC}屏幕上三行分别是红、绿、黄。每次打完必须用${NC}复位否则后面所有输出都带着这个颜色连下一个命令提示符都会变色。用tput更可读也不依赖硬编码码值RED$(tput setaf1)GREEN$(tput setaf2)NC$(tput sgr0)echo${RED}错误${NC}⚠️ 注意重定向到文件时颜色码会变成一堆^[[31m乱字符写进日志。判断一下输出是不是连着终端是才上色if[-t1];thenRED$(tput setaf1)GREEN$(tput setaf2)NC$(tput sgr0)elseRED;GREEN;NCfi-t 1测试标准输出是否连着终端是写彩色日志的标配。五、知识扩展read 为什么非得按回车read拿到一个字符就返回吗默认不是。这跟终端工作在 canonical规范模式有关内核的终端驱动会自己把一行字符攒起来、处理退格和 CtrlU攒够一个换行才把整行交给read。所以你敲一半按退格改字read根本看不到中间过程它只收到最终那一行。-n N和-s其实是偷偷把终端切到非规范模式或关掉回显读完再切回来。这也是为什么在管道里read -s读不到密码——没有 tty 可切。写交互式脚本遇到read 怎么不响应按键先检查是不是被 cron 或管道包着那种环境下根本没有终端。ANSI 转义码同理它不是 shell 干的是终端模拟器在解释。把带颜色的输出重定向到文件、再cat出来文件里存的就是那串\033[31m放到不支持转义的终端某些串口控制台、老的网络设备 CLI它就老老实实显示成乱码。颜色是终端的能力不是文本的一部分。