read 命令详解与参数实测(Ubuntu 22.04)
详解 bash 内置 read 命令:REPLY 与多变量拆分,-p/-t/-n/-N/-s/-a/-d/-r/-e/-i/-u 逐项实测,含 IFS 分隔符、while read 循环读文件与末行无换行的坑、超时确认菜单与密码静默输入实战。
read 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 bash 内置 read 命令的每一个常用参数——无参数 REPLY、多变量拆分、-p/-t/-n/-N/-s/-a/-d/-r/-e/-i/-u 逐项实测,以及 IFS 自定义分隔符、while read 循环读文件与末行无换行的坑,全部命令与回显已在 Ubuntu 22.04(bash 5.1.16)实测通过。
一、环境信息
本节记录实测环境的系统与 Shell 版本,后续所有命令与回显均来自该环境,可直接复现。
$ head -2 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
$ uname -m
x86_64
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- Shell 版本:GNU bash 5.1.16(1)-release
- 命令类型:read 为 bash 内置命令(builtin),没有独立的可执行文件
- 演示目录:/root/demo/read/(下文各小节的演示文件与脚本均保存在此目录)
二、命令概览
2.1 语法与测试数据
read 的语法(摘自 help 首行):
read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
read 的核心流程只有三步:从标准输入(或 -u 指定的文件描述符)读入一行 → 按 $IFS 拆分成字段 → 依次赋给 name1、name2……多余的词全部塞给最后一个 name;一个 name 都不写时整行存入 REPLY 变量。围绕这条主线,各参数只负责改造其中某一环(提示符、超时、字数、定界符、转义规则等)。
创建演示目录:
$ mkdir -p /root/demo/read
2.2 help 全量输出(逐行照抄)
help read 全文照抄如下(选项行的缩进为 help 原始输出中的制表符):
$ help read
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
if the -u option is supplied. The line is split into fields as with word
splitting, and the first word is assigned to the first NAME, the second
word to the second NAME, and so on, with any leftover words assigned to
the last NAME. Only the characters found in $IFS are recognized as word
delimiters.
If no NAMEs are supplied, the line read is stored in the REPLY variable.
Options:
-a array assign the words read to sequential indices of the array
variable ARRAY, starting at zero
-d delim continue until the first character of DELIM is read, rather
than newline
-e use Readline to obtain the line
-i text use TEXT as the initial text for Readline
-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than
NCHARS characters are read before the delimiter
-N nchars return only after reading exactly NCHARS characters, unless
EOF is encountered or read times out, ignoring any
delimiter
-p prompt output the string PROMPT without a trailing newline before
attempting to read
-r do not allow backslashes to escape any characters
-s do not echo input coming from a terminal
-t timeout time out and return failure if a complete line of
input is not read within TIMEOUT seconds. The value of the
TMOUT variable is the default timeout. TIMEOUT may be a
fractional number. If TIMEOUT is 0, read returns
immediately, without trying to read any data, returning
success only if input is available on the specified
file descriptor. The exit status is greater than 128
if the timeout is exceeded
-u fd read from file descriptor FD instead of the standard input
Exit Status:
The return code is zero, unless end-of-file is encountered, read times out
(in which case it's greater than 128), a variable assignment error occurs,
or an invalid file descriptor is supplied as the argument to -u.
重点信息:字段拆分只认 $IFS 中的字符;无 NAME 时存入 REPLY;超时时退出码大于 128;遇到 EOF 返回非零——这四条规则在后文实测中都会得到验证。
2.3 版本信息
$ bash --version | head -1
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
$ type read
read is a shell builtin
type read 确认 read 是 shell 内置命令,行为随 bash 版本而定,本文所有结论基于 bash 5.1.16。
三、参数逐项实测
3.1 无参数:整行存入 REPLY
不写变量名时,read 把整行(含空格)存入固定变量 REPLY。演示用 here-string(三个小于号)喂一行输入:
$ read <<< "hello world"; echo "REPLY=[$REPLY]"
REPLY=[hello world]
$ printf "ubuntu\n" | { read; echo "REPLY=[$REPLY]"; }
REPLY=[ubuntu]
两种喂输入的方式(here-string 与管道)效果一致,REPLY 都是完整的一行。
💡 要点:REPLY 是 read 的"无主"收容变量,每次 read 都会覆盖它;在循环里临时读一行做判断时用 REPLY 最省事,不用先想变量名。
3.2 多变量分配:按空白拆分、多余进最后一个
写出多个变量名时,read 按空白($IFS 默认值)拆分依次分配,多余的词全部归最后一个变量:
$ read a b c <<< "one two three"; echo "a=[$a] b=[$b] c=[$c]"
a=[one] b=[two] c=[three]
$ read a b c <<< "one two three four five"; echo "a=[$a] b=[$b] c=[$c]"
a=[one] b=[two] c=[three four five]
$ read a b <<< " hello world "; echo "a=[$a] b=[$b]"
a=[hello] b=[world]
$ read x y <<< "only-one"; echo "x=[$x] y=[$y]"
x=[only-one] y=[]
- 五个词分给三个变量:a/b 各得一个,c 把剩余 two 词整体收下;
- 行首尾与中间连续的空白被折叠,变量拿到的是干净值;
- 词比变量少时,多出来的变量为空。
💡 要点:"多余进最后一个"是解析"名称 + 带空格描述"这类行的天然工具;反之如果希望空白也原样保留,见 3.13 的 IFS= 写法。
3.3 -p:提示符
-p 在读取前输出提示字符串(不带换行)。演示脚本 /root/demo/read/prompt-demo.sh:
#!/usr/bin/env bash
read -p "终端输入: " t
echo
echo "t=[$t]"
终端形态(用 script 挂一个伪终端实测)下,提示符正常显示、输入被回显:
$ script -qec "bash /root/demo/read/prompt-demo.sh" /dev/null <<< "typed-value"
typed-value
终端输入:
t=[typed-value]
第一行是伪终端先回显的键入内容(喂入早于程序打印提示符所致),第二行是提示符本身。但要注意一个实测发现:标准输入不是终端时,提示符根本不会打印:
$ read -p "请输入用户名: " uname <<< "alice"; echo "uname=[$uname]"
uname=[alice]
$ echo alice | { read -p "用户名: " u; echo "u=[$u]"; }
u=[alice]
$ read -p "路径: " d < /dev/null; echo "d=[$d]"
d=[]
三组命令都拿到了值,但没有一组出现提示符——here-string、管道、文件重定向喂输入时 bash 5.1.16 均不输出 -p 提示符,只有真实终端(上例 script 伪终端)才显示。需要"输入源不固定也想看到提示"时,自行向 stderr 打印即可:
$ printf "请输入: " >&2; read u <<< "bob"; echo; echo "u=[$u]"
请输入:
u=[bob]
💡 要点:脚本里
read -p的提示符在echo xx | read -p ...、定时任务等场景下不会出现,交互提示请用printf "提示" >&2或确保从终端读取。
3.4 -t:超时
-t 限定等待秒数(支持小数),超时未读到完整一行则返回大于 128 的退出码(实测 142)。先测"永远等不到输入":
$ read -t 2 answer < <(sleep 5); echo rc=$?
rc=142
sleep 5 让输入端保持打开但 5 秒内无数据,read 等了 2 秒就超时返回 rc=142,没有任何错误文本输出。再测"输入及时到达":
$ read -t 2 answer <<< "fast input"; echo "rc=$? answer=[$answer]"
rc=0 answer=[fast input]
小数超时同样生效,time 计时确认确实只等了 0.5 秒:
$ read -t 0.5 answer < <(sleep 3); echo rc=$?
rc=142
$ time read -t 0.5 answer < <(sleep 3) 2>/dev/null; echo rc=$?
real 0m0.500s
user 0m0.000s
sys 0m0.000s
rc=142
💡 要点:判断超时不要写 rc 等于某个数,按 help 约定用区间判断
(( rc > 128 ));超时路径是交互脚本"按默认值处理"的入口(见 4.1)。
3.5 -n N:读到 N 个字符即返回
-n 读满 N 个字符就返回,不必等换行(若先遇到定界符则提前返回):
$ printf "abc" | { read -n 1 ch; echo "ch=[$ch]"; }
ch=[a]
$ printf "abc" | { read -n 2 part; echo "part=[$part]"; }
part=[ab]
输入没有换行符,-n 1 仍只取 1 个字符 a 就返回;-n 2 取 ab。交互终端上这就是"按一个键立即响应"的实现基础。
💡 要点:-n N 的返回不消耗剩余输入——上例中 c 还留在管道里;配合循环可以逐字符解析输入流。
3.6 -N N:必须读满 N 个字符(含空白与换行)
-N 与 -n 的差别在 help 里说得明确:-n "读到 NCHARS 个字符或先遇到定界符就返回",-N "除非 EOF 或超时,必须读满 NCHARS 个字符,忽略任何定界符"。用同一份输入对照:
$ printf "ab\ncd" | { read -n 10 x; echo "-n 10 => x=[$x]"; }
-n 10 => x=[ab]
$ printf "ab\ncd" | { read -N 10 y; echo "-N 10 => y=[$y]"; }
-N 10 => y=[ab
cd]
$ printf "a b c" | { read -N 3 z; echo "-N 3 => z=[$z]"; }
-N 3 => z=[a b]
- -n 10 遇到换行(默认定界符)提前返回,只拿到 ab;
- -N 10 无视换行,把 ab、换行、cd 一共 4 个字符全部读入(不足 10 个是因为 EOF 先到),所以 y 跨了两行;
- -N 3 按字符计数,空格也算一个字符,读满 3 个字符 a、空格、b 返回。
💡 要点:读"定长记录"(如固定宽度日志、二进制头)用 -N,读"最多 N 个字符的按键"用 -n。
3.7 -s:静默读入(密码场景)
-s 不回显来自终端的输入,是 shell 里实现密码输入的标准手段。但先看一个实测陷阱——管道会把 read 放进子 shell,变量赋不回来:
$ echo q1w2e3r4 | read -s pw; echo "pw=[$pw]"
pw=[]
$ read -s pw <<< "q1w2e3r4"; echo "长度=${#pw} pw=[$pw]"
长度=8 pw=[q1w2e3r4]
管道形式 pw 为空(read 在子 shell 里赋值,随子 shell 消失);here-string 形式在当前 shell 执行,pw 正常拿到 8 个字符。真实终端下的静默效果用两次密码校验脚本验证(/root/demo/read/passwd.sh,实战见 4.2):
#!/usr/bin/env bash
read -s -p "设置密码: " p1; echo
read -s -p "再次输入: " p2; echo
if [ "$p1" = "$p2" ] && [ ${#p1} -ge 6 ]; then
echo "密码设置成功(长度 ${#p1})"
else
echo "两次输入不一致或长度不足 6"
fi
伪终端实测(延迟 1 秒再"键入",模拟真人节奏):
$ script -qec "bash /root/demo/read/passwd.sh" /dev/null < <(sleep 1; printf "q1w2e3r4\n"; sleep 1; printf "q1w2e3r4\n")
设置密码:
再次输入:
密码设置成功(长度 8)
两个提示符之后没有任何回显——-s 生效,密码没有出现在屏幕上。
💡 要点:-s 只是不回显,变量里仍是明文;流程结束后
unset pw是好习惯。管道丢变量的根因与修复见 Q3。
3.8 -a:拆分为数组
-a 把拆分出的字段按序号 0、1、2 装进数组:
$ read -a arr <<< "nginx 8080 running"; echo "arr[0]=${arr[0]} arr[1]=${arr[1]} arr[2]=${arr[2]} 个数=${#arr[@]}"
arr[0]=nginx arr[1]=8080 arr[2]=running 个数=3
$ IFS="," read -a csv <<< "10,20,30"; echo "csv[1]=${csv[1]}"
csv[1]=20
第二行把 IFS 临时改成逗号,一行 CSV 直接变成数组,不必先 tr 再拆。
💡 要点:
IFS="," read -a csv这种"前置赋值 + read"的写法只影响这一条命令的 IFS,不污染当前 shell,是处理单行多分隔符的首选。
3.9 -d:自定义定界符
-d 改变"一行"的结束字符:读到 DELIM 首字符即返回,而不是等换行。
$ printf "first|second|third|" | { read -d "|" part; echo "part=[$part]"; }
part=[first]
$ printf "整个文件当作一条记录读入" > /root/demo/read/whole.txt
$ read -d "" whole < /root/demo/read/whole.txt; echo "whole=[$whole]"
whole=[整个文件当作一条记录读入]
- 第一组:以 | 为定界符,read 停在第一个 | 之前,只拿 first,其余留在输入流里;
- 第二组:-d ""(空定界符)配合文件重定向,把整个文件内容一次读入一个变量——这是 read 读取多行文本的常用技巧。
再看 -d 与字段拆分的区别(实测):
$ printf "key=value" | { read -d = k v; echo "k=[$k] v=[$v]"; }
k=[key] v=[]
$ printf "key=value=tail" | { read -d = k v; echo "k=[$k] v=[$v]"; }
k=[key] v=[]
-d 只决定"读到哪停",不负责拆字段:read 停在第一个 = 处,后面整个 value 根本没被读入,v 是空。要按 = 拆字段应该用 IFS(见 3.13)。
💡 要点:记牢分工——IFS 管"怎么拆",-d 管"读到哪算一条记录";
read -d "" whole < file是不依赖 cat 一次性读入全文件的写法。
3.10 -r:反斜杠不转义
默认 read 把反斜杠当转义前缀处理,会"吃掉"它;-r(raw)禁止转义。对照实验:
$ read line <<< "C:\path\to\file"; echo "无 -r => [$line]"
无 -r => [C:pathtofile]
$ read -r line <<< "C:\path\to\file"; echo "有 -r => [$line]"
有 -r => [C:\path\to\file]
无 -r 时 \p、\t、\f 都被解释成普通字母,Windows 路径变成了 C:pathtofile;加 -r 后原样保留。help 原文一句话:-r do not allow backslashes to escape any characters。
💡 要点:while read 循环不加 -r 迟早会在含 \ 的行上翻车,
while IFS= read -r line是逐行处理的固定搭配(见 3.13、3.14)。
3.11 -e 与 -i:readline 行编辑与默认值
-e 启用 readline 读入(方向键编辑、Tab 补全);-i 为 readline 提供初始文本(默认值)。演示脚本 /root/demo/read/readline-demo.sh:
#!/usr/bin/env bash
read -e -p "目录: " -i /root/demo/read v
echo
echo "v=[$v]"
伪终端实测——提示符后直接显示了预填的默认值,模拟用户直接回车接受默认:
$ script -qec "bash /root/demo/read/readline-demo.sh" /dev/null <<< ""
目录: /root/demo/read
v=[/root/demo/read]
注意 -i 的生效条件是真实/模拟终端(readline 工作):
$ read -e -p "目录: " -i /root/demo/read v <<< ""; echo "非终端下 v=[$v]"
非终端下 v=[]
非终端输入下 readline 不参与,-i 的默认值没有生效,变量拿到的是 here-string 里的空行;提示符同样没有打印(见 3.3 的实测规律)。
💡 要点:
read -e -p "提示: " -i "默认值" var是交互脚本"带默认值确认"的标准形态;-e 的另一价值是 Tab 补全文件路径。
3.12 -u:从指定文件描述符读取
-u 让 read 从指定文件描述符(fd)读输入,可与其他输入源并存、顺序推进:
$ printf "nginx\nredis\nmysql\n" > /root/demo/read/svcs.txt
$ exec 3< /root/demo/read/svcs.txt; read -u 3 line1; read -u 3 line2; echo "line1=[$line1] line2=[$line2]"; exec 3<&-
line1=[nginx] line2=[redis]
exec 3< 打开 fd 3 指向文件,两次 read -u 3 依次读到第一、二行(文件指针随读取前进),exec 3<&- 关闭描述符。
💡 要点:-u 的典型场景是"同时读两个数据源"——例如 fd 3 读配置、fd 4 读队列,逐条配对处理;不指定 -u 时 read 默认读 fd 0(标准输入)。
3.13 IFS:自定义字段分隔符
read 的拆分规则完全由 IFS 决定。前置赋值 IFS=: 只对本条 read 生效:
$ IFS=: read user pass <<< "root:q1w2e3r4"; echo "user=[$user] pass=[$pass]"
user=[root] pass=[q1w2e3r4]
$ IFS=: read a b c <<< "1:2:3:4:5"; echo "a=[$a] b=[$b] c=[$c]"
a=[1] b=[2] c=[3:4:5]
冒号拆分同样遵循"多余字段进最后一个变量"(3.2 的规则)。另一个方向——IFS= 置空则禁用拆分,整行原样保留(对照 3.10 的 -r):
$ printf " 前后有空格的行 \n" > /root/demo/read/spaces.txt
$ while read -r line; do echo "[$line]"; done < /root/demo/read/spaces.txt
[前后有空格的行]
$ while IFS= read -r line; do echo "[$line]"; done < /root/demo/read/spaces.txt
[ 前后有空格的行 ]
默认 IFS 下行首尾空白被剥掉;IFS= 后连空白一起原样读入。
💡 要点:
while IFS= read -r line三件套(IFS= 不拆分、-r 不转义、read 逐行)是逐行处理文本的黄金写法;需要拆字段时再局部放开 IFS。
3.14 while read 循环读文件
while read 是逐行处理文件的标准骨架。重定向形态(循环在当前 shell):
$ printf "nginx\nredis\nmysql\n" > /root/demo/read/svcs.txt
$ while read -r line; do echo "服务: $line"; done < /root/demo/read/svcs.txt
服务: nginx
服务: redis
服务: mysql
管道形态(循环体在子 shell):
$ cat /root/demo/read/svcs.txt | while read -r line; do echo "服务: $line"; done
服务: nginx
服务: redis
服务: mysql
两种形态输出相同,差异在变量作用域:管道右侧 while 在子 shell 里执行,循环内修改的变量(计数器、累加值)出了循环就丢失,重定向形态没有这个问题。配合 IFS 拆字段逐行解析:
$ printf "root:0\ndaemon:1\nsys:2\n" > /root/demo/read/uid.txt
$ while IFS=: read -r user uid; do echo "$user 的 UID 是 $uid"; done < /root/demo/read/uid.txt
root 的 UID 是 0
daemon 的 UID 是 1
sys 的 UID 是 2
末行无换行的坑——生成一个最后一行没有换行符的文件:
$ printf "a\nb\nc" > /root/demo/read/nonewline.txt
$ while read -r line; do echo "[$line]"; done < /root/demo/read/nonewline.txt
[a]
[b]
$ while read -r line || [ -n "$line" ]; do echo "[$line]"; done < /root/demo/read/nonewline.txt
[a]
[b]
[c]
读 c 那一行时 read 已到 EOF,返回非零导致循环提前结束——但变量里其实已经有 c 了。修复写法是在循环条件后追加 || [ -n "$line" ]:read 失败但变量非空时继续处理本轮。EOF 的返回码单独验证:
$ printf "" | { read -r x; echo rc=$?; }
rc=1
空输入直接 EOF,read 返回 rc=1,与 help 的 Exit Status 描述一致。
💡 技术拓展:逐行处理文本不止 while read 一条路,常见方式对比——
方式 特点 适用场景 while read(bash 内置) 逐行读入当前 shell,可随意拆字段、控制 IFS,行内容全程可控 需要逐行解析字段、逐行交互处理的脚本 cat 一次性把整个文件写到 stdout,自身不做任何解析 查看内容、拼接文件 awk 按模式与字段规则逐行解析并计算,单条命令完成统计 字段规整的结构化文本汇总统计 sed 逐行做替换、删除、打印等编辑 流式文本改写 本文主题是 read:需要"每行做 shell 逻辑"(调接口、拼命令、读写变量)时选 while read;纯文本统计交给 awk 更简洁。
四、实战示例
4.1 超时确认菜单(-p + -t + 超时走默认值)
运维脚本经典的"5 秒不输入按默认处理":超时(rc 大于 128)走默认分支,输入 y 走确认分支。脚本 /root/demo/read/confirm.sh:
#!/usr/bin/env bash
read -t 5 -p "是否继续? [y/N] " ans
rc=$?
if (( rc > 128 )); then
echo
echo "超时未输入,按默认 N 处理"
elif [[ "$ans" =~ ^[Yy]$ ]]; then
echo "继续执行"
else
echo "已取消"
fi
及时输入 y 的实测:
$ script -qec "bash /root/demo/read/confirm.sh" /dev/null < <(sleep 1; printf "y\n")
是否继续? [y/N] y
继续执行
5 秒内不给输入的实测:
$ script -qec "bash /root/demo/read/confirm.sh" /dev/null < <(sleep 7)
是否继续? [y/N]
超时未输入,按默认 N 处理
提示符显示、键入的 y 回显在提示符后;超时分支先补一个换行(把停在提示符后的光标换下来)再提示默认动作。
4.2 两次密码输入校验(-s 静默)
使用 3.7 的 /root/demo/read/passwd.sh,再验证两次输入不一致的分支:
$ script -qec "bash /root/demo/read/passwd.sh" /dev/null < <(sleep 1; printf "q1w2e3r4\n"; sleep 1; printf "short\n")
设置密码:
再次输入:
两次输入不一致或长度不足 6
两次输入全程无回显;short 长度不足 6 且与第一次不一致,脚本落到失败分支。生产中记得校验后 unset 变量。
4.3 解析 key=value 配置文件(跳过注释与空行)
先造一份带注释和空行的配置:
$ printf "# 服务配置\nhost=127.0.0.1\nport=8080\n\nmode=standalone\n" > /root/demo/read/app.conf
用 IFS= read -r 保留原行,case 跳过注释与空行,参数展开拆键值:
$ while IFS= read -r line; do case "$line" in "#"*|"") continue ;; esac; echo "键=${line%%=*} 值=${line#*=}"; done < /root/demo/read/app.conf
键=host 值=127.0.0.1
键=port 值=8080
键=mode 值=standalone
${line%%=*} 取第一个 = 之前的键,${line#*=} 取其后的值;遇到 # 开头或空行直接 continue。整套逻辑只有一个 bash 进程,比 grep + cut 拼管道更轻。
4.4 从 /etc/passwd 提取用户与 UID
冒号分隔的 /etc/passwd 是 IFS=: read 的教科书场景(用 head 限定前三行演示):
$ while IFS=: read -r user pass uid gid rest; do echo "$user -> UID=$uid GID=$gid"; done < <(head -3 /etc/passwd)
root -> UID=0 GID=0
daemon -> UID=1 GID=1
bin -> UID=2 GID=2
< <(head -3 /etc/passwd) 是进程替换:head 的输出当作文件喂给 while,循环仍在当前 shell 执行,规避了管道子 shell 的变量丢失问题。
4.5 read -a 拆日志字段
一行日志按空格拆进数组,前几个字段按序号取,剩余的消息体用数组切片整体取出:
$ logline="2026-10-16 10:00:01 INFO backup finished"; read -a f <<< "$logline"; echo "日期=${f[0]} 时间=${f[1]} 级别=${f[2]} 消息=${f[*]:3}"
日期=2026-10-16 时间=10:00:01 级别=INFO 消息=backup finished
${f[*]:3} 表示从下标 3 开始的所有元素,正好覆盖含空格的消息部分,与 3.2 的"多余进最后一个变量"异曲同工。
五、注意事项与常见问题
Q1:while read 循环为什么总是丢最后一行?
最后一行没有换行符时,read 读到 EOF 返回非零,循环条件失败提前退出,但行内容已经写进了变量。修复:while read -r line || [ -n "$line" ]; do ...; done < file(见 3.14 实测)。也可以在生成端保证末行有换行。
Q2:读进来的行反斜杠不见了、行首尾空白也没了?
两件事两个开关:反斜杠被默认的转义处理吃掉,加 -r;行首尾空白是默认 IFS 的空白剥离,加 IFS=。合起来就是惯用法 while IFS= read -r line(见 3.10、3.13 对照实验)。
Q3:管道里的 read(或 while read)为什么循环外变量全丢?
管道每一段都是子 shell,read/while 在子 shell 里赋的值随其退出而消失(3.7 中 echo q1w2e3r4 | read -s pw 的 pw 为空即此因)。改用重定向 while ...; done < file 或进程替换 done < <(cmd);bash 下还可 shopt -s lastpipe 让管道最后一段留在当前 shell 执行(需关闭 job control 的非交互脚本环境)。
Q4:read -p 的提示符怎么不显示?
实测 bash 5.1.16 只在标准输入是终端时打印 -p 提示符;here-string、管道、重定向喂输入时提示符静默(见 3.3 三组对照)。需要提示时用 printf "提示" >&2 自行输出,或保证从终端读取。
Q5:read -t 超时的返回码是多少、怎么判断?
大于 128,本环境实测为 142。判断用区间 (( rc > 128 )),不要与具体数字硬编码绑定(不同场景可能是 142 及以上),成功时 rc=0、EOF 时 rc=1(见 3.4、3.14)。
Q6:-n 和 -N 都是读 N 个字符,到底差在哪?
-n 读满 N 个字符或先遇到定界符就返回(换行算定界符);-N 无视一切定界符,必须读满 N 个字符,除非 EOF 或超时。同一输入 printf "ab\ncd" 的实测对照见 3.6:-n 10 拿到 ab,-N 10 拿到跨行的 ab 与 cd。
Q7:read 能一次读入整个文件吗?
可以:read -d "" whole < file,用空定界符一路读到 EOF(见 3.9 实测)。注意该写法把整个文件读进一个变量,大文件会占用大量内存,逐行处理仍应使用 while read。
六、参考链接
- GNU Bash 手册:Bash Builtins(read 条目)—— https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html
- bash(1) man 手册(man7.org)—— https://man7.org/linux/man-pages/man1/bash.1.html
- GNU Bash 手册:Readline(-e/-i 的行编辑能力)—— https://www.gnu.org/software/bash/manual/html_node/Readline.html
- GNU Bash 手册:Word Splitting(IFS 与字段拆分规则)—— https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html
- 本机速查:
help read、man bash(搜索read builtin)