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

sudo 逐参数实测详解:apt 从零安装与 sudoers.d 免密配置、-l/-ll 查授权、-u/-g/-P 指定目标身份、-i/-s 环境差异、-v/-k/-K/-n 时间戳管理(默认 15 分钟)、-S/-A/-p 密码输入、sudo !! 历史展开、secure_path 与 command not found、visudo -c 语法巡检与坏配置影响,全部回显在 Ubuntu 22.04(sudo 1.9.9)实测通过。

Linux命令Linux系统UbuntuUbuntu 22.04命令行用户权限2026/10/15

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

目标:全面掌握 sudo 的每一个常用参数——安装与 sudoers.d 免密配置、-l/-ll 查看授权、-u/-g/-P 指定目标身份、-i/-s 登录环境差异、-v/-k/-K/-n 时间戳管理(默认 15 分钟)、-S/-A/-p 密码输入方式、sudo !! 历史展开、secure_path 与 command not found、visudo -c 语法检查等,全部命令与回显已在 Ubuntu 22.04(sudo 1.9.9)实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • sudo 版本:1.9.9(deb 包 1.9.9-1ubuntu2.7,dpkg -l sudo 实测)
  • 测试账号:u4c1(uid=1006,已加入 sudo 组,且在 /etc/sudoers.d/u4c1 配置了 NOPASSWD: ALL)、u4c2(uid=1007,仅加入 sudo 组,提权需要输密码)
  • 演示目录:/root/demo/sudo/(其中 notes.txt 为演示文件、askpass.sh 为 -A 参数的图形化取密助手脚本)
  • 环境默认未预装 sudo,本篇完整记录了它的安装过程(见第三章),其余所有回显均为真实执行结果

二、命令概览

2.1 语法与测试数据

sudo(superuser do / substitute user do)是 Linux 下最主流的授权提权工具:系统管理员在 /etc/sudoers(及 /etc/sudoers.d/*)中声明「谁、在哪些机器、以谁的身份、能执行哪些命令」,sudo 运行时校验该策略并代为执行目标命令,成功认证后默认 15 分钟内免重复输密。

💡 技术拓展:Linux 下的提权/切换身份工具不止 sudo,常用方案对比——

工具原理与特点适用场景
su切换到目标用户完整 shell,需知道目标用户密码临时变为 root/他人
sudo按 sudoers 策略授权,需自己的密码,可只授权单条命令服务器日常运维、多管理员分权(主流)
doasOpenBSD 移植,配置极简(doas.conf 一行一人)轻量系统、个人机
pkexecpolkit 策略框架,图形桌面弹窗认证桌面应用提权
runuser仅 root 可用、无需密码直接以目标身份执行root 下的服务脚本降权

本文主角是 sudo:授权粒度最细、有完整审计日志(syslog authpriv),是 Ubuntu 默认推荐的管理方式。

本篇语法主线:sudo [选项] [VAR=value] [-i|-s] [命令],另有一组「查询/管理」形态(-l、-v、-k 等)。测试数据围绕两个账号展开:u4c1(免密提权路径)与 u4c2(需要密码路径),以便同时覆盖两类真实使用场景。

2.2 sudo -h 帮助全量输出

sudo 的帮助用 -h(等价 --help)查看,以下为实测全量输出,可作为参数速查表:

$ sudo -h
sudo - execute a command as another user

usage: sudo -h | -K | -k | -V
usage: sudo -v [-ABknS] [-g group] [-h host] [-p prompt] [-u user]
usage: sudo -l [-ABknS] [-g group] [-h host] [-p prompt] [-U user] [-u user]
            [command]
usage: sudo [-ABbEHknPS] [-r role] [-t type] [-C num] [-D directory] [-g group]
            [-h host] [-p prompt] [-R directory] [-T timeout] [-u user]
            [VAR=value] [-i|-s] [<command>]
usage: sudo -e [-ABknS] [-r role] [-t type] [-C num] [-D directory] [-g group]
            [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ...

Options:
  -A, --askpass                 use a helper program for password prompting
  -b, --background              run command in the background
  -B, --bell                    ring bell when prompting
  -C, --close-from=num          close all file descriptors >= num
  -D, --chdir=directory         change the working directory before running
                                command
  -E, --preserve-env            preserve user environment when running command
      --preserve-env=list       preserve specific environment variables
  -e, --edit                    edit files instead of running a command
  -g, --group=group             run command as the specified group name or ID
  -H, --set-home                set HOME variable to target user's home dir
  -h, --help                    display help message and exit
  -h, --host=host               run command on host (if supported by plugin)
  -i, --login                   run login shell as the target user; a command
                                may also be specified
  -K, --remove-timestamp        remove timestamp file completely
  -k, --reset-timestamp         invalidate timestamp file
  -l, --list                    list user's privileges or check a specific
                                command; use twice for longer format
  -n, --non-interactive         non-interactive mode, no prompts are used
  -P, --preserve-groups         preserve group vector instead of setting to
                                target's
  -p, --prompt=prompt           use the specified password prompt
  -R, --chroot=directory        change the root directory before running command
  -r, --role=role               create SELinux security context with specified
                                role
  -S, --stdin                   read password from standard input
  -s, --shell                   run shell as the target user; a command may
                                also be specified
  -t, --type=type               create SELinux security context with specified
                                type
  -T, --command-timeout=timeout terminate command after the specified time limit
  -U, --other-user=user         in list mode, display privileges for user
  -u, --user=user               run command (or edit file) as specified user
                                name or ID
  -V, --version                 display version information and exit
  -v, --validate                update user's timestamp without running a
                                command
  --                            stop processing command line arguments

2.3 版本信息

$ sudo --version | grep -E "^(Sudo|Sudoers)"
Sudo version 1.9.9
Sudoers policy plugin version 1.9.9
Sudoers file grammar version 48
Sudoers path: /etc/sudoers
Sudo log server timeout in seconds: 30
Sudoers I/O plugin version 1.9.9
Sudoers audit plugin version 1.9.9
$ dpkg -l sudo | tail -1
ii  sudo           1.9.9-1ubuntu2.7 amd64        Provide limited super user privileges to specific users

包版本为 1.9.9-1ubuntu2.7,程序版本 1.9.9。Ubuntu 的 sudo 编译期默认即带 15 分钟认证缓存(--with-timeout=15)与按 tty 细分的时间戳记录(--with-tty-tickets),3.6 的时间戳实测将再次验证。

2.4 安装 sudo(apt 全流程实测)

Ubuntu 桌面版默认带 sudo,最小化安装的服务器/精简系统可能没有(本环境即如此,which sudo 无输出)。标准安装流程为「更新索引 → 安装」,以下为完整实测。

先移除已存在的 sudo,以采集从零安装的真实输出:

$ apt-get remove -y sudo

输出节选(实测,省略数据库进度行):

The following packages will be REMOVED:
  sudo
0 upgraded, 0 newly installed, 1 to remove and 2 not upgraded.
After this operation, 2568 kB disk space will be freed.
Removing sudo (1.9.9-1ubuntu2.7) ...
Processing triggers for libc-bin (2.35-0ubuntu3.15) ...
$ apt-get update 2>&1 | tail -6
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Hit:2 http://security.ubuntu.com/ubuntu jammy-security InRelease
Hit:3 http://archive.ubuntu.com/ubuntu jammy-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu jammy-backports InRelease
Reading package lists...
$ DEBIAN_FRONTEND=noninteractive apt-get install -y sudo

输出节选(实测,省略数据库进度行):

The following NEW packages will be installed:
  sudo
0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.
Need to get 820 kB of archives.
After this operation, 2568 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 sudo amd64 1.9.9-1ubuntu2.7 [820 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 820 kB in 2s (423 kB/s)
Selecting previously unselected package sudo.
Unpacking sudo (1.9.9-1ubuntu2.7) ...
Setting up sudo (1.9.9-1ubuntu2.7) ...
Processing triggers for libc-bin (2.35-0ubuntu3.15) ...

安装后验证(二进制为 setuid root,sudoedit 是指向 sudo 的符号链接):

$ ls -l /usr/bin/sudo /usr/bin/sudoedit
-rwsr-xr-x 1 root root 232416 Oct  6 14:05 /usr/bin/sudo
lrwxrwxrwx 1 root root      4 Oct  6 14:05 /usr/bin/sudoedit -> sudo

sudo --version | head -3 能看到 Sudo version 1.9.9 即安装成功。

💡 说明:DEBIAN_FRONTEND=noninteractive 用于脚本中避免任何交互式提问;手工安装时直接 apt-get install -y sudo 即可。

2.5 创建测试用户并加入 sudo 组

先创建两个测试用户并设置口令(useradd/chpasswd/usermod 成功时均无输出):

useradd -m -s /bin/bash u4c1        # 创建 u4c1,带家目录与 bash
useradd -m -s /bin/bash u4c2
echo 'u4c1:q1w2e3r4' | chpasswd     # 非交互设置口令(演示口令)
echo 'u4c2:q1w2e3r4' | chpasswd
usermod -aG sudo u4c1               # 追加进 sudo 组
usermod -aG sudo u4c2

验证成员关系(groups=...,27(sudo) 即已在 sudo 组):

$ id u4c1
uid=1006(u4c1) gid=1009(u4c1) groups=1009(u4c1),27(sudo)
$ id u4c2
uid=1007(u4c2) gid=1010(u4c2) groups=1010(u4c2),27(sudo)

💡 说明:%sudo 组成员默认拥有「以任意身份执行任意命令」的权限(需密码)。u4c2 保持这条路径用于演示密码验证;u4c1 会在下节叠加一条 NOPASSWD 规则用于脚本化免密场景。

2.6 sudoers 与 /etc/sudoers.d 免密配置

查看 /etc/sudoers 的生效行(去掉注释与空行):

$ grep -v -E "^#|^$" /etc/sudoers
Defaults	env_reset
Defaults	mail_badpass
Defaults	secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
Defaults	use_pty
root	ALL=(ALL:ALL) ALL
%admin ALL=(ALL) ALL
%sudo	ALL=(ALL:ALL) ALL
@includedir /etc/sudoers.d

逐项含义:env_reset 提权时重置环境变量;mail_badpass 密码输错时给管理员发邮件;secure_path 提权后使用的固定 PATH;use_pty 在独立伪终端中执行命令;root ALL=(ALL:ALL) ALL root 全权;%admin/%sudo 组规则;@includedir /etc/sudoers.d 加载扩展目录(文件名含 . 或以 ~ 结尾的会被忽略)。

为 u4c1 追加免密规则(写入 sudoers.d 而非直接改 /etc/sudoers,且权限必须为 0440):

echo 'u4c1 ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/u4c1
chmod 440 /etc/sudoers.d/u4c1
$ cat /etc/sudoers.d/u4c1
u4c1 ALL=(ALL:ALL) NOPASSWD: ALL
$ ls -l /etc/sudoers.d/
total 8
-r--r----- 1 root root 1096 Aug  3  2022 README
-r--r----- 1 root root   33 Oct 15 08:56 u4c1

规则语法 用户 主机=(以谁:以哪个组) 标记: 命令:u4c1 在所有主机上可以以任意用户/组执行任意命令,NOPASSWD 表示免密码。

2.7 visudo -c 语法检查

任何 sudoers 变更前后都应做语法检查,避免写坏文件导致 sudo 整体不可用:

$ visudo -c
/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/u4c1: parsed OK

三个文件全部 parsed OK,配置生效。坏文件的报错形态见 3.14。

三、参数逐项实测

3.1 sudo -l / -ll:查看当前授权

-l(list)列出当前用户可执行的授权。u4c1 因「sudo 组规则 + sudoers.d 免密规则」同时命中,会列出两条:

$ su u4c1 -s /bin/bash -c "sudo -l"
Matching Defaults entries for u4c1 on f1034b9326b2:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User u4c1 may run the following commands on f1034b9326b2:
    (ALL : ALL) ALL
    (ALL : ALL) NOPASSWD: ALL

-ll 展示更结构化的长格式(RunAsUsers/RunAsGroups/Options):

$ su u4c1 -s /bin/bash -c "sudo -ll"
Matching Defaults entries for u4c1 on f1034b9326b2:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User u4c1 may run the following commands on f1034b9326b2:

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Commands:
	ALL

Sudoers entry:
    RunAsUsers: ALL
    RunAsGroups: ALL
    Options: !authenticate
    Commands:
	ALL

第二条的 Options: !authenticate 就是 NOPASSWD 的内部表示。-l 后跟具体命令可以检查「这条命令我能不能提权跑」:

$ su u4c1 -s /bin/bash -c "sudo -l /bin/ls"
/bin/ls

root 可用 -U 查看任意用户的授权(-u 在列表模式下的配对参数):

$ sudo -l -U u4c1
Matching Defaults entries for u4c1 on f1034b9326b2:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User u4c1 may run the following commands on f1034b9326b2:
    (ALL : ALL) ALL
    (ALL : ALL) NOPASSWD: ALL

对照组:u4c2 只有需密码的组规则,在无终端的脚本环境里 -l 无法提示输密,直接报错:

$ su u4c2 -s /bin/bash -c "sudo -l"
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

💡 说明:sudoers 的匹配规则是最后匹配优先——u4c1 的两条规则中,sudoers.d 的 NOPASSWD: ALL 排在后,因此执行命令时免密生效(3.2 可证);这也解释了 3.6 中 -v 的特殊行为。另外 -l 本身需要认证(防止未授权探测权限列表),终端上会提示输密码,无 tty 的脚本里就是上面的报错。

3.2 基本提权:sudo 命令

u4c1 免密提权执行任意命令(对比 3.3 中 u4c1 自身读不了 /root):

$ su u4c1 -s /bin/bash -c "sudo id"
uid=0(root) gid=0(root) groups=0(root)
$ su u4c1 -s /bin/bash -c "sudo ls /root/demo/sudo"
askpass.sh
notes.txt
$ su u4c1 -s /bin/bash -c "sudo wc -l /etc/shadow"
32 /etc/shadow

💡 说明:/root 目录权限 700、/etc/shadow 为 640 root:shadow,u4c1 原生身份均不可读,经 sudo 以 root 身份执行后正常输出——这是「授权执行」与「登录切换」的本质区别:命令仍以当前会话发起,只是运行身份换成目标用户。

3.3 sudo -u:以指定用户身份执行

root 自己调用 sudo 无需密码(root 在 sudoers 中为全权),可以直接演示 -u 的身份切换效果:

$ sudo -u u4c1 id
uid=1006(u4c1) gid=1009(u4c1) groups=1009(u4c1),27(sudo)
$ sudo -u u4c1 whoami
u4c1
$ sudo -u u4c1 ls /root
ls: cannot open directory '/root': Permission denied
$ sudo -u u4c2 id
uid=1007(u4c2) gid=1010(u4c2) groups=1010(u4c2),27(sudo)

💡 说明:-u 后可跟用户名或 UID(如 sudo -u 1006 id)。第三条最能说明问题:即使 sudo 本身以 root 发起,-u u4c1 之后命令就以 u4c1 的真实权限运行,读 /root 照样被拒——-u 是完整的身份切换,不是「借用 root 的权限」。

3.4 sudo -g / -P:指定组身份与保留组向量

-g 只切换组身份,用户身份保持不变:

$ sudo -g adm id
uid=0(root) gid=4(adm) groups=4(adm),0(root)
$ sudo -g adm id -gn
adm

普通用户 u4c1 通过免密规则同样可以使用 -g:

$ su u4c1 -s /bin/bash -c "sudo -g adm id"
uid=1006(u4c1) gid=4(adm) groups=4(adm),27(sudo),1009(u4c1)
$ su u4c1 -s /bin/bash -c "sudo -g adm id -gn"
adm

-P(preserve-groups)控制切换用户时是否保留调用者的附加组。对比实测:

$ sudo -u u4c2 id
uid=1007(u4c2) gid=1010(u4c2) groups=1010(u4c2),27(sudo)
$ sudo -P -u u4c2 id
uid=1007(u4c2) gid=1010(u4c2) groups=1010(u4c2),0(root)

💡 说明:不加 -P 时,组向量整体替换为目标用户 u4c2 自己的组(1010(u4c2) + 27(sudo));加 -P 后保留调用者 root 的组向量(0(root)),实测组列表变为 1010,0(root)。典型用途是保持调用者对某些组资源(如日志组 adm)的访问能力。

3.5 sudo -i / -s / -H:三种「进入 shell」的环境差异

-i 模拟目标用户的完整登录(读 /etc/profile 与目标用户 profile、切到家目录);-s 只启动目标用户的 shell(读 $SHELL,不切目录、不读 profile);不加这两者则直接执行命令。逐项实测:

$ sudo sh -c "echo PATH=\$PATH; echo HOME=\$HOME; pwd"
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
HOME=/root
/tmp
$ sudo -i sh -c "echo PATH=\$PATH; echo HOME=\$HOME; pwd"
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
HOME=/root
/root
$ sudo -s whoami
root
$ sudo -s sh -c "pwd"
/tmp

以普通用户为目标时,-i 的家目录切换更直观:

$ sudo -u u4c1 -i sh -c "echo HOME=\$HOME; pwd"
HOME=/home/u4c1
/home/u4c1

-H 显式把 HOME 设为目标用户家目录(sudo 1.9 默认已如此):

$ sudo -H sh -c "echo HOME=\$HOME"
HOME=/root
$ sudo sh -c "echo HOME=\$HOME"
HOME=/root

💡 说明:三者差异速查——

形态工作目录登录脚本HOME
sudo cmd保持当前目录(/tmp)否目标用户(/root)
sudo -s保持当前目录(/tmp)否目标用户(/root)
sudo -i切到目标家目录(/root)是目标用户(/root)

本环境下三者的 PATH 与 HOME 一致:PATH 都被 secure_path 固定(见 3.8),sudo 1.9 默认已把 HOME 指向目标用户(-H 只是显式写法)。最可靠的差别是工作目录与是否执行登录 profile。

3.6 sudo -v / -k / -K / -n:时间戳(认证缓存)管理

sudo 认证成功后会把凭证缓存为「时间戳」,默认 15 分钟内免重复输密。先取官方默认值证据:

$ sudo -V | grep -i -E "timestamp"
Authentication timestamp timeout: 15.0 minutes
Path to authentication timestamp dir: /run/sudo/ts
Type of authentication timestamp record: tty

💡 技术拓展:时间戳按什么维度记录(timestamp record type)有三种策略——

类型原理取舍
tty(本环境默认)每个终端独立缓存隔离好,多终端需分别认证
ppid按父进程会话缓存适合无 tty 的脚本/管道场景
global全机共享一份缓存最省事但边界最弱

可在 sudoers 中用 Defaults timestamp_type=... 调整;缓存时长用 Defaults timestamp_timeout=分钟数 调整(见 sudoers(5))。

完整的「认证 → 缓存生效 → -k 失效 → -K 彻底删除」序列(u4c2 在交互终端中逐条执行,密码输入时终端不回显):

u4c2@f1034b9326b2:/$ sudo id
[sudo] password for u4c2: 
uid=0(root) gid=0(root) groups=0(root)
u4c2@f1034b9326b2:/$ sudo -n id
uid=0(root) gid=0(root) groups=0(root)
u4c2@f1034b9326b2:/$ sudo -k
u4c2@f1034b9326b2:/$ sudo -n id
sudo: a password is required
u4c2@f1034b9326b2:/$ sudo -K
u4c2@f1034b9326b2:/$ sudo -n id
sudo: a password is required

逐行解读:第一条 sudo id 输入密码认证成功;第二条 sudo -n id(-n 表示绝不提示密码,只能用已有缓存)直接成功,证明缓存生效;sudo -k 失效当前终端的时间戳后,-n 立即失败;sudo -K 彻底删除时间戳文件后同样失败。

-v 的语义是「验证并刷新时间戳但不执行命令」。对 u4c1 有一个值得注意的实测现象——虽然它有 NOPASSWD 规则,-v 仍要求认证:

$ su u4c1 -s /bin/bash -c "sudo -vn; echo rc=\$?"
sudo: a password is required
rc=1

在真实终端中 -v 会提示输密码,认证后即完成缓存刷新:

u4c1@f1034b9326b2:/$ sudo -v
[sudo] password for u4c1: 
u4c1@f1034b9326b2:/$ 

💡 说明:原因在 3.1 已铺垫——u4c1 同时命中 %sudo ALL=(ALL:ALL) ALL(需密码)与 NOPASSWD: ALL(免密)两条规则,执行具体命令时按「最后匹配优先」走免密;而 -v 的目标是刷新缓存,只要用户存在任一需密码的授权,就会要求认证。-k 与 -K 的区别:-k 使当前会话的时间戳失效(下次命令需重新认证),-K 连时间戳文件一起删除;实测两者对后续 -n 的拦截效果一致。

3.7 sudo !!:.shell 历史展开的经典用法

忘加 sudo 时,不必重打长命令,用 bash 历史展开 !!(上一条命令)配合 sudo 重放。u4c1 交互终端实测:

u4c1@f1034b9326b2:/$ id -u
1006
u4c1@f1034b9326b2:/$ sudo !!
sudo id -u
0

bash 先回显展开后的实际命令 sudo id -u,再输出执行结果 0(root 的 uid)。

💡 说明:!! 是 bash 的历史展开(history expansion),仅交互式 shell 可用(脚本中默认关闭);它对 sudo 没有特殊性,sudo !! 等价于 sudo 上一条命令。同类还有 !$(上一条命令的最后一个参数)。

3.8 secure_path 与 sudo: command not found

sudoers 中的 Defaults secure_path=... 会在提权时替换 PATH。对比 u4c1 与 sudo 之后的 PATH:

$ su u4c1 -s /bin/bash -c "printenv PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
$ su u4c1 -s /bin/bash -c "sudo printenv PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

差异在尾部:普通用户 PATH 里的 /usr/games、/usr/local/games 被 secure_path 值替换掉了。当一个命令不在用户 PATH 但在 secure_path 覆盖的 sbin 目录里时,就会出现「直接跑报 command not found,加 sudo 就能跑」的现象。模拟普通用户常见的「无 sbin PATH」实测:

$ su u4c1 -s /bin/bash -c "export PATH=/usr/local/bin:/usr/bin:/bin; printenv PATH; useradd -D"
/usr/local/bin:/usr/bin:/bin
bash: line 1: useradd: command not found
$ su u4c1 -s /bin/bash -c "export PATH=/usr/local/bin:/usr/bin:/bin; sudo useradd -D"
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/sh
SKEL=/etc/skel
CREATE_MAIL_SPOOL=no

💡 说明:/usr/sbin 下的管理命令(useradd、usermod、visudo 等)不在精简后的用户 PATH 中,但一定在 secure_path 里,所以「sudo: command not found」通常意味着命令不在 secure_path 覆盖范围(如自装到 ~/bin 的工具),而不是权限问题。需要跑非标准路径命令时,写全路径(如 sudo /opt/tools/backup.sh)。

3.9 sudo -S / -A / -p:三种密码输入方式

-S 从标准输入读密码(脚本场景),提示符照常打印到 stderr,本例密码从管道送入:

$ printf 'q1w2e3r4\n' | su u4c2 -c 'sudo -S id'
[sudo] password for u4c2: uid=0(root) gid=0(root) groups=0(root)

-p 自定义密码提示符(支持 %p 目标用户、%u 调用者等转义):

$ printf 'q1w2e3r4\n' | su u4c2 -c 'sudo -S -p "请输入 u4c2 的密码:" id'
请输入 u4c2 的密码:uid=0(root) gid=0(root) groups=0(root)

-A 交给外部「askpass 助手程序」取密码(无终端的图形/远程场景)。先准备一个可执行的助手脚本(返回演示口令),再用 SUDO_ASKPASS 指定:

#!/bin/sh
echo q1w2e3r4
$ chmod 755 /tmp/askpass.sh && ls -l /tmp/askpass.sh
-rwxr-xr-x 1 root root 24 Oct 15 08:56 /tmp/askpass.sh
$ su u4c2 -s /bin/bash -c "SUDO_ASKPASS=/tmp/askpass.sh sudo -A id"
uid=0(root) gid=0(root) groups=0(root)

⚠️ 注意:askpass 助手必须有可执行权限,实测权限不足时 sudo 直接拒绝:sudo: unable to run /tmp/askpass.sh: Permission denied。

💡 说明:三种方式适用面——-S 最适合 CI/脚本(但密码会经过管道,注意不要落进 shell 历史;此处演示口令按团队规范固定为 q1w2e3r4);-A 适合桌面弹窗(ssh-askpass 等)与无 tty 环境;-p 只改提示文案,常与 -S 搭配。

3.10 sudo -E / --preserve-env / VAR=value:传递环境变量

默认 env_reset 会重置环境。实测三种把变量带过提权边界的方式(调用时注入 FOO=bar):

$ su u4c1 -s /bin/bash -c "FOO=bar sudo -E env | grep FOO"
FOO=bar
$ su u4c1 -s /bin/bash -c "FOO=bar sudo --preserve-env=FOO env | grep FOO"
FOO=bar
$ su u4c1 -s /bin/bash -c "sudo FOO=bar env | grep FOO"
FOO=bar

💡 说明:-E 保留调用者整个环境(是否允许取决于 sudoers 策略);--preserve-env=列表 精确保留指定变量;sudo VAR=value 命令 把变量当作命令行一部分传递。生产上推荐第三种——白名单式、影响面最小。

3.11 sudo -b / -B / -C / -D / -e:后台、响铃、文件描述符、工作目录与编辑

-b 后台运行命令(立即返回,无输出):

$ su u4c1 -s /bin/bash -c "sudo -b sleep 2"
(无输出,命令转入后台执行)

-B 在提示密码前响铃(终端支持时),对执行结果无影响:

$ su u4c1 -s /bin/bash -c "sudo -B id"
uid=0(root) gid=0(root) groups=0(root)

-C 关闭编号不小于 num 的文件描述符。默认被策略拒绝,需要在 sudoers 中开启 closefrom_override:

$ su u4c1 -s /bin/bash -c "sudo -C 10 id"
sudo: you are not permitted to use the -C option

写入 Defaults closefrom_override 后实测(注意 sudoers.d 文件权限规范应为 0440):

$ echo "Defaults closefrom_override" > /etc/sudoers.d/zz-demo && visudo -c
/etc/sudoers.d/zz-demo: bad permissions, should be mode 0440
/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/u4c1: parsed OK
$ su u4c1 -s /bin/bash -c "sudo -C 10 id"
uid=0(root) gid=0(root) groups=0(root)
$ su u4c1 -s /bin/bash -c "sudo -u u4c2 -C 20 id"
uid=1007(u4c2) gid=1010(u4c2) groups=1010(u4c2),27(sudo)

-D 切换工作目录后执行。同样默认被拒,需要在命令规则上加 CWD=* 标签:

$ su u4c1 -s /bin/bash -c "sudo -D /root pwd"
sudo: you are not permitted to use the -D option with /usr/bin/pwd
$ echo "u4c1 ALL=(ALL:ALL) CWD=* NOPASSWD: ALL" >> /etc/sudoers.d/zz-demo && visudo -c

输出与上一步相同(0440 权限告警 + 全部 parsed OK,此处略),规则生效后实测:

$ su u4c1 -s /bin/bash -c "sudo -D /root pwd"
/root

-e(即 sudoedit)以安全方式编辑受保护文件:生成临时副本、用调用者的编辑器打开、保存后以提权身份写回。用 /bin/cat 当编辑器演示内容读取:

$ su u4c1 -s /bin/bash -c "EDITOR=/bin/cat sudo -e /root/demo/sudo/notes.txt"
备份配置 demo
sudo: /root/demo/sudo/notes.txt unchanged

💡 说明:-C/-D 这类「改变进程运行环境」的参数默认关闭是有意设计(防止绕过策略),开启方式分别依赖 Defaults closefrom_override 与规则级 CWD= 标签。-e 与直接 sudo vi 文件 相比安全性更高:编辑器以普通用户身份运行,避免给 root 权限的编辑器加载不可信插件。

3.12 sudo -R / -r / -t / -T:chroot 与 SELinux、命令超时

-R 在 chroot 目录内执行命令。默认被策略拒绝,需要在规则上加 CHROOT=* 标签:

$ su u4c1 -s /bin/bash -c "sudo -R / id"
sudo: you are not permitted to use the -R option with /usr/bin/id

追加 CHROOT=* 标签并通过 visudo -c 检查(输出同样为 0440 告警 + 全部 parsed OK)后实测(chroot 到 / 等价于不换根):

$ su u4c1 -s /bin/bash -c "sudo -R / id"
uid=0(root) gid=0(root) groups=0(root)

-r(角色)与 -t(类型)用于 SELinux 系统。本系统未启用 SELinux,参数被接受但无可见影响:

$ su u4c1 -s /bin/bash -c "sudo -r sysadm_u id"
uid=0(root) gid=0(root) groups=0(root)
$ su u4c1 -s /bin/bash -c "sudo -t sysadm_t id"
uid=0(root) gid=0(root) groups=0(root)

-T 为命令设置超时。默认被策略拒绝,且实测 Defaults command_timeout 虽能通过语法检查但不生效:

$ su u4c1 -s /bin/bash -c "sudo -T 2 sleep 5; echo rc=\$?"
sudo: sorry, you are not allowed set a command timeout
rc=1

visudo -cf 确认该 Defaults 语法合法(parsed OK,新增文件列表见回显)后再次实测:

$ su u4c1 -s /bin/bash -c "sudo -T 2 sleep 5; echo rc=\$?"
sudo: sorry, you are not allowed set a command timeout
rc=1

在 sudoers 规则中尝试 TIMEOUT= 标签授权,1.9.9 的语法检查直接报错:

$ visudo -cf /etc/sudoers.d/zz-demo2
/etc/sudoers.d/zz-demo2:1:29: syntax error
u4c1 ALL=(ALL:ALL) NOPASSWD:TIMEOUT=5m: ALL
                            ^~~~~~~

💡 说明:-r/-t 是 SELinux 环境专用(Ubuntu 默认 AppArmor,不生效属正常);-T 的 TIMEOUT 标签是 sudo 较新版本特性,Ubuntu 22.04 携带的 1.9.9 无法授权使用,遇到该需求可用 timeout 命令替代(如 sudo timeout 5 命令)。-C/-D/-R/-T 的「默认拒绝 + 需显式授权」是同一设计思想:涉及执行环境本身的参数必须由管理员在策略里放开。

3.13 sudo -h host 与 --

-h 指定目标主机(仅 sudo -l 列表模式支持跨主机查询);用在执行模式会直接被拒:

$ sudo -h localhost id
sudo: a remote host may only be specified when listing privileges.

-- 表示后续参数不再是 sudo 的选项(用于执行以 - 开头的命令/文件名):

$ su u4c1 -s /bin/bash -c "sudo -- id"
uid=0(root) gid=0(root) groups=0(root)

3.14 visudo -c 与坏配置的实测影响

向 sudoers.d 写入一个语法错误的文件,visudo -c 能精确指出「文件:行:列」并用 ^~~~ 定位:

$ visudo -c
/etc/sudoers.d/zz-bad-demo:1:9: syntax error
THIS IS NOT VALID SUDOERS
        ^~~

坏文件存在期间执行 sudo:会打印同样的解析错误,且坏文件中的规则不生效,其余来源仍可用(实测提权命令正常执行):

$ su u4c1 -s /bin/bash -c "sudo id"
/etc/sudoers.d/zz-bad-demo:1:9: syntax error
THIS IS NOT VALID SUDOERS
        ^~~
uid=0(root) gid=0(root) groups=0(root)

-f 只检查指定文件:

$ visudo -cf /etc/sudoers.d/zz-bad-demo
/etc/sudoers.d/zz-bad-demo:1:9: syntax error
THIS IS NOT VALID SUDOERS
        ^~~

删除坏文件后恢复正常:

$ visudo -c
/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/u4c1: parsed OK
$ su u4c1 -s /bin/bash -c "sudo id"
uid=0(root) gid=0(root) groups=0(root)

💡 说明:visudo 默认模式(编辑 sudoers)在保存时自动做语法检查,-c 则用于对存量配置做巡检。坏文件的后果是「自身规则失效 + 每次执行刷报错」,虽然本实测中其余规则仍可用,但绝不能依赖这一点——历史上 sudo 版本对解析失败的处理有过变化,规范做法永远是改前备份、改后 visudo -c 验证。

四、实战示例

4.1 为运维账号开通免密 sudo(最小配置流程)

组合使用 2.5/2.6/2.7 的完整链路:建账号 → 设口令 → 加组 → 写 sudoers.d → 语法检查 → 验证授权。

useradd -m -s /bin/bash u4c1 && echo 'u4c1:q1w2e3r4' | chpasswd && usermod -aG sudo u4c1
echo 'u4c1 ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/u4c1
chmod 440 /etc/sudoers.d/u4c1
visudo -c

visudo -c 输出(实测):

/etc/sudoers: parsed OK
/etc/sudoers.d/README: parsed OK
/etc/sudoers.d/u4c1: parsed OK

以该账号登录后 sudo -l 确认出现 NOPASSWD: ALL(见 3.1 回显),再跑一条 sudo id 得到 uid=0(root) 即交付完成。更严格的最小授权则把命令列表写死,例如 u4c1 ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx,并用 sudo -l /usr/bin/systemctl 验证(见 3.1 的 -l /bin/ls 用法)。

4.2 无终端环境使用 sudo(CI/脚本三件套)

脚本里跑 sudo 的推荐姿势:优先 NOPASSWD 规则;否则用 -S 送密码;-n 兜底防卡死。

$ printf 'q1w2e3r4\n' | su u4c2 -c 'sudo -S id'
[sudo] password for u4c2: uid=0(root) gid=0(root) groups=0(root)
$ su u4c2 -s /bin/bash -c "sudo -n id"
sudo: a password is required
$ su u4c2 -s /bin/bash -c "SUDO_ASKPASS=/tmp/askpass.sh sudo -A id"
uid=0(root) gid=0(root) groups=0(root)

-n 的价值在于:没有可用缓存时立刻失败而不是挂起等输入,脚本可以据此走降级逻辑;askpass 方案见 3.9(助手脚本必须可执行)。

4.3 以指定身份执行运维操作(-u / -g / -D 组合)

排查「服务以特定用户运行」类问题时,用 -u 完整模拟该用户视角、用 -g 验证组权限:

$ sudo -u u4c1 ls /root
ls: cannot open directory '/root': Permission denied
$ sudo -g adm id -gn
adm
$ su u4c1 -s /bin/bash -c "sudo -D /root pwd"
/root

(-D 需先按 3.11 在 sudoers 中配置 CWD=* 标签。)三条命令组合即可回答「该用户能不能读这个目录、组对不对、在哪个工作目录下执行」。

4.4 忘加 sudo 的一键补救(sudo !!)

见 3.7 实测:id -u 输出 1006 后直接 sudo !!,bash 展开为 sudo id -u 输出 0。任何「Permission denied / command not found(secure_path 场景)」之后都可这样重放,长命令尤其省事。

4.5 sudoers 变更的安全发布流程(visudo -c 巡检)

把 3.14 的实测串成流程:修改前备份 → 写入 → visudo -cf 文件 单文件检查 → visudo -c 全量检查 → sudo -l 复核授权 → 观察一次真实 sudo 执行。坏配置的报错定位(文件:行:列 + ^~~~)与「坏文件规则不生效」的行为都以实测为准,流程中任何一步不过关立即回滚备份。

五、注意事项与常见问题

Q1:明明命令存在,为什么 sudo: command not found? 最常见原因是命令不在 secure_path 覆盖范围内(secure_path 会整体替换 PATH,见 3.8 实测)。自装工具(~/bin、/opt 下)要用全路径执行;sbin 管理命令则相反——普通用户 PATH 找不到、sudo 反而能找到(3.8 的 useradd 对照实测)。

Q2:脚本里 sudo 报 a terminal is required to read the password 或 a password is required 怎么办? 这是无 tty 环境下无法交互输密的报错(3.1/3.6 实测)。三条路:给账号配 NOPASSWD 规则;用 -S 从 stdin 送密码;或用 -A + askpass 助手。都可以再用 -n 保证无缓存时快速失败。

Q3:NOPASSWD 配了却不生效? 三个排查点(均有实测依据):规则顺序——sudoers 按最后匹配优先,检查是否有后面的规则覆盖了你的 NOPASSWD(3.1 中 u4c1 的两条规则);文件权限——sudoers.d 下文件应为 0440,否则 visudo -c 会报 bad permissions, should be mode 0440(3.11 实测);语法——用 visudo -c 确认 parsed OK。

Q4:sudoers 写坏了会不会把系统锁死? 实测(3.14):坏文件中的规则失效、每次执行 sudo 都会刷解析报错,其余来源正常时 sudo 仍可用;root 始终可以通过本地会话直接编辑修复(root 不经 sudo)。预防手段就是改动前后跑 visudo -c。

Q5:用户有 NOPASSWD: ALL,为什么 sudo -v 还要输密码? -v 的语义是刷新认证缓存,只要该用户存在任何「需要密码」的授权(如 %sudo 组规则),就会要求认证;NOPASSWD 只在执行具体命令、按最后匹配命中时生效(3.6 实测:-vn 报 a password is required,终端上 -v 提示输密码)。

Q6:脚本化测试/自动化里怎么处理交互式密码? 管道一次性送入是行不通的——PAM 读取前会清空终端输入缓冲,实测表现为 Password change has been aborted 类错误。可靠做法是「分步延时写入伪终端」:先等提示符出现,再逐行喂密码(本文 3.6/3.7 的交互序列即按此采集)。sudo -S 读 stdin 则不受此限制,是最省事的脚本路径。

Q7:sudo 与 su 该怎么选? 多人共管的服务器选 sudo:授权粒度到「单条命令」、认证用的是各自的密码(可吊销)、日志记录「谁在何时提权执行了什么」(syslog authpriv)。su 需要共享 root 口令且授权全有或全无,仅适合单人临时切换(见 2.1 的工具对比表)。

六、参考链接