write 命令详解与参数实测(Ubuntu 22.04)

write 命令逐参数实测详解:多终端登录的警告与默认投递、指定终端定点投递与 Message 横幅逐段解读、文件输入重定向、跨用户投递与 mesg 开关(root 不受限)、EOF 结束机制与中文八进制转义现象,全部回显在 Ubuntu 22.04 实测通过。

Linux命令Linux系统UbuntuUbuntu 22.04命令行网络工具2026/10/16

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

接收方屏幕上出现的三段内容逐段解读:

  1. 空行——write 先输出一个空行,把消息与对方屏幕上正在显示的内容隔开。
  2. Message from root@f1034b9326b2 on pts/0 at 01:01 ...——横幅:谁(root)从哪台主机(f1034b9326b2)在自己的哪个终端(pts/0,发送方自己的终端)什么时间(01:01)发来的。
  3. 消息正文 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 的警告式为准。

六、参考链接