Shell脚本入门教程:Shebang、变量、注释与执行方式

Shell 脚本是把一堆 Linux 命令按顺序写进一个文件里,让计算机替你重复执行。从定时备份日志、批量改文件名,到部署项目时一键起服务,绝大多数运维自动化的起点都是一个 .sh 文件。这一篇从 Shebang 讲起,把变量、注释和三种执行方式一次说清楚,写完就能跑。

一、第一个 Shell 脚本

打开任意编辑器,新建一个文件 hello.sh:

bash 复制代码
#!/bin/bash
# 这是我的第一个 Shell 脚本
echo "Hello, Shell!"
name="Benothing"
echo "你好,$name"

保存后先看一下它的权限:

bash 复制代码
ls -l hello.sh

预期输出(新建的文件默认没有可执行权限):

text 复制代码
-rw-r--r-- 1 user user 68 Sep 16 10:00 hello.sh

带 x 的那一位现在是 -,意思是不能直接执行。加上可执行权限再跑:

bash 复制代码
chmod +x hello.sh
./hello.sh

预期输出:

text 复制代码
Hello, Shell!
你好,Benothing

到这里你已经写完并运行了第一个脚本。接下来逐个拆解每一行在干什么。

二、Shebang:第一行 #! 的作用

脚本第一行固定长这样:

bash 复制代码
#!/bin/bash

#! 两个字符合起来叫 Shebang(也叫 hashbang)。它的作用是告诉系统:这个脚本该用哪个解释器来跑。

你写的 .sh 文件本质是纯文本,内核自己看不懂。当你执行 ./hello.sh 时,内核读到 Shebang,就会把文件剩下的内容交给 /bin/bash 这个程序去逐行解释。常见写法还有:

写法 含义
#!/bin/bash 用 bash 解释器(最常用)
#!/bin/sh 用系统默认的 sh,通常指向 dash 或 bash
#!/usr/bin/env bash 从 PATH 里找 bash,移植性最好
#!/usr/bin/python3 这个文件其实是 Python 脚本

第三种 #!/usr/bin/env bash 在团队协作和多台服务器之间最稳,因为不同发行版 bash 的安装位置可能不一样,写死 /bin/bash 在某些精简系统上会报 bad interpreter。

⚠️ 常见错误

  1. Shebang 前面有空行或 BOM :第一行必须真的是第一行。Windows 编辑器保存成 UTF-8 with BOM 时,文件开头会多三个不可见字节,内核会把 Shebang 识别成乱码,报 /usr/bin/env: 'bash\r': No such file or directory。用 cat -A hello.sh 能看到 ^M 或 M-oM-;M-? 这类隐藏符号。
  2. 写成 #! /bin/bash :#! 和路径之间不要有空格,虽然有些系统容忍,但不保证。
  3. 忘了 Shebang 也能跑 :用 bash hello.sh 这种方式启动时,Shebang 被当普通注释跳过了,所以你删掉第一行也能执行。但一旦改成 ./hello.sh 直接执行就会失败,Shebang 不能省。

三、变量:定义、引用与赋值规则

Shell 里定义变量非常直接:变量名=值,等号两边不能有空格。

bash 复制代码
greeting="早上好"
count=3

引用变量时在名字前面加 $:

bash 复制代码
echo $greeting
echo "今天是第 $count 天"

预期输出:

text 复制代码
早上好
今天是第 3 天

赋值的几条铁律

写法 是否合法 说明
name="Tom" 合法 等号两边无空格
name = "Tom" 报错 中间有空格,shell 会把 name 当命令
name=Tom 合法 无空格的值可以不加引号
name="Tom Lee" 合法 带空格的值必须加引号
name =Tom 报错 等号左边不能有空格
name="value" 合法 行尾空格无所谓

为什么等号两边不能有空格?因为 shell 把空格当命令的分隔符。name = "Tom" 会被拆成三个 token:命令 name、参数 =、参数 Tom,shell 去找一个叫 name 的命令,自然找不到。

${name} 和 $name 的区别

变量名后面紧跟普通字符时,花括号能帮 shell 界定边界:

bash 复制代码
fruit="apple"
echo "I like ${fruit}s"

预期输出:

text 复制代码
I like apples

如果写成 echo "I like $fruits",shell 会去找一个叫 fruits 的变量,结果是空的。花括号在拼接字符串、数组下标、路径拼接时几乎是必选项。

命令替换:把命令结果存进变量

反引号和 $(...) 都能把一条命令的输出抓回来:

bash 复制代码
now=$(date +%Y-%m-%d)
echo "今天日期是 $now"

预期输出:

text 复制代码
今天日期是 2026-09-16

推荐用 $(...),它可以嵌套,反引号嵌套时要写一堆反斜杠,可读性很差。

四、注释怎么写

单行注释用 #,从 # 到行尾全是注释。Shebang 那一行虽然也是 # 开头,但它是特例,内核会特殊识别。

bash 复制代码
#!/bin/bash
# 整行都是注释
echo "这行会执行"   # 这行后面也是注释

Shell 没有原生的多行注释。常见做法是用一个"什么都不做"的函数或 here document 来折叠一段,但最实用的方式就是每行前面都加 #,IDE 里按 Ctrl+/ 一键注释一片。

五、执行脚本的三种方式

同一个脚本,三种跑法,行为差别不小。

方式 命令 是否需要可执行权限 是否在当前 shell 生效
直接执行 ./hello.sh 需要(chmod +x) 子进程
解释器调用 bash hello.sh 不需要 子进程
source 调用 source hello.sh 或 . hello.sh 不需要 当前进程

前两种方式都会fork 一个子 shell 来跑脚本,脚本里定义的变量、cd 切换目录,跑完就消失,不会影响你当前终端。第三种 source 是在当前 shell 里逐行执行,脚本里 export 的变量、cd 进的目录会一直留着。

这个区别在写环境变量配置脚本时是关键:安装软件后改 PATH,你必须 source 那个配置文件,改动才对当前终端生效;直接执行一个 .sh 去改 PATH,跑完什么都没发生。

完整示例:对比三种执行方式

写一个 test_env.sh:

bash 复制代码
#!/bin/bash
my_var="from script"
cd /tmp
echo "脚本内 my_var=$my_var"
echo "脚本内 pwd=$(pwd)"

分别用三种方式跑:

bash 复制代码
bash test_env.sh
echo "跑完后 my_var=$my_var, pwd=$(pwd)"

预期输出:

text 复制代码
脚本内 my_var=from script
脚本内 pwd=/tmp
跑完后 my_var=, pwd=/home/user

换成 source:

bash 复制代码
source test_env.sh
echo "跑完后 my_var=$my_var, pwd=$(pwd)"

预期输出:

text 复制代码
脚本内 my_var=from script
脚本内 pwd=/tmp
跑完后 my_var=from script, pwd=/tmp

注意 source 把当前目录都改到 /tmp 了,用完记得 cd 回去。

六、echo 输出与引号的初步认知

echo 后面跟单引号和双引号,行为完全不同:

bash 复制代码
who="Benothing"
echo "双引号: $who"
echo '单引号: $who'

预期输出:

text 复制代码
双引号: Benothing
单引号: $who

双引号会先把里面的变量展开,单引号原样输出,连 $ 都不解释。写脚本时这个区别每天都在踩,后面讲变量那一篇会展开。

七、知识扩展:Shebang 背后,内核到底干了什么

你在 shell 里敲 ./hello.sh 并回车,整条链路是这样的:

  1. 当前 shell(比如你登录后的 bash)通过 fork() 复制出一个子进程。
  2. 子进程调用 execve("./hello.sh", ...),把自己替换成新程序。
  3. 内核打开 hello.sh,看文件头两个字节是不是 #!。
  4. 是,内核就把这一行剩下的字符串当作解释器路径,再 execve("/bin/bash", "/bin/bash", "./hello.sh")。
  5. bash 启动后逐行读取脚本,把每一行拆成命令、参数、变量,再去执行。

所以 Shebang 不是 shell 的功能,是操作系统内核 的约定。这也解释了为什么 Windows 上没有 Shebang------Windows 的 CreateProcess 根本不认这两个字符。

理解这条链路还能解释一个常见困惑:为什么 ./hello.sh 和 bash hello.sh 结果有时候不一样?因为前者是内核按 Shebang 找解释器,后者是你手动指定 bash,Shebang 那行被当注释。如果脚本第一行写的是 #!/bin/zsh 而系统没装 zsh,./hello.sh 直接报 No such file or directory,但 bash hello.sh 能跑------它绕开了 Shebang。

动手把这一篇里的 hello.sh、test_env.sh 都敲一遍,三种执行方式的差异是后面所有脚本知识的地基,值得花十分钟亲手验证。

相关推荐
java_nnnn1 小时前
JavaEE进阶-JavaScript初识
开发语言·前端·javascript·java-ee·ecmascript
裕晟资质规划1 小时前
涉密场所物理隔离与技术防护体系:标准矩阵、审查校验点与常见缺陷分析
大数据·前端·网络·人工智能·经验分享
变与不变8061 小时前
JSON基础详解
前端·javascript
吴声子夜歌1 小时前
HTML——结构化微数据语言简介
前端·数据库·html
TeamDev1 小时前
JxBrowser 9.5.2 版本发布啦!
java·前端·windows·chromium·混合应用·jxbrowser·浏览器控件
鬼手点金2 小时前
Claude Code示范案例-进阶学习路径
java·服务器·前端·学习·计算机视觉·前向传播
吴声子夜歌2 小时前
HTML——Web Components开发
前端·html
西索斯coding2 小时前
GPT-5.3-Codex 接口报 422 Unprocessable Entity 怎么办?排查到最后发现是 messages role 顺序校验的坑
chrome·gpt·ai·iphone