当前位置:首页>Linux>Linux Shell 脚本编写入门——变量、条件、循环、函数

Linux Shell 脚本编写入门——变量、条件、循环、函数

  • 2026-10-10 13:16:54
Linux Shell 脚本编写入门——变量、条件、循环、函数

大家好,我是冯哥的缓存。上篇终端配置有了基本了解。但如果你发现同一批操作需要反复执行,比如每天备份某个目录、批量重命名文件、自动检查磁盘空间发警报……这时候光靠 alias 就不够了,需要写脚本。

Shell 脚本就是把一堆命令写进文件,一次执行。不需要学任何新语言——那些 ls、cd、grep 就是脚本的原材料。今天这篇就聊一下脚本编写的核心内容:变量、条件、循环、函数,最后再附几个拿来就能用的实用脚本。

一、第一个脚本:Hello World

1. 创建脚本文件

脚本就是一个普通文本文件,约定俗成用 .sh 结尾(不加也可以,但加上更清晰):

nano hello.sh

写入以下内容:

#!/bin/bash
# 这是注释,
#号开头

echo "Hello, World!"
echo "当前用户:$USER"
echo "当前目录:$(pwd)"

2. 第一行:shebang

#!/bin/bash 叫做 shebang,告诉系统用哪个解释器来执行这个文件。

写法
意义
#!/bin/bash
用 bash 解释器,路径固定
#!/usr/bin/env bash
用 PATH 里找到的 bash,更通用(推荐)
#!/bin/sh
用最基础的 POSIX sh,兼容性最强但功能少

💡 提示: 推荐写 #!/usr/bin/env bash,在不同 Linux 发行版上兼容性最好。

3. 给脚本执行权限并运行

chmod +x hello.sh    # 加可执行权限
./hello.sh           # 执行(注意要加 ./)
# 如果你习惯了 Windows,可能会想双击运行,但在 Linux 中,脚本是通过终端执行的。

或者不加权限,直接用 bash 运行:

bash hello.sh

💡 提示: 为什么要加 ./?因为 Linux 出于安全考虑,不会在当前目录找命令,必须明确指出路径。

二、变量:存储数据

1. 基本赋值与引用

name="冯哥"
age=18

echo $name# 输出:冯哥
echo ${name}# 加花括号,更规范(避免歧义)
echo "我叫${name},今年${age}岁"

⚠️ 注意: 等号两边不能有空格,name = "冯哥" 是错的。

2. 引号的区别

写法
效果
"双引号"
变量会展开,特殊字符大多保留
'单引号'
完全原样输出,变量不展开
反引号或 $()
命令替换,把命令输出作为值

name="冯哥"
echo "你好 $name"# 输出:你好 冯哥
echo '你好 $name'# 输出:你好 $name(原样)
echo "今天是 $(date)"# 输出:今天是 2026年6月21日...

3. 命令替换:把命令输出存为变量

today=$(date +%Y-%m-%d)
free_space=$(df -h / | awk 'NR==2{print $4}')

echo "今天:$today"
echo "根分区剩余:$free_space"

4. 数值运算

bash 默认把所有变量当字符串,数值运算需要特殊写法:

a=10
b=3

# 方法一:$(( ))——推荐
echo $((a + b))     # 13
echo $((a * b))     # 30
echo $((a / b))     # 3(整数除法,丢掉小数)
echo $((a % b))     # 1(取余)

# 方法二:let
let c=a+b
echo $c# 13

# 浮点数运算(bash 不支持,用 bc)
# 如果 bc 未安装先安装
sudo apt install bc
# 如果 bc 已经安装直接
echo "scale=2; 10/3" | bc    # 3.33

5. 特殊变量

变量
含义
$0
脚本文件名本身
$1
$2 ...
第1、第2...个参数
$@
所有参数(分开的)
$*
所有参数(合并的)
$#
参数个数
$?
上一条命令的退出状态(0=成功,非0=失败)
$$
当前脚本的进程 ID(PID)

#!/usr/bin/env bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "参数总数:$#"
echo "所有参数:$@"

运行 ./test.sh hello world,输出:

脚本名:./test.sh
第一个参数:hello
参数总数:2
所有参数:hello world

三、字符串操作

str="Hello, Linux World"

# 长度
echo ${#str}  # 18

# 截取:${变量:起始位置:长度}
echo ${str:7:5}  # Linux

# 替换:${变量/查找/替换}(只替换第一个)
echo ${str/Linux/Shell}  # Hello, Shell World

# 替换所有:${变量//查找/替换}
echo ${str// /_}  # Hello,_Linux_World

# 删除前缀(最短匹配)
file="backup_2026-06-21.tar.gz"
echo${file#backup_}# 2026-06-21.tar.gz

# 删除后缀(最短匹配)
echo ${file%.tar.gz}  # backup_2026-06-21

# 转大写 / 小写(bash 4.0+)
echo ${str^^}  # HELLO, LINUX WORLD
echo ${str,,}  # hello, linux world

💡 提示: 字符串操作是 Shell 脚本里很常用的技巧,尤其是从文件名里提取日期、去掉扩展名等场景。

四、条件判断:if 与 case

1. if 语法

if [ 条件 ]; then
    命令
elif [ 另一个条件 ]; then
    命令
else
    命令
fi

⚠️ 注意:[ 和 ] 里面两侧必须有空格,[ $a -eq 0 ] 是对的,[$a -eq 0] 是错的。

2. 数值比较

写法
含义
[ $a -eq $b ]
a 等于 b
[ $a -ne $b ]
a 不等于 b
[ $a -gt $b ]
a 大于 b
[ $a -lt $b ]
a 小于 b
[ $a -ge $b ]
a 大于等于 b
[ $a -le $b ]
a 小于等于 b

也可以用 (( )) 做数值比较(更像 C 语言,支持 ><==):

a=10
if (( a > 5 )); then
echo"a 大于 5"
fi

3. 字符串比较

写法
含义
[ "$a" = "$b" ]
字符串相等(注意用 = 不是 ==)
[ "$a" != "$b" ]
字符串不相等
[ -z "$a" ]
字符串为空(zero length)
[ -n "$a" ]
字符串非空(non-zero length)

⚠️ 注意: 比较字符串时,变量一定要加引号:[ "$name" = "root" ],不加引号如果变量为空会报语法错误。

4. 文件判断

写法
含义
[ -f 文件 ]
文件存在且是普通文件
[ -d 目录 ]
目录存在
[ -e 路径 ]
路径存在(文件或目录都行)
[ -r 文件 ]
文件可读
[ -w 文件 ]
文件可写
[ -x 文件 ]
文件可执行
[ -s 文件 ]
文件存在且非空

if [ -f "/etc/hosts" ]; then
echo"hosts 文件存在"
fi

if [ ! -d "$HOME/backup" ]; then
mkdir"$HOME/backup"
echo"备份目录已创建"
fi

5. 逻辑运算符

# AND(两个条件都满足)
if [ -f file.txt ] && [ -r file.txt ]; then
echo"文件存在且可读"
fi

# OR(满足一个即可)
if [ "$user" = "root" ] || [ "$user" = "admin" ]; then
echo"有管理员权限"
fi

# NOT
if [ ! -f file.txt ]; then
echo"文件不存在"
fi

6. case 语句(多分支更清晰)

read -p "请输入操作(start/stop/restart):" action

case "$action" in
    start)
echo "启动服务..."
        ;;
    stop)
echo "停止服务..."
        ;;
    restart)
echo "重启服务..."
        ;;
    *)
echo "未知操作:$action"
        ;;
esac

💡 提示:case 比一堆 if-elif 更清晰,适合处理多个固定选项的分支,比如处理命令行参数时很常用。

五、循环:for 与 while

1. for 循环——遍历列表

# 遍历固定列表
for fruit in apple banana cherry; do
echo "水果:$fruit"
done

# 遍历数字范围(bash 内置)
for i in {1..5}; do
echo "第 $i 次"
done

# C 语言风格(数值循环)
for (( i=0; i<5; i++ )); do
echo "i = $i"
done

# 遍历文件(通配符)如果没有匹配的文件,file 会变成字面值 *.log,可以用 if [ -f "$file" ] 过滤。
for file in /tmp/*.log; do
echo "处理:$file"
rm "$file"
done

# 遍历命令输出(命令替换)
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "用户:$user"
done

2. while 循环——条件为真时持续执行

# 基本 while
count=1
while [ $count -le 5 ]; do
echo "第 $count 次"
    (( count++ ))
done

# 逐行读取文件(很常用)
while IFS= read -r line; do
echo "行内容:$line"
done < /etc/hosts

# 无限循环(用 break 退出)
while true; do
read -p "输入 quit 退出:" input
if [ "$input" = "quit" ]; then
break
fi
echo "你输入了:$input"
done

3. loop 控制

for i in {1..10}; do
if (( i == 3 )); then
continue  # 跳过这次,继续下一轮
fi
if (( i == 7 )); then
break  # 直接退出整个循环
fi
echo $i
done
# 输出:1 2 4 5 6

六、函数:把代码打包复用

1. 函数定义与调用

# 定义
say_hello() {
echo "你好,$1!"# $1 是传给函数的第一个参数
}

# 调用
say_hello "冯哥"# 输出:你好,冯哥!
say_hello "Linux"# 输出:你好,Linux!

2. 函数参数

函数内部的 $1 $2 $@ 是函数自己的参数,不是脚本的参数。

add() {
local result=$(( $1 + $2 ))    # local 让变量只在函数内有效
echo $result
}

sum=$(add 10 20)
echo "结果:$sum"  # 输出:结果:30

💡 提示: 函数内的变量尽量加 local,避免污染全局变量空间,尤其是在大脚本里。

3. 函数返回值

Shell 函数的"返回值"有两种:

方式
用法
适用场景
return Nreturn 0
 表示成功,非0表示失败,只能是整数
返回成功/失败状态
echo
 输出 + $() 接收
通过命令替换获取函数输出的字符串
返回字符串或数值

# 用 return 返回状态码
is_file_exist() {
if [ -f "$1" ]; then
return 0    # 成功
else
return 1    # 失败
fi
}

if is_file_exist "/etc/hosts"; then
echo "文件存在"
fi

# 用 echo 返回字符串
get_timestamp() {
echo $(date +%Y%m%d_%H%M%S)
}

ts=$(get_timestamp)
echo "时间戳:$ts"

七、错误处理:让脚本更健壮

1. 检查命令是否成功

cp source.txt dest.txt
if [ $? -ne 0 ]; then
echo "复制失败!"
exit 1    # 退出脚本,返回错误码
fi

更简洁的写法(用 ||):

cp source.txt dest.txt || { echo "复制失败!"; exit 1; }

2. set -euo pipefail(推荐在所有脚本开头加)

#!/usr/bin/env bash
set -euo pipefail

选项
效果
set -e
任何命令失败(退出码非0)立即退出整个脚本
set -u
使用未定义的变量时报错退出(防止变量名拼错)
set -o pipefail
管道中任意一个命令失败,整个管道视为失败

⚠️ 注意:set -e 有时会在你预期内的"失败"处退出(比如 grep 没找到东西返回1),这时候可以用 cmd || true 来告诉脚本"这里失败是正常的,继续走"。

3. 输出信息的规范

# 信息输出到 stdout
echo "正在备份..."

# 错误信息输出到 stderr(不会混进管道)
echo "错误:文件不存在" >&2

# 带颜色的输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'# No Color

echo -e "${GREEN}成功${NC}:备份完成"
echo -e "${RED}失败${NC}:源文件不存在"

八、实用脚本示例

复制脚本代码保存为文件,chmod +x 后执行。

示例一:自动备份目录

#!/usr/bin/env bash
set -euo pipefail

# ===== 配置区(修改这里)=====
SOURCE_DIR="$HOME/Documents"
BACKUP_DIR="$HOME/backup"
KEEP_DAYS=7    # 保留最近几天的备份
# ===== 配置结束 =====

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 生成带时间戳的文件名
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/docs_${TIMESTAMP}.tar.gz"

# 执行备份
echo "开始备份 $SOURCE_DIR ..."
tar -czf "$BACKUP_FILE" -C "$(dirname $SOURCE_DIR)""$(basename $SOURCE_DIR)"
echo "备份完成:$BACKUP_FILE"
echo "备份大小:$(du -sh $BACKUP_FILE | cut -f1)"

# 删除超过 KEEP_DAYS 天的旧备份
echo "清理 ${KEEP_DAYS} 天前的旧备份..."
find "$BACKUP_DIR" -name "docs_*.tar.gz" -mtime +$KEEP_DAYS -delete
echo "清理完成"

示例二:批量重命名文件

#!/usr/bin/env bash
# 用法:./rename.sh 目录 旧字符串 新字符串
# 例:./rename.sh /tmp/photos IMG img

set -euo pipefail

TARGET_DIR="${1:?请提供目标目录}"
OLD_STR="${2:?请提供要替换的字符串}"
NEW_STR="${3:?请提供替换成的字符串}"

if [ ! -d "$TARGET_DIR" ]; then
echo "错误:目录不存在:$TARGET_DIR" >&2
exit 1
fi

count=0
for file in"$TARGET_DIR"/*"$OLD_STR"*; do
    [ -f "$file" ] || continue  # 如果没有匹配文件,跳过
    new_name="${file/$OLD_STR/$NEW_STR}"
mv "$file""$new_name"
echo "重命名:$(basename $file) → $(basename $new_name)"
    (( count++ ))
done

echo "完成,共重命名 $count 个文件"

示例三:磁盘空间监控告警

#!/usr/bin/env bash
# 检查磁盘使用率,超过阈值就发警告
# 可以加入 cron 定时执行

THRESHOLD=80    # 告警阈值(百分比)

df -h | grep -vE '^(Filesystem|tmpfs|udev|none)' | whileread -r line; do
    usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
    mount=$(echo "$line" | awk '{print $6}')

if (( usage >= THRESHOLD )); then
echo "⚠️  警告:挂载点 $mount 使用率已达 ${usage}%,超过阈值 ${THRESHOLD}%"
# 如果配了邮件,可以在这里加发邮件命令
# echo "磁盘告警:$mount 使用率 ${usage}%" | mail -s "磁盘空间告警" admin@example.com
fi
done

把它加进 crontab,每天自动检查:

crontab -e
# 加入这行(每天早上8点执行)
0 8 * * * /home/用户名/bin/check_disk.sh >> /tmp/disk_check.log 2>&1

九、调试技巧

脚本出问题了,怎么找原因?

# 方法一:bash -x 追踪执行(每行命令都打印出来)
bash -x ./myscript.sh

# 方法二:在脚本里局部开启追踪
如果遇到脚本报错但看不出哪里错,用调试模式跑一遍。
set -x    # 开始追踪
...代码...
set +x    # 关闭追踪

# 方法三:在出问题的地方加 echo 打印变量
echo "DEBUG: file=$file, count=$count"

# 方法四:shellcheck 静态检查(需安装)
sudo apt install shellcheck
shellcheck myscript.sh

💡 提示:shellcheck 是 Shell 脚本的"语法检查+较佳实践检查"工具,推荐,能在运行前发现大量潜在问题。shellcheck myscript.sh 会列出所有警告,按提示修改即可。

十、快速参考:语法速查表

功能
写法
变量赋值
name="value"
变量引用
$name
 或 ${name}
命令替换
$(命令)
数值运算
$(( a + b ))
if 判断
if [ 条件 ]; then ... fi
数值比较
-eq -ne -gt -lt -ge -le
字符串比较
=
!=-z-n
文件判断
-f
-d-e-r-w-x
for 循环
for x in 列表; do ... done
while 循环
while [ 条件 ]; do ... done
函数定义
func_name() { ... }
局部变量
local var=value
退出脚本
exit 0
(成功)/ exit 1(失败)
错误保护
set -euo pipefail
调试模式
bash -x 脚本

如果记不住那么多,记住 if、for、while 和 $() 就够了,其他可以随时查。 脚本编写是 Linux 使用效率的倍增器。今天这篇是入门,重点在把语法搞清楚。

下篇预告:函数库管理——随着脚本越写越多,怎么把常用函数抽出来统一管理,在任意脚本里 source 一下就能用,建立自己的工具箱。

最新文章

随机文章