Shell脚本精读 · S02-02 | 转义、续行与注释

模块 :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 在引号外)
      • [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 写法建议)
      • [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

helloworld 之间会有多个空格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)

  1. shebang 是否合法在第一行。
  2. 块注释 :连续 # 或 here-doc 说明块。
  3. # 禁用的行:是废弃代码还是排错临时注释。
  4. 行末 \:向下合并逻辑行再读。
  5. << :找到结束 TAG,看是否 'TAG'

读脚本检查清单

  • # 是否在行首确定安全的行尾
  • 行末 \ 是否为该行最后一个字符(后无空格/Tab)?
  • 续行下一行顶格或引号包裹,避免缩进空格进参数?
  • here-doc 结束标记是否与开头 TAG 一致 、是否顶格
  • <<EOF 还是 <<'EOF'?是否故意不展开变量?
  • 大段 # 掉的老代码是否应删除以免误导?

练习

判断题

  1. #!/bin/bash 中的 # 与普通行内注释规则完全相同。
  2. <<'EOF' 中的 $HOME 会展开为路径。
  3. 行末 \ 用于把一条命令拆成多行书写。
  4. 续行时,下一行开头的 Tab/空格会被 Shell 忽略,不影响参数。
  5. # rm -rf / 这行会执行删除操作。

参考答案

  1. 错。shebang 由系统特殊处理。
  2. 错。引号定界符抑制展开。
  3. 对。
  4. 错。会保留进逻辑行,可能影响分词。
  5. 错。整行是注释。

实操题

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 集(专栏标注加厚篇)。

相关推荐
zzqssliu1 小时前
Next.js图片自适应压缩:跨境站点图片加载提速代码方案
linux·javascript·ubuntu
苏宸啊1 小时前
IPC(二)Syestem V
linux
干掉乔治的猪1 小时前
【如何恢复 Ubuntu 引导分区:Windows11 + Ubuntu22.04 双系统 GRUB 修复踩坑记录】
linux·ubuntu·grub·修复·双系统
流浪0011 小时前
Linux系统篇(五):Linux 进程控制全解:fork、exec、wait 核心原理与实战
linux·运维·服务器
不会就选b1 小时前
Linux之make,makefile
linux·运维·服务器
code monkey.1 小时前
【Linux之旅】HTTP 协议解析:从请求格式到构建 Web 服务器
linux·服务器·网络·http
vortex52 小时前
Linux 传统设计哲学:通过调用名区分行为的艺术
linux·运维·网络
嵌入式-老费2 小时前
esp32开发与应用(esp32-s3的usb转串口功能)
linux·运维·服务器
壮Sir不壮2 小时前
GO语言——GMP调度模型
linux·开发语言·golang·go·操作系统·线程·协程