文章目录
前言
希望通过本文的学习,你能够掌握Shell脚本的基本知识和实用技巧,将Shell脚本融入到你的日常工作中,成为你提高工作效率、实现自动化任务的得力助手。让我们一起踏上这段探索Shell脚本的旅程,开启一段充满无限可能的自动化之旅吧!
一.Shell脚本定义
Shell以文本方式提供了与操作系统内核进行交互的方式。用户可以在shell脚本文件中写入一系列系统命令,然后执行shell脚本就可以自动执行脚本文件中的命令从而节省大量时间。
Shell可以帮助用户高效地执行一系列命令和任务。通过学习和掌握Shell脚本的编写技巧,用户可以大大提高工作效率和自动化水平。
shell脚本书写规范
- 脚本命名:
- 脚本名应以
.sh
结尾,名称应尽量具有描述性,如ClearLog.sh
、SerRestart.sh
等。
- 脚本名应以
- 编码格式:
- 尽量使用UTF-8编码,注释及输出尽量使用英文。
- 执行权限:
- 一般应给予脚本执行权限,但一些仅用于变量定义的配置文件则无需加执行权限。
- 解释器声明:
- 脚本首行应使用
#!/bin/bash
(或#!/usr/bin/env bash
)来声明解释器,没有空格,不带任何选项。
- 脚本首行应使用
下面是一个简单的脚本案例:
#创建一个以.sh结尾的shell脚本文件
[root@localhost sh]# vim test_1.sh
首行添加解释器声明,在此之后可以添加需要执行的命令
#!/bin/bash
【添加需要执行的命令】
#添加完脚本内容后给脚本添加执行权限
[root@localhost sh]# chmod u+x test_1.sh
shell脚本执行方式
直接运行脚本
- 方法描述:
- 在终端中输入脚本文件的路径并按下回车键即可执行脚本。
- 例如,如果脚本文件名为
script.sh
,并且位于当前目录下,则可以在终端中输入./script.sh
来执行该脚本。
- 特点与要求:
- 需要确保脚本文件具有可执行权限。
- 可以使用
chmod
命令来设置脚本的权限,如chmod +x script.sh
。 - 脚本文件的第一行通常包含Shebang(如
#!/bin/bash
),用于指定解释器。
#利用路径的方式执行一下脚本
[root@localhost sh]# ./test_1.sh
#若无执行权限会出现如下报错:
-bash: ./test_1.sh: 权限不够
#提示“权限不够”代表当前用户无执行权限,需要添加权限
[root@localhost sh]# chmod u+x test_1.sh
使用shell解释器执行
- 方法描述:
- 通过显式地指定Shell解释器来执行脚本。
- 例如,使用
bash script.sh
或sh script.sh
来执行脚本。
- 特点与要求:
- 不需要脚本文件具有可执行权限。
- 可以选择不同的Shell解释器,如bash、sh、zsh等。
- 适用于不同Shell环境,提高了脚本的兼容性。
[root@localhost sh]# bash test_1.sh
[root@localhost sh]# sh test_1.sh
shell脚本退出状态码
在UNIX或者Linux中,每个命令都会返回一个退出状态码。退出状态码是一个整数,其有效范围为0~255。通常情况下,成功的命令返回0,而不成功的命令返回非0值。
Shell脚本中的函数和脚本本身也会返回退出状态码。在脚本或者是脚本函数中执行的最后的命令会决定退出状态码。另外,用户也可以在脚本中使用exit语句将指定的退出状态码传递给Shell。
通常退出状态码记录在$?中,且$?只记录刚刚执行过的命令的返回值。
二.Shell变量
顾名思义,变量就是程序设计语言中的一个可以变化的量,从本质上讲,变量就是在程序中保存用户数据的一块内存空间,而变量名就是这块内存空间的地址。
变量的定义
定义规范
定义变量有以下几点需要注意:
- 变量名可以包含下划线,数字,大小写字母,但不能以数字开头。
- "="两边不要有空格。
- “值”如果含有空格,要使用单引号' '或双引号“ ”引起来
- 定义变量时,变量名前是不需要加$的,引用变量时需要在变量名前加$
常见定义错误如下:
变量名不能以数字开头,所以会报错
[root@localhost ssh]# 1aa=123
bash: 1aa=123: command not found...
变量名只能以数字,字母,下划线组合,所以报错
[root@localhost ssh]# aa-1=123
bash: aa-1=123: command not found...
等号右边有空格,所以报错
[root@localhost ssh]# aa=1 2
bash: 2: command not found...
正常的变量定义如下:
[root@localhost ssh]# a=123
[root@localhost ssh]# echo $a
123
定义方式
基本定义式
格式如下:
变量=值
[root@localhost ssh]# a=123
[root@localhost ssh]# echo $a
123
命令结果定义式
格式如下:
变量=$(命令)
例如,定义一个名称是IP的变量,对应的值是ens160的IP,命令如下:
[root@localhost ~]# IP=$(ifconfig ens160 | awk '/inet /{print $2}')
[root@localhost ~]# echo $IP
192.168.23.143
交互式定义
使用read
命令从用户输入中获取变量的值。这种方法通常用于需要用户输入的场景。
格式如下:
read -p '提示信息' 变量
当执行read命令时,系统会提示用户输入一些内容,所输入的值会赋值给read后面的变量,这里我们输入的是123,所以打印a变量时看到的是123。
[root@localhost ~]# read -p "请输入数字:" a
请输入数字:123
[root@localhost ~]# echo $a
123
变量的运算
运算表达式
在Shell脚本中,运算表达式用于执行数值计算或逻辑判断,是脚本实现自动化处理的基础。下面是一些常见的运算表达式:
$[ ],(()),let | 用于整数运算 |
declare | 定义变量值和属性,-i参数可以用于定义整形变量,做运算 |
bc | 支持浮点运算 |
如果不用这样的表达式,看如下代码:
[root@localhost ~]# echo 2+3
2+3
这里并不会计算2+3,而是直接把这3个字符打印出来了,正确的做法如下:
[root@localhost ~]# echo $[2+3]
5
[root@localhost ~]# let a=2+3
[root@localhost ~]# echo $a
5
想要实现定义aa变量为整数类型,然后再做数学运算,命令如下:
[root@localhost ~]# declare -i aa
[root@localhost ~]# aa=3/2
[root@localhost ~]# echo $aa
1
首先declare -i aa 把aa定义为一个整数,所以3/2等于1.5取整数1,然后赋值给aa,所以aa的值为1。以上表达式不能求得小数,如果想要得到小数要使用bc命令,用法如下:
echo "scale=N ; 算法" | bc
这里的N是一个数字,表示小数点后面保留几位。
练习1:计算2/3,小数点后面保留3位
[root@localhost ~]# echo "scale=3 ; 2/3" | bc
.666
这里得到的结果是0.666,整数部分0没有显示。
算数运算:
+,-,*,/,**,% | 表示加法;减法;乘法;除法;次方;取余 |
赋值运算
=,+=,-=,*=,/=,%= | 直接赋值,(加/减/乘/除/取余)等于 |
逻辑运算
判断 1 && 判断 2
只有两个判断都为真(返回值为0),整体才为真,只要有一个为假,整体就为假。
[root@localhost ~]# [[ 1 -le 2 ]] && [[ 2 -ge 3 ]]
[root@localhost ~]# echo $?
1
这里有两个判断,第一个是1小于等于2,这个判断成立,第二个判断是2大于等于3,这个判断不成立,使用&&作为连接符号,需要两边判断都成立,整体才成立,所以判断为假,返回值为0。
变量的分类
全局变量
- 定义:全局变量是在函数外部定义的变量,全局变量在整个程序或脚本中都是可见的,即它们可以在程序的任何地方被访问和修改。
- 作用域:全局变量的作用域是整个程序,无论在哪个函数内部都可以访问。
- 生命周期:全局变量的生命周期贯穿整个程序的执行过程,从程序开始执行到程序结束。
- 使用方式:在程序中可以直接使用全局变量,无需额外的声明或初始化(尽管初始化是个好习惯)。
示例(Shell脚本):
#!/bin/bash
# 定义全局变量
GLOBAL_VAR="I am a global variable"
function print_global_var() {
echo $GLOBAL_VAR # 访问全局变量
}
#调用函数
print_global_var # 输出:I am a global variable
环境变量
- 定义:环境变量是操作系统级别的变量,用于存储配置信息。它们可以被操作系统、Shell以及运行在该Shell下的程序访问。
- 作用域:环境变量的作用域比全局变量更广。它们不仅在当前Shell会话中可用,还可以被该Shell会话启动的子进程继承。
- 特点:环境变量通常使用
export
命令来设置,以便在当前Shell会话及其子进程中可用自定义环境变量 - 定义方式:
-
export var
或export var="value"
-
declare -x var="value"
,declare +x var
可取消环境变量
-
-
bash内置环境变量
shell程序在运行时,会接受一组变量来确定登录用户名、命令路径、终端类型、登录目录等等,这些变量就是环境变量。shell内置的环境变量是所有的shell程序都可以使用的变量,环境变量会影响所有的脚本的执行结果。
局部变量
- 定义:局部变量是在函数内部定义的变量,它们只在定义它们的函数内部可见。局部变量通常用于存储函数执行过程中需要使用的临时数据。
- 作用域:局部变量的作用域仅限于定义它们的函数内部。函数执行完毕,局部变量会被销毁。
- 生命周期:局部变量的生命周期是从函数开始执行到函数结束。
- 使用方式:在函数内部使用
local
关键字声明的变量是局部变量。
示例(Shell脚本)
#!/bin/bash
function create_local_var() {
# 定义局部变量
LOCAL_VAR="I am a local variable"
echo $LOCAL_VAR # 访问局部变量
}
create_local_var # 输出:I am a local variable
# 尝试访问局部变量(会导致错误,因为局部变量在函数外部不可见)
# echo $LOCAL_VAR # 这行会报错:LOCAL_VAR: command not found
位置变量
很多情况下,shell脚本需要接受用户的输入,根据用户的输入来执行不同的操作。从命令行传递给shell脚本的参数又称为叫做位置参数,shell脚本会根据参数的位置使用不同的位置参数变量读取他们的值
- 定义:位置变量是用来存储Shell脚本的命令行参数的。
- 作用域:仅在Shell脚本中使用,用于接收传递给脚本的参数。
- 表示方法:
$1
、$2
、$3
等,分别表示传递给脚本的第一个、第二个、第三个参数。 - 使用:在Shell脚本中,位置变量用于访问传递给脚本的参数值。
练习1:通过命令行传递参数tom给脚本,使脚本输出tom
#创建一个案例脚本并写入以下内容
[系统未激活][root@localhost sh]# vim test1.sh
name=$1
name=$2
echo "命令行中传递的第一个位置变量是:$1"
echo "命令行中传递的第一个位置变量是:$2"
#执行脚本试试
[系统未激活][root@localhost sh]# sh test1.sh tom Tom
命令行中传递的第一个位置变量是:tom
命令行中传递的第一个位置变量是:Tom
预定义变量
预定义变量是由Shell脚本解释器或操作系统预先定义好的变量,它们通常用于提供脚本执行过程中的一些特殊信息。以下是一些常见的预定义变量:
$# | 命令行的参数的个数 |
$0 | 当前脚本的名称 |
$* | 以“参数1” “参数2” “参数3” 的形式返回所有参数的值 |
$? | 前一个命令、函数或者脚本的返回状态码,$?的返回值 |
案例(shell脚本)
#编辑一个案例脚本,内容如下:
[root@localhost sh]# vim test1.sh
#!/bin/bash
echo "这是我的第一个脚本,脚本名称是$0"
echo "第一个参数是:$1"
echo "第一个参数是:$2"
echo "此脚本一共有$#个参数,他们分别是:$*"
#运行脚本后结果如下:
[root@localhost sh]# sh test1.sh tom bob
这是我的第一个脚本,脚本名称是test1.sh
第一个参数是:tom
第一个参数是:bob
此脚本一共有2个参数,他们分别是:tom bob
运行这个脚本时,共指定了2个参数:tom,bob。他们赋值给了$1,$2,这里的$#被自动赋值为2,因为总共有2个参数,所有的参数都被赋值给$*。
三.Shell条件测试
为了能够正确处理Shell程序运行过程中遇到的各种情况,Linux Shell提供了一组测试运算符。通过这些运算符,Shell程序能够判断某种或者几个条件是否成立。
测试表达式
在shell程序中,用户可以使用测试语句来测试指定的条件表达式的条件的真或假。当指定的条件为真时,整个条件测试的返回值为0;反之,如果指定的条件为假,则条件测试语句的返回值为非0值。
符号 | 语法 | 说明 |
test | test <测试表达式> | test命令和<测试表达式>之间至少有一个空格 |
[ ] | [ <测试表达式> ] | 基础比较运算需要使用的符号和test作用一致 |
[ [ ] ] | [[ <测试表达式> ]] | 该表达式与[ ]不同能够识别通配符和正则表达式 |
(()) | ((<测试表达式>)) | 用于 if 语句中,测试对象只能是整数 |
需要注意的是,在比较时,中括号和后续提及的比较符两边都要留有空格。
数字比较
练习1:判断1等于2,命令如下:
[root@localhost ~]# [1 -eq 2 ]
bash: [1: command not found...
[root@localhost ~]# echo $?
127
1是不能等于2的,所以判断不成立,返回值是非零值。
练习2:判断1不等于2,命令如下:
[root@localhost ~]# [ 1 -ne 2 ]
[root@localhost ~]# echo $?
0
字符串比较
字符串比较一般是比较两个字符串是否相同,用到较多的比较符有以下两种:
做完比较之后,通过返回值来判断比较是否成立。
练习1:定义一个变量aa=tom,然后做判断,命令如下:
[root@localhost ~]# [ $aa == to? ]
[root@localhost ~]# echo $?
1
这里定义aa=tom,按照前面讲过的通配符,to?匹配的应该是前两个字符为to,第三个可以是任意字符,所以tom应该会被to?匹配到,为什么返回值为非零值呢?
原因在于在这一对中括号[ ]中是不能识别通配符的,aa的值是t,o,m三个字符,而等号后面是t,o,?这三个字符。并没有把问好当成通配符,所以判断不成立。
如果想要识别通配符,那么要使用[ [ ] ]
[root@localhost ~]# aa=tom
[root@localhost ~]# [[ $aa == to? ]]
[root@localhost ~]# echo $?
0
练习2:判断字符串的长度
[root@localhost ~]# [ -n "" ]
[root@localhost ~]# echo $?
1
[ -n "" ]
检查空字符串是否非空,因为字符串是空的,所以返回假(退出状态1)。echo $?
打印上一个命令的退出状态,即1
。
文件属性判断
参数 | 说明 |
-r | 文件具备可读权限则为真,返回值为0 |
-w | 文件具备可写权限则为真 |
-x | 文件具备可执行权限 |
-d | 文件是一个目录 |
-l | 一个软链接 |
-f | 文件是普通文件,且要存在 |
-e | 不管什么类型的文件/目录,只要存在就算判断成立 |
练习1:判断/etc/hosts具备r权限,命令如下:
#查看文件权限
[root@localhost ~]# ls -l /etc/hosts
-rw-r--r--. 1 root root 158 6月 23 2020 /etc/hosts
#判断文件是否存在可读权限
[root@localhost ~]# [ -r /etc/hosts ]
[root@localhost ~]# echo $?
0
通过第一条命令看到/etc/hosts是具备r权限的,判断/etc/hosts具备r权限是成立的,返回值0.
练习2:判断/etc/hosts具备X权限,命令如下:
[root@localhost ~]# [ -x /etc/hosts ]
[root@localhost ~]# echo $?
1
#判断没有X权限
[root@localhost ~]# [ ! -x /etc/hosts ]
[root@localhost ~]# echo $?
0
这里判断/etc/hosts具备x权限,但是/etc/hosts无论u,g,还是o都不具备x权限,所以判断成立,返回值为0。
四.Shell循环判断语句
if 判断语句
if 单分支语句
基本语法:
第一种语法:
if <条件表达式>
then
命令
fi
第二种语法:
if <条件表达式>;then
命令
fi
先判断if后面的判断是不是成立,则执行命令。
read -p "请输入数字:" num
if [ $num -eq 2 ]
then
echo "输入的数字是:$num"
fi
if [ $num -eq 1 ]
then
echo "输入的数字是:$num"
fi
if 多分支语句
基本语法:
语法:
if 条件表达式1
then
命令序列1
elif 条件表达式2
then
命令序列2
elif 条件表达式3
then
命令序列3
else
命令序列n
fi
- 先判断 if 后面的判断是不是成立
- 若成立则执行命令1,然后跳出 if 语句,
- 如果不成立,则判断elif后面的判断是否成立,若成立则执行命令2
- 若 elif 后面的判断不成立,则执行else 后的命令
练习1:写一个脚本/opt/scl.sh,要求只有root用户才能执行此脚本,其它用户不能执行
#!/bin/bash
id=$(whoami)
if [ $UID -eq 0 ];then
echo "当前所在用户$id"
exit 1
else
echo "只有root用户才能执行此脚本"
su root
fi
root 用户的 uid 是0,其它用户的uid不为0。第一个判断,如果uid等于0,则打印信息“当前所在用户” ,然后退出脚本。如果第一个判断不成立会执行else后面的语句并执行su命令切换到root用户
循环语句
for 循环语句
for循环是最简单,也是最常用的循环语句,for循环通常用于遍历整个对象或者数字列表。
列表遍历循环
基本语法:
for 变量 in 列表
do
循环体(命令)
done
上面的语法中,列表可以是一系列数字或字符串,for 循环会将列表中的内容赋值给变量,每次赋值都会执行do和done之间的循环体,即循环结构中重复执行的语句。
#!/bin/bash
for item in apple banana cherry; do
echo "Fruit: $item"
done
数值范围循环
#!/bin/bash
for ((i=1; i<=5; i++)); do
echo "Number: $i"
done
字符串遍历循环
#!/bin/bash
str="Hello World Shell Scripting"
for word in $str; do
echo "Word: $word"
done
文件/目录遍历循环
#!/bin/bash
# 遍历当前目录下的所有文件
for file in *; do
if [ -f "$file" ]; then
echo "File: $file"
fi
done
命令输出遍历循环
#!/bin/bash
# 遍历ls命令的输出
for file in $(ls); do
echo "Listed file: $file"
done
while 循环语句
基本语法
while 条件判断表达式
do
循环体(命令)
done
如果while后面的判断成立,则执行do和done之间的命令,在最后一个命令执行完成后,会回头再次判断一下while后面的判断是否成立,如果不成立,则会跳出循环执行done后面的命令,如果成立,则继续执行do和done之间的命令。
先看一个简单的例子,脚本如下:
#!/bin/bash
declare -i n=1
while [ $n -le 4 ];do
echo $n
let n=$n+1
done
这里先通过 declare -i n=1定义了一个整数变量n,初始值为1,然后进入while进行循环,先判断$n的值是不是小于等于4,如果成立,则执行do和done之间的命令。
一开始$n的值为1,[ $n -le 4 ]这个判断成立,则进入do和done之间执行命令。首先打印$n的值,然后在此基础上给n加上1,所以n的值变为了2,这样do和done之间的命令就执行完成了,然后中再次到while后面进行判断,此时$n的值为2,依然满足小于等于4,再次执行do和done之间的命令。
如此反复,当$n的值最终能增加到4时打印,然后加1,此时n的值变为了5,当$n的值变为5之后,while后面的判断就不再成立了,此时会跳出while循环。
循环读取文件内容
while read aa;do
命令
done < file
这里的read后面的变量aa是可以随意指定的,整体的意思是首先读取file的第一行内容赋值给aa,执行do和done之间的命令,然后file的第二行内容赋值给aa,执行do和done之间的命令,直到读取到file的最后一行。
#!/bin/bash
# 指定要读取的文件名
filename="yourfile.txt"
# 检查文件是否存在
if [ ! -f "$filename" ]; then
echo "File '$filename' not found!"
exit 1
fi
# 使用 while 循环和 read 命令读取文件内容
while read file
do
# 打印读取到的行
echo "$file"
done < "$filename"
上面的while循环中,首先读取filename变量也就是yourfile文件内容的第一行赋值给file,然后执行echo "$file"打印变量file的内容,然后读取第二行内容赋值给file......