Appearance
一、Shell 概念
1.1 生活中的类比
想象你在一家高级餐厅里
- 你:只需要说出你想吃什么(发出指令)。
- 服务员:把你的话翻译成厨房能懂的语言,然后端出菜品。
- 厨房(内核):真正做饭干活的地方。 在 Linux 系统中,Shell 就是这个服务员。它是一个“命令解释器”,把你输入的每一条命令(比如
ls看文件、mkdir建文件夹)翻译给操作系统内核,并把结果返回给你。
1.2 技术上的定义
Shell 是一种用 C 语言编写的程序,它是用户与 Linux 内核之间的桥梁。当你打开“终端”(Terminal)那个黑乎乎的窗口时,里面运行的就是 Shell。 常见的 Shell 种类:
| 名称 | 特点 |
|---|---|
| Bash | 最流行,Linux 默认,也是我们这份文档的主角。 |
| Zsh | 功能更强,有许多插件,macOS 默认已换成 Zsh。 |
| Sh | 最早的 Shell,Bash 兼容它。 |
| Fish | 对新手友好,语法高亮漂亮,但不完全兼容 Bash。 |
| 日常说“写 Shell 脚本”,通常就是指写一段 Bash 命令的集合,保存成文件,然后一次性执行。 |
1.3 学习目标
- 系统巡检类:检查内存、磁盘、CPU 负载,并输出结果。
- 数据备份类:带时间戳的备份、清理过期备份(find + rm)。
- 服务控制类:重启服务、检查端口、如果挂了就自动拉起(if + systemctl)。
- 初始化脚本:新机器一键配置 YUM 源、主机名、NTP、防火墙、SSH 密钥
二、打开 Shell
2.1 如何进入 Shell 环境?
- Linux / macOS:按
Ctrl + Alt + T打开终端。 - Windows:安装 Git Bash 或 WSL,或者用 PowerShell(不完全相同,但很多命令类似)。 打开后你会看到类似这样的提示符:
user@hostname:~$user:当前用户名hostname:计算机名~:当前所在的目录(~代表用户主目录)$:表示你是普通用户;如果是#表示 root(超级管理员)。
2.2 第一个命令:echo
echo "Hello, Shell!"解释:echo 是“打印输出”的意思,会把后面跟着的内容显示在屏幕上。 运行结果:
Hello, Shell!2.3 常用入门命令
| 命令 | 作用 | 举例 |
|---|---|---|
pwd | 显示当前所在的目录(Print Working Directory) | pwd → /home/user |
ls | 列出当前目录下的文件和文件夹 | ls -l 显示详细信息 |
cd | 切换目录(Change Directory) | cd /etc 进入 /etc 目录 |
mkdir | 新建文件夹(Make Directory) | mkdir myfolder |
rmdir | 删除空文件夹 | rmdir emptyfolder |
rm | 删除文件或文件夹(危险,需谨慎) | rm file.txt;rm -r folder 递归删除 |
touch | 新建空文件或更新文件时间戳 | touch newfile.txt |
cp | 复制文件或文件夹 | cp a.txt b.txt |
mv | 移动或重命名 | mv old.txt new.txt |
cat | 查看文件内容(Concatenate) | cat file.txt |
三、Shell 脚本
3.1 脚本概念
手动一条条输入命令太慢,可以把多条命令按顺序写在一个文本文件里,让 Shell 一次性执行。这个文件就叫 Shell 脚本。
3.2 第一个脚本
- 第 1 步:创建文件
vim first.sh- 第 2 步:写入内容
#!/bin/bash
# 这是注释,不会执行
echo "今天日期是:"
date
echo "当前目录下的内容:"
ls#!/bin/bash:告诉系统用 Bash 解释这个脚本(必须第一行)。#开头的行是注释,给人看的。
- 第 3步:给脚本执行权限并运行
chmod +x first.sh # 给脚本添加“可执行”权限
./first.sh # 运行当前目录下的脚本为什么需要 chmod +x?
在 Linux 中,文件默认没有“可执行”权限。+x 就是给文件加上执行权限(对应数字权限中的 1)。之后才能用 ./脚本名 直接运行。
3.3 脚本的三种执行方式
| 方式 | 含义 |
|---|---|
./first.sh | 要求文件有执行权限,且文件开头有 #!/bin/bash。 |
bash first.sh | 直接调用 bash 解释器来执行,文件不需要执行权限。 |
source first.sh 或 . first.sh | 在当前 Shell 进程中执行(不会开子进程,常用于加载环境变量)。 |
四、Shell 的变量
注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:
- 只包含字母、数字和下划线: 变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。
- 不能以数字开头:变量名不能以数字开头,但可以包含数字。
- 避免使用 Shell 关键字: 不要使用 Shell 的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。
- 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如 PI=3.14。
- 避免使用特殊符号:尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
- 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数。
4.1 变量的定义与使用
在 Shell 里,变量就是用来“存数据”的名字。
name="张三" # 定义变量(等号两边不能有空格)
age=25
echo $name # 使用变量时前面加 $ 符号
echo "他叫 $name, 今年 $age 岁" # 双引号内可解析变量
echo '他叫 $name' # 单引号内所有字符都是纯文本,不解析变量输出:
张三
他叫 张三, 今年 25 岁
他叫 $name4.2 命令替换:把命令执行结果存进变量
用 $(命令) 或 `命令` 包裹命令。
current_date=$(date +%Y-%m-%d)
echo "今天日期:$current_date"
files_count=$(ls | wc -l)
echo "当前目录下有 $files_count 个文件/文件夹"4.3 位置参数:脚本运行时传入的数据
当你执行 ./script.sh a b c 时:
$0表示脚本自己的名字(./script.sh)$1表示第一个参数a$2表示第二个参数b$3表示第三个参数c$#表示参数的个数$@表示所有参数(分开的列表) 示例脚本args.sh:
#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "参数总数:$#"
echo "所有参数:$@"运行:
./args.sh hello world 123输出:
脚本名:./args.sh
第一个参数:hello
参数总数:3
所有参数:hello world 1234.4 环境变量
执行 env 命令可以查看所有环境变量。常用的有:
| 变量 | 含义 |
|---|---|
$HOME | 用户主目录 |
$PATH | 命令搜索路径(告诉系统去哪里找可执行文件) |
$USER | 当前用户名 |
$SHELL | 当前 Shell 的路径 |
echo "你好 $USER,你的家目录在 $HOME"五、条件判断
5.1 if 语句的基本结构
if 条件
then
# 条件成立时执行
else
# 条件不成立时执行
fifi 是 if 的倒写,表示结束。
5.2 [ ] 和 test 命令
在 Shell 中,[ 条件 ] 就是 test 条件 的简写。注意方括号与内容之间必须有空格。
5.2.1 数值比较
| 运算符 | 英语单词 | 含义 |
|---|---|---|
-eq | equal | 等于 |
-ne | not equal | 不等于 |
-gt | greater than | 大于 |
-lt | less than | 小于 |
-ge | greater or equal | 大于等于 |
-le | less or equal | 小于等于 |
num=10
if [ $num -gt 5 ]
then
echo "num 大于 5"
fi5.2.2 字符串比较
| 运算符 | 含义 |
|---|---|
= 或 == | 等于 |
!= | 不等于 |
-z | 字符串为空 |
-n | 字符串非空 |
name=""
if [ -z "$name" ]
then
echo "名字是空的"
fi5.2.3 文件测试
| 运算符 | 含义 |
|---|---|
-e 文件 | 文件存在 |
-f 文件 | 存在且是普通文件 |
-d 目录 | 存在且是目录 |
-r 文件 | 存在且可读 |
-w 文件 | 存在且可写 |
-x 文件 | 存在且可执行 |
if [ -d "/etc" ]
then
echo "/etc 是一个目录"
fi5.3 if-elif-else 多分支
score=85
if [ $score -ge 90 ]; then
echo "优秀"
elif [ $score -ge 80 ]; then
echo "良好"
else
echo "继续努力"
fi; then 里面的分号表示“写完条件后,继续写 then”,可以把 then 和 if 写在同一行。
5.4 逻辑组合
&&表示“并且”||表示“或者”
if [ -f file.txt ] && [ -r file.txt ]
then
echo "file.txt 存在且可读"
fi六、循环语法
6.1 for 循环
遍历列表
for 变量名 in 列表
do
# 循环体
done示例:批量创建文件
for i in 1 2 3 4 5
do
touch "file_$i.txt"
echo "创建了 file_$i.txt"
done更常用的列表写法:
for i in {1..10} # 1 到 10
for i in $(seq 1 2 100) # 1 到 100,步长 2
for file in *.txt # 遍历当前目录所有 txt 文件6.2 C 语言风格的 for
for (( i=0; i<5; i++ ))
do
echo "第 $i 次循环"
done6.3 while 循环
条件为真时循环
count=1
while [ $count -le 5 ]
do
echo "count=$count"
count=$(( count + 1 )) # 算术运算
done6.4 until 循环
条件为假时循环(与 while 相反)
count=1
until [ $count -gt 5 ]
do
echo "count=$count"
count=$(( count + 1 ))
done6.5 循环控制
break:跳出整个循环continue:跳过本次循环的剩余部分,进入下一次
for i in {1..10}
do
if [ $i -eq 5 ]; then
break
fi
echo $i # 只会输出 1 2 3 4
done七、输入输出重定向与管道
7.1 标准输入、输出、错误
每个 Linux 程序默认有三个“数据流”:
| 名称 | 数字代号 | 说明 |
|---|---|---|
| 标准输入 (stdin) | 0 | 键盘输入 |
| 标准输出 (stdout) | 1 | 正常输出 |
| 标准错误 (stderr) | 2 | 错误信息 |
7.2 输出重定向:把结果写到文件里
| 符号 | 含义 |
|---|---|
> | 覆盖写入(会删掉文件原有内容) |
>> | 追加写入(保留原内容,加在末尾) |
2> | 只重定向错误信息 |
&> | 把正确和错误都重定向到同一个文件 |
echo "你好" > output.txt # 覆盖写入
date >> output.txt # 追加
ls 不存在的文件 2> error.log # 错误信息写入 error.log
ls /etc &> all.log # 正常和错误都写入 all.log7.3 输入重定向:从文件读取内容
< 符号可以让命令从文件而不是键盘获取输入。
# 统计文件里的行数
wc -l < /etc/passwd7.4 管道 |:把左边命令的输出作为右边命令的输入
这是 Shell 最强大的特性之一,可以串接多个命令。
# 查看所有进程,筛选出包含 "bash" 的行
ps aux | grep bash
# 列出当前目录下所有文件,按文件大小排序
ls -l | sort -k5 -n
# 统计当前目录有多少个文件(ls 列出,wc -l 统计行数)
ls | wc -l图解:命令A | 命令B
命令 A 的输出 → 直接喂给命令 B 的输入 → 命令 B 输出最终结果
八、后台运行与作业控制
8.1 在命令后面加 &:让程序在后台运行
sleep 100 &当你输入这条命令后,Shell 会立即返回一个提示符,而 sleep 在后台悄悄运行。屏幕会显示类似 [1] 12345,其中 [1] 是作业号,12345 是进程 ID。
8.2 查看后台作业:jobs
jobs输出:
[1]+ Running sleep 100 &8.3 把后台作业调回前台:fg
fg %1 # 将作业1调到前台之后可以用 Ctrl + C 终止它,或用 Ctrl + Z 暂停它。
8.4 暂停当前前台任务:Ctrl + Z
然后可以用 bg 让它在后台继续运行。
# 先运行一个很慢的命令
find / -name "*.txt"
# 按 Ctrl + Z 暂停,看到 [1]+ Stopped ...
# 输入 bg 让它后台继续跑
bg8.5 让脚本在退出终端后依然运行:nohup
普通后台程序(&)在关闭终端时会收到 SIGHUP 信号而退出。用 nohup 可以忽略该信号。
nohup ./long_task.sh &输出会被自动写入 nohup.out 文件。
九、函数:把重复代码包起来
9.1 定义与调用
# 定义函数
say_hello() {
echo "Hello, $1"
}
# 调用函数(就像调用命令一样)
say_hello "张三"
say_hello "李四"$1在函数内部表示函数的第一个参数。- 函数里也可以使用
$2,$#,$@等。
9.2 函数返回值
使用 return 返回数字(0 表示成功,非 0 表示失败)。但要获取函数的输出结果,一般用 echo 配合命令替换。
get_sum() {
total=$(($1 + $2))
echo $total # 输出结果
}
result=$(get_sum 10 20)
echo "和是 $result"9.3 局部变量:local
函数内部用 local 声明的变量只在函数内生效,不影响外部。
var="全局"
test_func() {
local var="局部"
echo "函数内: $var"
}
test_func
echo "函数外: $var"输出:
函数内: 局部
函数外: 全局十、实用示例:一个完整的备份脚本
结合前面学到的知识,写一个每天备份指定文件夹的脚本。
#!/bin/bash
# 配置区域
SOURCE_DIR="/home/user/documents"
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.tar.gz"
# 检查源目录是否存在
if [ ! -d "$SOURCE_DIR" ]; then
echo "错误:源目录 $SOURCE_DIR 不存在" >&2
exit 1
fi
# 如果备份目录不存在则创建
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
fi
# 执行打包压缩
echo "开始备份 $SOURCE_DIR ..."
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
# 检查上一步是否成功
if [ $? -eq 0 ]; then
echo "备份成功: $BACKUP_FILE"
# 可选:删除30天前的旧备份
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +30 -delete
else
echo "备份失败" >&2
exit 2
fi解释要点:
>&2:把错误消息重定向到标准错误。$?:上一条命令的退出状态码(0 成功)。-mtime +30:修改时间超过 30 天的文件。 保存为backup.sh,运行chmod +x backup.sh,然后./backup.sh就可以手动备份。配合下一章的crontab,可以每天自动运行。
十一、自动化:用 crontab 定时执行脚本
11.1 编辑当前用户的 crontab 任务
crontab -e会打开一个编辑器,每一行代表一个定时任务,格式如下:
分 时 日 月 周 要执行的命令| 位置 | 含义 | 取值范围 |
|---|---|---|
| 第 1 段 | 分钟 | 0-59 |
| 第 2 段 | 小时 | 0-23 |
| 第 3 段 | 日 | 1-31 |
| 第 4 段 | 月 | 1-12 |
| 第 5 段 | 星期 | 0-7(0 和 7 都代表周日) |
| 第 6 段 | 命令 | 需要完整路径 |
| 常用例子: |
# 每天凌晨2:30运行备份脚本
30 2 * * * /home/user/backup.sh
# 每周一早上8点
0 8 * * 1 /home/user/weekly_report.sh
# 每15分钟执行一次
*/15 * * * * /home/user/check_status.sh
# 每月1号中午12点
0 12 1 * * /home/user/monthly_clean.sh11.2 查看已有的定时任务
crontab -l11.3 删除所有定时任务
crontab -r11.4 注意事项
- 在 crontab 中执行的脚本,尽量使用绝对路径(
/home/user/backup.sh而不是./backup.sh)。 - 如果脚本需要用到环境变量(比如
$PATH),最好在脚本开头重新定义,或者在 crontab 文件开头设置PATH=/usr/local/bin:/usr/bin:/bin。 - crontab 脚本的输出默认会发送邮件给当前用户(如果没有配置邮件服务,可能就丢了)。建议把输出重定向到日志文件:
30 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1十二、调试与排错
12.1 使用 -x 选项打印每一条执行的命令
bash -x script.sh或者在脚本开头加 set -x,结束时用 set +x 关闭。
12.2 检查语法错误
bash -n script.sh只检查语法,不实际执行。
12.3 常见错误及解决
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
command not found | 命令写错了,或者没有安装该程序 | 检查拼写,用 which 命令 查看路径 |
permission denied | 文件没有执行权限 | chmod +x 文件 |
bad interpreter | 脚本第一行的 #!/bin/bash 路径不对 | 用 which bash 查看正确路径 |
[: too many arguments | [ ] 里变量没加双引号,且变量值含空格 | 始终用 [ "$var" = "abc" ] |
syntax error near unexpected token | 缺少 then、do、fi 等关键字 | 检查 if 与 then 是否配对 |
12.4 推荐的安全习惯
- 始终用双引号包裹变量(
"$var")防止空格造成单词拆分。 - 检查文件或目录是否存在之后再操作。
- 使用
set -e让脚本在遇到错误时立即退出(避免雪崩)。 - 不要用
rm -rf /之类危险命令;如果需要删除,先ls确认。