在Linux日常运维里,su命令切换用户是绕不开的基本操作。不管是为了调试服务去切到某个业务账号,还是临时需要root权限去处理配置,su都是那个直接、利落的工具。但很多刚开始接触Linux的人——甚至干了两三年运维的——对su的认知都停在“su root输密码”这一步,结果切换完发现命令找不到、环境变量还是旧的、脚本跑起来各种报错。这篇文章我就从实际运维的角度,把su命令的用法、参数、易错点和安全习惯一次讲透。
这不是什么高深的技术,但恰恰是越基础的东西越值得抠细节。我会尽量讲得接地气一些,涉及环境变量、登录Shell、密码机制这些概念时用例子说明,保证刚入门的朋友也能照着操作。如果你已经在用su了,那重点看加-和不加-的区别、su和sudo的取舍,这两块是很多人踩坑的重灾区。
1. su命令到底在解决什么问题——先认清它的定位
1.1 从一次真实的故障聊起
先讲个常见的场景。有次某开发者在服务器上部署一套应用,安装脚本要求必须以某个专用的业务账号运行,否则目录权限对不上。他先su appuser切了过去,然后执行python3 setup.py install,结果系统直接提示python3: command not found。奇怪了,刚才用root还运行得好好的,怎么切个用户就找不到了?
原因很简单:他用的su appuser没有加载目标用户的完整环境变量,PATH还是root的。而root的PATH里配置的python3路径,在目标用户的PATH里根本不存在。后来我用su - appuser重新切了一遍,问题立刻没了。
这就是su命令最容易被忽视的一个点:su切换的是用户身份,但不一定切换环境上下文。很多人第一次接触su时,都以为它像“换个账号登录”一样,所有东西都跟着变,但实际并不是这样的。理解这一层,后面的所有操作才有根基。
1.2 su的本质是身份切换,不是权限提升
su的全称是switch user,意思是“切换用户”。执行su之后,当前Shell会变成目标用户的Shell,后续命令都以目标用户的身份去运行。注意,这个过程中涉及两个关键机制:
- 密码机制:普通用户切换到root,需要输入root的密码;root切换到普通用户,不需要输入任何密码,直接切换。
- 身份标识:切换后,
uid、gid、groups都会变成目标用户的,内核根据这些标识来决定文件访问权限。
很多教程喜欢把su和sudo放在一起对比,但两者的定位差异非常大。su是“变成另一个人”,而sudo是“以另一个人的身份执行某条命令”。你可以把su理解成一把能打开所有门的万能钥匙,而sudo更像一张有特定权限范围的工作证。日常运维中,如果只是偶尔需要执行一两条特权命令,优先考虑sudo;如果需要长时间以某个特定用户身份工作,比如调试应用、处理日志文件,那su更适合。
不过,正因为su切换后“什么都能干”,它也就成了一个安全敏感操作。后面我会单独专门讲安全习惯,这里先记住一个结论:谨慎把root密码到处分发,能用sudo尽量别用su切root。
2. 基础用法与核心参数:把每个选项都吃透
2.1 最基础的切换方式:su、su -、su - root
先看su命令的语法骨架:
su [选项] [用户名] [参数]如果不指定用户名,默认切换到root。所以下面两条命令在这个层面是等价的:
su su root但注意,这俩都不加载root的登录环境。想加载的话,要加一个短横线-(也写作-l或--login):
su - su - root su -l root su --login root这四种写法都可以,推荐直接用su -,简短又准确。
实际使用中,我见过不少老运维直接敲su切root,然后发现很多命令要写完整路径,比如得打/usr/sbin/nginx而不是nginx。这就是没加-导致的PATH不完整。我自己在操作服务器时,默认都加-,只有在明确不需要加载登录环境、只想切换用户身份执行个别命令时才不加。
关于用户名,还有一个小细节:你可以写成su - username,也可以写成su -l username,效果一样。有些发行版还支持su - username -c '命令'这种组合写法,后面会详细说。
2.2 关键差异:加不加-到底差在哪
这是整个su命令里最值得单独拎出来讲的部分。我直接用一个实验来说明。
假设当前是root用户,我们要切到另一个普通用户testuser:
# 不加加横线 su testuser echo $PATH echo $HOME pwd执行结果大概率还是root的PATH、root的HOME(也就是/root),当前目录也还在原来的地方。
# 加横线 su - testuser echo $PATH echo $HOME pwd执行结果就完全变成testuser的了:HOME变成/home/testuser,PATH变成登录Shell配置的那个PATH,当前工作目录也会自动切到/home/testuser。
为什么会这样?因为su -会模拟一次完整的登录过程,它会去读取目标用户的/etc/profile、~/.bash_profile(或~/.profile)、~/.bashrc等初始化脚本,把整个用户环境的变量都重建一遍。而纯su只改变用户身份(uid),Shell进程本身不重新初始化,环境变量保持原样。
这个区别直接影响了使用场景:
- 需要用到目标用户的命令别名、自定义函数、环境变量时,必须用
su -。 - 只是临时切换身份去操作文件,不依赖目标用户的环境变量时,可以用
su。 - 实际排查“明明切换了用户为什么命令还是找不到”这类问题,十有八九就是没加
-。
2.3 用-c执行单条命令:少切多用的正确姿势
有时候我们不想真的把Shell切过去,只是要“以某个用户的身份”执行一条命令,比如用www用户重启PHP服务,或者用postgres用户执行一个数据库导入操作。这时候用-c参数最合适:
su - www -c "php artisan migrate" su - postgres -c "psql -f /tmp/init.sql"关键点在于:-c后面的命令会以目标用户的身份和登录Shell环境去执行,所以可以放心使用目标用户安装的软件、配置的环境变量。这也避免了我们频繁切来切去导致忘记切回root的尴尬。
如果不加-,直接su www -c "php artisan migrate",也能执行,但环境变量是原样的,结果就是可能报找不到php、找不到某个库。所以实际写脚本时,我几乎都是su - 用户名 -c "命令"这种形式。
2.4 指定Shell切换:处理一些特殊场景
默认情况下,su切换后启动的Shell是目标用户在/etc/passwd里指定的登录Shell,比如root是/bin/bash,而有些系统账号可能是/sbin/nologin或/bin/false。
如果你想临时用别的Shell,可以用-s参数指定:
su - www -s /bin/sh su - mysql -s /bin/bash这有什么用呢?举个例子:很多数据库账号出于安全考虑,登录Shell被设置成/sbin/nologin,直接su - mysql会提示This account is currently not available。但我们调试时确实需要以mysql用户执行SQL命令,那就可以临时指定bash:
su -s /bin/bash mysql注意:这里是su而不是su -的话,可能还是不加载完整环境。通常我会写成su -s /bin/bash - mysql,把-s和登录环境一起用。这里有个写法顺序的问题,su -s /bin/bash - mysql中的-放在用户名前面,表示登录环境;不同发行版对参数顺序要求不太一样,通常这样写都能生效。
3. su与sudo怎么选——运维实践中真正该纠结的问题
3.1 三者对比:su、su -、sudo
网上讨论su和sudo的文章非常多,但很多把概念混在一起讲,实际上三者各有侧重。我整理了一张表,方便对照:
| 对比项 | su | su - | sudo |
|---|---|---|---|
| 身份变化 | 切换用户身份 | 切换用户身份并加载登录环境 | 以目标用户身份执行单条命令 |
| 密码要求 | 目标用户的密码 | 目标用户的密码 | 当前用户的密码(或免密) |
| 环境变量 | 保留原用户环境 | 重建为目标用户登录环境 | 默认重置为安全环境(可用-i/-E调整) |
| 典型场景 | 快速切身份 | 完整切换到目标用户工作 | 授权执行特权命令 |
| 权限粒度 | 无限制,切到啥就是全部权限 | 无限制 | 可通过sudoers精细控制 |
| 适合场景 | 临时身份切换 | 日常以某个用户工作 | 需要授权、审计、细粒度控制的团队环境 |
表格里可以看到,sudo最大的优势是不用把root密码给每个人,而是通过/etc/sudoers文件给特定用户授权特定命令。如果团队成员多,每次都用su切root,意味着所有人都要知道root密码,出了事根本没法追踪是谁操作的。
3.2 我的选择建议:什么场景用sudo,什么场景保留su
我自己的经验是这样:
- 单机管理的个人服务器:用su完全没问题,因为就你一个人,密码不外泄,操作直接。
- 多人协作的服务器:强烈建议给每个人配独立的普通账号,然后用sudo授权所需命令,root密码收起来放到密码管理工具里,最好干脆锁起来不用。
- 自动化脚本:能不用交互式su就不用。脚本里处理密码非常麻烦,也不安全,优先考虑sudo的
NOPASSWD配置、或者直接以服务账号运行。 - 调试目标用户环境问题:比如排查某个应用在自己的启动脚本里为什么环境变量不对,那必须用
su -完整模拟这个用户登录后的环境。
有一种情况值得特别提一下:很多运维喜欢用sudo su -。意思是,当前用户有sudo权限,执行sudo su -后可以不需要知道root密码,直接切到root的完整登录环境。这本质上是通过sudo的授权,间接实现了“切root”。如果团队里有人的sudo授权是ALL=(ALL) ALL,那他和知道root密码没什么区别——但好处是,这个动作会被sudo的日志记录下来,出了事可以追溯。
4. 实操实录:五个高频场景一步步来
4.1 场景一:管理员临时切到root处理配置
最常见的就是登录服务器后,发现需要改nginx配置。假设当前用户是opsadmin,操作流程应该这样:
su - # 输入root密码 # 此时已经进入root的登录环境 vim /etc/nginx/nginx.conf nginx -t systemctl reload nginx exit注意这里我用了su -而不是su,因为修改完配置我要用systemctl命令,而systemctl的完整路径/usr/bin/systemctl在那个环境里已经通过root的PATH配好了。如果直接用su切过去,PATH可能不完整,nginx -t这种命令就要写绝对路径。
还有个小习惯:切到root后干完事赶紧exit退回来,不要一直挂在root身份上。我见过有人切到root后忘了退出,整个人以root身份操作了半小时,后来一条误删命令下去直接损失惨重。切换后第一件事不是干别的,而是看一眼whoami确认身份没错。
4.2 场景二:切到业务账号调试服务
部署完一个Java服务后,项目以appuser身份运行。现在出了日志权限问题,需要切到appuser去复现操作。
su - appuser whoami echo $HOME cd ~/logs tail -n 50 error.log这里为什么用su -?因为Java应用启动脚本里用到了$JAVA_HOME,而$JAVA_HOME是在appuser的~/.bashrc里定义的。不加-切过去,JAVA_HOME还是老的或者为空,复现出来的问题和实际生产环境不一致,容易得出错误结论。
这个场景也是很多刚入门者最容易忽略的:调试时必须完整模拟目标用户的身份与环境。否则你调试没问题,部署上去却有各种找不到环境的报错,极难排查。
4.3 场景三:以目标用户身份执行单条命令
举一个实际的例子:PostgreSQL默认不允许root直接执行psql,官方推荐以postgres系统用户身份操作。你不想每次都切过去再退回来,那就可以用-c:
su - postgres -c "psql -c 'SELECT version();'"注意引号层级:外层双引号包住整条命令,内层SQL里的单引号需要单独处理。如果SQL里有系统用户不需要交互,这可能是一款比较省心的方式。但引号嵌套一旦复杂,很容易出错。我自己的处理原则是:单条简单命令用-c,超过三行逻辑就写成脚本,再用su - 用户 -c "bash /path/to/script.sh"执行,这样既绕开了引号地狱,又能保证环境加载。
再补一个细节:如果命令里有管道、重定向、通配符,它们是在哪个Shell里解析的?答案是:这些特殊符号会在su -c的目标Shell里解析,而不是在调用方Shell里解析。比如:
su - appuser -c "ls /tmp/*.log | wc -l"这个*.log和管道都是在appuser的Shell里展开的。这个特性在脚本调试时要特别注意,不能想当然以为它在当前Shell里执行。
4.4 场景四:验证环境变量是否真正加载
这个场景特别适合新手理解su -和su的差别。依次执行下面三条命令:
su appuser echo "PATH=$PATH" env | grep JAVA exit再执行:
su - appuser echo "PATH=$PATH" env | grep JAVA exit对比输出,你会惊讶地发现两组结果是完全不同的。第一次可能还是root的PATH,JAVA_HOME也可能为空;第二次才是appuser真正登录后的环境。想要直观看到差异,还可以用which java对比一下,通常第一组会找不到或指到别的版本,第二组才会指到appuser环境里配置的Java。
我建议所有刚接触Linux的朋友都亲手做一次这个实验。做过一次,你就永远不会忘记su -的重要性了。
4.5 场景五:root切到普通用户的快捷方式
root切换到普通用户不需要密码,直接执行:
su - testuser exit这个特性在排查问题时很高效。比如某个文件root能读、普通用户读不了,你切过去验证一下就知道权限设置有没有问题。注意,如果普通用户的登录Shell是/sbin/nologin,这样切不过去,需要用-s /bin/bash指定Shell。
5. 常见问题与排查技巧实录
5.1 认证失败与nologin用户
症状:执行su - appuser后提示su: Authentication failure,或者This account is currently not available。
排查步骤:
- 先确认密码是不是真的输错了。注意,Linux密码输入时终端不回显,这是正常的,不代表键盘没反应。
- 检查
grep appuser /etc/passwd,看登录Shell是什么。如果末尾是/sbin/nologin或/bin/false,那就说明这个账号本来就不允许登录。 - 如果确实需要以这个身份工作,临时用
su -s /bin/bash appuser,但要注意这是在绕过安全策略,操作要克制。
这里要扩展一句:很多人因为Authentication failure就以为密码错误,其实可能是PAM配置限制了某个组的用户使用su。比如某些发行版默认只允许wheel组用户执行su,其他普通用户即使密码正确也会被拒绝。遇到这种情况,检查/etc/pam.d/su里是否有pam_wheel.so相关的配置。
5.2 切换后环境变量不生效
症状:切换用户后,执行某个命令提示command not found,或者程序运行时的行为与直接登录不一致。
原因几乎都是没加-。解决方案也很简单:重新用su - 用户切入。
可以快速验证:su后执行echo $PATH,对比su -后的输出,差异一目了然。如果确实需要保留原来的一些环境变量,但又想加载目标用户的环境,可以加了-之后再手动export引入需要的变量,不过这种情况很少见,不必过度设计。
5.3 su -c的引号与通配符陷阱
刚刚在场景三里提到过,这里再展开讲一下。
如果命令里既有双引号又有单引号,比如:
su - appuser -c "echo \"$HOSTNAME\""你看到$HOSTNAME会在哪个阶段被解析?答案:在调用方Shell阶段就会被解析,因为外层双引号属于调用方Shell。如果想让目标用户的Shell去解析,得这样:
su - appuser -c 'echo "$HOSTNAME"'把外层改成单引号,$HOSTNAME就不会被当前Shell展开,而是传给目标Shell展开。这个细节很容易搞混,我在给别人讲解时经常强调:哪一层引号,决定哪一个Shell来解析里面的变量。
5.4 登录历史与密码泄露风险
su在输入密码时,密码不会被记录到shell history里。但有一个坑:如果你用了su -c,并且命令里带了密码(比如su - appuser -c "echo 'password' | sudo -S command"),那这个密码很可能出现在进程列表里,甚至被记录到审计日志中。我把这个列为高危操作,强烈不建议在命令行里暴露密码。
另外一个容易被忽略的地方:su切到root后,你敲的所有命令都会以root身份运行,如果开启了shell history记录,时间久了日志文件里会积累大量敏感操作。这不是su本身的问题,但值得在日常操作中保持意识。
6. 安全使用su的几个好习惯
6.1 能用sudo解决的问题不要用su
前面已经反复强调过,这里再落到可操作层面:
- 维护一个精简的sudoers授权列表,比如授予开发人员
systemctl restart nginx的能力,而不是ALL=(ALL) ALL。 - 如果团队人数不多,也可以给每个人独立的普通账号,配合sudo提权,这样审计日志里能明确看到是谁在什么时间干了什么。
- 真正需要使用root完整环境的操作,才考虑
sudo su -,并且操作完立即退出。
我把这个习惯放在最前面,因为实际生产中,密码扩散是比命令误用更可怕的风险。
6.2 限制root直接远程登录
很多服务器默认允许root通过SSH直接登录,如果root只有一个强密码还好,一旦密码泄露,后果不堪设想。建议在/etc/ssh/sshd_config里把PermitRootLogin改为no。之后所有人必须先用普通账号登录,再通过su或sudo切root。
这个做法不是为了禁止使用root,而是为了增加一层“你是谁”的审计记录。配合前面说的sudo日志,整个链路就清晰了。
6.3 把审计日志用起来
Linux下的/var/log/auth.log或/var/log/secure会记录su和sudo的认证记录。比如下面这条命令可以快速查看最近的su切换记录:
grep "su:" /var/log/auth.log | tail -n 50也可以看sudo的:
grep "sudo:" /var/log/auth.log | tail -n 50开启审计后,任何人什么时候用过su、切到哪个用户、成功还是失败,都有迹可循。这在团队协作中非常有价值。我自己在每次接手的服务器上,都会先确认这个日志是正常记录的,再考虑要不要进一步启用更严格的auditd。
6.4 自动化脚本时认真掂量
有些运维图省事,在shell脚本里直接写su - appuser <<EOF ... EOF,用here-doc把密码喂给su。这样做确实能跑通,但非常不安全。密码会以明文出现在脚本里,一旦脚本被其他人读取,密码就泄露了。
更好的做法:
- 使用sudo的
NOPASSWD配置来执行特定命令。 - 如果目标用户就是服务运行账号,直接用cron的
@reboot或者systemd服务来启动,而不是靠su去切。 - 如果实在摆脱不了交互式su,考虑用
expect或sshpass这类工具,但仍然要注意密码存储问题。
我一直主张:自动化脚本里尽量避免出现任何密码交互,这不仅是安全习惯,也是脚本稳定性的基础。
7. 最后再分享一个实用小技巧
操作服务器时,我习惯在提示符上显示当前用户和当前目录。这样切换了用户后,提示符会立刻变化,能大幅降低“忘了自己是谁”的风险。在~/.bashrc里加一行:
export PS1='[\u@\h \w]\$ '\u就是当前用户名。如果你用root,提示符会显示[root@host ~]#,普通用户则是[user@host ~]$。配合su -使用,每次切换后扫一眼提示符就知道身份对了没。这个小习惯帮我避免过好几次误操作,也推荐你用起来。
回到su命令本身——它并不复杂,但使用细节确实不少。希望这篇文章能把关于su的关键点都讲清楚:加不加-、要不要-c、用不用sudo、以及如何安全地管理root权限。在实际操作中,如果能在动手前想清楚这三个问题,绝大部分由su引发的运维问题都可以提前避免。最后再啰嗦一句:别怕在小机器上多试验几次,把su和su -的环境差异亲手验证一遍,比看任何文章都管用。