write 命令详解与参数实测(Ubuntu 22.04)
write 命令逐参数实测详解:多终端登录的警告与默认投递、指定终端定点投递与 Message 横幅逐段解读、文件输入重定向、跨用户投递与 mesg 开关(root 不受限)、EOF 结束机制与中文八进制转义现象,全部回显在 Ubuntu 22.04 实测通过。
write 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 write 的每一种用法——
write user不指定终端时的多终端警告与默认投递、write user tty定点投递与 Message 横幅逐段解读、文件输入重定向、跨用户投递与接收方 mesg 开关(mesg y放行 /mesg n拦截 / root 不受限)、EOF 结束机制与中文消息的八进制转义现象,全部命令与回显已在 Ubuntu 22.04 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- write 实现:util-linux 的 write(
/usr/bin/write软链接到/usr/bin/write.ul) - write 版本:util-linux 2.37.2(
write --version实测;包 bsdextrautils 2.37.2-4ubuntu3.6,见 2.3) - 演示用户:
root(同时「登录」三个终端)与普通用户tester(主组 tty,原因见 3.5) - 演示终端:pts/1、pts/2(mesg 默认允许)与 pts/3(已执行
mesg n),均为script会话模拟的第二终端;会话通过 util-linux 的 utmpdump 登记进 utmp,finger -s可见:
$ finger -s
Login Name Tty Idle Login Time Office Office Phone
root root pts/1 Oct 16 01:01 (10.20.30.40)
root root pts/2 Oct 16 01:00 (10.20.30.40)
root root *pts/3 Oct 16 01:00 (10.20.30.40)
注意 pts/3 行 Tty 列的 * 前缀——finger 用它标记「已关闭消息(mesg n)」的终端,与下文 3.6 的拦截实测互相印证。三个终端的设备权限(mesg n 的本质就是改终端设备权限位):
$ ls -l /dev/pts/
total 0
crw--w---- 1 root tty 136, 0 Oct 16 01:01 0
crw--w---- 1 root tty 136, 1 Oct 16 01:01 1
crw--w---- 1 root tty 136, 2 Oct 16 01:01 2
crw------- 1 root tty 136, 3 Oct 16 01:01 3
crw-rw-rw- 1 root root 5, 2 Oct 16 01:01 ptmx
对比 pts/1(crw--w----,组 tty 可写)与 pts/3(crw-------,仅属主可写):这就是 mesg 允许/拦截的全部秘密。接收终端的「屏幕内容」由各 script 会话记录到 /tmp/s1.log、/tmp/s2.log、/tmp/sm.log,下文以 cat 展示。
二、命令概览
2.1 语法与参数总览
write 是最古老的终端即时消息工具:它把文本行直接写进目标用户的终端设备(/dev/pts/N),对方屏幕上立即出现内容。它没有业务参数,全部用法由两个位置参数决定:
| 参数 | 作用 | 本篇实测节 |
|---|---|---|
user | 目标登录名(必须在 utmp 里有登录会话) | 3.1 / 3.8 |
ttyname | 目标终端(可省略;目标在多终端登录时用于定点投递,支持 pts/N 相对写法) | 3.2 |
| 标准输入 | 消息来源:交互敲入、管道、或 < 文件 重定向 | 3.2 / 3.3 |
-h, --help | 帮助 | 2.2 |
-V, --version | 版本 | 2.3 |
完整语法(write --help 实测):
Usage:
write [options] <user> [<ttyname>]
Send a message to another user.
Options:
-h, --help display this help
-V, --version display version
For more details see write(1).
💡 技术拓展:write 从不单独工作,它与 mesg、wall 构成经典的「终端消息三件套」——
命令 角色 一句话分工 write 点对点 给某个用户的某个终端发私信 wall 广播 给所有登录终端发同一条公告(shutdown 等维护场景) mesg 开关 控制自己的终端「接不接收」他人消息(改设备权限位) 三者的权限关系见 3.5/3.6 的实测:普通用户受 mesg 约束,root 无视 mesg。选定建议:单人沟通用 write,全员公告用 wall,不想被打扰就 mesg n。
2.2 帮助形态:--help 的实测回显
$ write --help; echo rc=$?
Usage:
write [options] <user> [<ttyname>]
Send a message to another user.
Options:
-h, --help display this help
-V, --version display version
For more details see write(1).
rc=0
短选项 -h 与 --help 等价(输出同上,退出码同为 0)。
无效选项的报错形态(write -Z):
$ write -Z; echo rc=$?
write: invalid option -- 'Z'
Try 'write --help' for more information.
rc=1
2.3 版本信息
$ which write; readlink -f $(which write)
/usr/bin/write
/usr/bin/write.ul
$ dpkg -S $(readlink -f $(which write))
bsdextrautils: /usr/bin/write.ul
$ write --version 2>&1 | head -1
write from util-linux 2.37.2
$ dpkg -l bsdextrautils | tail -1
ii bsdextrautils 2.37.2-4ubuntu3.6 amd64 extra utilities from 4.4BSD-Lite
/usr/bin/write 是软链接,真实程序为 util-linux 的 write.ul,版本 2.37.2(源自 bsdextrautils 2.37.2-4ubuntu3.6)。Ubuntu 22.04 默认自带该命令,无需安装。
三、参数逐项实测
3.1 write user:不指定终端——多终端登录时的警告与默认投递
目标用户在多个终端登录时,省略 ttyname 不会报错,而是打一条警告并投递到其中一个终端(util-linux 2.37 会挑选最近登录的会话)。实测(root 有 pts/1、pts/2、pts/3 三个会话):
$ echo hello | write root; echo rc=$?
write: root is logged in more than once; writing to pts/1
rc=0
警告说明写到了 pts/1。投递结果在 pts/1 的屏幕记录里验证:
$ cat /tmp/s1.log
Script started on 2026-10-16 01:01:00+00:00 [<not executed on terminal>]
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
hello
EOF
Message from ... 横幅、消息正文、EOF 结尾全部到达(逐段解读见 3.2)。若目标用户不在登录状态,则是另一种形态(见 3.8)。
💡 要点:write 的投递单位是「终端」而非「用户」——省略 tty 时多终端登录会挑一个并警告(本节实测),关键通知务必写全
write 用户 终端。对端用户会话被消息打断属预期行为,重要服务器上给 root 发消息前先w确认对方在哪个终端。
3.2 write user tty:指定终端定点投递(横幅逐段解读)
标准用法:echo 消息 | write 用户 终端。向 pts/2 投递一条消息并查看接收方屏幕:
$ echo "hello from main" | write root /dev/pts/2; echo rc=$?
rc=0
$ cat /tmp/s2.log
Script started on 2026-10-16 01:01:00+00:00 [<not executed on terminal>]
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
hello from main
EOF
接收方屏幕上出现的三段内容逐段解读:
- 空行——write 先输出一个空行,把消息与对方屏幕上正在显示的内容隔开。
Message from root@f1034b9326b2 on pts/0 at 01:01 ...——横幅:谁(root)从哪台主机(f1034b9326b2)在自己的哪个终端(pts/0,发送方自己的终端)什么时间(01:01)发来的。- 消息正文
hello from main——原样显示。
投递结束后,write 向接收方追加一行 EOF(3.9 详述其触发机制),随后接收方按回车即可找回自己的 shell 提示符。
💡 要点:write 是「插屏」式投递——它直接写终端设备,不管对方正在跑 vim 还是 top,消息都会插进当前画面。这正是它即时高效的代价,也是建议重要内容改用 wall/邮件的原因。
3.3 标准输入重定向:write user tty < 文件
消息来源可以是文件(多行公告场景):
$ printf 'Routine maintenance notice:\nDB instance restarts at 23:00 tonight.\nExpected impact: 10 minutes.\n' > /root/demo/write/notice.txt
$ write root /dev/pts/1 < /root/demo/write/notice.txt; echo rc=$?
rc=0
$ cat /tmp/s1.log
Script started on 2026-10-16 01:01:00+00:00 [<not executed on terminal>]
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
hello
EOF
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
Routine maintenance notice:
DB instance restarts at 23:00 tonight.
Expected impact: 10 minutes.
EOF
文件的三行内容以独立的一次会话送达(新横幅 + 三行正文 + EOF),与此前 pts/1 收到的 hello 分界清晰。文件方式免交互、可脚本化,是运维通知模板的常用形态。
💡 要点:
write 用户 tty < 模板文件是「可复用通知」的标准形态——通知模板存档在文件里,改动走版本管理;对照 wall 的< 文件广播(多终端同文),write 适合点对点、wall 适合全员,模板可以共用。
3.4 跨用户投递:普通用户给 root 发消息(mesg y)
换普通用户 tester 向 root 的 pts/2 发消息(tester 通过自己的 script 会话获得发送终端,横幅里的 pts/4 即 tester 的终端):
$ su tester -c "script -qfec \"bash /tmp/wt-ok.sh\" /tmp/wt-ok.log >/dev/null 2>&1"
$ cat /tmp/wt-ok.log
Script started on 2026-10-16 01:01:04+00:00 [TTY="/dev/pts/0" COLUMNS="-1" LINES="-1"]
^@
Script done on 2026-10-16 01:01:04+00:00 [COMMAND_EXIT_CODE="0"]
发送侧无任何报错(退出码 0)。接收方屏幕:
$ cat /tmp/s2.log
Script started on 2026-10-16 01:01:00+00:00 [<not executed on terminal>]
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
hello from main
EOF
Message from tester@f1034b9326b2 on pts/4 at 01:01 ...
hi-from-tester
EOF
新横幅 Message from tester@f1034b9326b2 on pts/4 at 01:01 ...——root 一眼看出这条消息来自用户 tester 的终端 pts/4。mesg 默认允许(终端 crw--w----,组 tty 可写)时,普通用户之间的跨终端消息畅通无阻。
💡 要点:横幅里的
用户@主机 on 终端 at 时间四元组自带溯源能力——收到骚扰消息时可凭终端号回查w/who定位来源会话。多用户服务器建议在 /etc/motd 或入职文档里说明mesg n的防打扰用法。
3.5 mesg n:接收方关闭消息后的拦截形态
先看拦截实验的前提:write(util-linux 2.37)会校验发送者的有效组 ID 与其自身终端的属组一致(防止借 su 劫持他人终端投递),因此演示用户 tester 的主组需为 tty(usermod -g tty tester,id tester 实测 uid=1012(tester) gid=5(tty) groups=5(tty))。
接收方(root)在 pts/3 上执行过 mesg n(设备权限变为 crw-------,见第一章)。tester 向 pts/3 发消息:
$ su tester -c "script -qfec \"bash /tmp/wt-deny.sh\" /tmp/wt-deny.log >/dev/null 2>&1"
$ cat /tmp/wt-deny.log
Script started on 2026-10-16 01:01:04+00:00 [TTY="/dev/pts/0" COLUMNS="-1" LINES="-1"]
write: root has messages disabled on pts/3
Script done on 2026-10-16 01:01:04+00:00 [COMMAND_EXIT_CODE="1"]
write: root has messages disabled on pts/3——write 在打开终端前就识别出对方 mesg n,明确拒绝且退出码 1。接收方屏幕(/tmp/sm.log)中没有任何新消息。想恢复接收,接收方执行 mesg y 即可(权限位回到 crw--w----)。
💡 要点:mesg 的本质是改终端设备的写权限位(
crw--w----↔crw-------),所以「发不出去」先查ls -l $(tty)对方设备权限即可预判。脚本群发通知时要把 rc=1 当正常分支处理(部分用户关了消息),不要当失败中断。
3.6 root 不受 mesg n 限制
同一终端 pts/3(mesg n),换 root 发送:
$ echo "root bypasses mesg n" | write root /dev/pts/3; echo rc=$?
rc=0
$ cat /tmp/sm.log
Script started on 2026-10-16 01:01:00+00:00 [<not executed on terminal>]
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
root bypasses mesg n
EOF
消息照常送达——root 拥有设备写权限,mesg 的权限位拦不住它。这是三件套的一致设计:write、wall 均如此,管理员必须能联系到任何在线用户(如紧急维护通知)。
💡 要点:这一设计是「紧急维护通知」的兜底保障——用户即使
mesg n也挡不住 root 的 write/wall。对使用者而言意味着:防打扰只能挡普通用户,遇到 root 来信必须重视;对管理员而言,别滥用该能力发非紧急消息,否则用户会关 SSH 逃逸。
3.7 中文消息的八进制转义形态
write(util-linux 2.37)对消息中的非 ASCII 字节做可见化转义后再投递。发一条中文:
$ printf '例行维护公告:今晚 23:00 重启数据库\n' | write root /dev/pts/1; echo rc=$?
rc=0
$ tail -4 /tmp/s1.log
Message from root@f1034b9326b2 on pts/0 at 01:01 ...
\344\276\213\350\241\214\347\273\264\346\212\244\345\205\254\345\221\212\357\274\232\344\273\212\346\231\232 23:00 \351\207\215\345\220\257\346\225\260\346\215\256\345\272\223
EOF
接收方看到的中文变成了 \344\276\213... 八进制转义序列(ASCII 部分 23:00 原样保留)。这是 write 2.37 的实现行为(把不可打印/高位字节转成八进制防终端解析混乱),意味着中文内容不适合用 write 传递——需要中文公告请用 wall(对照见本专栏 wall 一篇)或改写英文。
💡 要点:八进制转义的根源是 write 对「非 ASCII 字节」一律可见化处理,UTF-8 中文每字 3 字节全中招;同理英文里夹 Unicode 符号(如 emoji)也会被转义。跨语言通知的可靠通道是邮件/IM,write 只适合短英文/数字快讯。
3.8 不存在的用户
$ echo hi | write nosuchuser; echo rc=$?
write: nosuchuser is not logged in
rc=1
write 只能给「当前已登录」的用户发消息;utmp 里查无此人直接报错、退出码 1。发送前可先 who/finger 确认对方在线。
💡 要点:「不在线」与「关了消息」(mesg n)是两种不同失败——前者报
user is not logged on,后者报has messages disabled,脚本里可按报错文本分流(离线改投邮件,禁言则跳过)。write 只认 utmp 的登录会话,su切换出的用户若未登录 tty 也找不到。
3.9 技术拓展:终端消息通知工具对比
「往别人的终端上写字」有一族工具,横向对比——
| 工具 | 投递范围 | 防打扰(mesg n) | 适用场景 |
|---|---|---|---|
| write | 点对点(一个终端) | 拦截并报错 | 两个人的一对一实时对话(本文主线) |
| wall | 全体在线终端广播 | 静默跳过 | 全员公告(维护通知等) |
| talk | 双向分屏对话 | 需对方应答 | 早期交互式聊天(现代已罕用) |
| mesg | 非通知工具,开关本身 | - | 控制自己终端可否被写 |
| echo > /dev/pts/N | 直写设备 | 权限位允许即达 | 调试/演示(绕过协议,不推荐日常用) |
💡 关键差异一句话:write 管对话、wall 管广播、mesg 管开关,三者共用同一权限位体系。本文以 write 为主线;root 不受 mesg 约束是三件套的一致设计。现代协作已迁移到 IM 工具,这组命令的价值在于「系统级应急通道」——IM 挂了、网络受限时,同机用户的终端消息仍畅通。
四、实战示例
4.1 给在线同事发一条即时消息
标准流程:先找终端,再定点投递。
$ finger -s
Login Name Tty Idle Login Time Office Office Phone
root root pts/1 Oct 16 01:01 (10.20.30.40)
root root pts/2 Oct 16 01:00 (10.20.30.40)
root root *pts/3 Oct 16 01:00 (10.20.30.40)
$ echo "hello from main" | write root /dev/pts/2; echo rc=$?
rc=0
(finger -s 的 Idle 列还能帮你避开正忙的终端;Tty 列带 * 表示对方关了消息,发了也白发,见 3.5。)
4.2 用文件发多行操作指引
把步骤写成文件再重定向投递,避免交互敲错(3.3 的完整流程):
$ write root /dev/pts/1 < /root/demo/write/notice.txt; echo rc=$?
rc=0
接收方一次收到完整三行,横幅、正文、EOF 分明。
4.3 排查「消息发不出去」
现象是 write: ... has messages disabled ...(3.5)时的三板斧:① ls -l /dev/pts/N 看设备权限是否 crw-------(被 mesg n);② 让对方执行 mesg y;③ 无人在对方身边时,改用 wall 广播(root 身份不受限)或邮件。同理,对方从未登录(is not logged in,3.8)时只能等上线或走离线渠道。
五、注意事项与常见问题
Q1:接收方屏幕被消息「插花」了怎么办?这是 bug 吗? 不是。write 的机制就是直接写终端设备(TTY),消息会插进对方屏幕当前显示内容的任意位置,看起来像乱掉了;对方按一次回车,shell 会重新打印提示符,继续工作不受影响。重要/长内容建议改文件或邮件,write 适合一两行的短消息。
Q2:交互式怎么用?Ctrl-D 是什么角色?
直接敲 write root pts/2 回车后进入逐行输入模式:每敲一行对方立即收到一行,Ctrl-D(EOF)结束会话。本篇实测用管道喂入,stdin 关闭同样是 EOF——接收方看到的结尾 EOF 行正是发送侧输入结束的标志(3.2/3.3/3.6 的屏幕记录均可佐证)。也就是说:一次 write 会话的结束以 EOF 为界,接收方以 EOF 行感知。
Q3:write/wall/mesg 的权限关系一句话总结?
普通用户之间:接收方 mesg y(默认)才收得到,mesg n 则 write/wall 都被拒(write 报 has messages disabled);root 发送无视 mesg(3.6)。接收方无法对「root 的消息」设置屏蔽。三件套选型见 2.1 拓展表。
Q4:为什么 write 2.37 对 su 过的用户报 effective gid 错误?
util-linux 2.37 的 write 会校验「发送者有效组 ID == 发送者自身终端的属组」(防终端劫持)。通过 su/sg 换用户后,若 egid 与新终端属组不一致会拒绝发送(实测见 3.5 前提说明)。给演示用户设置主组 tty(usermod -g tty)即可通过校验;日常使用以本账号登录终端发送则天然满足。
Q5:中文消息为什么变成 \344\276\213 这样的编码? write 2.37 对非 ASCII 字节做八进制转义后投递(3.7 实测),中文不可直发。需要中文公告用 wall(其转义行为见本专栏 wall 一篇实测)或邮件;跨终端协作消息建议统一英文。
Q6:目标用户登录了多个终端,不指定 tty 会发给谁?
util-linux 2.37 会打警告 user is logged in more than once; writing to pts/N 并投递到其中一个(本环境实测挑选最近登录的会话,3.1);要确保送达指定屏幕,永远写全 write user /dev/pts/N。老式 BSD write 在此场景直接报错——不同实现行为有差异,以 2.37 的警告式为准。
六、参考链接
- Ubuntu 手册页(write 1,util-linux 实现):https://manpages.ubuntu.com/manpages/jammy/en/man1/write.1.html
- mesg 手册页(终端消息开关):https://manpages.ubuntu.com/manpages/jammy/en/man1/mesg.1.html
- wall 手册页(全员广播):https://manpages.ubuntu.com/manpages/jammy/en/man1/wall.1.html
- utmp 数据库格式(登录会话的记录来源):https://man7.org/linux/man-pages/man5/utmp.5.html
- util-linux 上游发行说明:https://github.com/util-linux/util-linux