一,Shell 介绍
1.1 shell 概念
在类 Unix 操作系统(如 Linux)中,Shell 是一个命令行解释器(Command-Line Interpreter),它为用户提供了一个与操作系统内核交互的接口。用户通过 Shell 向系统发出命令,Shell 解析并传递这些命令给操作系统执行。
Shell 是一种“外壳”,包裹在操作系统内核之外,用于接收用户输入、解释命令、管理程序运行。
简单一点就是:
Shell 是一种运行在 Linux 系统上的命令行解释器,它不仅可以执行单条命令,还可以将多条命令进行组织、编排,形成具备逻辑控制能力的脚本,实现批量处理与自动化操作。
Shell 的主要功能
- 命令解析与执行
Shell 接收用户输入的命令,并将其翻译为内核可以理解的系统调用。 - 脚本编程能力
Shell 支持编写具有流程控制(如条件判断、循环、函数等)的脚本,广泛用于任务自动化与系统管理。 - 环境管理
Shell 允许配置环境变量、启动程序、加载资源配置文件等操作。 - 程序控制
Shell 能够调用、调度和管理进程,包括前台、后台、管道和重定向等功能。
Shell = 命令行界面 + 命令解释器
你在终端输入的每一句命令,都是 Shell 在帮你执行。
1.2 shell 和 平日的命令有什么区别
🧐 Shell 脚本和命令行有什么区别?
其实核心区别可以简单理解成:
命令行是你“一条一条”地敲;Shell 脚本是你“提前写好一堆命令”一次性执行。
✅ 具体区别如下:
对比项 | 命令行 | Shell 脚本 |
使用方式 | 在终端一条条输入 | 写在 |
适用场景 | 临时操作、调试 | 自动化任务、重复工作、定时执行 |
可读性与复用性 | 操作完就没了 | 可以保存、修改、复用 |
支持逻辑控制 | 有限(需要逐条输入) | 完整支持流程控制语句(if、for、while 等) |
变量/函数支持 | 临时变量 | 支持变量、函数封装 |
还是这句话:
Shell 是一种运行在 Linux 系统上的命令行解释器,它不仅可以执行单条命令,还可以将多条命令进行组织、编排,形成具备逻辑控制能力的脚本,实现批量处理与自动化操作。
二,开始编程
在学习shell编程之前,我们先安装一个用于校验shell 语法的校验工具。shellcheck。命令如下:
# Debian/Ubuntu
sudo apt install shellcheck
# Red Hat/CentOS/Fedora
sudo yum install ShellCheck / sudo dnf install ShellCheck
# MacOS (使用 Homebrew)
brew install shellcheck
在线地址:ShellCheck – shell script analysis tool
2.1 shell 注意事项
在编写 shell 脚本的一定要指定shell脚本的解析器。
#!/bin/bash # shell 的指定的解释器是 bash, 其路径在/bin/
name="Alice"
echo "Hello, ${name}!" # 输出:Hello, Alice!
🧠 常见的 Shell 脚本解释器列表(Shebang 用法)
在脚本文件的第一行写上:
#!<解释器路径>
用于指定该脚本由哪个解释器运行。
Shebang 语句 | 描述 |
| Bash Shell,最常用的解释器,Linux 默认配置之一 |
| 标准 Shell,是 Bourne Shell 的简化版本,兼容性好 |
| Z Shell,功能更强大的交互式 shell,支持自动补全等 |
| Debian 系的轻量级 shell,常用作 |
| 推荐写法,自动在环境变量中查找 bash 所在路径 |
| 查找 sh 的路径,适用于不同平台 |
| 用于 zsh 的通用平台兼容写法 |
| Korn Shell,历史比较悠久,用于企业脚本系统 |
| C Shell,语法类似于 C,较少使用 |
| C Shell 的增强版本 |
| 指定用 Perl 解释器执行脚本 |
| 用 Python 解释器运行脚本(非 Shell,但也可做脚本解释) |
2.2 🧾shell 脚本的变量定义的介绍
🧩 2.2.1 变量定义的基本语法
变量名=值
⚠️ 等号两边不能有空格!
✅ 正确示例:
#!/bin/bash
name="Tom"
age=25
❌ 错误示例:
#!/bin/bash
name = "Tom" # 错误,等号前后不能有空格
#!/bin/bash
name="Alice"
echo "Hello, ${name}!" # 输出:Hello, Alice!
🧠 2.2.2 变量的引用
echo $变量名
示例:
#!/bin/bash
echo $name # 输出:Tom
如果变量名后面紧跟字符,推荐使用花括号括起来避免歧义:
#!/bin/bash
echo "My name is ${name}man" # 避免输出 $nameman
🧾 2.2.3 $name 和 ${$name} 引用区别
在 Shell 脚本或类 Bash 语言中,$name
和 ${name}
是用于变量引用的两种语法。这里是它们的主要区别和使用场景:
✅ $name
这是最常见、最简洁的变量引用方式。
#!/bin/bash
name="world"
echo "Hello, $name"
# 输出: Hello, world
✅ ${name}
这是 更通用、推荐的写法,尤其在变量后面紧跟其他字符时,用花括号 {}
能避免歧义。
#!/bin/bash
name="world"
echo "Hello, ${name}123"
# 输出: Hello, world123
# 如果用 $name123,Shell 会以为你是想引用名为 name123 的变量:
echo "Hello, $name123"
# 如果没有 name123,会输出 Hello, (空值)
🚫 ${$name}
这是 不合法或不推荐的用法,但它背后的意图可能是“变量的变量”,即 动态变量名。
比如:
#!/bin/bash
name="user"
user="Alice"
echo ${!name}
# 输出: Alice
解释:
$name
的值是user
!
表示引用变量名为user
的值- 所以
${!name}
等价于${user}
,输出Alice
但 ${$name}
是错误的语法,Shell 会报错或输出空。
总结表:
表达式 | 含义 |
| 简单引用变量 |
| 更安全、更通用的变量引用方式 |
| 错误的语法(可能想实现变量变量) |
| 有效的“变量变量”写法(间接引用) |
2.3 shell 脚本的常用全局变量的介绍
在上面我们学习了shell脚本中怎么定义变量,以及怎么引用变量。在Linux 系统当中,也提供一些系统常用的全局变量提供给开发者使用。我们只需要进行引用全局变量即可。
命令查看全局变量
printenv # 显示所有环境变量
env # 同上
set # 显示所有变量(包括环境变量和 shell 本地变量)
declare -x # 只显示 export 的变量(全局)
常见全局变量列表
变量名 | 含义 |
| 当前用户的主目录,如 |
| 当前登录的用户名 |
| 当前登录的用户名(与 USER 通常一致) |
| 当前使用的 shell 路径(如 |
| 可执行程序的搜索路径,多个路径用 |
| 当前工作目录(Print Working Directory) |
| 上一次的工作目录 |
| 默认语言和字符集设置,如 |
| 终端类型(如 |
| 默认编辑器(如 |
| 当前主机的名称 |
| 当前用户的用户 ID |
| 用户的主目录路径 |
| 图形显示设置(X11) |
| 当前用户的邮箱路径 |
| Shell 嵌套层级(例如你开了几个 subshell) |
| Shell 提示符的格式 |
| 字段分隔符(默认是空格、Tab、换行) |
示例:
#!/bin/bash
remark="自定义变量"
username=$(whoami) # 调用执行 Linux 的 whoami 命令
if [ "${username}" = "root" ]; then
echo "you are root"
else
echo "you are ${username}"
fi
echo " ========================= global var ================================= "
echo " 当前用户的home目录: ${HOME} "
echo " 当前用户名: ${USER} "
echo " 当前用户ID: ${UID} "
echo " 当前 shell 路径(如/bin/bash): ${SHELL} "
echo " 当前工作目录: ${PWD} "
echo " 可执行文件查找路径(which) : ${PATH} "
echo " 主机名: ${HOSTNAME} "
~
还可以直接使用
[toast@localhost shell-script]$ echo $USER
toast
[toast@localhost shell-script]$ echo $HOME
/home/toast
自定义全局变量
[toast@localhost shell-script]$ vim my-token.sh
#!/bin/bash
# 定义全局变量(环境变量)
export MY_TOKEN_VAR="TOKEN-TOAST"
# 启动一个子进程来验证变量是否可用
bash -c 'echo "来自子 Shell 的 GREETING: $GREETING"'
执行结果
[toast@localhost shell-script]$ ./my-token.sh
来自子 Shell 的 GREETING: TOKEN-TOAST # 另外一个程序读取到全局变量的 MY_TOKEN_VAR
如果想直接在终端交互界面直接使用全局变量。需要定义到环境配置文件上面。如下:
文件 | 是什么? | 作用说明 |
| Bash 的初始化脚本 | 每次你打开 交互式 Bash 终端 都会执行它 |
| Zsh 的初始化脚本 | 每次你打开 Zsh 终端 都会执行它 |
| 通用 shell 登录脚本 | 适用于登录 Shell,有时 GUI 启动的也走它 |
| 系统级环境初始化脚本 | 所有用户的登录 Shell 都会执行 |
【查看当前小编的环境】小编使用的是 Bash Shell 的终端进行交互
[toast@localhost shell-script]$ vim ~/
.bash_history .bash_profile .config/ file.sh test.txt
.bash_logout .bashrc .viminfo shell-script/
因为我是 普通用户(toast)在用 Bash 登录交互式 shell,所以:
👉 ~/.bashrc
是配置「交互式 shell 环境变量」的首选位置。
所有文件作用一览
文件名 | 作用 | 你该用吗? |
| 每次打开终端都会执行(交互式 shell) | ✅ 推荐使用 |
| 登录 shell 执行一次,通常用于一次性加载 | ⚠️ 可选 |
| 用户退出 shell 时执行的脚本 | ❌ 基本不用 |
| 历史命令记录,不是用来写变量的 | ❌ 不要动 |
| Vim 的历史/缓存记录 | ❌ 不相关 |
🛠 怎么用 .bashrc
定义环境变量?
1. 打开 .bashrc
vim ~/.bashrc
2. 添加你要的环境变量,例如:
3. 保存后让它立即生效:
[toast@localhost shell-script]$ source ~/.bashrc # 保存后让它立即生效
[toast@localhost shell-script]$ echo $MY_TOKEN_VAR # 验证
TOKEN-TOAST
2.3 shell 脚本分支语句的介绍
在 Shell 脚本中,**分支语句(条件语句)**是用来根据条件执行不同代码块的结构,就像其他编程语言中的 if-else
、switch-case
一样。掌握 Shell 分支语句对于编写逻辑清晰、自动化强的脚本非常关键。
🌱 2.3.1 基本的 if 语句
单分支 if
if [ 条件 ]; then
命令
fi
🔹 示例:
if [ -f "test.txt" ]; then
echo "文件存在"
fi
if-else 语句
if [ 条件 ]; then
命令1
else
命令2
fi
🔹 示例:
if [ "$USER" = "root" ]; then
echo "管理员登录"
else
echo "普通用户登录"
fi
if-elif-else 语句
if [ 条件1 ]; then
命令1
elif [ 条件2 ]; then
命令2
else
命令3
fi
🔹 示例:
if [ "$score" -ge 90 ]; then
echo "优秀"
elif [ "$score" -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
🔄 2.3.2 case 分支语句(类似 switch)
当要根据一个变量的不同值来选择不同分支时,用 case
比较方便:
case "$变量" in
模式1)
命令1
;;
模式2)
命令2
;;
*)
默认命令
;;
esac
🔹 示例:
read -p "请输入(y/n): " answer
case "$answer" in
y|Y)
echo "你选择了 Yes"
;;
n|N)
echo "你选择了 No"
;;
*)
echo "无效输入"
;;
esac
🔁 2.3.3 条件判断表达式详解
下面是 Shell 脚本中 字符串、文件、数字 相关的判断条件分类整理,清晰易查,适合学习和实战中快速参考 👇
🧵 字符串相关判断
条件表达式 | 含义 | 示例 |
| 字符串为空 |
|
| 字符串非空 |
|
| 字符串相等 |
|
| 字符串不等 |
|
| 字符串是否匹配正则(高级) |
|
🔸 示例:
str="hello"
if [ -n "$str" ]; then
echo "字符串不为空"
fi
📂 文件相关判断
条件表达式 | 含义 | 示例 |
| 文件是否存在 |
|
| 是否为普通文件 |
|
| 是否为目录 |
|
| 是否可读 |
|
| 是否可写 |
|
| 是否可执行 |
|
| file1 比 file2 新 |
|
| file1 比 file2 旧 |
|
🔸 示例:
if [ -f "run.sh" ] && [ -x "run.sh" ]; then
./run.sh
else
echo "脚本不存在或不可执行"
fi
🔢 数字相关判断
条件表达式 | 含义 | 示例 |
| 等于 |
|
| 不等 |
|
| 大于 |
|
| 小于 |
|
| 大于等于 |
|
| 小于等于 |
|
🔸 示例:
read -p "请输入年龄: " age
if [ "$age" -ge 18 ]; then
echo "成年人"
else
echo "未成年"
fi
🧠 小技巧:数值比较时不要用 [[ "$a" > "$b" ]]
那是 字符串字典序比较,不是数学意义上的大小比较,用 [ "$a" -gt "$b" ]
才是对的。
🧪 2.3.4 组合条件
可以使用逻辑运算符组合多个条件:
&&
:与(and)||
:或(or)!
:非(not)
🔸 示例:
if [ -f "$file" ] && [ -r "$file" ]; then
echo "文件存在且可读"
fi
🔁 2.3.5 [ ] 与 [[ ]] 判断区别
这是一个非常经典也很容易混淆的点!我们来深入讲讲 [[ ... ]]
和 [ ... ]
的区别,简单总结 + 示例说明,让你一看就懂 👇
✅ 基本区别概览
比较项 |
|
|
是否为内建命令 | 否,调用的是 | 是 Bash 的关键字,执行效率更高 |
字符串比较支持 | 基础比较(=, !=) | 支持模式匹配 |
括号中是否可用 | ||
是否支持正则 | ❌ 不支持 | ✅ 支持 |
是否需要变量加引号 | 建议加 | 可以不加,避免脚本崩溃 |
✅ 必须使用 #!/bin/bash
或其他兼容 Bash 的解释器,才能正常使用 [[ ... ]]
。
✅ 字符串比较
#!/bin/bash
str="abc"
# 使用 []
if [ "$str" = "abc" ]; then
echo "相等"
fi
# 使用 [[ ]]
if [[ $str == abc ]]; then
echo "相等"
fi
✔️ [[ ]]
中变量不加引号也不会出错;[ ]
中变量最好加引号,防止空值导致语法错误。
✅ 模式匹配
#!/bin/bash
filename="test.txt"
# [ ] 中不支持通配符匹配
# if [ "$filename" == *.txt ]; then ❌ 错误
# [[ ]] 支持通配符匹配
if [[ $filename == *.txt ]]; then
echo "是 txt 文件"
fi
✅ 正则匹配(只有 [[ ]] 支持)
email="user@example.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-z]+\.[a-z]+$ ]]; then
echo "邮箱合法"
fi
🔹 这个正则在 [ ]
里是完全无法用的。
✅ 逻辑运算符
a=5
b=10
# [ ] 不支持内部逻辑运算符,只能外部组合:
if [ $a -lt 10 ] && [ $b -gt 5 ]; then
echo "满足条件"
fi
# [[ ]] 内部直接支持:
if [[ $a -lt 10 && $b -gt 5 ]]; then
echo "满足条件"
fi
📌 总结一句话:
[ ]
是传统的 test 命令,兼容性强;[[ ]]
是 Bash 扩展,功能强大、安全性更高、语法更灵活。
✅ 必须使用 #!/bin/bash
或其他兼容 Bash 的解释器,才能正常使用 [[ ... ]]
。
2.4 shell 脚本循环语句的介绍
Shell 中的循环语句主要有三种:for
循环、while
循环 和 until
循环。下面分别介绍这三种循环语句的基本用法和示例:
for
循环
语法形式一:遍历列表
for var in item1 item2 item3
do
command
done
示例:
#!/bin/bash
for name in 张三 李四 王五
do
echo " Hello, ${name} "
done
输出结果:
[toast@localhost shell-script]$ chmod u+x for-demo1.sh
[toast@localhost shell-script]$ ./for-demo1.sh
Hello, 张三
Hello, 李四
Hello, 王五
语法形式二:数值范围(Bash 特有)
for i in {1..5}
do
echo "Number: $i"
done
语法形式三:C语言风格(适用于 bash)
for ((i=0; i<5; i++))
do
echo "i=$i"
done
while
循环
语法:
while [ condition ]
do
command
done
示例:
count=1
while [ $count -le 5 ] # 小于5
do
echo "Count is $count"
count=$((count + 1))
done
until
循环
语法:
until [ condition ]
do
command
done
与 while
相反:while
是条件成立就执行;until
是条件不成立就执行。
示例:
count=1
until [ $count -gt 5 ]
do
echo "Count is $count"
count=$((count + 1))
done
跳出循环
break
:跳出整个循环
for i in {1..10}
do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
continue
:跳过当前循环,继续下一次
for i in {1..5}
do
if [ $i -eq 3 ]; then
continue
fi
echo $i
done
2.5 shell 脚本方法定义的介绍
在 Shell 脚本中,方法(或函数) 是用来组织和重用代码的一个非常实用的结构。它可以让脚本更加模块化、可维护、易于调试。下面是对 Shell 脚本中方法(函数)的全面介绍:
🧩 1. Shell 函数的定义语法
function_name () {
command1
command2
...
}
或者:
function function_name {
command1
command2
...
}
推荐使用第一种 function_name ()
的方式,它兼容性更好。
🛠 2. 示例
#!/bin/bash
say_hello() {
echo "Hello, $1!"
}
say_hello "Alice" # say_hello 调用方法, "Alice" 传入参数
输出:
Hello, Alice!
📥 3. 传参方式
Shell 函数的参数用 $1
, $2
, ..., $@
表示:
参数变量 | 含义说明 |
| 脚本或函数的名称 |
| 第一个参数 |
| 第二个参数 |
| 第三个参数 |
| 传递给函数/脚本的参数个数 |
| 以 "$1" "$2" ... 的形式展开所有参数 |
| 以 "foo bar baz" 的形式展开所有参数 |
| 每个参数独立展开(推荐写法) |
| 所有参数合并为一个字符串 |
| 当前脚本的进程 PID |
| 上一个命令的退出状态码(0=成功,非0=失败) |
#/bin/bash
add() {
result=$(( $1 + $2 ))
echo "Result: $result"
}
# 调用方法
add 3 5 # 最终结果等于 8
🔁 4. 返回值
Shell 函数不能直接返回字符串,只能通过 return
返回整数(0~255)作为状态码。要返回字符串,可以用 echo
+ 命令替换获取。
#!/bin/bash
# 返回状态码
check_file() {
if [ -f "$1" ]; then
return 0
else
return 1
fi
}
check_file "/etc/passwd"
if [ $? -eq 0 ]; then
echo "File exists"
else
echo "File not found"
fi
或者用 echo
返回字符串:
#!/bin/bash
get_date() {
echo "$(date +%F)"
}
today=$(get_date)
echo "Today is $today"
🧹 5. 本地变量 (local)
使用 local
声明变量仅在函数内部生效:
#!/bin/bash
greet() {
local name=$1
echo "Hi, $name"
}
🔁 6. 函数嵌套调用
函数可以调用其他函数:
#!/bin/bash
greet() {
echo "Hello, $1"
}
run() {
greet "$1"
}
run "Bob"
2.6 shell 对shell脚本 import 支持
在 Shell 脚本中虽然没有像 Python /Java 那样的 import
关键字,但它 支持通过 source
或 .
(点命令)来“引入”另一个脚本文件,起到类似 import
的作用。
🧩 Shell 脚本的“导入”语法
方式 | 语法示例 | 说明 |
|
| 读取并执行另一个脚本内容 |
|
| 等同于 |
📁 使用示例
👉 场景:你有两个脚本,一个是utils.sh 里面提供一个查看磁盘基本的使用情况,一个是main.sh 是主入口。
查看所在位置:
[toast@localhost shell-import]$ pwd
/home/toast/shell-script/shell-import
[toast@localhost shell-import]$ ll
total 8
-rwxr--r--. 1 toast toast 151 Apr 21 15:15 main.sh
-rwxr--r--. 1 toast toast 540 Apr 21 15:16 utils.sh
#!/bin/bash
# 文件名:utils.sh
# 显示磁盘空间的表格函数
show_disk_info() {
echo "磁盘空间使用情况如下:"
echo "--------------------------------------------"
printf "%-20s %-10s %-10s %-10s %-10s\n" "挂载点" "总大小" "已用" "可用" "使用率"
echo "--------------------------------------------"
df -h --output=target,size,used,avail,pcent | tail -n +2 | while read mount size used avail usep; do
printf "%-20s %-10s %-10s %-10s %-10s\n" "$mount" "$size" "$used" "$avail" "$usep"
done
}
#!/bin/bash
# 文件名:main.sh
# 引入 utils.sh(相对路径,也可以写成绝对路径)
source ./utils.sh
# 调用函数
show_disk_info
[toast@localhost shell-import]$ ll
total 8
-rwxr--r--. 1 toast toast 151 Apr 21 15:15 main.sh
-rwxr--r--. 1 toast toast 540 Apr 21 15:16 utils.sh
[toast@localhost shell-import]$ ./main.sh
磁盘空间使用情况如下:
--------------------------------------------
挂载点 总大小 已用 可用 使用率
--------------------------------------------
/dev 4.0M 0 4.0M 0%
/dev/shm 1.8G 0 1.8G 0%
/run 732M 8.6M 724M 2%
/ 45G 2.8G 43G 7%
/boot 960M 225M 736M 24%
/run/user/0 366M 0 366M 0%
🔄 三、相对路径与绝对路径
使用 source
时最好用绝对路径或动态路径判断:
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" #获取当前所在的绝对路径
source "$DIR/utils.sh"
[toast@localhost shell-import]$ echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
/home/toast/shell-script/shell-import
[toast@localhost shell-import]$ cd ~
[toast@localhost ~]$ echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
/home/toast
[toast@localhost ~]$
这样可确保无论从哪个目录运行主脚本都能正确找到依赖脚本。
📦 四、支持内容
使用 source
导入的脚本,可以包含:
- 变量定义
- 函数定义
- 任何 Shell 命令
- 甚至环境设置(比如 export)
⚠️ 五、注意事项
- source 是在当前 shell 中执行,所以引入的变量和函数会在当前脚本中直接生效;
- 被引入的脚本不能有
exit
,否则会直接退出主脚本; - 如果只想引入变量或函数,不要在被引入的脚本中写执行逻辑。