模块 :S02 词法、引用与解析
篇号 :S02-02 / 42
预计阅读 :40 分钟
主线:Bash
文章目录
-
- 本篇目标
- [30 秒速览](#30 秒速览)
- 正文
-
- [1. `#` 注释:三种常见位置](#` 注释:三种常见位置)
-
- [1.1 整行注释](#1.1 整行注释)
- [1.2 行尾注释](#1.2 行尾注释)
- 1.3 不是注释的 `#`:shebang
- [1.4 注释里不会再有注释](#1.4 注释里不会再有注释)
- [2. 注释写什么、不写什么](#2. 注释写什么、不写什么)
- [3. 反斜杠 `\`:转义](#3. 反斜杠
\:转义) -
- [3.1 在双引号 `"..."` 内(与 S02-01 衔接)](#3.1 在双引号
"..."内(与 S02-01 衔接)) - [3.2 在单引号 `'...'` 内](#3.2 在单引号
'...'内) - [3.3 在引号外](#3.3 在引号外)
- [3.1 在双引号 `"..."` 内(与 S02-01 衔接)](#3.1 在双引号
- [4. 行续接:行末 `\` 的规则(重点)](#4. 行续接:行末
\的规则(重点)) -
- [4.1 `\` 后面:必须「立刻换行」](#4.1
\后面:必须「立刻换行」) - [4.2 `\` 前面:按正常语法解析](#4.2
\前面:按正常语法解析) - [4.3 下一行开头:缩进会进入逻辑行(不是注释)](#4.3 下一行开头:缩进会进入逻辑行(不是注释))
- [4.4 对照表:三个位置一览](#4.4 对照表:三个位置一览)
- [4.5 在双引号内续行(补充)](#4.5 在双引号内续行(补充))
- [4.6 写法建议](#4.6 写法建议)
- [4.1 `\` 后面:必须「立刻换行」](#4.1
- [5. 注释 `\` 与续行 `\` 不要混淆](#5. 注释
\与续行\不要混淆) - [6. here-doc 初识:`<<` 与 `TAG`](#6. here-doc 初识:
<<与TAG) - [7. 对照表:`\` 与 `#` 速查](#` 速查)
- [8. 读脚本时的顺序(配合 S02-01)](#8. 读脚本时的顺序(配合 S02-01))
- 读脚本检查清单
- 练习
- 下一篇预告
本篇目标
掌握 # 注释 的写法与限制、反斜杠 \ 的转义与行续接 ,并初步认识 here-doc (<<)与引号定界符的关系。读完本篇,你能正确写脚本头说明、把长命令拆成多行,并分清「注释掉代码」与 S12-03 的排错用注释法(那是调试手法,不是本篇语法重点)。
30 秒速览
#到行末 为注释;行首#整行忽略;行尾#后忽略。- shebang
#!/usr/bin/env bash是特例:仅当#在整行最前 且为#!时由系统解释,不是普通注释。 \转义 :在双引号内取消特殊含义;在引号外可转义$、空格等(规则见下文)。- 行末
\+ 换行 :把两行接成一条逻辑行 ;\后不能有任何字符(含空格、Tab)再换行。 - 下一行开头的空格/Tab 会保留,可能变成参数的一部分(不是「排版无关」)。
- here-doc
<<TAG:从下一行到TAG为止作为输入;<<'TAG'不展开变量。 - 读脚本:区分 文档注释 、被
#禁用的代码 、续行。
正文
1. # 注释:三种常见位置
Shell 把 # 到该行结尾 都当作注释(不执行)。
1.1 整行注释
bash
# 这是说明,Shell 会忽略整行
# TODO: 增加日志轮转
export LOG_DIR="/var/log/myapp"
用于:文件头说明、章节分隔、TODO、解释「为什么」而不是「做什么」。
1.2 行尾注释
bash
timeout=30 # 秒
retry=3 # 最大重试次数
注意 :# 前面的代码会先被解析。不要在没有引号保护的表达式中间写 #,容易误判。
bash
# 危险示例:未引号的路径里若有 # 会截断(极少见)
# echo /foo#bar # 从 # 起是注释
行尾注释前建议留至少一个空格,可读性更好。
1.3 不是注释的 #:shebang
bash
#!/usr/bin/env bash
第一行的 #! 由内核/系统用于选择解释器(S01-02),不是 「从 # 起整行忽略」那种注释。若 shebang 前有空格或 BOM,会失效。
1.4 注释里不会再有注释
bash
# 这是注释 # 后面也全是注释
# 之后到换行全部忽略,没有「嵌套注释」语法。多行说明就写多个 # 行,或用 here-doc(下文)。
2. 注释写什么、不写什么
| 建议写 | 少写或不写 |
|---|---|
| 非显而易见的业务规则 | 与代码字面重复的 i++ # i 加 1 |
| 外部依赖、环境前提 | 过时的注释(比错代码更害人) |
| 危险操作的警告 | 大段被 # 掉的死代码(应删除或进版本库历史) |
被 # 禁用的可执行行(排错时常用):
bash
# rm -rf "$staging_dir"
mv "$staging_dir" "$archive_dir"
这是合法语法;S12-03 会讲如何用这种写法二分定位 bug ,此处只确认:一个 # 让整行命令不执行。
3. 反斜杠 \:转义
3.1 在双引号 "..." 内(与 S02-01 衔接)
| 序列 | 含义 |
|---|---|
\" |
双引号字符 |
\$ |
字面 $ |
| ````` | 反引号 |
\\ |
一个 \ |
\n |
在普通 "..." 里通常是字面 \ 和 n;换行用 $'\n'(Bash) |
bash
echo "path: \$HOME is $HOME"
# path: $HOME is /home/user
3.2 在单引号 '...' 内
单引号内没有 转义表(除 '\'' 表示一个 ' 的拼接技巧)。\ 就是字面反斜杠。
3.3 在引号外
bash
echo hello\ world # hello world(转义空格,合成一个词)
echo \$HOME # $HOME(字面)
无引号时仍会做词拆分与通配(S02-03),\ 可用来拼接特殊字符。
4. 行续接:行末 \ 的规则(重点)
续行的含义:Shell 删掉 反斜杠 + 换行符 这一对字符,把前后文本拼成同一逻辑行 再解析。
因此有三个位置要分清:\ 前面 、\ 后面 、下一行开头。
4.1 \ 后面:必须「立刻换行」
\ 后面 |
能否续行 |
|---|---|
直接回车 (\ 是该行最后一个字符) |
可以 |
| 空格再回车 | 不可以 (\ 只转义了空格,没有转义换行) |
| Tab 再回车 | 不可以(同上) |
| 任意可见字符 | 不可以 |
bash
# 错:反斜杠后面有空格
echo hello \
world
# 很多情况下 \ 只转义了空格,两行仍是两条命令;或报语法错
# 对:反斜杠是行尾最后一个字符(行末不要留空格)
echo hello \
world
实用习惯:续行前删掉行尾空白;编辑器可开启「保存时删除行尾空格」,避免肉眼看不见的空格破坏续行。
Windows 换行 :若文件是 \r\n,\ 后面若是 \r 再 \n,有时会导致续行异常;脚本用 LF(S01-02)。
4.2 \ 前面:按正常语法解析
\ 前面 的空格、Tab、引号、参数,都按未续行时 的规则处理;\ 只负责「吃掉紧跟着的那一个换行」。
bash
grep -r \
--include='*.sh' \
'pattern' \
/opt/projects
逻辑上等价于把各行拼成一行(见下节 4.3)。-r 与 \ 之间的空格是正常的参数分隔。
不要 在需要「无空格拼接」的地方误加空格再接 \:
bash
# 意图不明:path= 与 /tmp 之间可能有空格
path=/data \
/tmp
# 拼接后是 path=/data /tmp → 赋值语法错误
4.3 下一行开头:缩进会进入逻辑行(不是注释)
续行后,下一行最前面的空格、Tab 都会保留 ,参与后续解析------Shell 不会因为「缩进好看」就忽略它们。
bash
echo hello \
world
删掉 \ 与换行后,逻辑上接近:
text
echo hello world
hello 与 world 之间会有多个空格 (hello 后的空格 + 续行后两个空格的缩进)。echo 会折叠输出为一个空格,所以结果仍是 hello world,看起来没问题。
但对把续行后内容当作一个参数的命令,缩进会坑人:
bash
ssh user@host \
ls -la /tmp
等价于在一条逻辑行里执行类似 ssh user@host ls -la /tmp,远程命令往往是 ls -la /tmp(前面带空格)。多数情况还能跑,但属于隐患;更稳妥:
bash
ssh user@host "$(cat <<'EOF'
ls -la /tmp
EOF
)"
# 或写在一行;或第二行顶格且不用前导空格(见下)
ssh user@host \
ls -la /tmp
# 逻辑行:ssh user@host ls -la /tmp(host 与 ls 之间仅正常分词)
bash
grep "error" \
*.log
逻辑行类似 grep "error" *.log:一般仍可行;若第二行是路径且前有空格,可能多出一个「空参数」或意外分词(与 set -u、空 glob 组合时更麻烦,S02-03)。
4.4 对照表:三个位置一览
| 位置 | 规则 | 记忆 |
|---|---|---|
\ 之前 |
正常语法;该有空格就空格 | \ 不替你「修」前面的写法 |
\ 之后 |
只能是换行;不能有空格/Tab/别的字符 | 行尾「干净」 |
| 下一行行首 | 空格/Tab 全部保留 | 缩进 = 真实字符,不是 Python 式缩进 |
4.5 在双引号内续行(补充)
双引号字符串里的 \ + 换行,会去掉换行但保留为一个空格(Bash 规则),与引号外的续行略有不同:
bash
msg="hello \
world"
echo "$msg"
# 常见输出:hello world(中间一个空格,不是两行)
引号外续行是「物理拼接行」;引号内是「字符串里的续行」。读脚本时先看 \ 是否在 "..." 内部。
4.6 写法建议
| 建议 | 原因 |
|---|---|
| 续行符前删行尾空白 | 避免 \ 失效 |
续行后要么顶格 写内容,要么整段用 "..." 包住 |
避免缩进空格进参数 |
| 优先在 选项之间 断开(` | 前、-f` 前) |
| 很长命令用 here-doc、函数、数组(S09、S08) | 少依赖多层 \ |
bash
# 较稳妥的续行:每行是完整选项
long_cmd \
--option-a \
--option-b \
/safe/path
5. 注释 \ 与续行 \ 不要混淆
| 写法 | 效果 |
|---|---|
# comment \ |
# 后整行都是注释,\ 不续行 |
cmd arg \ + 换行 + more |
一条命令拆两行 |
bash
# 这不是续行 \
echo never runs
第二行 echo never runs 仍是独立命令(若没注释则会执行)------上面 # 行里 \ 在注释内,不起续行作用。
6. here-doc 初识:<< 与 TAG
here-document :把多行文本作为命令的标准输入(或部分命令的 HERE 字符串)。
bash
cat <<EOF
第一行
第二行,当前用户: $USER
EOF
- 结束标记
EOF必须单独一行顶格 (前后无空格,除非用<<-允许 Tab 缩进,S09 详讲)。 - 未加引号的
EOF:中间会展开$变量、命令替换。 - 加引号的定界符
<<'EOF'或<<"EOF":中间全部字面,不展开。
bash
cat <<'EOF'
$USER 不会展开
EOF
常见用途:
| 用途 | 示例 |
|---|---|
| 生成配置文件片段 | cat <<EOF > config.ini |
| 嵌入 SQL、awk 程序 | psql <<SQL ... SQL |
| 多行消息 | mail ... <<MAIL |
与注释的区别 :here-doc 内容是数据 ,不是 # 注释;会传给命令。
完整用法 (<<-、缩进、tee、嵌套)在 S09-01 与重定向一起讲。读脚本时看到 <<,先找结束标记和定界符是否带引号。
7. 对照表:\ 与 # 速查
| 写法 | 作用 |
|---|---|
# ... |
注释到行末 |
#! 行首 |
shebang,非普通注释 |
\ + 换行(行末,且 \ 后无空格) |
续行 |
\" \$ 等(在 " 内) |
转义 |
<<TAG |
here-doc,可展开 |
<<'TAG' |
here-doc,不展开 |
8. 读脚本时的顺序(配合 S02-01)
- shebang 是否合法在第一行。
- 块注释 :连续
#或 here-doc 说明块。 - 被
#禁用的行:是废弃代码还是排错临时注释。 - 行末
\:向下合并逻辑行再读。 <<块 :找到结束TAG,看是否'TAG'。
读脚本检查清单
-
#是否在行首 或确定安全的行尾? - 行末
\是否为该行最后一个字符(后无空格/Tab)? - 续行下一行顶格或引号包裹,避免缩进空格进参数?
- here-doc 结束标记是否与开头 TAG 一致 、是否顶格?
-
<<EOF还是<<'EOF'?是否故意不展开变量? - 大段
#掉的老代码是否应删除以免误导?
练习
判断题
#!/bin/bash中的#与普通行内注释规则完全相同。<<'EOF'中的$HOME会展开为路径。- 行末
\用于把一条命令拆成多行书写。 - 续行时,下一行开头的 Tab/空格会被 Shell 忽略,不影响参数。
# rm -rf /这行会执行删除操作。
参考答案
- 错。shebang 由系统特殊处理。
- 错。引号定界符抑制展开。
- 对。
- 错。会保留进逻辑行,可能影响分词。
- 错。整行是注释。
实操题
写 heredoc_demo.sh:
bash
#!/usr/bin/env bash
set -euo pipefail
cat <<EOF
USER=$USER
EOF
echo "---"
cat <<'EOF'
USER=$USER
EOF
运行后对比两段输出。
续行观察题
在同一目录创建 cont.sh:
bash
#!/usr/bin/env bash
set -x
echo A \
B
echo C \
B
分别运行,观察 set -x 打印的实际执行行,说明「缩进的两空格」是否进入逻辑命令。
改错题
bash
#!/usr/bin/env bash
grep -r \
--include='*.log' \
"error pattern" \
/var/log
第二行 /var/log 前多了一个空格,可能导致 grep 把路径前空格当参数。改为去掉多余空格,或合并为更清晰的续行写法。
参考
bash
grep -r \
--include='*.log' \
"error pattern" \
/var/log
下一篇预告
S02-03 :《词拆分、通配符与未加引号的变量》--- 无引号时 Shell 如何拆词、*.txt 何时展开,以及 "$var" 经典 bug 集(专栏标注加厚篇)。