Skip to content

一、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 学习目标

  1. 系统巡检类:检查内存、磁盘、CPU 负载,并输出结果。
  2. 数据备份类:带时间戳的备份、清理过期备份(find + rm)。
  3. 服务控制类:重启服务、检查端口、如果挂了就自动拉起(if + systemctl)。
  4. 初始化脚本:新机器一键配置 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.txtrm -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. 第 1 步:创建文件
vim first.sh
  1. 第 2 步:写入内容
#!/bin/bash
# 这是注释,不会执行
echo "今天日期是:"
date
echo "当前目录下的内容:"
ls
  • #!/bin/bash:告诉系统用 Bash 解释这个脚本(必须第一行)。
  • # 开头的行是注释,给人看的。
  1. 第 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 的变量

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

  1. 只包含字母、数字和下划线: 变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。
  2. 不能以数字开头:变量名不能以数字开头,但可以包含数字。
  3. 避免使用 Shell 关键字: 不要使用 Shell 的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。
  4. 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如 PI=3.14。
  5. 避免使用特殊符号:尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
  6. 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数。

4.1 变量的定义与使用

在 Shell 里,变量就是用来“存数据”的名字。

name="张三"          # 定义变量(等号两边不能有空格)
age=25
echo $name          # 使用变量时前面加 $ 符号
echo "他叫 $name, 今年 $age 岁"   # 双引号内可解析变量
echo '他叫 $name'   # 单引号内所有字符都是纯文本,不解析变量

输出:

张三
他叫 张三, 今年 25 岁
他叫 $name

4.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 123

4.4 环境变量

执行 env 命令可以查看所有环境变量。常用的有:

变量含义
$HOME用户主目录
$PATH命令搜索路径(告诉系统去哪里找可执行文件)
$USER当前用户名
$SHELL当前 Shell 的路径
echo "你好 $USER,你的家目录在 $HOME"

五、条件判断

5.1 if 语句的基本结构

if 条件
then
    # 条件成立时执行
else
    # 条件不成立时执行
fi

fi 是 if 的倒写,表示结束。

5.2 [ ] 和 test 命令

在 Shell 中,[ 条件 ] 就是 test 条件 的简写。注意方括号与内容之间必须有空格。

5.2.1 数值比较

运算符英语单词含义
-eqequal等于
-nenot equal不等于
-gtgreater than大于
-ltless than小于
-gegreater or equal大于等于
-leless or equal小于等于
num=10
if [ $num -gt 5 ]
then
    echo "num 大于 5"
fi

5.2.2 字符串比较

运算符含义
= 或 ==等于
!=不等于
-z字符串为空
-n字符串非空
name=""
if [ -z "$name" ]
then
    echo "名字是空的"
fi

5.2.3 文件测试

运算符含义
-e 文件文件存在
-f 文件存在且是普通文件
-d 目录存在且是目录
-r 文件存在且可读
-w 文件存在且可写
-x 文件存在且可执行
if [ -d "/etc" ]
then
    echo "/etc 是一个目录"
fi

5.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 次循环"
done

6.3 while 循环

条件为真时循环

count=1
while [ $count -le 5 ]
do
    echo "count=$count"
    count=$(( count + 1 ))   # 算术运算
done

6.4 until 循环

条件为假时循环(与 while 相反)

count=1
until [ $count -gt 5 ]
do
    echo "count=$count"
    count=$(( count + 1 ))
done

6.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.log

7.3 输入重定向:从文件读取内容

< 符号可以让命令从文件而不是键盘获取输入。

# 统计文件里的行数
wc -l < /etc/passwd

7.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 让它后台继续跑
bg

8.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.sh

11.2 查看已有的定时任务

crontab -l

11.3 删除所有定时任务

crontab -r

11.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缺少 thendofi 等关键字检查 if 与 then 是否配对

12.4 推荐的安全习惯

  • 始终用双引号包裹变量("$var")防止空格造成单词拆分。
  • 检查文件或目录是否存在之后再操作。
  • 使用 set -e 让脚本在遇到错误时立即退出(避免雪崩)。
  • 不要用 rm -rf / 之类危险命令;如果需要删除,先 ls 确认。