Linux之shell脚本

该篇文章主要介绍shell是什么、shell脚本的基本语法以及一些实例展示,带我们快速入门shell脚本编程。

shell与shell脚本

Shell 是操作系统中用户与内核之间的桥梁,它是一种"命令行解释器"(Command Line Interpreter),可以接收用户输入的命令并将其传递给操作系统执行。我们通常所说的 "Shell 编程" 或 "Shell 脚本",就是用这种命令行语言编写的程序。所以我们要区分两种概念,shell本身是一个"命令行解释器",是用户输入的命令与操作系统之间交互的桥梁;而shell脚本就是一组写在文件里的命令,本质上shell脚本就是把多个命令写在一个文件中,让shell解释器一次执行完。

shell在类Unix系统中扮演的角色

在类 Unix 系统中,系统结构大致如下:

复制代码
+----------------------------+
|        用户 (User)         |
+-------------+--------------+
              |
              v
+-------------+--------------+
|        Shell(命令行)     |  ⇦⇨ 用户输入命令 & 获取输出
+-------------+--------------+
              |
              v
+-------------+--------------+
|     系统调用接口(API)     |  ⇦⇨ Shell 通过系统调用与内核沟通
+-------------+--------------+
              |
              v
+-------------+--------------+
|      操作系统内核(Kernel)|   ⇦⇨ 管理硬件资源、调度程序等
+-------------+--------------+
              |
              v
+-------------+--------------+
|      硬件(CPU、内存等)    |
+----------------------------+

假设我们在命令行中输入了 ls -l:

复制代码
用户输入命令:
+-------------------+
|   User Terminal   |
|   输入:ls -l     |
+---------+---------+
          |
          v
  Shell 解释命令:
+---------+---------+
|     Shell (bash)  |
|  解析为 exec('ls')|
+---------+---------+
          |
          v
   通过系统调用执行程序:
+---------+---------+
|系统调用接口 (syscall) 
+---------+---------+
          |
          v
  操作系统调度运行 ls:
+---------+---------+
|     Kernel(内核)|
+---------+---------+
          |
          v
  调用磁盘、文件系统等
+---------+---------+
|    硬件/文件系统   |
+-------------------+

Shell 的基本作用

  • 解析命令:用户输入的命令由 Shell 解释并执行。

  • 提供交互式界面:用户可以输入命令、查看输出、进行交互操作。

  • 脚本执行:Shell 可以将一系列命令写入文件中,以脚本形式批量执行。

  • 控制程序流程:支持变量、条件判断、循环等控制结构。

shell脚本的创建

shell脚本文件的后缀为.sh

我们可以通过vim编辑器直接创建脚本文件或使用touch创建脚本文件

复制代码
vim myscript.sh
touch myscript.sh

在创建好.sh文件后,在该文件的第一行一般我们要指定下shell解释器(注意这不是必须的,但推荐在sh文件的第一行指定下具体的shell解释器,否则直接运行./xxx.sh时会根据系统默认的解释器如/bin/sh来执行脚本,如果你的.sh是基于bash解释器编写的那么可能会导致不兼容或报错)。

bash 复制代码
#指定解释器为sh
#!/bin/sh

#指定解释器为bash
#!/bin/bash

.....

常见的shell解释器

shell解释器是一种统称,具体又会有不同的解释器,常见的shell解释器有sh、bash、zsh、dash等

解释器名称 路径 特点简述
sh /bin/sh 最基础的 Unix Shell,兼容性强,功能有限(POSIX 标准)
bash /bin/bash Linux 默认 Shell,功能强大,支持数组、[[ ]] 条件表达式等
zsh /bin/zsh 更强大、可配置、语法更灵活,配合 oh-my-zsh 很流行
dash /bin/dash Ubuntu 中 /bin/sh 的默认指向,轻量、执行快,但语法限制多

shell脚本的执行

shell脚本的执行方式有多种:
方式一:

首先为shell脚本加权限

复制代码
chmod +x myscript.sh

直接运行该脚本

复制代码
./myscript.sh

注意:如果创建脚本时我们在第一行制定了具体的解释器,那此时执行该脚本时会使用制定好的解释器,否则使用默认shell解释器sh

方式二:

显示指定解释器执行

bash 复制代码
#指定用sh解释器
sh myscript.sh

#指定用bash解释器
bash myscript.sh

方式三:

在子shell中执行脚本内容(仍在当前shell环境中执行脚本,不是在新进程中),常用于设置环境变量、加载配置等

bash 复制代码
source myscript.sh  
或
. source myscript.sh

注意:source和.是shell命令,用来在当前shell环境中执行脚本内容,这意味着脚本中的所有命令都会在当前的shell环境中执行,而不是启动一个新进程。如果我们用./myscript.sh执行脚本,系统会启动一个新的shell进程来执行脚本,脚本中的环境变量或设置(如定义的变量、函数)会只在这个进程中有效 ,执行完后就消失了。但是,如果你用 source myscript.sh. myscript.sh 执行脚本,脚本中的内容会在当前的 Shell 环境 中执行,这样脚本里设置的环境变量、函数等都能在执行完脚本后继续存在

示例:

myscript.sh

bash 复制代码
#!/bin/bash
export MY_VAR="hello"

如果用./执行后再打印MY_VAR

bash 复制代码
$ ./myscript.sh
$ echo $MY_VAR

输出:

bash 复制代码
(没有任何输出)

但如果使用source再打印

bash 复制代码
$ source myscript.sh
$ echo $MY_VAR

输出:

bash 复制代码
hello

shell脚本中的变量

1.系统环境变量

系统环境变量是在操作系统启动或用户登录时自动定义的变量,用于保存系统配置、用户信息、路径设置等。

变量名 含义
$HOME 当前用户的主目录路径(如 /home/username
$USER 当前登录的用户名(如 root, john
$PATH 系统查找可执行命令的路径列表(冒号 : 分隔)
$SHELL 当前默认使用的 Shell 解释器路径(如 /bin/bash
$PWD 当前工作目录的完整路径(print working directory)
$LANG 当前语言/区域设置(如 en_US.UTF-8
bash 复制代码
echo "你的用户名是:$USER"
echo "你的家目录是:$HOME"
echo "当前目录是:$PWD"
echo "Shell 程序是:$SHELL"
echo "命令搜索路径是:$PATH"

2.shell特殊变量

这些变量在脚本执行过程中自动生效,不需要定义,直接使用

特殊变量 说明
$0 当前脚本的文件名
$1~$9 传入脚本的第 1~9 个参数
$# 传入参数的个数
$@ 以"参数列表"的方式展开所有参数(保留参数引用)
$* 以"一个整体"的方式展开所有参数(不保留引用)
$? 上一条命令的退出状态码(0 表示成功)
$$ 当前脚本运行的进程 ID
$! 最后一个后台运行的进程的 PID

myscript.sh

bash 复制代码
#!/bin/bash
echo "脚本名是:$0"
echo "第一个参数是:$1"
echo "第二个参数是:$2"
echo "参数个数是:$#"
echo "所有参数(\$@):$@"
echo "所有参数(\$*):$*"
echo "当前进程 PID:$$"

执行脚本

bash 复制代码
chmod +x test.sh
./test.sh apple banana

输出

bash 复制代码
脚本名是:./test.sh
第一个参数是:apple
第二个参数是:banana
参数个数是:2
所有参数($@):apple banana
所有参数($*):apple banana
当前进程 PID:3921

3.用户自定义变量

bash 复制代码
name="Tom"
age=25

注意:等号两边不能有空格,否则会被解释成命令;变量名建议只使用字母、数字、下划线,不能以数字开头。

使用变量:

bash 复制代码
echo "姓名是:$name"
echo "年龄是:$age"

补充:对于{变量}来说,的作用是引用变量的值,即用来访问变量的值,建议用{}包起变量名后再用取值,为了防止歧义。运算符还有多种作用,后续会统一介绍。

变量的基本操作:

操作 示例 说明
赋值 x=10 定义变量
取值 echo $x 读取变量
删除变量 unset x 删除变量
只读变量 readonly x 定义只读变量,不能再修改

示例:

bash 复制代码
#!/bin/bash

myname="Alice"
readonly myname     # 冻结变量
echo "Hello, $myname"

unset myname        # 会报错:不能删除只读变量

shell脚本基本语法结构

1.特殊符号

$

$ 符号用于 引用变量获取命令的输出指定参数执行进程替换 等多种用途。它是一个多用途的符号,非常常见且重要。

①引用变量的值

bash 复制代码
my_var="Hello, World!"
echo $my_var

输出:Hello, World!

②获取命令的输出

bash 复制代码
current_date=$(date)
echo "今天的日期是: $current_date"

输出:今天的日期是: Tue May  1 10:00:00 CST 2025

③获取位置参数

在 Shell 脚本中,$ 用于引用 脚本的参数 。脚本在执行时可以接收外部传入的参数,这些参数通常通过 $1, $2, ..., $n 来引用,其中 1 表示第一个参数,2 表示第二个参数,以此类推。

bash 复制代码
# 脚本执行时传入参数
# ./script.sh arg1 arg2 arg3

echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"

第一个参数: arg1
第二个参数: arg2
第三个参数: arg3

④获取所有位置参数

当你想要获取 所有参数 时,可以使用 $@$*。它们都表示所有的命令行参数,但有所区别:

  • $@:将每个参数视为单独的字符串,适用于循环等操作;

  • $*:将所有参数视为一个整体的字符串。

bash 复制代码
# 脚本执行时传入参数
# ./script.sh arg1 arg2 arg3

echo "所有参数 (\$@): $@"
echo "所有参数 (\$*): $*"

输出:
所有参数 ($@): arg1 arg2 arg3
所有参数 ($*): arg1 arg2 arg3

⑤获取脚本执行状态(退出状态)

$? 用于获取上一条命令的退出状态。每个命令在执行时都会返回一个退出状态(exit status),通常返回 0 表示成功,非 0 表示失败。

bash 复制代码
mkdir new_dir
echo "上一条命令的退出状态: $?"

上一条命令的退出状态: 0

⑥获取后台进程PID

$!用于记录最近一次后台进程的PID

&

①后台运行命令,不阻塞当前shell

bash 复制代码
#在后台运行command
command &

sleep 10 &  # 后台休眠10秒,立即返回提示符,返回格式:[作业号] PID(如 [1] 12345)

②在子shell中并发执行多条命令

bash 复制代码
{ command1 & command2 & }  

{ sleep 3; echo "A"; } & { sleep 1; echo "B"; } &

输出(可能顺序):
B  # 先完成的任务先输出
A

③重定向中的特殊用途

bash 复制代码
command > file.txt 2>&1  #将stderr重定向到stdout输出的位置,即file.txt中


2>&1 表示将文件描述符 2(stderr)指向描述符 1(stdout)的当前位置。
此时执行command产生的stdout和stderr都被重定向写入到了file.txt中

④逻辑操作符与运算操作符

bash 复制代码
[[ -f file.txt ]] & echo "检查文件"  # 异步检查文件是否存在

echo $(( 5 & 3 ))  # 输出 1(二进制 101 & 011 = 001)

注释#

Shell 中注释以 # 开头,整行或行尾注释,Shell 执行时会忽略这些内容。

bash 复制代码
# 这是一个注释
name="Alice"  # 设置用户名变量

换行\

每条命令默认使用换行分隔

bash 复制代码
echo "第一行"
echo "第二行"

输出结果:
第一行
第二行

也可以使用 \ 实现 命令换行(续行:该行未结束,下一行是当前行的继续):

bash 复制代码
echo "这是一条非常 \
长的命令"

输出结果:
这是一条非常 长的命令

转义\

当我们需要在字符串中输出一些特殊字符(例如空格、引号、美元符号等)时,可以使用 \ 来转义它们。

bash 复制代码
echo "He said, \"Hello!\""

输出:

bash 复制代码
He said, "Hello!"

分号;

使用分号可以在同一行中执行多个命令

bash 复制代码
echo "hello"; echo "world"

等价于:

bash 复制代码
echo "hello"
echo "world"

&& ||

&&:前一个命令成功(返回值为 0)时才会执行后一个命令

||:前一个命令失败(返回值非 0)时才会执行后一个命令

bash 复制代码
# 如果命令成功,则执行第二条命令
mkdir new_dir && cd new_dir

# 如果命令失败,则执行第二条命令
mkdir new_dir || echo "目录创建失败"

2.输入输出

echo:输出内容到终端

bash 复制代码
echo "Hello, World!"

echo -n "不换行输出"
echo -e "换行符:\n下一行"

参数	      作用
-n	         不换行
-e	  启用转义字符(如 \n、\t)

read:从用户输入读取数据

bash 复制代码
read name
#用户再命令行中输入abc
echo "你好,$name"   #打印 你好,abc

带提示信息:
read -p "请输入你的名字:" name
echo "你好,$name"

多个变量:
read first last
echo "First: $first, Last: $last"

3.运算符

算术运算符

|-----|---|--------------|
| + | 加 | $((3 + 2)) |

|-----|---|--------------|
| - | 减 | $((5 - 1)) |

|-----|---|--------------|
| * | 乘 | $((2 * 3)) |

|-----|---|---------------|
| / | 除 | $((10 / 2)) |

|-----|----|--------------|
| % | 取余 | $((7 % 4)) |

比较运算符(整数)

用于 test[ ]

|-------|----|---------------------|
| -eq | 相等 | [ "$a" -eq "$b" ] |

|-------|----|---------------------|
| -ne | 不等 | [ "$a" -ne "$b" ] |

|-------|----|---------------------|
| -gt | 大于 | [ "$a" -gt "$b" ] |

|-------|----|---------------------|
| -lt | 小于 | [ "$a" -lt "$b" ] |

|-------|------|---------------------|
| -ge | 大于等于 | [ "$a" -ge "$b" ] |

|-------|------|---------------------|
| -le | 小于等于 | [ "$a" -le "$b" ] |

字符串比较运算符

|------------|-------|-------------------|
| === | 字符串相等 | [ "$a" = "$b" ] |

|------|-------|--------------------|
| != | 字符串不等 | [ "$a" != "$b" ] |

|------|----------|---------------|
| -z | 字符串长度为 0 | [ -z "$a" ] |

|------|-----------|---------------|
| -n | 字符串长度不为 0 | [ -n "$a" ] |

逻辑运算符

|-----|---|---------------------|
| ! | 非 | [ ! -f file.txt ] |

|------|---|----------------------------|
| -a | 与 | [ -f a.txt -a -r a.txt ] |

|------|---|----------------------------------|
| -o | 或 | [ "$a" -lt 0 -o "$b" -gt 100 ] |

4. 条件判断结构

** :**条件测试命令test的简写

bash 复制代码
[ 条件表达式 ]

注意:"" 与 "条件" 与 ""都要用空格分开

**\[ ]:**加强版的

支持字符串比较

bash 复制代码
name="admin"

# 传统写法
[ "$name" = "admin" ] && echo "匹配"

# Bash 写法
[[ $name == "admin" ]] && echo "匹配"

支持通配符

bash 复制代码
name="Alice"

[[ $name == A* ]]&& echo "名字以 A 开头"
[[ $name == *ce ]] && echo "名字以 ce 结尾"

支持正则匹配

bash 复制代码
email="user@example.com"

=~:使用正则表达式进行匹配
不需要加引号(加了反而当普通字符串处理)

[[ $email =~ ^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$ ]] && echo "这是合法邮箱"

支持多条件判断(支持|| &&)

bash 复制代码
age=25

if [[ $age -gt 18 && $age -lt 65 ]]; then
  echo "在工作年龄范围内"
fi

更好的鲁棒性:变量为空不报错

bash 复制代码
name=""

# 传统写法可能出错
# [ $name = "admin" ]    # 报错:一元运算符预期

# Bash 写法不会出错
[[ $name == "admin" ]]   # 安全 ✅

if-then

bash 复制代码
if [ condition ]; then
  commands
fi

if-else

bash 复制代码
if  [ "$age" -ge 18 ]; then
  echo "成年人"
else
  echo "未成年人"
fi

if-elif-else

bash 复制代码
if [ "$score" -ge 90 ]; then
  echo "优秀"
elif [ "$score" -ge 60 ]; then
  echo "及格"
else
  echo "不及格"
fi

test 命令

bash 复制代码
test $a -gt 5 && echo "a > 5"

#等价于
[ $a -gt 5 ] && echo "a > 5"

shell脚本流程控制

1.if-else、elif语句

bash 复制代码
if 条件; then
    命令
elif 另一个条件; then
    命令
else
    命令
fi
bash 复制代码
#!/bin/bash

read -p "请输入一个数字:" num

if [[ $num -gt 100 ]]; then
    echo "大于100"
elif [[ $num -eq 100 ]]; then
    echo "等于100"
else
    echo "小于100"
fi

2 case 语句

bash 复制代码
case $变量 in
  模式1)
    命令
    ;;
  模式2)
    命令
    ;;
  *)
    默认命令
    ;;
esac
bash 复制代码
#!/bin/bash

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

case $action in
  start)
    echo "服务已启动"
    ;;
  stop)
    echo "服务已停止"
    ;;
  restart)
    echo "服务已重启"
    ;;
  *)
    echo "未知命令"
    ;;
esac

3 for 循环

语法格式 1:遍历列表

bash 复制代码
for 变量 in 列表; do
    命令
done
bash 复制代码
for name in Alice Bob Charlie; do
    echo "你好,$name"
done

语法格式 2:数值循环(C 风格)

bash 复制代码
for ((i=1; i<=5; i++)); do
    echo "第 $i 次循环"
done

4 while 循环

bash 复制代码
while 条件; do
    命令
done
bash 复制代码
count=1
while [[ $count -le 3 ]]; do
    echo "循环第 $count 次"
    ((count++))
done

也可用于读取文件逐行:

bash 复制代码
while read line; do
    echo "$line"
done < 文件名.txt

5 break 与 continue

bash 复制代码
for ((i=1; i<=5; i++)); do
    if [[ $i -eq 3 ]]; then
        continue  # 跳过第3次
    fi
    if [[ $i -gt 4 ]]; then
        break     # 提前结束
    fi
    echo "第 $i 次"
done

shell脚本中的函数

1.函数的定义与调用

①函数的定义方式

bash 复制代码
# 方式一:推荐使用
function 函数名() {
    命令列表
}

# 方式二:兼容写法
函数名() {
    命令列表
}

②函数的调用方式

调用方式非常简单,直接写函数名即可:

bash 复制代码
say_hello() {
    echo "Hello, Shell!"
}

say_hello  # 调用无参函数


say_hello() {
    echo "Hello $0!"
}

say_hello bob # 调用有参函数

注意:shell脚本是逐行运行,不会像其它语言一样先编译,所以必须在函数调用前声明函数

③函数体中的局部变量与全局变量

bash 复制代码
foo() {
    local name="Alice"    # 局部变量,仅在函数内有效
    echo "Hi, $name"
}

name="Bob"
foo
echo "外部变量 name = $name"  # 不受 foo 中的修改影响

2.函数中的参数与返回值

①参数

Shell 函数接收参数的方式和脚本一样,用 $1$2$3 表示第 1、2、3 个参数:

bash 复制代码
greet() {
    echo "你好,$1!你今天感觉怎么样?"
}

greet "小明"

输出:

bash 复制代码
你好,小明!你今天感觉怎么样?

②返回值

函数返回值方式一:用 echo

bash 复制代码
get_name() {
    echo "Shell脚本"
}
name=$(get_name)
echo "返回值是:$name"

函数返回值方式二:用 return 返回状态码(0~255)

bash 复制代码
is_even() {
    if (( $1 % 2 == 0 )); then
        return 0
    else
        return 1
    fi
}

is_even 8
if [[ $? -eq 0 ]]; then
    echo "是偶数"
else
    echo "是奇数"
fi

shell脚本进阶

1 数组与字符串处理

①数组(仅bash支持)

定义数组

bash 复制代码
arr=(apple banana cherry)

访问数组元素

bash 复制代码
echo ${arr[0]}

获取所有元素

bash 复制代码
echo ${arr[@]}

获取数组长度

bash 复制代码
echo ${#arr[@]}

获取数组中某一元素的长度

bash 复制代码
echo ${#arr[0]}

遍历数组

bash 复制代码
for item in "${arr[@]}"; do
    echo "$item"
done

修改元素

bash 复制代码
arr[1]="orange"

②字符串处理

字符串长度

bash 复制代码
str="Hello Shell"
echo ${#str}   #输出11

子串提取

bash 复制代码
echo ${str:6:5}   #从下标6开始向后截取5个长度的元素

查找并替换

bash 复制代码
echo ${str/Shell/Bash}  #将str中的Shell替换为Bash,输出Hello Bash

字符串拼接

bash 复制代码
a="hello"
b="world"
c="$a $b"
echo $c

2 .输入输出重定向

①输出重定向

bash 复制代码
echo "写入文件" > file.txt     # 将"写入文件"覆盖写入到文件file.txt 
echo "追加内容" >> file.txt    # 将"追加内容"追加写入到文件file.txt 

②输出重定向

bash 复制代码
while read line; do    #每次读取file.txt中一行数据,重定向输出到控制台
    echo "$line"
done < file.txt

③多行输入

bash 复制代码
cat <<EOF > file.txt     #将多行数据一次性写入到file.txt中
这是多行内容
可以写很多行
EOF

④标准错误重定向

bash 复制代码
#执行ls一个不存在的文件会产生一个标准错误,
#可以通过 2> error.log 将这个标准错误重定向到error.log文件中
#2指的就是stderr的文件描述符
ls not_exist_file 2> error.log  

⑤将命令执行结果同时重定向给stdout和stderror

bash 复制代码
#将command的运行结果stdout重定向到out.log中
#将command运行产生的标准错误重定向到error.log中
command > output.log 2> error.log 

#将command的运行结果的标准输出和标准错误均重定向到all.log文件中
command > all.log 2>&1   #&1指的是获取文件描述符1即stdout的当前位置,即all.log
等价于
command > all.log 2> all.log

#将ping这条指令的标准输出stdout和标准错误均重定向到空设备(黑洞设备),丢弃所有正常输出
ping -c 1 www.baidu.com > /dev/null 2>&1

3.错误处理与调试

①退出码检查 $?

bash 复制代码
cp file.txt /some/path/
if [ $? -ne 0 ]; then
    echo "拷贝失败!"
fi

注意:$?存储的是最近一条指令执行的退出状态码

set -e:遇到错误立即退出脚本

bash 复制代码
#!/bin/bash
set -e

cp file.txt /path/       # 若失败,则退出脚本
echo "这行不会执行"

set -x:打印执行过程(调试用)

自动打印 实际执行的每一行命令及其参数(展开变量后的真实命令)

bash 复制代码
set -x
echo "当前用户: $USER"

#控制台输出
+ echo '当前用户: alice'  # 这是set -x的调试输出
当前用户: alice           # 这是echo的输出

4.使用 trap 捕捉信号

trap 是 Shell 脚本中用于 捕获并处理信号 的关键命令,它能让脚本在收到特定系统信号时执行自定义操作(比如清理资源、打印日志等),而不是直接终止。

在 Linux/Unix 系统中,信号是进程间通信的一种方式,用于通知进程发生了某些事件。常见信号:

  • INTSIGINTCtrl+C 触发)

  • TERMSIGTERM,默认的 kill 命令发送的信号)

  • KILLSIGKILL,强制终止,不可捕获)

  • HUPSIGHUP,终端挂断)

  • EXIT(非标准信号,脚本退出时触发)

trap 的基本语法

bash 复制代码
trap '执行的命令' 信号列表

单信号:trap 'echo 收到信号' INT

多信号:trap 'echo 退出' INT TERM EXIT

忽略信号:trap '' INT(空字符串表示忽略)

恢复默认:trap - INT(取消自定义处理)

示例一:优雅处理终端

bash 复制代码
#!/bin/bash
trap 'echo "捕获中断,正在退出..."; exit' INT

while true; do
    echo "运行中..."
    sleep 1
done

示例二:脚本退出时清理资源

bash 复制代码
#!/bin/bash
tempfile="/tmp/mytemp.$$"  # 临时文件
trap 'rm -f "$tempfile"; echo "清理完成"' EXIT

touch "$tempfile"
echo "脚本运行中..."
sleep 10

示例三:捕捉后执行函数

bash 复制代码
#!/bin/bash
cleanup() {
    echo "正在清理..."
    rm -f *.tmp
    exit
}
trap cleanup INT TERM

# 模拟任务
touch {1..3}.tmp
sleep 60

shell脚本实战

示例一:批量重命名文件

在一个目录中,所有图片文件命名格式杂乱无章,比如:IMG001.jpgimg_02.JPGphoto3.jpeg,你希望将所有图片统一重命名为 image_001.jpgimage_002.jpg ... 的格式,便于管理。

bash 复制代码
#!/bin/bash

count=1
# 遍历所有支持的图片格式(包括大小写变体)
for file in *.jpg *.jpeg *.JPG *.JPEG; do
    # 跳过无效匹配(当没有文件时会返回原始模式如"*.jpg")
    [ -f "$file" ] || continue

    # 生成新文件名(强制使用.jpg扩展名):
    #   %03d 表示3位数字(001, 002...)
    new_name=$(printf "image_%03d.jpg" "$count")
    
    # 执行重命名(原扩展名无论是什么,都改为.jpg)
    mv -- "$file" "$new_name"
    
    # 计数器增加
    ((count++))
done

示例二:Git 仓库自动部署系统

企业服务端项目部署,要求:

  1. 监听某个 Git 仓库的更新;

  2. 自动拉取 main 分支最新代码;

  3. 构建项目并备份旧版;

bash 复制代码
#!/bin/bash

# 基础配置
REPO_DIR="/var/www/myapp"   # 监控的Git仓库目录
SERVICE_NAME="myapp"        # 服务名称(需与systemd单元名一致)
LOG_FILE="/var/log/git_deploy.log"  # 日志文件路径

# 进入Git仓库目录
cd "$REPO_DIR" || {
    echo "$(date) - ERROR: 无法进入项目目录" >> "$LOG_FILE"
    exit 1
}

# 获取当前和远程的main分支差异
git fetch origin main >> "$LOG_FILE" 2>&1
LOCAL_COMMIT=$(git rev-parse main)
REMOTE_COMMIT=$(git rev-parse origin/main)

# 如果没有更新则退出
[ "$LOCAL_COMMIT" = "$REMOTE_COMMIT" ] && exit 0

# 记录部署开始
echo "$(date) - 检测到新提交: $REMOTE_COMMIT" >> "$LOG_FILE"

# 备份当前可执行文件(假设编译产物在build目录)
[ -f build/myapp ] && cp build/myapp build/myapp.bak

# 拉取最新代码
if ! git reset --hard origin/main >> "$LOG_FILE" 2>&1; then
    echo "$(date) - ERROR: Git拉取失败" >> "$LOG_FILE"
    exit 2
fi

# 构建项目(假设使用CMake)
{
    mkdir -p build
    cd build || exit 1
    cmake .. && make -j$(nproc)
} >> "$LOG_FILE" 2>&1

if [ $? -ne 0 ]; then
    echo "$(date) - ERROR: 构建失败" >> "$LOG_FILE"
    # 尝试恢复备份
    [ -f build/myapp.bak ] && mv build/myapp.bak build/myapp
    exit 3
fi

示例三:部署前自动检查依赖并构建 CMake + Make 项目

你维护一个使用 CMakemake 的 C++ 项目,部署前需要执行以下步骤:

  • 检查是否安装了 g++cmake

  • build/ 目录不存在,则创建并进入;

  • 执行 cmake ..make 构建;

  • 所有输出统一写入日志文件 deploy.log

bash 复制代码
#!/bin/bash

# 定义日志文件路径(当前目录下的deploy.log)
LOG_FILE="./deploy.log"
# 定义构建目录路径(当前目录下的build文件夹)
BUILD_DIR="./build"

# 在日志文件中记录部署开始时间(tee命令同时输出到终端和日志文件)
echo "----- DEPLOY STARTED: $(date) -----" >> "$LOG_FILE"

# 1. 检查必要的编译工具是否安装

# 检查cmake是否可用(command -v会返回命令路径,&>/dev/null丢弃所有输出)
if ! command -v cmake &>/dev/null; then
    # 如果cmake不存在,输出错误信息(tee -a表示追加到日志文件)
    echo "[ERROR] cmake is not installed!" | tee -a "$LOG_FILE"
    exit 1  # 退出状态码1表示常规错误
fi

# 检查g++是否可用
if ! command -v g++ &>/dev/null; then
    echo "[ERROR] g++ is not installed!" | tee -a "$LOG_FILE"
    exit 1
fi

# 2. 准备构建目录

# 检查构建目录是否存在(-d测试目录是否存在)
if [ ! -d "$BUILD_DIR" ]; then
    echo "[INFO] Creating build directory..." | tee -a "$LOG_FILE"
    mkdir "$BUILD_DIR"  # 创建构建目录
fi

# 进入构建目录,如果失败则报错(|| 表示前一个命令失败时执行后续命令)
# { } 命令组用于组合多个命令,必须用分号结尾
cd "$BUILD_DIR" || { 
    echo "[ERROR] Cannot enter build directory!" | tee -a "$LOG_FILE"
    exit 1 
}

# 3. 运行CMake配置项目

echo "[INFO] Running cmake..." | tee -a "$LOG_FILE"
# 执行cmake并将标准输出和错误输出都重定向到日志文件(2>&1)
cmake .. >> "$LOG_FILE" 2>&1

# 检查上一条命令的退出状态($?)
if [ $? -ne 0 ]; then  # -ne表示不等于0(非成功状态)
    echo "[ERROR] cmake failed!" | tee -a "$LOG_FILE"
    exit 2  # 使用不同的退出码便于识别错误阶段
fi

# 4. 使用Make编译项目

echo "[INFO] Building project with make..." | tee -a "$LOG_FILE"
# -j$(nproc) 使用所有CPU核心并行编译,nproc获取CPU数量
make -j$(nproc) >> "$LOG_FILE" 2>&1

if [ $? -eq 0 ]; then  # -eq表示等于0(成功状态)
    echo "[SUCCESS] Build completed successfully!" | tee -a "$LOG_FILE"
else
    echo "[ERROR] Build failed!" | tee -a "$LOG_FILE"
    exit 3  # 不同的退出码表示不同阶段的错误
fi

# 在日志文件中记录部署结束时间
echo "----- DEPLOY ENDED: $(date) -----" >> "$LOG_FILE"
相关推荐
HLC++14 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室15 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy18 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕18 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫19 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生20 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1121 小时前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器