Shell 脚本入门指南
- 引言
-
- [1.1 什么是 Shell 脚本?](#1.1 什么是 Shell 脚本?)
- [1.2 为什么学习 Shell 脚本?](#1.2 为什么学习 Shell 脚本?)
- 准备工作
-
- [2.1 选择和安装 Shell(Bash)](#2.1 选择和安装 Shell(Bash))
- [2.2 设置脚本编辑环境(文本编辑器)](#2.2 设置脚本编辑环境(文本编辑器))
- [Hello World!](#Hello World!)
- 基本语法
-
- [4.1 变量](#4.1 变量)
- [4.2 条件判断](#4.2 条件判断)
- [4.3 循环](#4.3 循环)
- 文件和目录操作
- 函数
- 简单实用脚本示例
-
- [7.1 自动化备份脚本](#7.1 自动化备份脚本)
- [7.2 添加定时任务](#7.2 添加定时任务)
引言
1.1 什么是 Shell 脚本?
Shell 脚本是一种用于自动化任务的脚本语言,通常在 Unix/Linux 系统中使用。通过结合条件判断和循环来实现命令的自动运行以完成一些相对固定的操作。
1.2 为什么学习 Shell 脚本?
Shell 脚本可以简化系统管理任务、自动化重复性工作、提高工作效率。并且维护简单,实际应用中非常有用。另外我们还可以在脚本文件中设置环境变量,方便管理每个项目不同的环境变量。
准备工作
2.1 选择和安装 Shell(Bash)
大多数 Linux 发行版默认安装了 Bash,可以通过 bash --version 检查。本文演示的系统环境为Linux。
2.2 设置脚本编辑环境(文本编辑器)
推荐使用简单的文本编辑器如 nano、vim 或者图形化编辑器如 VS Code。本文使用推荐使用功能强大的vim编辑器。
Hello World!
现在我们先从一个简单的hello world文件开始本文的学习。该样例通过读取用户输入的循环次数来打印字符串"hello world"。
bash
#!/bin/bash
# 提示用户输入循环次数
echo "请输入循环次数:"
read count
# 检查输入是否为有效的数字
if [ "$count" lt 0 ]; then
echo "请输入一个有效的数字。"
exit 1
fi
# 使用 for 循环打印 "hello world"
for ((i = 1; i <= count; i++))
do
echo "hello world"
done
下面是对代码的解释:
#!/bin/bash 是一个称为 "shebang" 的特殊注释行,它告诉操作系统使用哪个解释器来执行脚本。Linux有很多的解释器,不同的解释器语法略有不用。使用 echo 命令提示用户输入,然后使用read命令读取用户输入并存储在变量 count 中。
使用 if 语句和 -lt 比较操作符检查 count 是否小于 0。如果是,则提示用户输入一个非负数并退出脚本。
使用 for 循环从 1 到 count,每次循环打印 "hello world"。
运行sh test.sh或./test.sh效果如下:
如果发现运行失败则有可能是脚本文件的权限不够,解决方案如下:
使用chmod a+x test.sh命令手动给文件添加权限。
基本语法
通过上面一个简单的hello world样例我们已经初步的认识了bash shell脚本文件。接下来就开始系统的学习shell的一些基本语法。
4.1 变量
变量是用来存储数据的值的名称,和其他高级编程语言的变量含义一样。同时也拥有和其他高级编程语言对变量命名上的限制:
1:字母和数字:变量名只能包含字母(a-z, A-Z)、数字(0-9)和下划线(_)。
2:不能以数字开头:变量名不能以数字开头。例如,1variable 是无效的,而 variable1 是有效的。
3:区分大小写:变量名是区分大小写的。例如,VAR 和 var 是两个不同的变量。
4:避免使用特殊字符:避免在变量名中使用特殊字符(如 !, @, #, $, %, ^, &, (, ), -, +, =, :, ;, ", ', ,?, / 等)。
变量命名惯例:
使用大写字母:环境变量和常量通常使用大写字母。例如,PATH, HOME, USER。
使用小写字母:普通变量通常使用小写字母。例如,filename, count, index。
使用下划线分隔单词:为了提高可读性,可以使用下划线分隔单词。例如,file_name, user_count。
无效的变量命名:
1variable 不能以数字开头
file-name 不能包含连字符
user@name 不能包含特殊字符
有效的变量命名:
filename
file_name
userCount
USER_COUNT
_var123
在 Shell 中,变量赋值时不需要使用等号两边的空格:
variable_name=value 正确示例
variable_name = value 错误示例
定义好变量之后,我们需要后续的代码中使用时也与高级编程语言有所不同。使用变量时,需要在变量名前加上美元符号$
echo $name
echo $age
如果变量名紧跟在其他字符后面,可以使用花括号 {} 来明确变量名的边界:
echo "Hello, ${name}"
另外还有一些特殊的变量,比如$1表示传递给脚本或者函数的第一个参数。
我们对上面的hello world代码做一些改变,以遍在运行脚本时就获取循环的次数。
test.sh
bash
#!/bin/bash
# 提示用户输入循环次数
# echo "请输入循环次数:"
# read count
count=$1
# 检查输入是否为有效的数字
if [ "$count" lt 0 ]; then
echo "请输入一个有效的数字。"
exit 1
fi
# 使用 for 循环打印 "hello world"
for ((i = 1; i <= count; i++))
do
echo "hello world"
done
运行脚本:sh test.sh 3
上述代码获取循环次数的方式便不再是通过输入读取,而是传参的方式获取,注意两种方式的运行情况。
4.2 条件判断
在上一节中我们的脚本文件还运用了if判断语句来保证循环的次数不是负数。本节就来详细介绍条件判断的语法。
if else语法格式:
bash
if condition
then
...
fi
# 也可采用不同风格的缩进风格
if condition; then
...
fi
# 或是写成一行
if condition; then ...; fi
一定要注意不同缩进风格的空格控制,少了空格或多了空格都会导致脚本运行报错,因为一定要格外注意。
在condition中通过涉及到对整数以及字符串的判断,如下所示:
运算符 | 作用 |
---|---|
-eq | 是否等于(=) |
-ne | 是否不等于(!=) |
-gt | 是否大于 (>) |
-lt | 是否小于 (<) |
-le | 是否小于或等于(<=) |
-ge | 是否大于或等于(>=) |
运算符 | 作用 |
---|---|
= | 比较字符串内容是否相同 |
!= | 比较字符串内容是否不相同 |
-z | 比较字符串内容是否为空 |
下面来看一下使用示例:
condition.sh
bash
#!/bin/bash
a=10
b=20
if [ $a gt $b ]; then
echo "${a} 大于 ${b}"
else
echo "${a} 小于等于 ${b}"
fi
运行sh condition.sh
用过上述也向大家介绍了if else的结构语法
bash
if condition then;
...
else
...
fi
另外还有if else if结构
bash
if condition1
then
...
elif condition2
then
...
else
...
fi
大家自行验证。
在上述条件判断表达式中我们都是使用的[ ... ](注意空格),但是还可以使用(( ... ))这种双括号的形式来替换[ ... ],并且这样做的好处是可以直接使用我们常见的<,>,!=,=等直接对数字进行判断。
bash
#!/bin/bash
a=10
b=20
if (( $a > $b )); then
echo "${a} 大于 ${b}"
else
echo "${a} 小于等于 ${b}"
fi
有人可能已经发现了上述我们说的都是整数的比较,那浮点数怎么比较呢。
使用 bc 进行浮点数比较
bc 是一个强大的工具,可以处理浮点数运算。以下是一个示例,演示如何使用 bc 来比较两个浮点数:
bash
#!/bin/bash
echo "请输入第一个浮点数:"
read num1
echo "请输入第二个浮点数:"
read num2
# 使用 bc 进行比较
if [ "$(echo "$num1 > $num2" | bc)" -eq 1 ]; then
echo "$num1 大于 $num2"
elif [ "$(echo "$num1 < $num2" | bc)" -eq 1 ]; then
echo "$num1 小于 $num2"
else
echo "$num1 等于 $num2"
fi
解释:
使用 echo 将比较表达式传递给 bc,然后使用 bc 进行计算。bc 返回 1 表示真,返回 0 表示假。然后再对bc返回的结果进行比较,如果是1,则num1大于num2。
也可以使用 awk 进行浮点数的比较,用法自行搜索
4.3 循环
常见的循环结构包括 for 循环、while 循环和 until 循环。
bash
# for 循环用于遍历一组值或列表。它的基本语法如下:
for variable in list
do
...
done
# 使用示例:
for item in apple banana cherry
do
echo "Fruit: ${item}"
done
bash
# while 循环在给定条件为真时重复执行一组命令。它的基本语法如下:
while condition
do
...
done
# 使用示例;
count=1
while (( count <= 5 ))
do
echo "Count: ${count}"
count=$((count + 1))
done
bash
# 与while正好相反,在循环在给定条件为假时重复执行一组命令
until condition
do
...
done
# 使用示例:
#!/bin/bash
# 使用 until 循环打印数字 1 到 5
count=1
until (( $count > 5 ))
do
echo "Count: ${count}"
count=$((count + 1))
done
循环可以嵌套使用,即在一个循环内部包含另一个循环。
bash
for i in {1..3}
do
for j in {1..3}
do
result=$((i * j))
echo "${i} * ${j} = ${result}"
done
done
在循环中,可以使用 break 和 continue 控制循环的执行。
- break:立即退出整个循环。
- continue:跳过当前迭代,继续下一次迭代
bash
#!/bin/bash
# 使用 break 和 continue 控制循环
for i in {1..5}
do
if [ $i -eq 3 ]; then
continue # 跳过当前迭代
fi
if [ $i -eq 5 ]; then
break # 退出循环
fi
echo "Number: ${i}"
done
还有一种特殊的结构case...case 。
case 语句在 Shell 脚本中用于多分支条件判断,类似于其他编程语言中的 switch 语句。
bash
case variable in
pattern1)
commands1
;;
pattern2)
commands2
;;
pattern3)
commands3
;;
*)
default_commands
;;
esac
# 使用示例:
echo "请输入一个选项 (start/stop/restart/status):"
read option
case $option in
start)
echo "启动服务..."
# 在这里添加启动服务的命令
;;
stop)
echo "停止服务..."
# 在这里添加停止服务的命令
;;
restart)
echo "重启服务..."
# 在这里添加重启服务的命令
;;
status)
echo "查看服务状态..."
# 在这里添加查看服务状态的命令
;;
*)
echo "无效的选项,请输入 start, stop, restart 或 status。"
;;
esac
文件和目录操作
下面给出一些常用的文件和目录的判断操作:
判断文件是否存在
bash
if [ -f "filename" ]; then
echo "文件存在"
else
echo "文件不存在"
fi
判断目录是否存在
bash
if [ -d "directoryname" ]; then
echo "目录存在"
else
echo "目录不存在"
fi
判断文件或目录是否存在
bash
if [ -e "path" ]; then
echo "文件或目录存在"
else
echo "文件或目录不存在"
fi
获取文件的大小
bash
filesize=$(stat -c%s "filename")
echo "文件大小: $filesize 字节"
使用 while 循环逐行读取文件内容
bash
while IFS= read -r line # line为filename文件的每一行内容
do
echo "${line}"
done < "filename"
使用重定向写入文件
echo "一些内容" > filename 覆盖写入
echo "一些内容" >> filename 追加写入
结合常用的Linux命令能让你的脚本文件更加完善健壮,实现更高的自动化工作。
函数
函数的简单定义形式为:
bash
func() {
...
return $((...))
}
下面我们写一个简单的计算两数之和的函数,并通过 $? 获取函数的返回值进行打印。
bash
#!/bin/bash
add(){
echo "输入第一个数字: "
read a
echo "输入第二个数字: "
read b
return $(($a+$b))
}
add # 调用函数
echo "输入的两个数字之和为 $? !"。# $?获取上个命名或函数的返回结果。
请注意与高级编程语言一样,函数在调用之前必须存在对其的定义,可以把调用放在文件的第二行看看会发生什么?。
与高级编程语言不同的是,这里的return其实不能叫做函数的返回结果,更应该叫做函数的返回状态码,返回状态码是有范围限制的(0 - 255之间 ),返回码常用来表示函数的或者命名的执行情况,比如常见的0表示成功,1表示失败等等。所以我们不应该使用return返回计算的结果。
我们可以使用echo来代替return进行返回:
bash
#!/bin/bash
add() {
echo "输入第一个数字: "
read a
echo "输入第二个数字: "
read b
echo $(($a + $b)) # 使用 echo 输出结果
}
add
在Shell中,调用函数也时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...
bash
#!/bin/bash
func(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数: $# 个"
echo "作为一个字符串输出所有参数: $* "
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
这点我们在变量那节的结尾也有提到。
简单实用脚本示例
7.1 自动化备份脚本
bash
#!/bin/bash
# 全局变量
BACKUP_DIR="/path/to/backup"
LOG_FILE="/path/to/logfile.log"
MAX_RETRIES=3
# 函数:记录日志
log_message() {
# local表示局部变量
local message=$1
echo "$(date +'%Y-%m-%d %H:%M:%S') - ${message}" >> ${LOG_FILE} # 以追加的方式将日志记录在日志文件中
}
# 函数:备份数据库
backup_database() {
local db_name=$1
local backup_file="${BACKUP_DIR}/${db_name}_$(date +%Y%m%d).sql"
log_message "开始备份数据库 ${db_name} 到 ${backup_file}..."
# 假设使用 mysqldump 进行备份
mysqldump ${db_name} > ${backup_file}
if [ $? -eq 0 ]; then # 这里的$?拿到的就是mysqldump的执行结果
log_message "备份成功"
else
log_message "备份失败"
fi
}
# 函数:生成报告
generate_report() {
local report_file=$1
log_message "生成报告到 ${report_file}..."
# 假设生成一个简单的报告
echo "报告内容" > ${report_file}
if [ $? -eq 0 ]; then
log_message "报告生成成功"
else
log_message "报告生成失败"
fi
}
# 函数:检查备份目录是否存在
check_backup_dir() {
# !表示逻辑取反
if [ ! -d ${BACKUP_DIR} ]; then
log_message "备份目录 ${BACKUP_DIR} 不存在,创建中..."
mkdir -p ${BACKUP_DIR}
if [ $? -eq 0 ]; then
log_message "备份目录创建成功"
else
log_message "备份目录创建失败"
exit 1
fi
fi
}
# 主程序
main() {
local db_name="example_db"
local report_file="/path/to/report.txt"
check_backup_dir
backup_database ${db_name}
generate_report ${report_file}
}
# 执行主程序
main
7.2 添加定时任务
我们可以为脚本文件添加定时策略,以便在凌晨或者其他时间点不用程序员再自己打开电脑手动运行脚本。
我们使用Linux系统默认启用的crond服务就是用来干这事的:
crond 是 Linux 和 Unix 系统中的一个守护进程,用于定期执行计划任务。它通过读取配置文件(通常是 crontab 文件)来确定何时执行哪些任务。
crontab -l:列出当前用户的 crontab 文件内容。
可以看到目前用户还没有crontab文件。接下我们使用crontab -e:
crontab -e:编辑当前用户的 crontab 文件。
在crontab文件中输入19 17 * * * /data/home/tommwwu/shell-test/func.sh。定时策略,解释如下:
bash
* * * * * command
- - - - -
| | | | |
| | | | +---- 星期几 (0 - 7) (0 和 7 都表示星期天)
| | | +------ 月份 (1 - 12)
| | +-------- 一个月中的第几天 (1 - 31)
| +---------- 小时 (0 - 23)
+------------ 分钟 (0 - 59)
1 * * * * /data/home/tommwwu/shell-test/func.sh的意思就是这个func.sh脚本会在每天下午的17:19分执行一次。
crontab -r:删除当前用户的 crontab 文件。
定时任务执行,日志一般输出在
在大多数 Linux 系统中,cron 的日志记录在 /var/log/cron 或 /var/log/syslog 中。如下:
另外在crontab配置文件中还可以设置环境变量等
SHELL=/bin/bash 指定使用的 shell
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 指定命令的搜索路径。
MAILTO=user@example.com 指定任务执行结果的邮件接收者。
完。