Bootstrap

Linux---Shell脚本

文章目录

文章目录

前言

一.Shell脚本定义

shell脚本书写规范

 shell脚本执行方式

shell脚本退出状态码 

 二.Shell变量

变量的定义

定义规范

定义方式

 变量的运算

运算表达式

算数运算:

赋值运算 

逻辑运算 

变量的分类

全局变量

环境变量 

局部变量

位置变量

预定义变量

三.Shell条件测试

测试表达式

数字比较

字符串比较

文件属性判断

四.Shell循环判断语句

if 判断语句

if 单分支语句

​编辑

if 多分支语句

循环语句

for 循环语句

列表遍历循环 

数值范围循环

 字符串遍历循环

​编辑 文件/目录遍历循环

 命令输出遍历循环

while 循环语句 

    循环读取文件内容

未完待续......


前言

希望通过本文的学习,你能够掌握Shell脚本的基本知识和实用技巧,将Shell脚本融入到你的日常工作中,成为你提高工作效率、实现自动化任务的得力助手。让我们一起踏上这段探索Shell脚本的旅程,开启一段充满无限可能的自动化之旅吧!


一.Shell脚本定义

Shell以文本方式提供了与操作系统内核进行交互的方式。用户可以在shell脚本文件中写入一系列系统命令,然后执行shell脚本就可以自动执行脚本文件中的命令从而节省大量时间。

Shell可以帮助用户高效地执行一系列命令和任务。通过学习和掌握Shell脚本的编写技巧,用户可以大大提高工作效率和自动化水平。

shell脚本书写规范

  1. 脚本命名
    • 脚本名应以.sh结尾,名称应尽量具有描述性,如ClearLog.shSerRestart.sh等。
  2. 编码格式
    • 尽量使用UTF-8编码,注释及输出尽量使用英文。
  3. 执行权限
    • 一般应给予脚本执行权限,但一些仅用于变量定义的配置文件则无需加执行权限。
  4. 解释器声明
    • 脚本首行应使用#!/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脚本执行方式

直接运行脚本

  1. 方法描述
    • 在终端中输入脚本文件的路径并按下回车键即可执行脚本。
    • 例如,如果脚本文件名为script.sh,并且位于当前目录下,则可以在终端中输入./script.sh来执行该脚本。
  2. 特点与要求
    • 需要确保脚本文件具有可执行权限
    • 可以使用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解释器执行

  1. 方法描述
    • 通过显式地指定Shell解释器来执行脚本。
    • 例如,使用bash script.shsh script.sh来执行脚本。
  2. 特点与要求
    • 不需要脚本文件具有可执行权限
    • 可以选择不同的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 varexport 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值。

符号语法说明
testtest  <测试表达式>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......


未完待续.......

;