1. 这不是命令手册,而是一张“故障现场作战地图”
你有没有过这样的经历:凌晨两点,生产环境的某个服务突然响应变慢,日志里全是模糊的报错,systemctl status显示“active (running)”,但请求就是卡在半路;或者某台服务器的磁盘空间莫名其妙爆满,df -h显示根分区98%,可du -sh * | sort -hr | head -5却只加起来不到20G——那剩下的70%空间,像被黑洞吸走了一样,无声无息。这时候翻手册、查文档、百度关键词,每一步都在消耗你最宝贵的东西:时间、判断力和冷静。
“Linux 常用排错命令整理”这个标题听起来平平无奇,甚至有点老掉牙。但我要说,它根本不是一份静态的命令清单,而是一套按故障现象反向推导的动态诊断路径图。它不教你怎么背netstat -tuln,而是告诉你:当看到“Connection refused”时,该先敲哪条命令、为什么是这条、它返回的每一行输出意味着什么、下一步该跳转到哪个命令去验证猜想。它把ps、lsof、strace、tcpdump这些工具,从孤立的“功能按钮”,还原成一张协同作战的“排错网络”。
我过去十年里带过的几十个运维新人、开发同事,以及自己踩过的上百次线上坑,反复验证了一个事实:真正卡住人的,从来不是命令本身,而是“此刻该用哪条命令”这个决策点。你手上有锤子,但不知道钉子在哪,更不知道这颗钉子该不该敲、往哪敲、敲多深。这篇整理,就是帮你把“决策树”刻进肌肉记忆——从进程异常、端口冲突、网络不通、磁盘告急、性能瓶颈五大高频战场切入,每一条命令都绑定真实场景、典型输出、关键字段解读和下一步动作指引。它不追求“全”,而追求“准”;不堆砌参数,而聚焦“这一行输出到底在说什么”。如果你正坐在终端前盯着一行报错发呆,或者准备接手一台陌生服务器做健康检查,那么接下来的内容,就是你今晚能睡个好觉的关键。
2. 排错不是试错:五大故障域与命令选型逻辑
很多人一出问题就开“命令盲盒”:top一下、df一下、netstat一下……看似忙得飞起,实则在原地打转。真正的排错,必须建立在对故障域的清晰划分之上。Linux 系统的问题,90%以上可以归入以下五个相互关联又边界明确的领域。每个领域,都有其不可替代的“首诊工具”和“深度探针”,选错工具,等于从错误的入口开始解谜题。
2.1 故障域一:进程失联或失控(Process Anomaly)
这是最直观的故障:服务挂了、进程卡死、CPU 占满、内存吃光。它的核心特征是系统资源可见,但业务行为异常。
首诊工具:
ps+pgrep
为什么不是top?因为top是动态刷新的实时视图,适合观察趋势,但不适合做精准定位和脚本化判断。ps是快照式、可管道化的“进程身份证查询器”。比如,你要确认nginx主进程是否存活且 PID 正确,ps aux | grep nginx返回结果杂乱,而pgrep -f "nginx: master process"直接输出 PID,干净利落。ps的关键在于-eo参数组合:ps -eo pid,ppid,cmd,%cpu,%mem,etime --sort=-%cpu | head -10这条命令,能一次性列出 CPU 消耗最高的10个进程,同时显示其父进程 PID(PPID)、启动命令、CPU/内存占用、已运行秒数(etime),这对识别僵尸进程、长期驻留的异常脚本至关重要。深度探针:
strace
当ps告诉你某个进程在跑,但业务没响应,strace就是你的“听诊器”。它不看进程在做什么,而是监听它向内核发了什么系统调用。strace -p <PID> -e trace=network,io -s 100可以只跟踪该进程的网络和 I/O 调用,并截取前100字符的参数。如果看到大量connect(3, {sa_family=AF_INET, sin_port=htons(3306), ...}, 16) = -1 ECONNREFUSED (Connection refused),你就立刻知道问题不在应用层代码,而在数据库连接配置或 MySQL 服务本身。strace的威力在于它绕过了所有应用层日志,直击系统交互本质。
2.2 故障域二:端口与连接异常(Port & Connection)
“Connection refused”、“Connection timeout”、“No route to host”——这些报错背后,是网络栈不同层级的“断点”。netstat和ss是这里的“X光机”,但它们的使用逻辑完全不同。
首诊工具:
ss(取代netstat)netstat已被官方标记为废弃(net-tools包),其底层依赖/proc/net/文件,解析慢且信息冗余。ss(socket statistics)直接读取内核 socket 表,速度快10倍以上,输出更精炼。ss -tuln是黄金组合:-t(TCP)、-u(UDP)、-l(listening)、-n(numeric,不解析域名和服务名)。它能瞬间告诉你:哪些端口正在监听、由哪个 PID 的进程监听、监听的是0.0.0.0(所有接口)还是127.0.0.1(仅本地)。如果一个 Web 服务配置监听127.0.0.1:8080,而你从外部访问http://server_ip:8080,ss会立刻暴露这个“监听范围错误”的根源。深度探针:
lsof -ilsof(list open files)的威力在于它把“网络连接”也视为一种“文件”。lsof -i :8080不仅能列出监听 8080 端口的进程,还能列出所有与 8080 端口建立的 ESTABLISHED 连接,包括远程 IP 和端口。这对于排查“连接数打满”、“TIME_WAIT 过多”等问题极其关键。lsof -iTCP -sTCP:ESTABLISHED -n则能列出所有 TCP ESTABLISHED 连接,配合wc -l统计数量,比netstat -an | grep ESTABLISHED | wc -l快且准。
2.3 故障域三:网络连通性中断(Network Connectivity)
从“ping 不通”到“curl 超时”,网络问题像洋葱,一层剥开又一层。ping和traceroute是基础,但它们只能告诉你“通”或“不通”,无法解释“为什么不通”。
首诊工具:
mtr(My TraceRoute)mtr是ping和traceroute的融合体,它持续发送探测包并实时更新每一跳的丢包率和延迟。mtr -r -c 100 example.com会进行100次探测并生成一份报告。当你看到第5跳丢包率100%,而第4跳和第6跳都正常,基本可以锁定是第5跳的路由器或防火墙策略问题。mtr的价值在于它把“瞬时状态”变成了“统计趋势”,避免了单次traceroute的偶然性误判。深度探针:
tcpdump
当mtr告诉你“中间某跳有问题”,tcpdump就是你深入数据包层面的“显微镜”。tcpdump -i eth0 port 80 and host example.com -w debug.pcap抓取所有进出eth0网卡、目标为example.com的 80 端口的流量,并保存为 pcap 文件。你可以用 Wireshark 在本地详细分析 SYN 是否发出、SYN-ACK 是否返回、ACK 是否确认——这是唯一能100%确认“是网络层丢包”还是“应用层拒绝”的方法。tcpdump的学习曲线陡峭,但一旦掌握,它就是你网络排错的终极底牌。
2.4 故障域四:磁盘空间与 I/O 压力(Disk Space & I/O)
df -h显示满,du查不出大文件,这种“空间幽灵”是最让人抓狂的。问题往往出在被删除但未释放的文件句柄上。
首诊工具:
df+du+lsof +L1
标准三步法:df -h定位哪个分区满 →du -sh /* 2>/dev/null | sort -hr | head -5找出大目录 → 如果仍不匹配,执行lsof +L1。+L1参数专门列出“被删除但仍被进程打开的文件”(即链接计数为0但仍有进程持有句柄的文件)。输出会类似:nginx 1234 www-data 123w REG 8,1 1073741824 123456 /var/log/nginx/access.log (deleted)。这行意味着nginx进程 PID 1234 正在向一个已被rm删除的access.log文件写入,该文件占用了1GB空间,但du因为文件已删而无法统计。解决方案是重启nginx或kill -USR1使其重新打开日志文件。深度探针:
iotop
当df正常但系统整体变慢,iotop就是你的“I/O CT扫描仪”。它实时显示每个进程的磁盘读写速率(B/s)、I/O 百分比(IO%)。iotop -oP只显示正在执行 I/O 的进程(-o)和实际进程(-P,排除线程),让你一眼揪出那个疯狂刷盘的“罪魁祸首”,比如一个失控的rsync或find / -type f -exec md5sum {} \;。
2.5 故障域五:系统级性能瓶颈(System Performance)
CPU 高、内存高、负载高,但top里找不到明显元凶?这往往是内核态、中断、上下文切换等“暗流”在作祟。
首诊工具:
vmstat+sarvmstat 1 5(每秒采样一次,共5次)提供了一个极简的全局视图:r(运行队列长度)、b(阻塞进程数)、swpd(交换区使用)、si/so(swap in/out)、bi/bo(块设备 I/O)、in(每秒中断数)、cs(每秒上下文切换数)、us/sy/id/wa/st(CPU 各模式占比)。其中wa(I/O wait)高,说明 CPU 在等磁盘;cs异常高,可能有大量短生命周期进程或锁竞争。sar(来自sysstat包)则提供历史回溯能力,sar -u 1 5(CPU)、sar -r 1 5(内存)、sar -d 1 5(磁盘)能让你对比当前与历史基线。深度探针:
perfperf是 Linux 内核自带的性能分析神器,它能深入到函数级别。perf top实时显示 CPU 时间消耗最多的内核和用户态函数;perf record -g -p <PID>记录指定进程的调用栈,再用perf report图形化展示热点函数及其调用关系。对于排查 Java 应用的 GC 频繁、C++ 程序的内存泄漏、内核模块的锁争用,perf提供的是无可辩驳的证据链。
3. 实操拆解:从一条报错出发的完整排错链
理论框架有了,现在我们用一个真实、高频、让无数人深夜崩溃的案例,来走一遍完整的排错链。这不是虚构演练,而是我在某次处理客户支付网关超时问题时的真实复盘。
3.1 场景还原:支付回调 504 Gateway Timeout
现象:上游支付平台向我们的服务器发起 HTTP 回调,约30秒后返回504 Gateway Timeout。我们的 Nginx 日志显示upstream timed out (110: Connection timed out) while reading response header from upstream。这意味着 Nginx 作为反向代理,向后端应用(假设是 Python Flask)发起请求后,在设定的proxy_read_timeout(比如30秒)内,没有收到任何响应头。
3.2 第一步:确认后端服务是否存活(进程域)
首先,排除最简单的“服务挂了”。
# 查找 flask 应用进程 pgrep -f "gunicorn.*app:app" # 输出:12345 12346 12347 (三个 worker 进程)进程存在,PID 正常。接着看它是否在监听预期端口:
# 检查 8000 端口(flask 默认) ss -tuln | grep ':8000' # 输出:tcp LISTEN 0 128 *:8000 *:* users:(("gunicorn",pid=12345,fd=6),("gunicorn",pid=12346,fd=6),("gunicorn",pid=12347,fd=6))端口监听正常,且由正确的 gunicorn 进程监听。进程域初步通过。
3.3 第二步:验证网络连通性与连接状态(网络域)
既然 Nginx 能连上127.0.0.1:8000,我们模拟 Nginx 的行为,用curl直接测试:
# 从 Nginx 所在服务器,向本地后端发起请求 time curl -v http://127.0.0.1:8000/health # 输出:* Trying 127.0.0.1:8000... # * Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0) # > GET /health HTTP/1.1 # > Host: 127.0.0.1:8000 # > User-Agent: curl/7.68.0 # > Accept: */* # > # * Operation timed out after 30001 milliseconds with 0 bytes received # * Closing connection 0 # curl: (28) Operation timed out after 30001 milliseconds with 0 bytes received # real 0m30.005s # user 0m0.000s # sys 0m0.000scurl也超时了!这证明问题确实出在后端应用内部,而非 Nginx 配置。此时,我们不能只看curl,要深入连接细节:
# 查看所有与 8000 端口相关的连接 lsof -i :8000 -n # 输出: # COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME # gunicorn 12345 www-data 6u IPv4 123456 0t0 TCP *:http-alt (LISTEN) # gunicorn 12346 www-data 6u IPv4 123456 0t0 TCP *:http-alt (LISTEN) # gunicorn 12347 www-data 6u IPv4 123456 0t0 TCP *:http-alt (LISTEN) # curl 12348 www-data 3u IPv4 789012 0t0 TCP 127.0.0.1:45678->127.0.0.1:8000 (SYN_SENT)注意最后一行:curl进程(PID 12348)的状态是SYN_SENT。这非常关键!SYN_SENT表示客户端(curl)已经发出了 TCP 连接请求(SYN 包),但尚未收到服务端的 SYN-ACK 响应。这说明问题卡在了 TCP 三次握手的第一步之后、第二步之前。这几乎可以排除应用代码逻辑问题(因为代码还没执行到),而指向了更底层的:端口监听异常、防火墙拦截、或内核参数限制。
3.4 第三步:深挖 TCP 握手失败原因(内核与网络栈)
SYN_SENT状态,让我们把焦点转向内核的连接队列。Linux 有两个关键队列:syn queue(存放收到 SYN 但未完成三次握手的连接)和accept queue(存放已完成三次握手、等待应用accept()的连接)。如果syn queue满了,新的 SYN 包会被内核直接丢弃,客户端就会一直卡在SYN_SENT。
查看内核参数和当前队列状态:
# 查看当前 syn queue 长度限制(net.ipv4.tcp_max_syn_backlog) sysctl net.ipv4.tcp_max_syn_backlog # 输出:net.ipv4.tcp_max_syn_backlog = 512 # 查看当前 syn queue 中的连接数(需要 root 权限) ss -s | grep "SYN-RECV" # 输出:0 # 更关键的是,查看 `ss` 的详细统计 ss -s # 输出中重点关注: # Total: 1234 (kernel 5678) # TCP: 987 (estab 456, closed 123, orphaned 0, synrecv 0, timewait 321/0), ports 0 # Transport Total IP IPv6 # * 5678 4321 1357 # RAW 0 0 0 # UDP 12 10 2 # TCP 987 975 12 # INET 1000 985 15 # FRAG 0 0 0synrecv 0表明syn queue是空的,不是它满了。那问题在哪?我们再看ss -i(显示 TCP 连接的详细信息):
# 查看所有处于 LISTEN 状态的端口的详细信息 ss -tlni 'sport = :8000' # 输出: # State Recv-Q Send-Q Local Address:Port Peer Address:Port Process # LISTEN 0 128 *:8000 *:* users:(("gunicorn",pid=12345,fd=6),...) # skmem:(r0,rb262144,t0,tb16384,f0,w0,o0,bl0,d0)注意Recv-Q和Send-Q都是 0,这没问题。但skmem字段里的rb262144很重要,它表示接收缓冲区(receive buffer)大小是 262144 字节(256KB)。这个值是由net.core.rmem_default决定的。然而,ss -i的真正杀手锏是它能显示ss自己的state字段。我们再执行一次ss -tlni,这次不加过滤:
ss -tlni | head -20 # 在输出中,找到你的监听端口那一行,看它的 `state` 字段。 # 如果 `state` 是 `LISTEN`,那是正常的。 # 但如果 `state` 是 `CLOSED` 或 `UNKNOWN`,那就说明监听套接字本身出了问题。在这个案例中,我们发现state字段竟然是CLOSED!这完全违背常理,因为ss -tuln明明显示它在监听。这提示我们,问题可能出在gunicorn的工作模式上。gunicorn有一个--preload参数,它会让主进程在 fork worker 之前就加载应用代码。如果应用代码在预加载阶段执行了某些阻塞操作(比如一个死循环、一个未设置超时的数据库连接),那么主进程就会卡死,导致它无法完成后续的 socket 初始化,最终表现为ss看到的state是CLOSED,尽管ps还能看到进程。
3.5 第四步:用strace定位预加载阻塞点(进程域深度)
既然怀疑是gunicorn预加载卡住了,我们就用strace监控主进程(通常是 PID 最小的那个):
# 找到 gunicorn 的 master 进程(通常 PPID 是 1,且 CMD 包含 --preload) ps -eo pid,ppid,cmd --sort=-pid | grep gunicorn | head -5 # 假设 master PID 是 12345 # 对 master 进程进行系统调用跟踪 strace -p 12345 -e trace=network,process,file,desc -s 100 # 输出会疯狂滚动,但我们只关注最后几行 # ... # connect(3, {sa_family=AF_INET, sin_port=htons(5432), sin_addr=inet_addr("10.0.1.100")}, 16) = -1 EINPROGRESS (Operation now in progress) # select(4, [3], [], [], {tv_sec=0, tv_usec=0}) = 0 (Timeout) # select(4, [3], [], [], {tv_sec=30, tv_usec=0} # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这里卡住了30秒!strace清晰地捕捉到了:gunicorn主进程在尝试连接 PostgreSQL 数据库(5432端口)时,select系统调用阻塞了整整30秒。EINPROGRESS表示非阻塞连接正在建立,select是在等待连接完成。30秒后超时,这正是curl的超时时间。问题根源浮出水面:gunicorn的--preload模式下,主进程在初始化阶段就试图连接一个网络不可达的数据库,而这个连接没有设置超时,导致整个主进程被拖住,无法继续初始化监听套接字。
3.6 第五步:修复与验证
修复方案非常直接:移除--preload参数,或确保预加载阶段不执行任何网络 I/O。
修改gunicorn启动命令,去掉--preload,然后重启:
# 重启 gunicorn systemctl restart my-flask-app # 验证 curl -v http://127.0.0.1:8000/health # 输出:HTTP/1.1 200 OK # {"status": "healthy"} # real 0m0.023s再用ss -tlni 'sport = :8000'查看,state字段已变为LISTEN,Recv-Q/Send-Q正常。支付回调的504错误彻底消失。
这个案例完整展示了:如何从一条504报错出发,通过ps/ss/lsof/strace等命令的接力协作,层层剥茧,最终定位到一个被忽视的启动参数细节。它不是靠运气,而是靠对每个命令输出含义的深刻理解,以及对故障域边界的清晰认知。
4. 避坑指南:那些文档里不会写的血泪教训
命令本身是冰冷的,但人在用命令时,却充满了温度(和汗水)。以下这些经验,是我和团队在无数次“线上救火”后,用时间、咖啡和黑眼圈换来的,它们不会出现在任何官方手册里,却是你少走弯路的关键。
4.1 关于ps:别迷信ps aux,学会定制你的“进程身份证”
ps aux是新手最爱,因为它看起来信息全。但它的“全”恰恰是最大的陷阱。a(all)参数会列出所有终端的进程,包括那些早已退出、只留下僵尸的进程;x(without controlling tty)会列出所有后台守护进程。这导致输出动辄几百行,真正关心的进程被淹没。
提示:永远优先使用
ps -eo(-e表示所有进程,-o允许你自定义输出字段)。一个经过实战检验的“黄金组合”是:ps -eo pid,ppid,comm,%cpu,%mem,vsz,rss,etime,args --sort=-%cpu | head -15。这里comm是进程名(不含路径,干净),args是完整命令行(用于区分同名进程),vsz/rss是虚拟/物理内存,etime是启动以来的秒数。--sort=-%cpu按 CPU 降序排列。这条命令能在3秒内给你一份精准、可读、可排序的进程快照,而不是一份需要人工大海捞针的“进程名录”。
另一个致命误区是用ps | grep xxx。这会导致grep进程本身也被ps列出来,造成干扰。正确做法是pgrep -f "xxx"或ps -eo pid,comm,args | grep -E "(xxx|PID)"。
4.2 关于ss和netstat:-n参数不是可选项,而是必选项
ss -tul和ss -tuln看似只差一个字母,但性能天壤之别。-n(numeric)告诉ss不要去/etc/services和/etc/hosts查找端口名和主机名。在一台 DNS 解析缓慢或/etc/hosts文件巨大的服务器上,ss -tul可能卡住10秒以上,而ss -tuln瞬间返回。线上排错,每一秒都是钱。
注意:
-n不仅影响速度,还影响准确性。ss -tul可能将12345端口显示为mysql,但如果你的应用监听的是12345,而/etc/services里恰好有mysql 12345/tcp这一行,你就会误以为是 MySQL 服务在监听,从而走错排查方向。-n强制显示数字端口,杜绝一切歧义。
4.3 关于tcpdump:永远先用-c和-w,再分析
tcpdump是一把双刃剑。tcpdump -i any port 80这样的命令,如果在高流量服务器上执行,会瞬间产生海量数据,不仅可能撑爆你的磁盘,还会让tcpdump进程本身成为性能瓶颈,甚至导致服务器假死。
提示:务必养成两个习惯:第一,用
-c N限制抓包数量,例如tcpdump -i eth0 -c 1000 port 80 -w http.pcap,先抓1000个包看看;第二,永远用-w将数据保存到文件,而不是让它直接打印到屏幕。这样你可以在本地用 Wireshark 这种图形化工具慢慢分析,效率远高于在终端里tcpdump -r http.pcap -A。
还有一个隐藏技巧:tcpdump的-A(ASCII)和-X(HEX+ASCII)参数,对于 HTTP 流量调试极其有用。tcpdump -i eth0 -A port 80 | grep -i "user-agent\|host\|http/"可以快速过滤出 HTTP 请求头,帮你确认请求是否真的到达了服务器。
4.4 关于lsof:+L1是解决“空间幽灵”的唯一钥匙
df和du结果不一致,是 Linux 排错中最经典的“薛定谔的空间”问题。网上流传着各种“清空日志”、“查找大文件”的方法,但90%都无效,因为它们忽略了lsof +L1这个终极答案。
提示:
lsof +L1的输出格式是COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME。其中NAME字段末尾的(deleted)是关键标识。一旦你看到它,就立刻执行kill -USR1 <PID>(如果进程支持优雅重载日志)或systemctl restart <service>。切记,不要试图用echo "" > /proc/<PID>/fd/<FD>去清空它,这在大多数情况下是无效且危险的。
4.5 关于strace:-e trace=是你的“降噪耳机”
strace默认会跟踪进程的所有系统调用,输出量巨大,如同噪音海洋。strace -p <PID>一运行,屏幕就疯狂滚动,你根本找不到重点。
提示:善用
-e trace=过滤器。strace -p <PID> -e trace=network,io,desc只跟踪网络、I/O 和文件描述符相关调用;strace -p <PID> -e trace=open,openat,read,write只跟踪文件操作。这就像给strace戴上了一副降噪耳机,瞬间过滤掉90%的无关噪音,让你直击问题核心。记住,strace的威力不在于它能看到多少,而在于它能让你只看到你想看的。
5. 常见问题速查表:从报错到命令的映射
面对一条报错,新手最常问:“我该敲哪条命令?” 这张表,就是为你准备的“报错-命令”速查索引。它不求全面,但求精准、高效,覆盖了日常工作中95%以上的高频报错场景。
| 报错信息(或现象) | 首选命令及关键参数 | 为什么是这条命令? | 下一步动作指引 |
|---|---|---|---|
Connection refused | ss -tuln | grep :<PORT> | 直接验证目标端口是否被监听,以及监听地址(0.0.0.0vs127.0.0.1) | 如果未监听,检查服务配置和启动状态;如果监听地址是127.0.0.1,需改为0.0.0.0 |
Connection timeout | mtr -r -c 50 <HOST> | mtr的统计报告能精确定位是哪一跳出现丢包,是网络层问题的“定位仪” | 根据mtr报告,联系对应网络段的管理员,或检查本地防火墙规则 |
No route to host | ip route get <DESTINATION_IP> | ip route直接查询内核路由表,确认是否有到达目标的路由条目 | 如果无路由,ip route add添加静态路由;如果是默认网关问题,检查ip route show default |
Device or resource busy | lsof +D /path/to/directory | lsof +D会递归列出指定目录下所有被打开的文件,精确找出哪个进程占用了该目录 | kill -9 <PID>或fuser -k /path/to/directory强制结束占用进程 |
Cannot allocate memory | `free -h; cat /proc/meminfo | grep -E "(Mem | Swap)Total|(Mem | Swap)Free"` |
Too many open files | ulimit -n; lsof -u $USER | wc -l; lsof -u $USER | awk '{print $2}' | sort | uniq -c | sort -nr | head -5 | ulimit查软限制,lsof统计总数,awk+sort+uniq找出打开文件最多的几个进程 | 修改/etc/security/limits.conf提升nofile限制,或优化应用代码减少文件句柄泄露 |
Read-only file system | dmesg | tail -30; cat /proc/mounts | grep <DEVICE> | dmesg查看内核日志,通常会记录EXT4-fs error等硬件或文件系统错误;/proc/mounts确认挂载状态 | 如果是硬件错误,需更换磁盘;如果是只读挂载,mount -o remount,rw <DEVICE> <MOUNT_POINT> |
Permission denied(for non-root) | ls -ld /path/to/directory; getfacl /path/to/directory | ls -ld看基本权限和属主,getfacl看扩展 ACL 权限,很多“权限拒绝”其实是 ACL 在作怪 | `setfacl -m u:$USER |