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="[email protected]"

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

[[ $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"
相关推荐
Johny_Zhao5 小时前
Ubuntu堡垒机搭建与设备管理指南
linux·网络·人工智能·信息安全·云计算·yum源·系统运维·teleport
程序员-King.5 小时前
【网络服务器】——回声服务器(echo)
linux·运维·服务器
华纳云IDC服务商6 小时前
华纳云:centos如何实现JSP页面的动态加载
java·linux·centos
云中飞鸿7 小时前
加载ko驱动模块:显示Arm版本问题解决!
linux·arm开发
二进制coder7 小时前
ARM32静态交叉编译并使用pidstat教程
linux
郑梓妍7 小时前
(持续更新)Ubuntu搭建LNMP(Linux + Nginx + MySQL + PHP)环境
linux·ubuntu·php
xflm8 小时前
qemu(3) -- qemu-arm使用
linux
xflm8 小时前
qemu(4) -- qemu-system-arm使用
linux
Tesseract_95279 小时前
【Linux】VSCode用法
linux·c语言·vscode
A_Tai23333339 小时前
Linux-04-搜索查找类命令
linux·运维·服务器