news 2026/9/30 1:17:05

Linux Shell脚本入门:从创建到运行,Bash实战详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux Shell脚本入门:从创建到运行,Bash实战详解

先问个问题:你第一次打开 Linux 终端,是不是对着黑乎乎的窗口发呆,然后默默关掉,心想“这玩意儿到底能干嘛”?我当年的反应一模一样。直到后来我明白了一个事实——终端不是一个“窗口”,而是一个“对话入口”。你每敲一条命令,都是在和操作系统说一句话。而 Shell 脚本,就是把这一串对话提前写好,让系统一次性执行完。

这篇文章要讲的,就是 Bash 初学者最需要掌握的一条完整路径:创建脚本、写内容、给权限、运行它。我会把每个步骤背后的“为什么”也讲清楚,而不是只丢给你几条命令。毕竟 Linux 下最不缺的就是命令,缺的是理解。

这篇内容适合完全零基础的新手,也适合那些已经会敲 ls、cd,但从来没正经写过脚本的人。看完你会知道:脚本到底长什么样、为什么第一行是那串奇怪的符号、chmod 755 是什么鬼、为什么运行要加 ./,以及遇到最常见的那几个报错该怎么处理。

1. 先搞清楚:Shell、Bash、脚本这三者到底是什么关系

新手容易绕晕的第一个点,就是 Shell、Bash、脚本这三个词的交错出现。我会把这三者的关系简化成一句话:Shell 是 Linux 系统的“翻译官”,Bash 是翻译官中最常见的一位,而脚本就是给这位翻译官的“提词稿”。

1.1 Shell 是用户和内核之间的翻译层

Linux 系统真正干活的叫“内核”,也就是 Kernel。但内核听不懂人话,你也不可能直接跟它交流。中间就需要一层“壳”,也就是 Shell,来接收你的输入、翻译成内核能理解的指令、再把执行结果反馈给你。

你可以这样理解:你去一家外国餐厅吃饭,服务员负责把你想吃的菜转达给后厨。这个服务员就是 Shell。你换一个服务员,点菜的方式可能略有不同,但最终后厨做的还是那道菜。常见的 Shell 有 Bash、Zsh、Fish、Sh 等,它们都是“服务员”,只是服务风格略有差异。

这个类比里最重要的启发是:Shell 本身是一个程序,不是系统底层的东西。Linux 默认安装了 Bash,你开机看到的那个终端界面,本质上就是 Bash 这个程序在运行。

1.2 Bash 为什么能成为默认 Shell

Bash(Bourne Again Shell)是 Steve Bourne 写的 Bourne Shell(sh)的增强版本。它兼容了 sh 的语法,又加入了很多便利功能,比如命令历史、别名、数组、算术运算等。多年下来,它成了几乎所有 Linux 发行版默认安装和默认使用的 Shell。

你可以用这条命令查看当前系统用的什么 Shell:

echo $SHELL

输出一般是/bin/bash或者/usr/bin/bash,这就说明你的默认 Shell 是 Bash。

另外要明白一个容易混淆的概念:sh、bash、zsh是不同的程序。虽然很多脚本开头写的是#!/bin/sh,但在绝大多数 Linux 系统上,/bin/sh其实是一个指向bash的符号链接,只是运行时会进入“POSIX 兼容模式”。所以初学者不必过度纠结这两者的差异,写脚本时统一用#!/bin/bash就好。

1.3 脚本的本质就是“批处理命令”

脚本不是一个特殊的文件格式,它就是一个普通的文本文件,里面一行一行写着待执行的命令。Bash 会从上到下逐行读取、逐行执行。

这就像你写了一个“今日待办清单”:

  • 打开浏览器
  • 查邮件
  • 写日报
  • 提交代码

脚本也一样,只不过清单里的每一项换成了 Linux 命令。手动执行和脚本执行的区别在于:手动是你在终端里一条一条敲,脚本是一次性交给系统自动完成。

所以“Shell 脚本”更准确的称呼其实是“Shell 命令的集合”,只是我们习惯上叫“脚本”而已。明白这一点,你就不会觉得它有多高深了——你本来就会敲命令,现在只是把命令写进文件让它批量跑。

2. 环境准备:确认你的系统里有 Bash

写脚本之前,先确认工作环境没问题。这一步听起来简单,但我在教学过程中确实遇到过有人卡在这儿——他们用的是 Windows,以为自己在“写 Linux 脚本”。

2.1 先确认你用的是不是 Linux 环境

Bash 脚本是给 Linux/Unix 类系统用的,所以你得先有一个能运行 Bash 的环境。常见的选择有:

  • 本机安装了完整的 Linux 发行版(Ubuntu、CentOS、Debian、Kali 等)
  • 虚拟机里装了 Linux
  • 云服务器上的 Linux 环境
  • Windows 上通过 WSL(Windows Subsystem for Linux)运行 Linux
  • Windows 上的 Git Bash(提供有限的 Bash 环境)

判断标准很简单:打开终端,能执行ls、pwd这些命令,就说明你的环境是可用的。

注意:在 Windows 的 Git Bash 里,部分行为和完整 Linux 环境有差异,比如路径写法、磁盘挂载方式等。如果你是想正经学 Linux,建议直接装一个虚拟机或者用云服务器,别拿 Git Bash 将就。

2.2 确认 Bash 版本与可用性

在终端里执行:

bash --version

正常会输出类似这样:

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

看到这个,你的环境就没有问题。

2.3 准备一个专门放脚本的目录

强烈建议不要随便把脚本写到系统目录里。我自己习惯建一个~/scripts目录专门放脚本:

mkdir -p ~/scripts cd ~/scripts

这样做的好处是:脚本集中管理,好备份,也好找。等以后脚本多了,你就知道这个习惯多重要了。

3. 手把手:创建你的第一个 Bash 脚本

进入正题。我会带你走完整个流程:创建文件、写入内容、加执行权限、运行脚本。每一步都会解释为什么这么做,而不是只让你复制粘贴。

3.1 用 touch 创建文件,还是直接用编辑器?

创建脚本文件有两种常见方式:

方式一:先创建再编辑

touch hello.sh

然后使用文本编辑器打开编辑:

vim hello.sh

方式二:直接用编辑器新建

vim hello.sh

我个人推荐直接方式二,少敲一条命令。如果你不熟悉 vim 的操作,可以先使用 nano,它更友好:

nano hello.sh

进入编辑器后,写下如下内容:

#!/bin/bash echo "Hello, World!"

然后保存退出。nano 的保存快捷键是Ctrl+O,回车确认,再Ctrl+X退出。vim 则是按Esc,输入:wq,回车。

3.2 第一行#!/bin/bash的作用是什么

这一行叫Shebang(也称为释伴行)。它看起来像注释,但它绝不是普通注释。

它的作用是告诉系统:执行这个脚本时,要用哪个解释器来解读后面的内容。

一个很形象的比喻:你写了一份英文报告,首页写着“请让英语翻译来阅读”——Shebang 就是这个说明。Linux 内核在执行脚本时,会先看第一行,发现是#!/bin/bash,就会调用/bin/bash这个程序来执行后续内容;如果是#!/bin/python3,就会调用 Python 解释器。

用which bash查看你系统上 Bash 的绝对路径:

$ which bash /usr/bin/bash

如果你不确定路径,可以在脚本里写#!/usr/bin/env bash,这种写法会让系统在环境变量里自动找到 bash,兼容性更好。我个人的习惯是直接写#!/bin/bash,因为 Linux 上这个路径几乎固定。但如果你的脚本打算在 macOS 或者各种不同的发行版之间传播,#!/usr/bin/env bash会更可靠。

3.3 给脚本加执行权限:chmod 到底做了什么

写完脚本后,如果你直接运行,大概率会收到Permission denied。这是因为 Linux 系统的安全模型要求“文件要有执行权限才能被当作程序运行”。

查看文件权限:

ls -l hello.sh

输出类似:

-rw-r--r-- 1 user user 31 Jan 1 12:00 hello.sh

最前面的-rw-r--r--表示文件权限。第一个r代表可读,第二个w代表可写,但没有任何x,也就是不可执行。

给它加上执行权限:

chmod +x hello.sh

再看一次:

-rwxr-xr-x 1 user user 31 Jan 1 12:00 hello.sh

出现了 3 个x,分别代表文件所有者、所属组、其他用户都可以执行。

注意:chmod +x给所有人加了执行权限,对于自己私人的脚本没有安全问题。但如果你在意最小权限原则,也可以只给文件所有者加执行权限:chmod u+x hello.sh。

3.4 为什么运行脚本要加./,不能直接写文件名

这是新手问得最多的一个问题。运行方式如下:

./hello.sh

会有这样的输出:

Hello, World!

如果你直接输入hello.sh,系统会提示:

bash: hello.sh: command not found

原因在于:Linux 在寻找可执行程序时,只会在PATH环境变量指定的目录里找。当前目录.默认并不在PATH里。./的意思是明确告诉系统“就在当前目录下找这个文件”。

这个设计其实是为了安全——避免一个恶意程序伪装成普通命令放在当前目录,诱导你误执行。

另外还有两种运行脚本的方式:

bash hello.sh sh hello.sh

用这种方式,不需要文件有执行权限,因为你是直接调用 Bash 来解释执行文件里的内容。这和不加./报错的原理不同,它是把脚本当作 Bash 的一个参数。

3.5 路径的讲究:相对路径与绝对路径

运行脚本时,用./hello.sh是相对路径,表示“相对于当前目录的位置”。你也可以用绝对路径运行:

/home/你的用户名/scripts/hello.sh

完全没问题。相对路径的好处是简短、不用关心脚本放在哪里;绝对路径的好处是无论你在哪个目录下都能运行。

如果你希望某个脚本能在任何地方直接运行,可以把它放到/usr/local/bin目录,或者把~/scripts加入PATH。这一步不急于现在做,等你的脚本多了再研究也不迟。

4. 脚本的基本语法:从变量、条件到循环

会创建和运行脚本之后,下一步就是真正“写点有用的东西”。这一节不铺开讲所有语法,而是把新手写脚本时最高频用到的语法拎出来,各配一个简单例子。这几块学会了,你已经能写 80% 的日常脚本了。

4.1 变量与输入:让脚本不再是死的

一个永远输出Hello, World!的脚本没太大意义。真正有用的脚本会读取用户输入或接收参数。

使用命令行参数:

#!/bin/bash echo "第一个参数: $1" echo "第二个参数: $2" echo "所有参数: $@" echo "参数个数: $#"

保存后运行:

./args.sh hello world

输出:

第一个参数: hello 第二个参数: world 所有参数: hello world 参数个数: 2

$1、$2是位置参数,$@表示所有参数,$#表示参数个数。这些特殊变量在脚本里非常常用。

使用 read 读取用户输入:

#!/bin/bash echo -n "请输入你的名字: " read name echo "你好, $name!"

read命令会让脚本暂停,等待用户在键盘输入,然后存到变量里。echo -n的意思是输出后不换行,这样光标会停留在同一行,界面更自然。

4.2 条件判断:让脚本能做决定

shell 脚本经常会遇到“如果……就……”的场景,比如检查文件是否存在、比较数字大小等。

#!/bin/bash if [ -f /etc/passwd ]; then echo "文件 /etc/passwd 存在" else echo "文件不存在" fi

语法要点说明:

  • if以fi结束(就是把 if 反过来写,这是 shell 的传统)
  • [ -f 文件 ]是“判断文件是否存在”的写法,注意方括号两边必须留空格
  • [实际上是一个命令,不是语法符号,这也是为什么[和]两侧都必须有空格的原因
  • 条件判断后面要加; then,或者把then换行写

常见的判断写法可以参考这张表:

写法含义
[ -f 文件 ]判断文件是否存在
[ -d 目录 ]判断目录是否存在
[ -z 字符串 ]判断字符串是否为空
[ "$a" = "$b" ]判断两个字符串是否相等
[ "$a" -gt "$b" ]判断 a 是否大于 b(数字)
[ "$a" -lt "$b" ]判断 a 是否小于 b(数字)

特别注意:字符串比较用=,数字比较用-eq或-gt。初学者经常搞混这一点,导致脚本判断结果完全不对。

变量比较时,强烈建议加上双引号。比如[ "$name" = "admin" ],如果$name是空字符串,不加引号时这一行就变成了[ = admin ],语法直接报错。加引号是最稳妥的做法。

4.3 for 循环:批量处理的高效姿势

场景:你想对一批文件做同样的操作。比如在txt_files目录下有 100 个.txt文件,你想在每个文件末尾加一行“EOF”。

#!/bin/bash for file in /home/user/txt_files/*.txt; do echo "EOF" >> "$file" echo "处理完成: $file" done

for … in …的结构含义是:“对于这个列表中的每一项,执行一次循环体”。列表可以是文件通配符、数字序列、或者是命令的结果。

另一个常见写法是 C 风格的循环:

#!/bin/bash for i in $(seq 1 10); do echo "第 $i 次循环" done

这里的$(seq 1 10)会先执行seq命令,生成一到十的数字序列。$()是命令替换的写法,意思是“先执行括号里面的命令,把输出结果放到这里”。

4.4 while 循环:适合读取文件内容

for 循环适合处理“已知数量”的迭代,而 while 循环更适合“知道条件才停止”或“逐行读取文件”的场景。

#!/bin/bash count=1 while [ $count -le 5 ]; do echo "count = $count" count=$((count + 1)) done

这里有一个新手容易忽略的地方:count=$((count + 1))里的$(( ))表示“做算术运算”。Shell 中的变量本质上都是字符串,直接写count=count+1是错误的,必须用$(( ))告诉 shell 这是数学运算。

另一种常见用法是逐行读取文件:

#!/bin/bash while read line; do echo "本行内容: $line" done < /etc/hostname

<是输入重定向,意思是“把文件内容作为这个循环的输入流”。

4.5 函数:把重复逻辑封装起来

脚本稍微长了以后,总会发现有些代码块反复出现。这时候就该用函数了。

#!/bin/bash log_info() { echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_error() { echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" >&2 } log_info "脚本开始执行" log_error "发生了一个错误"

函数定义的格式很简单:函数名() { … },调用时直接写函数名。$1在函数内部代表“传给这个函数的第一参数”,而不是脚本的第一参数。

>&2表示把输出重定向到标准错误输出(stderr)。这样做的意义是:当脚本输出被重定向到日志文件时,错误信息和正常信息可以分开处理。

$(date '+%Y-%m-%d %H:%M:%S')会先执行 date 命令,得到当前时间,然后嵌入到字符串里。这是脚本中最常用的“日志加时间戳”模式。

5. 跑起来之后:调试技巧与常见的报错处理

写脚本最耗时的从来不是写,而是改。新手遇到报错时第一反应往往是慌,其实只要掌握系统的排查思路,绝大多数问题都能在几分钟内定位。

5.1 开启调试模式:bash -x 是你的放大镜

当你不知道脚本为什么会出错时,用调试模式跑一遍:

bash -x script.sh

输出会多出很多带+号的行,这些是系统在执行每一行命令前打印的“展开后的实际内容”,相当于把脚本执行过程慢放给你看。

比如脚本里有:

name="Alice" echo "你好, $name"

使用bash -x运行时,你会看到:

+ name=Alice + echo '你好, Alice' 你好, Alice

你就能直观地看到:变量名是否被正确替换、命令是否真的执行了、引号是否导致参数被错误合并。调试完记得把-x去掉,不然正常执行时也会打印这些过程信息。

5.2/bin/bash^M: bad interpreter的经典坑

这个报错十有八九来自 Windows 编辑器。现象是这样的:

bash: ./hello.sh: /bin/bash^M: bad interpreter: No such file or directory

原因是 Windows 保存文件时,行尾用的是\r\n(回车加换行),而 Linux 只认\n(换行)。多了的\r被系统当成文件名的一部分,它去找/bin/bash^M这个解释器,自然找不到。

新手最容易踩这个坑的场景是:用记事本写脚本,再通过 U 盘或网盘传到 Linux 上运行。

两个解决办法:

方案一:在 Linux 上转换行格式

sed -i 's/\r$//' hello.sh

方案二:用正确的工具编辑

在 Windows 上用 Visual Studio Code 编辑文件时,注意右下角设置换行符格式为 LF 而不是 CRLF;或者干脆直接在 Linux 上使用 vim 编辑。

5.3command not found的三种可能原因

遇到command not found,别急着觉得系统坏了。排查顺序如下:

第一种情况是命令真的不存在。确认方式:which 命令名,若没有输出说明系统里没装这个命令。

第二种情况是脚本运行方式不对。前面提到过,直接输入脚本名而不用./前缀,系统找不到。这是新手最常犯的错误。

第三种情况是你的PATH环境变量出了问题。比如某些软件安装后,它的可执行文件目录没有加进PATH。热搜词里有个典型例子:-bash: crontab: command not found,很多时候是因为crontab所在目录不在当前用户的PATH中,多数发行版的crontab位于/usr/bin/crontab,完整路径还是能执行的。解决方法是使用绝对路径,或者把目录加进PATH。

5.4 权限问题和换行问题之外的隐蔽错误

除了上面两种常见问题,新手还容易踩这些坑:

  • 中英文标点混用:写脚本时括号、引号、冒号必须是英文半角,如果你在中文输入法状态下写,一个中文引号能让你排查半小时。
  • $忘记加:引用变量时要写$变量名,但赋值时写变量名=值即可。有人习惯性地在赋值时也加$,就会报错。
  • 空格问题:a = 1会报错,正确的是a=1;但[ $a = 1 ]这条命令式的写法又必须留空格。规则不一样,容易弄混。
  • 命令替换写成单引号:$(date)会执行 date 命令,但'$(date)'只是字面字符串,单引号会阻止所有展开行为。

5.5 让脚本“用起来安全”的几条建议

作为从新手走过来的人,我特别想分享几条关于脚本安全的经验:

  • 关键操作前加检查:比如删除文件前,先echo出要删除的文件名,确认无误再真正执行。
  • 给脚本加set -e:放在脚本第二行,表示“只要任何一条命令出错,就立即退出”,避免错误被连带执行下去。
  • 考虑加set -u:使用未定义的变量时直接报错,这能避免变量名拼写错误导致的隐性 bug。
#!/bin/bash set -euo pipefail echo "开始执行脚本"

set -euo pipefail是很多生产环境脚本都会加的一行,初学者可能不完全理解每一段含义,但是先养成加上的习惯,能少踩很多坑。

6. 综合实战:写一个日志备份脚本

基础语法讲完了,我带你从头到尾写一个真正有点用的小脚本:把指定目录下超过 7 天的日志文件压缩归档,并清理原始文件,整个过程输出日志。

这个例子综合了变量、条件、循环、时间运算、函数等前面所有知识点,你可以照抄后改成自己的需求。

6.1 需求分析与脚本设计

需求拆解如下:

  • 指定一个日志目录
  • 找到目录下后缀是.log的文件
  • 对超过 7 天的文件进行压缩归档
  • 压缩成功后删除原始文件
  • 打印操作日志

写成脚本后结构如下:

#!/bin/bash set -euo pipefail LOG_DIR="/var/log/myapp" BACKUP_DIR="/var/log/myapp/archive" KEEP_DAYS=7 mkdir -p "$BACKUP_DIR" archive_log() { local file="$1" local filename filename=$(basename "$file") local target="$BACKUP_DIR/${filename}.$(date +%Y%m%d).tar.gz" echo "正在压缩: $file" tar -czf "$target" "$file" if [ $? -eq 0 ]; then echo "压缩成功,删除原文件: $file" rm "$file" else echo "压缩失败,保留原文件: $file" fi } export -f archive_log find "$LOG_DIR" -maxdepth 1 -name "*.log" -type f -mtime +$KEEP_DAYS -exec bash -c 'archive_log "$@"' _ {} \; echo "备份任务完成"

6.2 逐段解析为什么这样写

这一段脚本建议你自己手动敲一遍,感受一下每个部分存在的意义:

  • set -euo pipefail:一开始就定义了“出错就退出”的原则。
  • LOG_DIR等变量集中定义在头部:方便修改,也保持脚本整洁。
  • archive_log函数里的local file="$1":函数的局部变量,不会污染全局命名空间。
  • filename=$(basename "$file"):从完整路径中提取文件名。
  • tar -czf:c 表示创建归档,z 表示用 gzip 压缩,f 指定文件名,这是最常用的打包压缩组合。
  • if [ $? -eq 0 ]:$?是上一条命令的返回值,0 表示成功,非 0 表示失败。
  • find ... -exec bash -c '...':对每个符合条件的文件执行一次命令。这里用了-mtime +7来匹配修改时间超过 7 天的文件。
  • {} 和 \;:find找到的每个文件名会替换进{},\;表示命令结束。

6.3 测试脚本的正确姿势

写完之后不要直接在真实目录上跑,先在测试目录模拟:

mkdir -p /tmp/test_log cd /tmp/test_log for i in $(seq 1 10); do echo "日志内容 $i" > access_$i.log done

然后批量改时间让文件“变老”:

touch -d "10 days ago" /tmp/test_log/*.log

接着把脚本里的LOG_DIR改成/tmp/test_log,BACKUP_DIR改成/tmp/test_log/archive,用bash -x跑一遍,仔细看输出过程是否符合预期。

确认没问题后再部署到真实环境。这个“先测试后生产”的习惯,是脚本写作中特别重要的一环。

一些个人体会

写脚本这件事,入门门槛其实非常低,你今天学会的已经够用了。但要想真正提升,我的经验是:别背语法,去写脚本。你写一个自动备份目录的脚本、写一个批量改文件名的脚本、写一个定时清理临时文件的脚本,每一个真实需求都会逼你去查语法、踩坑、调试,这一轮下来比你看十篇教程都有效。

另外想提醒一点:脚本里能用中文注释就尽量写清楚。我自己早期写的脚本为了省事没有注释,三个月后回来看完全不知道那段逻辑在干嘛。加注释不是给别人看的,是给未来的自己看的。

最后分享我的一个小习惯——每次写完脚本,我会刻意问自己两个问题:如果这个目录不存在,脚本会怎样?如果输入的文件名里带了空格,脚本会怎样?把这两个问题想明白并处理掉,你的脚本就已经超过了大多数初学者。

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

深入理解CNN参数共享:从卷积核到图像分类的核心原理

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

作者头像 李华
网站建设 2026/9/30 1:16:28

Playwright+Pytest实战:打造稳定高效的Web UI自动化测试方案

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

作者头像 李华
网站建设 2026/9/30 1:16:24

微信H5调用支付宝支付的合规实现方案

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

作者头像 李华
网站建设 2026/9/30 1:16:05

Win10重装不是一键的事:UEFI/BIOS/PE底层原理与实战排错

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

作者头像 李华
网站建设 2026/9/30 1:15:09

多源Transformer信贷评分:融合流水与文本的多源风控方案

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

作者头像 李华
网站建设 2026/9/30 1:14:51

I2C多主机仲裁与时钟延展:开漏输出、线与逻辑及工程避坑指南

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

作者头像 李华