news 2026/10/9 8:31:51

Shell脚本用户身份与文件权限实战:告别Permission denied

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shell脚本用户身份与文件权限实战:告别Permission denied

前几天在OpenEuler上写一个部署脚本,前面一切顺利,执行到cp xxx /opt/service/这一行时,屏幕突然给我甩了一句冷冰冰的“Permission denied”。我第一反应是文件权限设错了,结果ls -ld /opt/service一看,属主是root,而我正用普通用户test在跑脚本。问题根本不在文件,而在执行身份上。

这个系列一路到第8篇,前面聊过变量、条件判断、循环、函数,跑得都挺顺。但说实话,一旦脚本开始要落盘写文件、要安装服务、要切换执行用户,用户身份与文件权限这道坎立刻就会现出原形。这一篇就把这两个事讲透:脚本里“当前是谁在执行”怎么判断,文件“该给谁读、给谁写、给谁执行”怎么设计。内容不绕弯子,命令说话,案例带路,适合正在用OpenEuler做运维和部署自动化的朋友,也适合刚学Shell想补齐权限基础的新手。

1. 为什么Shell脚本必须懂用户身份与文件权限

1.1 先看一个真实翻车现场

我用一个具体场景把问题讲明白。假设你写了一个初始化脚本,用root执行,把项目的日志目录建好了,目录属主自然是root。第二天同事用普通用户work跑启动脚本,往日志目录里写文件,结果报Permission denied。很多人的第一反应是“那我加权限不就行了”,于是chmod -R 777,表面上问题消失了,实际上风险敞开了:任何用户都能读写这个目录,日志文件随便被人改被人删,安全边界直接归零。

这类问题在运维脚本里极其常见。读文档时觉得“权限不就看个rwx吗”,一上手就发现,哪个用户以什么身份运行脚本,决定了脚本能不能写某些目录、能不能执行某些命令、能不能读某些配置文件,牵一发动全身。所以权限不是孤立的文件属性,它必须和用户身份放在一起看。这篇就是要把这条线梳理清楚。

再补一个翻车案例。我在一台OpenEuler上部署Nginx时,用普通用户执行了systemctl start nginx,报错说没有权限。我检查了半天nginx配置文件,全对,最后才意识到systemctl里很多服务管理动作必须由root或具备相应sudo权限的用户执行。身份不对,命令本身再正确也没用。

1.2 脚本执行身份的三个关键点

Shell脚本本身没有特权,它继承执行者的身份。这个“继承”非常重要,因为su和sudo都会改变脚本最终的运行身份,但改变的方式和时机不一样,搞不清楚就会埋雷。

要判断脚本运行时到底是谁,绕不开三个概念:

  • UID:用户的唯一编号,root是0,普通用户一般从1000开始。
  • GID:用户主组的编号。
  • EUID/EGID:有效用户ID和有效组ID,决定实际权限检查时的身份。su和sudo切换后,EUID会跟着变。

在脚本里获取这些信息,最推荐用id命令。以下几条是我高频使用的:

  • id:显示完整的身份信息。
  • id -u:只显示UID,脚本判断最常用。
  • id -un:只显示用户名。
  • id -g:显示主组GID。

我强调用id -u而不是whoami,是因为在某些切换身份的场景下,whoami给出的是当前有效用户名,但id -u更稳定。我的脚本里判断是否root,固定写法是if [ "$(id -u)" -eq 0 ],不比较字符串,干净利落。

2. 用户身份管理:脚本里快速判断“我是谁”

2.1 常用身份命令速查表

权限问题的第一步永远是确认身份。在OpenEuler上打开终端,几个命令就能把当前上下文摸清楚。我把常用的整理成了表,方便你直接参考。

命令输出示例核心用途
iduid=1000(test) gid=1000(test) groups=1000(test),10(wheel)身份信息最全面,含UID、GID、附加组
id -u1000只输出UID,脚本条件判断首选
whoamitest当前有效用户名
who am itest pts/0 2024-...显示登录终端时的原始用户
groupstest wheel显示用户所属的所有组

这里有一个很容易绕晕的点:同一个用户可能属于多个组,比如上面的test既属于主组test,也属于附加组wheel。ls -l显示的属组是文件所属的组,通常对应文件属组那一项。但在访问文件时,系统按组成员关系来判定,只要用户属于文件属组,就能匹配到组的权限位。这个细节在排障时特别有用,尤其是“我是组成员为什么还访问不了”这类问题,大概率是权限位只给了属主,没给组。

2.2 脚本里身份检查的三种写法

第一种,要求必须root运行。脚本开头直接挡掉:

if [ "$(id -u)" -ne 0 ]; then echo "请使用root身份运行此脚本" >&2 exit 1 fi

第二种,允许普通用户运行,但对特权操作单独用sudo。这时可以先做一次sudo可用性预检:

if ! sudo -n true 2>/dev/null; then echo "当前用户无法免密使用sudo" >&2 exit 1 fi

sudo -n的意思是不交互输入密码,如果用户没有配置免密sudo,会直接失败。这样能把异常挡在真正执行命令之前,而不是让脚本跑到一半弹密码提示,交互环境下脚本行为会变得不可控。

第三种,针对服务型脚本,只允许特定用户运行。比如一个日志采集脚本只想让work用户跑:

if [ "$(id -un)" != "work" ]; then echo "本脚本只允许work用户执行" >&2 exit 1 fi

这三种写法覆盖了绝大多数场景。特别强调一点:身份判断一定要放在脚本最前面。如果放在脚本中部,跑到一半才发现身份不对,中间可能已经创建了半成品文件、改了部分配置,清理起来非常头疼。

2.3 su和sudo的分工与注意事项

su是切换用户,把整个会话变成另一个用户;sudo是临时提权,保留当前用户环境,但以root或其他指定用户的权限去执行某条命令。在自动化脚本里,sudo通常更安全,因为它能做到最小化提权——只给某几条命令放权,而不是整段脚本都以root身份跑。

sudoers配置是另一个大话题,但这里给一个最常用的运维模板:在/etc/sudoers.d/下面建一个独立文件,给某用户开放特定命令的权限:

work ALL=(ALL) NOPASSWD: /usr/bin/systemctl, /usr/bin/install

这样work用户只能免密执行systemctl和install,其他命令一概不放开。比直接把用户加进wheel组开全量sudo要收敛得多。

写脚本时还有一个细节:sudo会缓存凭据。脚本里连续多个sudo本身不会重复输密码,但如果你在循环里频繁调用sudo,可能在凭据过期后突然卡住,或者弹出一个意料之外的密码提示。稳妥做法是脚本开头先执行sudo -v刷新一次凭据,全部命令跑完后执行sudo -k失效凭据,把安全窗口缩到最小。

3. 文件权限基础:看懂rwx与数字权限

3.1 ls -l输出里的十个字符到底在说什么

在OpenEuler上执行ls -l,第一列是一串字符,比如-rw-r--r--,拆开看就是:

  • 第1个字符:文件类型。-普通文件,d目录,l符号链接,b块设备,c字符设备,s套接字,p管道。
  • 第2到第4个字符:属主权限,简称u。
  • 第5到第7个字符:属组权限,简称g。
  • 第8到第10个字符:其他人权限,简称o。

所以-rw-r--r--翻译过来是:普通文件,属主可读写,属组可读,其他人可读。这是最常见的644权限,普通配置文件基本都是它。

如果看到drwxr-xr-x,那就是目录,属主可读可写可进入,属组可读可进入,其他人可读可进入,这是最经典的755目录权限。注意这里“进入”对应的是执行权限x,目录的执行权限和文件的执行权限含义完全不同,下面专门展开。

3.2 数字权限怎么算:r=4 w=2 x=1

Linux权限的数字表示法,实质是一个按二进制位映射到十进制的过程。r占4,w占2,x占1,把对应权限位的数字加起来就是最终值:

  • rwx = 4+2+1 = 7
  • rw- = 4+2 = 6
  • r-x = 4+1 = 5
  • r-- = 4

于是常见组合就有了明确含义:

  • 644:属主读写,组与其他只读。普通配置文件首选。
  • 755:属主读写执行,组与其他读执行。脚本和程序的标准权限。
  • 700:只有属主读写执行,组和其他人完全不给。私密脚本和私密目录用。
  • 600:只有属主读写,组和其他人不给。密钥文件、密码文件推荐这个。

选权限时我有一条原则:能不给就不给。默认写644或755,特殊需要才上600或700,千万别随手777。很多安全事件都是从777这扇门进来的。

这里顺带提一下umask。当脚本创建新文件时,最终权限是“最大默认权限减去umask”。OpenEuler默认umask一般是022,所以文件默认644、目录默认755。如果脚本处理敏感数据,在脚本前段执行umask 077,后面创建的文件就只有属主自己能读写。很多人容易忽略这一行,但它对安全性影响很大。

3.3 文件与目录的权限差异,最容易踩的坑

同样是rwx,放在文件和目录上含义差很多:

  • 文件上:r是读文件内容,w是修改文件内容,x是执行文件。
  • 目录上:r是列出目录下的文件名,w是在目录内创建、删除、改名条目,x是进入目录、访问目录内条目,也就是路径解析权限。

这个差异引发一个经典问题:为什么对目录只有r权限时,ls能看到文件名,却无法访问文件?因为你没有x权限,没法通过目录解析到具体文件路径,系统会告诉你Permission denied。反过来,有x没r,你能cd进目录,但ls列不出名字,只能凭已知文件名访问。

所以排查“目录能看不能进”或“能进不能列”时,先想x和r各自缺了谁。

还有一个反直觉的知识点:删除一个文件的权限,取决于该文件所在目录的写权限,而不是文件本身的写权限。很多人把删除失败怪到文件权限上,改了一通文件权限没用,其实目录权限才是那个扣扳机的角色。这个误区在共享目录里尤其容易踩,所以共享目录的Sticky位才会那么重要,后面会讲。

4. 权限修改实操:chmod、chown、chgrp的正确姿势

4.1 chmod两种模式,各有用武之地

chmod可以用符号模式,也可以用数字模式。

符号模式的好处是可读性好、改动局部。chmod u+x deploy.sh表示给属主加执行权限,别的权限不动;chmod go-w config表示去掉组和其他人的写权限。手工调试时用符号模式非常顺手,改完立即生效,影响面容易判断。

数字模式的好处是确定性高,一次指定全部权限。chmod 755 deploy.sh执行完,最终状态就是755,不存在“哪一位没改到”的模糊。写进部署脚本时我基本只用数字模式,因为可预期、可审计,查看脚本的人一眼就能看到权限全貌。

实操中我是这样分工的:手工调试用符号模式,因为灵活;自动化部署用数字模式,因为稳定。如果你担心不小心给可执行文件留下了写权限,可以直接chmod 555,属主、组、其他都只能读和执行,彻底断开写路径。

4.2 chown改属主,chgrp改属组

权限位再好,属主错了也会出问题。经典场景:你用root把项目目录放到了/home/test/project,但目录属主仍然是root,普通用户test进去自然四处碰壁。这时候chown -R test:test /home/test/project就派上用场了。

-R递归对目录生效,但要慎用:递归改属主会把底下所有文件全部归给指定用户。如果一个目录里混着共享文件或者不同服务的配置,一次性全改成一个人的,可能造成别的问题。我通常在明确知道整个目录树都归同一用户时才用chown -R。

chgrp改属组相对少用,但多用户协作时价值很高。比如建一个team组,把共享目录设成组内可写:

chown root:team /data/shared chmod 770 /data/shared

这样组内成员可读写,组外进不来,比777安全多了。

有一个内核层面的限制要记住:普通用户不能把文件属主改成别人,只有root能做到。用户能chgrp,也仅限于把自己文件的属组改成自己所属的组。所以脚本需要改属主时,你必须以root身份或sudo去执行,这个前置条件要在设计阶段就想清楚。

4.3 批量为脚本加权限的实操示例

实际项目里经常需要批量授权。比如你拉下来一批脚本,要统一添加执行权限:

find /opt/scripts -type f -name "*.sh" -exec chmod 755 {} \;

这句的命令含义是:在/opt/scripts目录下找出所有.sh文件,逐个执行chmod 755。如果只想给当前用户加执行权限,避免把脚本暴露给其他用户:

find /opt/scripts -type f -name "*.sh" -exec chmod 700 {} \;

find -exec会对每个文件启动一次chmod,文件数量少时没问题。文件特别多时,用xargs批量处理效率更高:

find /opt/scripts -type f -name "*.sh" -print0 | xargs -0 chmod 755

-print0和-0的作用是以空字符分隔文件名,避免文件名里有空格导致命令被拆碎。批量授权时这个细节能帮你避开一堆小麻烦。

5. 进阶层:SUID、SGID、Sticky与ACL

5.1 特殊权限位的识别与含义

除了rwx,Linux还有三个特殊权限位,它们平时不显山露水,但一旦处理不好就是安全漏洞等级的问题。

SUID,权限位对应数字4。设置后,可执行文件运行时以文件属主身份运行,而不是执行者身份。最典型的例子是/usr/bin/passwd,普通用户能修改自己的密码,就是因为passwd带着SUID,执行时能临时以root身份去写/etc/shadow。

SGID,权限位对应数字2。作用有两个:在文件上,执行时以文件属组身份运行;在目录上,新创建的文件自动继承目录的属组,这对共享目录意义重大。

Sticky,权限位对应数字1。主要用于目录,限制“只有文件属主或root才能删除目录内文件”。/tmp目录就是典型,谁都能写,但不能删别人的东西。

在ls -l显示中,特殊权限会占据x的位置,小写表示该位同时有x权限,大写表示没有。比如rws表示SUID且可执行,r-s表示SUID但不可执行。数字方式设置时就是chmod 4755、chmod 2755、chmod 1777。

写脚本时对特殊权限要非常克制。SUID如果被滥用,等于给任何能执行这个文件的人开了一扇以特定身份运行代码的门。我的原则是:非必要不上SUID;目录共享优先用SGID加组权限;临时目录才考虑Sticky。

5.2 目录共享的SGID实战

如果一个小组要共享一个项目目录,并且希望任何成员创建的文件都自动归组所有,而不是归创建者的主组,那就用SGID目录:

mkdir -p /data/team chown root:team /data/team chmod 2770 /data/team

注意数字开头的2,表示设置了SGID位。2770的含义是:SGID位生效、属主rwx、属组rwx、其他人无权限。这时团队任意成员在这个目录里创建文件,文件的属组都会自动变成team,而不是成员自己的主组。组内互写文件非常流畅。

如果不加SGID,只用普通770,每个成员创建的文件属组是自己的主组,组员之间可能互相覆盖不了、写了文件别人没法改,排查起来很容易绕乱。这是多用户协作运维里一个高频坑,值得提前设好。

5.3 ACL:当基本权限不够用时

基本权限只有u/g/o三个维度,一旦遇到“给特定几个用户分别开放不同权限”的需求就绕不开了。比如一个项目目录,要让alice有读写权限、bob只读权限、运维组有完整权限,用chmod根本表达不出来。这时候用ACL:

setfacl -m u:alice:rwx /data/project setfacl -m u:bob:r-x /data/project setfacl -m g:dev:rwx /data/project

用getfacl /data/project查看结果。设置ACL后,ls -l的输出末尾会多一个+号,比如-rw-r--r--+,这就是提醒你:这个文件有ACL扩展规则。

ACL适合权限策略比较复杂的共享目录,优势是灵活,坏处是排查时多一层东西。我的使用习惯是:优先组权限,组解决不了的再用ACL。而且上了ACL之后一定要留清晰的操作记录,因为ACL容易被后续的chmod覆盖,后来的维护者很可能根本意识不到有这层规则存在,排查半天才发现是ACL在起作用。

6. 常见权限问题排查与修复技巧

6.1 Permission denied排查五步法

第一步,确认当前身份。运行id,看是不是你想的那个用户。身份不对,后面全白查。

第二步,查看目标文件权限。ls -ld把目录也一起看,重点确认属主、属组、权限位,以及有没有+特殊标记。如果带+,就要进一步getfacl看ACL。

第三步,向上检查目录链。文件在目录A下,A上面还有B和C,任何一个目录缺少x权限,最终都会导致拒绝访问。这是新手最容易漏的一层。

第四步,检查挂载选项和文件系统。用mount查看挂载点有没有noexec、nosuid、ro这类标志。如果挂载点禁止执行,即使文件本身是755,运行脚本一样会弹Permission denied。OpenEuler上某些数据分区挂载时可能带noexec参数,脚本放进去直接跑就会遇到这种情况。

第五步,SELinux。OpenEuler的SELinux默认Enforcing,文件的安全上下文不对时也会被拒绝。此时用journalctl -xe看日志,通常能看到avc denied字样。临时调整可以用chcon或setsebool,长期使用建议写策略模块。不要图省事直接关掉SELinux,生产环境的安全基线不能这么破。

6.2 权限不足时,是不是改755就完事

很多资料告诉你“权限不够就修改文件权限为755”,这个说法对脚本执行、配置文件读取场景确实有效,因为755意味着属主读写执行、组和其他读执行。但要注意两点。

第一,把敏感文件改成755等于扩大暴露面。如果文件里有连接串、密钥这类信息,755会让其他用户也能读。所以我在改权限前一定问一句:这个文件真的需要组和其他人访问吗?

第二,有些权限问题改文件本身没用,要改的是目录或父目录。请求被拒发生在路径解析环节时,只改末端文件解决不了问题。排查顺序一定是先目录后文件。

我的建议是:不随手改777。热词里的755是个很通用的修复值,但先确认清楚你为什么需要755。给脚本加执行权限,chmod +x或chmod 755都可以;给配置文件读取权限,644往往就够了,不必升到755。

6.3 内存文件系统权限问题怎么处理

“用户拒绝访问内存文件权限怎么办”这类问题,指的是tmpfs这类挂载在内存里的文件系统,常见的有/dev/shm、/run、/var/run。这类目录重启后内容清空,但权限控制和普通磁盘目录一样受rwx控制。

排查内存文件权限的思路和普通目录一致:先ls -ld看目录权限,再检查属主。但有一个区别:tmpfs挂载参数可能在mount命令里显式指定了mode。比如某些应用把/dev/shm下面的子目录挂成700,只有特定用户能访问。这时候从外部看起来空间充足、目录也正常,但指定用户以外的人一碰就报拒绝访问。

修复时按业务需求调整。如果确认目录应该共享,用chmod 1777;如果目录属于特定服务用户,用chown加chmod 700配合服务账号运行。核心是确认业务对权限的预期,而不是盲目放开。

6.4 Windows与Linux的跨系统权限差异

做混合环境运维时,能看到Windows上类似“setnamedsecurityinfo failed”的ACL错误。这是Windows系统在设置NTFS ACL时,目标对象不支持或拒绝写入安全描述符。很多人把Windows的权限思路原样搬到Linux,结果水土不服。

Windows ACL有owner、group、DACL、SACL这一整套体系,而Linux主要是owner、group和mode位。Windows共享挂载到Linux后,通过SMB映射过来的权限位与Windows侧的细粒度ACL并不完全等价。在Linux这端看到权限没问题,进Windows一看共享的ACL规则可能完全是另一回事。

处理跨系统权限问题的思路很简单:Linux侧文件用chmod和chown统一管理基础权限;Windows共享目录的细粒度ACL,要到Windows服务器上管理。两边各管各的,不要指望在Linux端把Windows ACL彻底修好。明白这道边界,能省下大把两头调试的时间。

6.5 把权限自检写进脚本,友好报错

把权限检查写进脚本开头,比出了问题再查要高效得多。我的习惯写法是预检目标路径:

target_dir=/data/app if [ ! -d "$target_dir" ]; then echo "目标目录 $target_dir 不存在" >&2 exit 1 fi if [ ! -w "$target_dir" ]; then echo "当前用户 $(id -un) 对 $target_dir 没有写权限" >&2 exit 1 fi

这样脚本在真正写文件之前就把权限问题暴露出来,报错信息直接指明原因,而不是让你面对一个莫名其妙的拒绝再自己猜。操作日志同理,重定向之前先确认日志目录可写,能省掉不少半夜被监控叫醒的时间。

还有一件事值得养成习惯:脚本内创建的文件,默认权限用umask约束。如果脚本处理敏感数据,在脚本前段执行umask 077,创建的文件就只有属主能读能写。这一行的安全价值,抵得上后面补十次修权限。

我在几台OpenEuler上折腾脚本,最后总结了一句自我提醒:凡是脚本要落盘、要改文件、要切换用户,先画一条“身份-路径-权限”的线。身份用id查,路径看父目录链每一层的x权限,权限看ls -ld和特殊位。把这条线固定成肌肉记忆后,大部分权限问题几分钟就能定位。

还有一个小习惯分享给你:每次准备发布脚本前,我会运行shellcheck做一遍静态检查,它能帮你挡掉一部分变量未引用、权限判断不严谨之类的低级错误。最后再啰嗦一句:权限收紧永远比放开容易,755和700之间,动手之前想清楚你真正需要的是什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 8:29:36

IPv6中小企业网设计与实现:从地址规划到排障

简介:这份文档完整呈现了基于IPv6的中小型企业网络设计与实现方案,适合网络工程师、企业IT运维人员以及高校网络专业学生阅读参考。文档首先剖析IPv6协议的核心机制,包括128位地址空间、简化报头、无状态自动配置及内建安全特性,并…

作者头像 李华
网站建设 2026/10/9 8:25:52

像拆玩具一样定制oh-my-zsh:改造robbyrussell主题

1. 为什么要改robbyrussell:默认主题的痛点与定制思路 1.1 先看清楚robbyrussell到底做了什么 oh-my-zsh 安装完成后,绝大多数人见到的第一个提示符长这样: userhostname ~/workspace/project git:(main) $这就是 robbyrussell 主题的默认…

作者头像 李华
网站建设 2026/10/9 8:25:28

Linux指令实战:从背参数到理解系统设计逻辑

不知道你有没有过这种经历:刚接触 Linux 时,对着满屏的字符窗口手足无措,别人敲几条命令就把文件权限、服务状态、日志问题全搞定了,自己只能一遍遍地百度“Linux 删除文件夹命令是什么”。我当时就是这么过来的,硬生生…

作者头像 李华
网站建设 2026/10/9 8:24:42

医生排班与患者预约系统核心实现:从数据库设计到并发控制

先聊一个很常见的场景:医院门诊大厅里,患者早上七点就排到窗口,结果被告知今天坐诊的医生临时调班了;另一边,科室排班表还是靠手工Excel维护,导诊护士每周都要花半天时间核对哪个医生撞了时间段。这套“医生…

作者头像 李华
网站建设 2026/10/9 8:24:39

网安人必须啃透的计算机网络基础:从TCP握手到抓包实战

网上聊网安,十个帖子有八个在讲漏洞利用、工具链和赏金平台,但真正决定一个安全从业者能不能走远的,往往是最基础的计算机网络知识。我见过不少新人,第一天装好Kali,敲两行命令跑出反弹Shell就兴奋得不行,可…

作者头像 李华
网站建设 2026/10/9 8:24:19

鸿蒙上RN登录页开发:记住密码与深色模式适配实践

去年有个项目要从 iOS/Android 迁到鸿蒙生态,登录页是我接手的第一块。当时拿到设备真机跑起来,第一个感觉就是“又回到了刚学 RN 时的那种猜谜状态”:平台 API 叫法相同但行为不一样,第三方组件一半靠移植一半靠手写,…

作者头像 李华