news 2026/10/2 9:52:04

Linux终端特殊符号详解:从通配符到管道重定向的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux终端特殊符号详解:从通配符到管道重定向的完整指南

第一次打开 Linux 终端的人,心里通常有种说不出的忐忑:屏幕上一行[user@host ~]$,里面每个符号好像都有意义,又抓不准。其实这行看起来吓人的提示符,已经在用符号给你报信息了:[和]只是区分用户和主机名的边界,~代表你现在处于家目录,$说明你是普通用户,要是你切成 root,最后的$会变成#。可以说,还没敲任何命令,符号就已经把“你是谁、你在哪、你是什么权限”讲完了。

这篇文章我想把 Linux 终端里出现频率高的特殊符号一次性讲透:从路径符号、通配符、管道重定向,到引号转义、shell 变量、括号和感叹号的易混用法,最后再给你一份查错速查表。内容不挑发行版,Debian、Ubuntu、Kali、CentOS 都一样,Bash 和 Zsh 也基本通用。适合刚摸到 Linux 终端的新手,也适合那种用了一段时间、但看到$@、${var:-def}就发怵的半熟手。我尽量用实际场景说话,避免教科书式的罗列。

1. 路径符号:先搞懂你在哪个目录

路径类符号是终端里最高频的存在,因为无论敲什么命令,都得先知道自己脚下在哪。我见过不少新手把~当装饰品,也有人看到./就蒙圈。其实这部分内容很少,翻来覆去就是几个符号的组合。

1.1 家目录、当前目录、父目录:~、.、..

~代表当前用户的家目录。root 用户的~是/root,普通用户 user1 的~是/home/user1。所以cd ~永远回当前用户的家,cd ~user1还能直接切到别的用户家目录(前提是你有权限)。这个符号在配置文件里也经常出现,比如~/.bashrc就是“当前用户私有的 bash 配置”,如果你用ls ~和ls /home/user1对比,结果是一样的。

.有两个含义:作为单个符号出现在路径开头,它表示当前目录;作为文件名开头,它表示隐藏文件。所以ls看不到.bashrc,ls -a才能看到。./install.sh是显式告诉 shell“我要执行当前目录下的 install.sh”,这个写法在默认不带当前目录进 PATH 的系统里是必须的,否则会报command not found,很多第一次编译安装软件的人就栽在这里。

..是父目录,也就是上一级目录。cd ..往上走一级,cd ../..走两级。这里有个容易混淆的地方:..和.看起来都像一个目录名,其实它们在文件系统里确实是一个真实存在的目录项,只是被所有目录默认创建了。你在任何目录下执行ls -a,基本都能看到这两个白点。

1.2 根目录、路径分隔符和“上一目录”的妙用

/在最开始的位置代表根目录,夹在路径中间代表目录层级分隔符。Linux 世界里没有盘符,所有东西从/长出来,所以/etc/hosts就是根目录下 etc 目录里的 hosts 文件。用 Windows 习惯的人最容易把\带进来,在 Linux 里\是转义符,不是路径分隔符,这一点后面细说。

还有一个很容易被忽略的-:在cd -里表示“上一个工作目录”,它会把你带回之前所在的地方,并且打印出那个路径。比如你在/home/user1下cd /var/log,再敲cd -,就回到了/home/user1。这非常适合在两个目录之间来回切换,比反复敲绝对路径省事得多。

--是一个更冷门但有用的符号:它告诉命令“后面的内容不要再当成选项”。比如有一个文件叫-f,你想删掉它,直接rm -f会被解释成“强制删除”选项,而rm -- -f会真正删除名为-f的文件。这个细节在处理异常命名的文件时非常管用。

符号名称/含义常见场景注意事项
~家目录cd ~、~/downloads在不同用户下指向不同路径
.当前目录 / 隐藏文件前缀./run.sh、ls -a单独一个点必须作为路径出现
..父目录cd ..可以多级连用,如../../
/根目录 / 路径分隔符cd /、/etc/hosts不要和\混用
-上一个目录 / 选项前缀cd -、ls -lcd -会输出切换后的路径

2. 通配符与花括号:让命令自己匹配文件

通配符解决的核心问题是:不想一个个敲文件名,又或者文件名记不全。它的本质是 shell 在解析命令时先对参数做展开,再交给程序执行。理解了“展开”这一步,很多怪现象就说得通了。

2.1 *、?、[] 的匹配规则

*匹配任意长度字符串,包括空串。ls *.log会列出所有以 .log 结尾的文件,rm backup-*会删除所有以 backup- 开头的文件。需要提醒的是,*不会匹配以.开头的隐藏文件,这是 shell 的默认行为,也是很多新手备份目录时发现漏了.env类文件的原因。

?匹配任意单个字符。file?.txt能匹配file1.txt、fileA.txt,但不会匹配file10.txt,因为?只对应一个位置。

[]表示字符集匹配,匹配括号内的“一个”字符。file[0-9].txt匹配file0.txt到file9.txt,[a-f]*匹配所有以小写 a 到 f 开头的文件。如果中括号内第一个字符是!或^,表示取反,比如[!0-9]*匹配所有不以数字开头的文件。

这里有一个特别容易踩的坑:如果你把通配符放在引号里,shell 就不会展开它。find . -name "*.log"里的*.log会被原样传给 find,再由 find 自己做匹配;而ls *.log里的*是 shell 在展开。两种姿势在不同场景下都有用,但不能混用,否则要么匹配不到、要么结果出乎意料。

2.2 花括号 {} 的展开功能

{}不是通配符,它做的是“枚举展开”,和文件系统是否存在没有关系。echo {1..5}会输出1 2 3 4 5,mkdir -p ./{docs,scripts,src}/sub会一次性创建六个目录。这个能力特别适合批量生成文件名或目录结构。

更进阶一点的写法是{1..10..2},表示从 1 到 10 步长为 2,输出1 3 5 7 9。也有补零写法{01..10},会输出01 02 ... 10,在做固定宽度文件名时非常实用。

花括号和通配符的区别在于展开时机:花括号在通配符之前执行,而且是纯文本生成,不关心文件存不存在;通配符则在之后做文件系统匹配,匹配不到会保持原样。比如当前目录没有.txt文件时,ls *.txt会报“无法访问”,而echo {a,b}.txt永远会输出a.txt b.txt。我在写删除命令前有个习惯:先用echo rm -rf *看看展开结果,确认没问题再把 echo 去掉。符号越复杂,越要养成这种先观察再执行的习惯。

3. 管道与重定向:数据流的灵魂

Linux 的设计哲学是一切皆文件,命令跑起来之后,默认会有三个文件描述符:标准输入 0、标准输出 1、标准错误 2。管道和重定向的核心,就是控制这三个流的去向。搞懂这个逻辑,再看2>&1这种“天书”就不难了。

3.1 |、>、>>、< 的基础用法

|管道符把左边命令的标准输出接到右边命令的标准输入。比如cat /var/log/system.log | grep error,就是把 system.log 的内容逐行喂给 grep 做过滤。管道可以串联多层,history | awk '{print $2}' | sort | uniq -c | sort -rn就是这样叠出来的。要注意,管道右侧命令吃的是标准输出,如果左侧命令把错误信息输出到标准错误,没经过重定向的话,它是不会进入管道的,会在屏幕上直接显示。

>把标准输出写到文件,覆盖原有内容;>>是追加,不清空文件。echo "hello" > new.txt会新建或清空 new.txt 再写入。用>时要格外小心,因为覆盖是不可逆的,我见过不少同事把配置文件的>写成习惯性操作,结果一台服务器的关键配置被清空了。

<是从文件读入作为标准输入。sort < input.txt等价于sort input.txt的简单场景,区别在于:前者是 shell 把文件内容喂进命令的 stdin,后者是命令自己打开文件。绝大多数时候两者效果一样,但当你写管道组合时,用<能更清晰地表达“数据从哪里来”。

3.2 标准错误处理:2>、2>&1、&> 的区别

2>表示把标准错误重定向到文件:cmd 2> err.log只保存错误信息,正常输出还在屏幕。2>&1表示把标准错误重定向到标准输出当前所指的地方。要理解这里的核心:重定向是从左到右解析的,所以cmd > all.log 2>&1是先让标准输出指向 all.log,再把标准错误也指向 all.log,两者都在文件里。如果写成cmd 2>&1 > all.log,标准错误会先继承终端的标准输出,标准输出再去文件,结果是错误信息跑到屏幕上,文件里只有正常输出。这个顺序问题,是面试和实战中特别高频的考点。

bash 还支持&>和&>>,直接把标准输出和标准错误一起重定向到文件:cmd &> all.log。写起来方便,但可读性不如2>&1直观,团队协作时我一般会统一用完整写法。

3.3 Here Document 和 Here String

<<是 Here Document,用于把一段多行文本作为标准输入传给命令。常见姿势是配合cat或 ssh 写文件:

cat <<EOF > /tmp/example.txt 第一行 第二行 EOF

这里EOF只是一个约定俗成的结束标记,只要前后一致就行。如果我在某些场景使用中文内容,推荐用带引号的写法<<'EOF',这样 shell 不会把内容里的$和反引号当作变量或命令去展开,适合粘贴配置文件或脚本片段。

<<<是 Here String,把单个字符串作为输入传给命令。bc <<< "1 + 2"会输出3,省去了 echo 再管道的步骤。实际用起来频率不高,但偶尔在调试接口时需要把 JSON 字符串喂给 jq 或 curl,非常顺手。

4. 命令控制符号:&&、||、;、& 的逻辑配合

这组符号解决的是“什么时候执行下一条命令”的问题。它们虽小,但很考验人对 exit code 的理解。Linux 命令执行结束后都会返回一个退出码,0 代表成功,非 0 代表失败。控制符号就是围绕这个退出码在决策。

4.1 &&、||、; 三段式判断

&&表示“前一条命令成功才执行后一条”。mkdir test_dir && cd test_dir的意思非常明确:目录创建成功才进去,如果创建失败,就不执行 cd。这比两条独立命令安全,避免了目录没建成、人却已经跑进去的错乱。

||表示“前一条命令失败才执行后一条”。command_that_may_fail || echo "失败了"是常见的容错提示写法。更实用的组合是cd /some/path || exit 1,一旦切目录失败,脚本立刻退出,避免后面所有命令跑在错误的目录下。

;则是无条件执行,不管前面成不成功。echo "开始"; date; echo "结束"三条命令会按顺序走完。很多新手分不清&&和;,一句话总结:;是“不管死活都要走下一步”,&&是“你争气我才陪你玩”。实际写脚本时,这两个符号还经常被组合成A && B || C,表示“A 成功就做 B,否则做 C”,但要注意这不等同于 if-else,因为 B 的退出码也会影响结果,复杂逻辑还是建议老老实实写 if。

4.2 后台执行 & 与任务控制

在一行命令的末尾放一个&,这条命令会被放到后台执行,shell 立即返回提示符。sleep 100 &会立刻输出类似[1] 12345的任务号和进程号。任务号用jobs查看,进程号用ps或pgrep查看。

和后台任务相关的几个键位和符号也很常碰:Ctrl+Z把当前前台任务挂起,bg %1把挂起的任务放到后台继续跑,fg %1再把它拉回前台。这里的%1就是任务号,前面加%是为了和普通命令区分开。如果你启动了一个长时间任务又忘了加&,可以在挂起后用bg补救,不用杀死重来。

还有个线上部署时经常用的nohup command > output.log 2>&1 &,其中nohup让进程忽略挂断信号,&放后台,组合起来的效果就是“进程在后台跑,而且终端关掉也不影响”。这个组合看着复杂,其实拆开就是几层含义的叠加,理解底层再记忆就不难了。

5. 引号、命令替换与转义

引号是 shell 里最容易出鬼打墙的地方。很多人粘贴了一长串内容到终端后,发现光标一直停在>等待输入,敲回车也没反应,十有八九是引号没有闭合。理解三种引号的机制,能省下大把排查时间。

5.1 单引号、双引号、反引号的区别

单引号' '是最强的“保真”符号,里面的所有字符都按字面处理,shell 不会展开变量、不会执行命令、也不会解释反斜杠。echo '$HOME'会输出字符串$HOME,而不是 /root 或 /home/xxx。

双引号" "是部分保真,保留空格和通配符的字面意义,但$、反引号\`` 和` 仍然被处理。echo "$HOME"会输出具体的家目录路径,echo "$HOME" != '$HOME'这个对比是理解两种引号最直观的实验。

反引号`是历史遗留的命令替换符号,\date`表示把 date 命令的输出作为内容插入当前位置。现代写法更推荐$(date),因为$()可以嵌套,而反引号嵌套很痛苦。比如echo "文件数:$(ls | wc -l)"`,逻辑一眼就能看懂。

5.2 $() 和 $(( )) 的区分

$()做命令替换,括号里放命令,整体替换为命令输出。now=$(date +%Y-%m-%d)是脚本里最常见的用法,等号右边先执行 date,再把结果赋给变量 now。

$(( ))做算术展开,括号里放表达式,结果被替换成数值。echo $(( 3 + 5 * 2 ))输出13。赋值给变量时,count=$((count + 1))可以安全地完成自增。还有更简洁的((count++))语法,但这个属于 bash 的算术命令,不是简单的替换,注意区分。

5.3 反斜杠 \ 的转义作用

\是转义符,把下一个字符从“特殊含义”变成“普通字符”。echo \$HOME会输出$HOME的字符串形式,echo \\会输出一个反斜杠。文件名带空格时也常用转义:cat my\ file.txt,但我建议直接用双引号cat "my file.txt",可读性好得多。

转义和引号经常被拿来解决“粘贴内容出问题”的场景。有一次同事往终端贴了一段带 JSON 的 curl 命令,里面的双引号和 shell 的字符串边界打架,肉眼根本看不出来。我的建议是:涉及复杂字符串时,优先用单引号包裹整个内容,比如curl -d '{"name":"test"}',否则内部的双引号和变量展开会让命令变得难以预测。

另一个常见事故是粘贴大段含特殊符号的文本后,shell 进入“半解释”状态,屏幕上出现>提示符等待续行。这通常是因为粘贴内容里有不完整的引号、括号或 heredoc 标记。最快捷的办法是Ctrl+C放弃当前输入,再重新粘贴,不要和它硬刚。

6. Shell 特殊变量与参数符号

特殊变量是 shell 自带的一些“内置信息源”,它们的名字就是$加上一个或几个字符。这些符号在脚本里极其常用,也常出现在面试题里。

6.1 $?、$$、$! 的含义与用途

$?是上一条命令的退出码。command; echo $?如果输出 0,说明 command 成功;非 0 就是失败,具体数值通常是命令定义的错误类型。这个符号在调试脚本时是最好用的“探针”,我会在不确定某条命令是否成功时,临时加一行echo $?来验证。

$$是当前 shell 的 PID,也就是进程号。在脚本里生成唯一临时文件名时常用:tmpfile="/tmp/tmp_$$.log",这样每次运行脚本都得到不同文件名,避免多实例互相覆盖。

$!是最近一次放入后台的进程 PID。my_cmd &之后立刻用echo $!拿到它的进程号,方便后续wait $!或kill $!。注意它和$$的区别:$$是当前 shell 自己,$!是后台子任务的。

6.2 位置参数:$0、$1、$#、$@ 与 $*

位置参数是 shell 脚本的功能“入口”。./myscript.sh arg1 arg2里,$0是脚本名myscript.sh,$1是 arg1,$2是 arg2,$#是参数个数 2。$@和$*都代表所有参数,但在双引号里行为不同。

我写过一段小函数来演示这个区别:

print_args() { echo "使用 \$@:" for x in "$@"; do echo "[$x]"; done echo "使用 \$*:" for x in "$*"; do echo "[$x]"; done } print_args "a b" "c"

"$@"会保留参数边界,输出[a b]和[c];"$*"会把所有参数拼成一个字符串,输出变成[a b c]。所以脚本里遍历参数时,标准写法一定是for arg in "$@",除非你明确就是想合并。

6.3 ${} 变量扩展的进阶用法

光写$var只能取变量值,写成${var}则能让解析边界更清楚,比如echo "${var}sible不会把变量名误读成varsible。更多时候,${}配合不同符号能实现默认值、字符串截取等操作。

  • ${var:-default}:变量为空或未定义时,用 default 代替;变量本身没变。
  • ${var:=default}:变量为空或未定义时,赋值成 default。
  • ${var:?error msg}:变量为空或未定义时,输出错误并退出。
  • ${var#prefix}:从开头删除最短匹配 prefix。
  • ${var%suffix}:从结尾删除最短匹配 suffix。
  • ${var^^}:转大写;${var,,}:转小写。

举几个实际例子。echo "${port:-8080}"在没设置 port 变量时输出 8080;filename="test.log",echo "${filename%.log}"输出 test,这个在批量改文件后缀时很常用。这些看起来像“魔法”的写法,本质上都是 bash 的参数扩展特性,在man bash里可以查到完整清单。

7. 容易混淆的符号:()、[]、{}、! 的多种面孔

这一节是最多人用错的地方。核心原因是同一个符号在不同语境下代表完全不同的含义。比如[]既是通配符语法,也是 test 命令的标志。掌握语境的判断方法,比死记硬背更有效。

7.1 () 子shell 与 {} 当前shell 组命令

(command)会创建一个子 shell 来执行里面的命令,外层环境的变量、目录都不会被影响。(cd /tmp && pwd)执行时确实进入了 /tmp,但命令结束后,当前 shell 的目录还停在原地。这个特性常用于“临时改环境执行一段命令”,非常优雅。

{ command1; command2; }是当前 shell 的分组命令,同样能合并多条命令,但不会创建子 shell,因此变量和目录变化会保留。写法上有两个细节:{后面必须跟一个空格,最后一个命令之后必须加上;或换行。如果漏了这些,bash 会报语法错误。

7.2 [ ] 当 test 命令、[[ ]] 当增强版

if [ "$name" = "tom" ]里的[其实是一个命令(test 命令的别名),所以它的左右两侧必须有空格,[ "a" = "b" ]才合法。它内部对变量非常敏感,如果变量为空,很容易报[: =: unary operator expected,所以更稳的写法是在变量上加双引号,比如[ "$name" = "tom" ]。

bash 还提供了增强版[[ ]],它不是普通命令,而是 shell 关键字。里面的变量不用加引号也相对安全,支持&&、||、正则=~、模式通配等更丰富的语法:[[ $name = tom ]]、[[ $file = *.log ]]、[[ $num =~ ^[0-9]+$ ]]都能正常工作。写脚本时我个人更推荐[[ ]],但要注意它只在 bash/zsh 等 shell 中可用,在普通 sh 里会报错,跨脚本兼容时要谨慎。

7.3 ! 历史命令和取反逻辑

!在交互式终端里默认是历史展开:!!执行上一条命令,!$代表上一条命令的最后一个参数,!ls表示执行最近一条以 ls 开头的命令。这在手动操作时能省重复敲击,但在脚本里一般不会开启这个机制,因为不确定性太高。

同时!也用作逻辑取反,最典型的是if ! command; then ...。!放在命令前表示“如果 command 失败则执行 then 分支”。我经常用这种方式判断服务是否存在:if ! pgrep -x sshd; then echo "sshd 未运行"; fi。

一个实际坑:在双引号内写感嘆句,bash 既把它当历史展开又找不到对应条目时,会报event not found。比如echo "hello!"在部分交互式 bash 里会报错,解决办法是转义\!或用单引号,也可以在交互环境里执行set +H关闭历史展开功能。这个问题困扰过不少刚接触 shell 的新手。

7.4 正则表达式里的特殊符号和 Shell 里的不是一回事

很多人在做文本处理时,容易把 shell 通配符和正则混淆。举例来说,.在正则里表示匹配任意字符,而在 shell 里表示当前目录;*在正则里表示前一个字符重复任意次,在 shell 里则匹配任意字符串;?在正则里通常表示前一个字符出现 0 次或 1 次,在 shell 里则匹配单个字符。

比如grep "file?.txt"里的?是正则语法,而ls file?.txt里的?是 shell 通配符。同样是*,find . -name "*.txt"里它是被引号保护后交给 find 的模式,find 内部又把它当成类似通配符的匹配方式。这些“看似一样、实际不一样”的场景,最好的解决思路是:先确认当前上下文到底是谁在解释这个符号,是 shell、是 find、还是 grep,再决定怎么处理。理解这一点,比背一百个命令更值钱。

8. 常见问题与排查技巧速查

这一节把平时最容易被特殊符号坑到的现象汇总成表,方便你遇到问题时直接对号入座。这些都是我实际运维和带新人过程中反复遇到的典型情形。

现象可能原因解决办法
按回车没反应,提示符变成>有未闭合的引号、括号或 heredocCtrl+C放弃当前输入,检查后重新粘贴
报command not found,但文件就在当前目录当前目录不在 PATH,需要./前缀用./script.sh或把目录加入 PATH
ls *.txt报 cannot access当前目录没有匹配的 txt 文件确认目录内容;先echo *.txt看展开结果
rm -rf误删通配符展开结果和预期不符删除前先echo展开结果,或用 find 带 -delete
[: =: unary operator expected[ ]里变量为空或缺少空格变量加双引号:[ "$var" = "x" ]
脚本里$@和$*结果不一致双引号下两者行为不同遍历参数统一用"$@"
文件名带空格,命令拆分出错没有用引号保护参数改为"$file"写法
echo "hello!"报 event not foundbash 历史展开把!当特殊符号用单引号,或set +H关闭历史展开
重定向后屏幕还有报错信息只重定向了 stdout,stderr 没处理改成2>&1或&>
解压文件出现乱码字符编码不匹配确认系统 locale,必要时用unzip -O指定编码

我自己排查符号问题时有一套固定流程:先看当前交互的是哪个 shell,用echo $0确认,再看变量有没有被引号正确保护,最后把命令拆成多个小段用echo打印中间结果。很多看起来玄乎的问题,拆到最小环节就原形毕露了。

建议有条件的读者在本地装一下 shellcheck,这是检查 shell 脚本的静态分析工具,能自动指出变量没加引号、[ ]该换成[[ ]]等常见隐患。把它跑一遍,比自己盯着屏幕看有效得多。

最后再分享一个小习惯:我在终端里遇到不认识的符号,第一反应不是搜“Linux 符号大全”,而是先看它出现的上下文。符号落在路径里、重定向里、还是正则里,含义可能完全不同。学会了区分语境,你才能真正掌控这排键盘上的“魔法字符”。

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

/etc/passwd提权原理与四种实战路径详解

1. 为什么修改/etc/passwd能提权&#xff1f;这不是“改个密码”那么简单很多人第一次听说“通过/etc/passwd提权”&#xff0c;第一反应是&#xff1a;“这文件不就是存用户名和密码哈希的吗&#xff1f;改它有啥用&#xff1f;现在密码都存在/etc/shadow里&#xff0c;普通用…

作者头像 李华
网站建设 2026/10/1 7:11:38

基于GAT和GRU的动态信任评估模型DTEM实践详解

简介&#xff1a;图神经网络&#xff08;GNN&#xff09;是处理关系数据的强大范式&#xff0c;它通过消息传递聚合邻居信息&#xff0c;让模型能够学习节点间的复杂依赖。在众多GNN变体中&#xff0c;图注意力网络&#xff08;GAT&#xff09;利用注意力机制为不同邻居分配权重…

作者头像 李华
网站建设 2026/10/1 7:10:03

用objcopy分离调试信息实现GDB精准定位崩溃行号

1. 项目概述&#xff1a;为什么要把调试信息从可执行文件里“抠”出来&#xff1f;你有没有遇到过这样的场景&#xff1a;线上服务突然崩溃&#xff0c;系统生成了一个 core dump 文件&#xff0c;你想用 GDB 去查——结果一加载就报错&#xff1a;“warning: .debug_* section…

作者头像 李华
网站建设 2026/10/1 7:09:56

AWD攻防赛脚本集合:从手动加固到半自动防守的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 7:09:44

跑在你电脑上的 AI 智能体:从写代码到做 PPT,TaoToken 一句话搞定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华