前言:
1.Shell脚本是Linux系统管理和自动化任务的利器。
2.系统梳理Shell脚本核心语法、高级技巧及实战案例,助读者快速提升脚本开发能力。
目录
一、Shell脚本语言的基本结构
1.Shell脚本的用途:
2. Shell脚本基本结构:
3.创建Shell脚本过程编辑
4.脚本注释规范
5.第一个脚本编辑
6.执行脚本
7.脚本错误
二、Shell脚本语言的变量用法详解
1.变量
2.变量类型
3.Shell中变量命名法则
4. 变量定义和引用
5.环境变量
6.只读变量
7.位置变量
8.退出状态码变量
9. 展开命令行
(1)展开命令执行顺序
(2)防止扩展
(3) 加引号来防止扩展
(4)变量扩展
三、 Shell字符串详解
1.Shell字符串拼接
2.Shell字符串截取
3.汇总
4.Shell的格式化输出printf
(1)语法格式:
(2)常用格式替换符:
(3)常用转义字符:
四、Shell脚本语言的运算
1.算数运算
2.Bash中的算术运算
(1)实现算术运算
(2)内建的随机数生成器变量:
(3)增强型赋值:
3.逻辑运算
4.短路运算
5.条件测试命令
条件测试命令及其语法
变量测试
文件测试表达式
字符串测试表达式
整数测试表达式
逻辑操作符
总结
一、Shell脚本语言的基本结构
1.Shell脚本的用途:
自动化常用命令
执行系统管理和故障排除
创建简单的应用程序
处理文本或文件
2. Shell脚本基本结构:
Shell脚本编程:是基于过程式,解释执行的语言
编程语言的基本结构:
各种系统命令的组合
数据存储:变量,数组
表达式:a+b
控制语句:if、case、for、while
shell脚本:包含一些命令或声明,并符合一定格式的文本文件
#声明后续语句是通过那种语言写的#!/bin/bash#!/usr/bin/python#!/usr/bin/perl
格式要求:首行执行shebang机制
3.创建Shell脚本过程

(1) 使用vim创建文本文件,第一行必须包括shell声明序列
#!/bin/bash
(2)加执行权限,给予执行权限,在命令行上指定脚本的绝对或相对路径
[root@ding ~]# chmod +x script1.sh
(3) 运行脚本,直接运行解释器,将脚本作为解释器程序的参数运行
[root@ding ~]# /root/shellScript/hello.sh
4.脚本注释规范
-
第一行一般为调用使用的语言
-
程序名,避免更改文件名为无法找到正确的文件
-
版本号
-
更改后的时间
-
作者相关信息
-
该程序的作用,及注意事项
-
最后是各版本的更新简要说明
5.第一个脚本

[root@ding ~]# chmod +x shell/hostname.sh
[root@ding ~]# shell/hostname.sh #输出结果
My hostname is ding
Time is 2025-06-05 03:49:54
6.执行脚本
(1)增加执行权限,执行脚本时会创建一个子shell,不影响现有的shell环境
chmod +x sh./sh/root/shellScript/sh
(2)使用 . 或者source,执行脚本时不会创建一个子shell,会影响现有的shell环境
source sh. sh
#注意:尽量不要使用该方式执行脚本!!!
企业级实例:备份脚本
[root@ding ~]# mkdir /backup/
[root@ding ~]# vim shell/backup.sh
[root@ding ~]# chmod +x shell/backup.sh
[root@ding ~]# ./shell/backup.sh
'/etc/' -> '/backup/etc2025-06-05/'
'/etc/dnf' -> '/backup/etc2025-06-05/dnf'
'/etc/dnf/aliases.d' -> '/backup/etc2025-06-05/dnf/aliases.d'
'/etc/dnf/dnf.conf' -> '/backup/etc2025-06-05/dnf/dnf.conf'
...
Backup is finished
7.脚本错误
语法错误,会导致后续的命令不继续执行,可以用bash -n shellname检查错误
命令错误,后续的命令还会继续,可以使用bash -x shellname检查
逻辑错误,只能使用bash -x进行观察
二、Shell脚本语言的变量用法详解
1.变量
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
2.变量类型
变量类型:
-
内置变量:如PS1,PATH,UID,HOSTNAME,HISTSIZE
-
用户自定义变量
-
预定义变量
-
位置变量
不同的变量存放的数据不同,决定了以下:
-
数据存储方式
-
参与的计算
-
表示的数据范围
变量数据类型:
-
字符串
-
数值:整型,浮点型(小数)、bash不支持浮点数
3.Shell中变量命名法则
-
不能使用程序中的保留字,如:if,for
-
只能使用数字,字母及下划线,且不能以数字开头
-
见名思意,用英文名字,并体现真正含义
-
统一命名规则:驼峰命名法
-
全局变量名大写
-
局部变量小写
-
函数名小写
4. 变量定义和引用
变量的生效范围(变量作用域)
普通变量:生效范围为当前shell进程;对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
本地变量:生效范围为当前shell进程中某代码片段,通常指函数
变量赋值:
name="value"value可以是以下多种类型直接字符串:name='root'变量引用:name="$USER"命令应用:name=`command` || name=$(command)通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存。
变量引用:
$name${name}
弱引用和强引用:
“$name”:弱引用,其中的变量引用会被替换成为变量值
‘$name’:强引用,其中的变量引用不会被替换成变量值,而保持原字符串
实例:
[root@ding ~]# name='Mike'
[root@ding ~]# NAME="$USER"
[root@ding ~]# hostname=`hostname`
[root@ding ~]# echo "My name is $name"
My name is Mike
[root@ding ~]# echo "My name is $NAME"
My name is root
[root@ding ~]# echo "My hostname is $hostname"
My hostname is ding
[root@ding ~]# NUM=`seq 10`
[root@ding ~]# echo $NUM
1 2 3 4 5 6 7 8 9 10
[root@ding ~]# echo "$NUM"
1
2
3
4
5
6
7
8
9
10
查看已定义的所有变量:
[root@ding ~]# set
删除变量
[root@ding ~]# unset shellname1 shellname2
实战:
[root@ding ~]# echo $name
Mike
[root@ding ~]# unset name
[root@ding ~]# echo $name
实例:"{}"的使用
[root@ding ~]# NAME=mage
[root@ding ~]# AGE=20
[root@ding ~]# echo $NAME
mage
[root@ding ~]# echo $AGE
20
[root@ding ~]# echo $NAME $AGE
mage 20
[root@ding ~]# echo $NAME_$AGE
20
[root@ding ~]# echo ${NAME}_$AGE
mage_20
显示系统信息
[root@ding ~]# vim shell/OS.sh
[root@ding ~]# cat shell/OS.sh
#!/bin/bash
RED="\E[1;31m"
GREEB="\E[1;32m"
END="\E[0m"
echo -e "\E[1;32m----------Host systeminfo----------$END"
echo -e "HOSTNAME: $RED `hostname`$END"
echo -e "IPADDR: $RED `ifconfig ens160 | grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1` $END"
echo -e "OSVERSION: $RED `cat /etc/redhat-release`$END"
echo -e "KERNEL: $RED `uname -r`$END"
echo -e "CPU: $RED `lscpu | grep "型号名称:" | tr -s ' ' ' '|cut -d ' ' -f 2-5` $END"
echo -e "MEMORY: $RED `free -h | grep Mem | tr -s ' ' ' '|cut -d ' ' -f 4` $END"
echo -e "DISK: $RED `lsblk | grep '^sda' | tr -s ' ' | cut -d ' ' -f 4` $END"
echo -e "\E[1;32m---------- END ----------$END"
[root@ding ~]# chmod +x shell/OS.sh
[root@ding ~]# cd shell
[root@ding shell]# ./OS.sh
----------Host systeminfo----------
HOSTNAME: ding
IPADDR: 192.168.72.8
OSVERSION: Rocky Linux release 8.10 (Green Obsidian)
KERNEL: 4.18.0-553.el8_10.x86_64
CPU: AMD Ryzen 7 5800H
MEMORY: 1.2Gi
DISK:
---------- END ----------
利用变量实现动态命令
[root@ding ~]# CMD=hostname
[root@ding ~]# $CMD
ding
5.环境变量
环境变量:
可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量。
一旦子进程修改了从父进程继承的变量,将会传递新的值给孙子进程
一般只在配置文件中使用,在脚本中较少使用
普通变量生效的范围与环境变量生效的范围
[root@ding ~]# vim shell/father.sh
[root@ding ~]# cat shell/father.sh
#!/bin/bash
NAME=father
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh[root@ding ~]# vim shell/son.sh
[root@ding ~]# cat shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100
[root@ding ~]# chmod -R +x shell/*
[root@ding ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=127349
son.sh:NAME=
son.sh:NAME=son
son.sh PID is 127350
son.sh father pid is 127349#子进程无法使用父进程的变量,需要自己赋值
变量声明和赋值:
export name=VALUEdeclare -x name =VALUE#或者先赋值再声明~value可以是以下多种类型直接字符串:name='root'变量引用:name="$USER"命令应用:name=`command` || name=$(command)通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
declare命令详解
declare 为 shell 指令,在第一种语法中可用来声明变量并设置变量的属性([rix]即为变量的属性),在第二种语法中可用来显示 shell 函数。若不加上任何参数,则会显示全部的 shell 变量与函数(与执行 set 指令的效果相同)
语法
declare [+/-][rxi][变量名称=设置值] 或 declare -f
参数说明:
-
+/- "-“可用来指定变量的属性,”+"则是取消变量所设的属性。
-
-f 仅显示函数
-
r 将变量声明为只读变量。注意,一旦设置为只读变量,既不能修改变量的值也不能删除变量,甚至不能通过+r取消只 读属性
-
x 指定的变量会成为环境变量,可供shell以外的程序来使用
-
i 将变量声明为整数型(integer)
-
p 显示指定变量的被声明类型。
实例
1.声明整数型变量
#!/bin/bashdeclare -i ab //声明整数型变量ab=56 //改变变量内容echo $ab //显示变量内容--->56
改变变量属性
#!/bin/bash#声明整数型变量declare -i ef ef=1echo $ef#变量赋值,赋予文本值ef="wer"echo $ef#取消变量属性declare +i efef="wer"echo $ef
3.设置变量只读
#!/bin/bashdeclare -r ab #设置变量为只读ab=88 #改变变量内容echo $ab #显示变量内容--->declare.sh:行3: ab: 只读变量
4.声明数组变量
#!/bin/bash#声明数组变量declare -a cdcd[0]=acd[1]=bcd[2]=c#输出数组的指定内容echo ${cd[1]}#显示整个数组变量内容echo ${cd[@]}
5.显示函数
#!/bin/bash#声明函数declare -f function command_not_found_handle(){if [ -x /usr/lib/command_not_found_handle ];then/usr/bin/python /usr/lib/command_not_found_handle -- $1;return $?;elseif [ -x /usr/share/command_not_found_handle ];then/usr/bin/python /usr/share/command_not_found_handle --$1;return $?;elsereturn 127;fi;fi;}
变量引用:
$name${name}
完善(普通变量生效的范围与环境变量生效的范围)实战:
[root@ding ~]# vim shell/father.sh #!/bin/bashNAME=fatherexport NAMEecho "father.sh:NAME=$NAME"echo "fatther is PID=$BASHPID"shell/son.sh
[root@ding ~]#vim shell/son.sh#!/bin/bashecho "son.sh:NAME=$NAME"NAME=sonecho "son.sh:NAME=$NAME"echo "son.sh PID is $BASHPID"echo "son.sh father pid is $PPID"sleep 100
[root@ding ~]# chmod -R +x shell/*
[root@ding ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=128429
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 128430
son.sh father pid is 128429
^C
[root@ding ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=128457
son.sh:NAME=father
son.sh:NAME=son
son.sh PID is 128458
son.sh father pid is 128457
#父进程定义了一个环境变量,在子进程上可以进行调用
#父进程无法使用子进程的变量
#子进程自己定义了一个同名变量,就覆盖环境变量
显示所有环境变量:
[root@ding ~]# env[root@ding ~]# printenv[root@ding ~]# export[root@ding ~]# declare -x
删除变量
[root@ding ~]#unset shellname1 shellname2
Bash内建的环境变量
PATHSHELLUSERUIDPWDSHLVL #shell的嵌套层数,即深度LANGMAILHOSTNAMEHISTSIZE_ #下划线,表示前一命令的最后一个参数
6.只读变量
只读变量:只能声明定义,但后续不能修改和删除
声明只读变量:
readonly namedeclare -r name
查看只读变量:
readonly [-p]declare -r
7.位置变量
位置变量:在Bash Shell中内置的变量,在脚本代码中调用命令行传递给脚本的参数
$1,$2,... 对应第一个,第二个等参数,shift[n]换位置,最多9个#预定义变量$0 命令本身,包括路径$* 传递给脚本的所有参数,全部参数合成一个字符串$@ 传递给脚本的所有参数,每个参数为独立字符串$# 传递给脚本的参数的个数$? 上个命令的退出状态,或函数的返回值$$ 当前shell进程ID。对于Shell脚本,就是这些脚本所在的进程ID注意:$@,$*只有被双引号括起来的时候才会有差异
清空所有位置变量
set --//写在脚本内部
实战演示1:
[root@ding ~]# vim shell/ARG.sh
[root@ding ~]# cat shell/ARG.sh
#!/bin/bash
echo "1st arg is $1"
echo "2st arg is $2"
echo "3st arg is $3"
echo "4st arg is $4"
echo "The number of are is $#"
echo "All args are $*"
echo "All args are $@"
echo "The scriptname is `basename $0`"
[root@ding ~]# chmod +x shell/ARG.sh
[root@ding ~]# shell/ARG.sh {1..10}
1st arg is 1
2st arg is 2
3st arg is 3
4st arg is 4
The number of are is 10
All args are 1 2 3 4 5 6 7 8 9 10
All args are 1 2 3 4 5 6 7 8 9 10
The scriptname is ARG.sh
实战演示2:编写一个移动文件脚本
[root@ding ~]# vim shell/move.sh#!/bin/bashWANGING_COLOR="echo -e \E[1;31m"END="\E[0m"DIR=/tmp/`date +%F_%H-%M-%S`mkdir $DIRmv $* $DIR${WANGING_COLOR} MOVE $* to $DIR $END[root@ding ~]# chmod +x shell/move.sh[root@ding ~]# touch {a,b,c}[root@ding ~]# lsa anaconda-ks.cfg b c shell[root@ding ~]# shell/move.sh a b cMOVE a b c to /tmp/2022-08-16_10-07-55 [root@ding ~]# tree /tmp//tmp/└── 2022-08-16_10-07-55├── a├── b└── c1 directory, 3 files
8.退出状态码变量
进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败,的取值范围为[0,255]
$?的值为0 代表成功$?的值不为0 代表失败
用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
实战:
[root@ding ~]# ping -c 2 www.baidu.com > /dev/null
[root@ding ~]# echo $?
1
[root@ding ~]# cmd
bash: cmd: 未找到命令...
相似命令是: 'mcd'
[root@ding ~]# echo $?
127
注意:
-
脚本中一旦遇到了exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
-
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
实例1:$?获取上一个命令的退出状态
#!/bin/bashif [ "$1"==100 ];thenexit 0 #参数正确,退出状态为0elseexit 1 #参数错误,退出状态为1fi
exit
表示退出当前 Shell 进程,我们必须在新进程中运行 test.sh,否则当前 Shell 会话(终端窗口)会被关闭,我们就无法取得它的退出状态了
[root@localhost re_study]# bash test.sh 100[root@localhost re_study]# echo $?0[root@localhost re_study]# bash test.sh 99[root@localhost re_study]# echo $?1
实例2:$?获取函数的返回值
#!/bin/bash#得到两个数相加的和function add(){return `expr $1 + $2`}add 23 50 #调用函数echo $? #获取函数返回值# 运行结果:[root@localhost re_study]# bash test.sh73
9. 展开命令行
(1)展开命令执行顺序
把命令行分成单个命令词展开别名展开大括号的声明{}展开波浪符声明(~)命令替换$()和``再次把命令行分成命令词展开文件通配(*,?,[abc]等)准备I/O重导向(<,>)运行命令
(2)防止扩展
反斜线(\)会使随后的字符按原意解释
实例:
[root@localhost ~]# echo Your cost: \$5.00Your cost: $5.00[root@localhost ~]#
(3) 加引号来防止扩展
单引号(' ')防止所有扩展双引号(" ")可防止扩展,但是以下清空例外:$(美元符号)
(4)变量扩展
``:反引号,命令替换\:反斜线,禁止单个字符扩展!:叹号,历史命令替换
三、 Shell字符串详解
字符串(String)就是一系列字符的组合。字符串是Shell编程中最常用的数据类型之一
字符串可以由单引号''
包围,也可以由""
包围,也可以不用引号,三种方式的区别
通过代码演示一下三种形式的区别
#!/bin/bashn=74str1=c.biancheng.net$n str2="shell \"Script\" $n"str3='C语言中文网 $n'echo $str1echo $str2echo $str3# 运行结果c.biancheng.net74shell "Script" 74C语言中文网 $n
str1 中包含了$n
,它被解析为变量 n 的引用。$n
后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作为字符串 str1 的一部分
str2 中包含了引号,但是被转义了(由反斜杠\
开头的表示转义字符)。str2 中也包含了$n
,它也被解析为变量 n 的引用
str3 中也包含了$n
,但是仅仅是作为普通字符,并没有解析为变量 n 的引用
获取字符串长度
在Shell中获取字符串长度很简单,具体方法如下:
${#string_name}string_name:表示字符串名字
1.Shell字符串拼接
在脚本语言中,字符串的拼接(也称为字符串连接或者字符串合并)往往都非常简单,例如:
-
在
PHP
中使用.
即可连接两个字符串 -
在
JavaScript
中使用+
即可将两个字符串合并为一个
然而,在Shell中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接
#!/bin/bashname="shell"url="http://c.biancheng.net/shell/"str1=$name$url #中间不能有空格str1=$name":"$urlstr2="$name $url" #如果被双引号包围,那么中间可以有空格,也可以出现别的字符串str3="$name:$url" str4="${name}Script:${url}Index.html" #在变量后加上字符串,需要给变量名加上大括号
2.Shell字符串截取
Shell截取字符串通常有两种方式,从指定位置开始截取和从指定字符(子字符串)开始截取
从指定位置开始截取
这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串
既然需要指定起始位置,那么就要涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数?答案是:Shell同时支持两种计数方式
(1)从字符串左边开始计数
如果想从字符串的左边开始计数,那么截取字符串的具体格式如下:
${string:start:length}`其中,Sting是要截取的字符串,start是起始位置(从左边开始,从0开始计数),length是要截取的长度(省略的话表示直到字符串的末尾)
例如:
url="c.biancheng.net"echo ${url:2:9}>结果为:bianchengurl="c.biancheng.net"echo ${url:2} #省略length,截取到字符串末尾>结果为:biancheng.net
(2)从右边开始计数
如果想从字符串的右边开始计数,那么截取字符串的具体格式如下:
${string:0-start:length}`同第 1) 种格式相比,第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。这里需要强调两点:从左边开始计数时,起始数字是 0(这符合程序员思维);从右边开始计数时,起始数字是 1(这符合常人思维)计数方向不同,起始数字也不同。不管从哪边开始计数,截取方向都是从左到右。
例如:
url="c.biancheng.net"echo ${url:0-13:9}>结果为:biancheng 从右边数:b是第13个字符url="c.biancheng.net"echo ${url:0-13} #省略length,直接截取到字符串末尾>结果为:biancheng.net
从指定字符(子字符串)开始截取
这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符
1)使用#号截取右边字符
使用#
号可以截取指定字符(或子字符串)右边的所有字符,具体格式如下:
${string#*chars}#其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
例如:
url="http://c.biancheng.net/index.html"echo ${url#*:}>结果为://c.biancheng.net/index.htmlecho ${url#*p:}echo ${url#*ttp:}`如果不需要忽略chars左边的字符,那么也可以不写*url="http://c.biancheng.net/index.html"echo ${url#http://}>结果为:c.biancheng.net/index.html`注意:以上写法遇到第一个匹配的字符(子字符串)就结束了url="http://c.biancheng.net/index.html"echo ${url#*/}>结果为:/c.biancheng.net/index.html。url 字符串中有三个/,输出结果表明,Shell 遇到第一个/就匹配结束了
使用##
可以直到最后一个指定字符(子字符串)再匹配结束
${string##*chars}
例如:
#!/bin/bashurl="http://c.biancheng.net/index.html"echo ${url#*/}# 结果为:/c.biancheng.net/index.htmlecho ${url##*/}# 结果为:index.htmlstr="-----aa+++aa@@@"echo ${str#*aa}# 结果为:+++aa@@@echo ${str##*aa}# 结果为:@@@
2)使用%截取左边字符
使用%
号可以截取指定字符(或者子字符串)左边的所有字符
${string%chars*}`请注意*的位置,因为要截取 chars 左边的字符,而忽略 chars 右边的字符,所以*应该位于 chars 的右侧。其他方面%和#的用法相同#!/bin/bashurl="http://c.biancheng.net/index.html"echo ${url%/*} #结果为 http://c.biancheng.netecho ${url%%/*} #结果为 http:str="---aa+++aa@@@"echo ${str%aa*} #结果为 ---aa+++echo ${str%%aa*} #结果为 ---
3.汇总
格式 | 说明 |
---|---|
${string:start :length} | 从string字符串的左边第start个字符开始,向右截取length个字符。 |
${string:start} | 从string字符串的左边第start个字符开始截取,直到最后。 |
${string:0-start:length} | 从string字符串的右边第start个字符开始,向右截取length个字符。 |
${string:0-start} | 从string字符串的右边第start个字符开始截取,直到最后。 |
${string#*chars} | 从string字符串第一次出现chars的位置开始,截取chars右边的所有字符。 |
${string##*chars} | 从string字符串最后一次出现chars的位置开始,截取chars右边的所有字符。 |
${string%chars*} | 从string字符串第一次出现chars的位置开始,截取chars左边的所有字符。 |
${string%%chars*} | 从string字符串最后一次出现chars的位置开始,截取chars左边的所有字符。 |
4.Shell的格式化输出printf
(1)语法格式:
printf "指定的格式" "文本1" "文本2" .....
(2)常用格式替换符:
替换符 | 功能 |
---|---|
%s | 字符串 |
%f | 浮点格式,保留小数点位数%.nf,n为数字 |
%d,%i | 十进制整数 |
%% | 表示%本身 |
说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,-表示左对齐
(3)常用转义字符:
转义符 | 功能 |
---|---|
\a | 警告字符,通常为ASCII的BEL字符 |
\b | 后退 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | 表示\本身 |
实例
[root@localhost ~]# printf "%s\n" 1 2 3 41234[root@localhost ~]# printf "%f\n" 1 2 3 41.0000002.0000003.0000004.000000[root@localhost ~]# printf "%.2f\n" 1 2 3 4 #.2f表示保留两位小数1.002.003.004.00 [root@localhost ~]# printf "(%s)" 1 2 3 4;echo " "(1)(2)(3)(4) [root@localhost ~]# printf " (%s) " 1 2 3 4;echo " "(1) (2) (3) (4) [root@localhost ~]# printf " (%s) (%s)\n" 1 2 3 4;echo " "(1) (2)(3) (4)[root@localhost ~]# printf " %s %s\n" 1 2 3 4;echo " "1 23 4[root@localhost ~]# printf "%s %s %s\n" 1 2 3 41 2 34 [root@localhost ~]# #%-10s表示宽度10个字符,左对齐[root@localhost ~]# printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男性 20岁 70KG 小红 女性 18岁 50KG 姓名 性别 年龄 体重 小明 男性 20岁 70KG 小红 女性 18岁 50KG [root@localhost ~]# #将十进制的1000转换为16进制数[root@localhost ~]# printf "%X\n" 10003E8[root@localhost ~]# printf "%x\n" 10003e8[root@localhost ~]# #将十六进制的C转换为十进制[root@localhost ~]# printf "%d\n" 0xc12[root@localhost ~]# [root@localhost ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" $varwelcometostudy[root@localhost ~]# var="welcome to study";printf "\033[31m%s\033[0m\n" "$var
-- + 加法运算-- - 减法运算-- * 乘法运算-- / 除法运算-- % 取模,即取余数-- ** 乘方 #乘法符号在有些场景需要转义"welcome to study[root@localhost ~]#
四、Shell脚本语言的运算
1.算数运算
shell支持算术运算,但只支持整数,不支持小数
2.Bash中的算术运算
(1)实现算术运算
1. let var=算术表达式2. var=$[算术表达式]3. var=$((算术表达式))4. var=$(expr arg1 arg2 arg3 ...)5. declare -i var = 数值6. echo '算术表达式' | bc (支持浮点数)
实例:使用bc计算小数和declare -i计算
[root@localhost ~]# echo "scale=3;20/3"|bc6.666[root@localhost ~]# echo "scale=3;2/3"|bc.666[root@localhost ~]# i=20[root@localhost ~]# j=20[root@localhost ~]# declare -i sum=i*j[root@localhost ~]# echo $sum400[root@localhost ~]#
(2)内建的随机数生成器变量:
$RANDOM 取值范围:0-32767
实例:生成0-49之间的随机数
[root@localhost ~]# echo $[$[$RANDOM%50]+1]40[root@localhost ~]# echo $[$RANDOM%50]44[root@localhost ~]# echo $[$[$RANDOM%50]+1] #生成1~50之间的随机数[root@localhost ~]# echo $[RANDOM % 100 + 1]
实例:生成随机颜色字符串
[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"Study[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"Study[root@localhost ~]#
(3)增强型赋值:
+= i+=10 <==> i=1+10-=i-=j <==> i=i-j*=/=%=++ i++,++1 <==> i=i+1 (自增)--i--,--i <==> i=i-1 (自减)
格式:
let varOPERvalue
实例:自增,自减
[root@localhost ~]# let var+=1[root@localhost ~]# echo $var1[root@localhost ~]# let var++[root@localhost ~]# echo $var2[root@localhost ~]# let var-=1[root@localhost ~]# echo $var1[root@localhost ~]# let var--[root@localhost ~]# echo $var0[root@localhost ~]#[root@localhost ~]# unset i j ;i=1;let j=i++;echo "i=$i,j=$j" i=2,j=1[root@localhost ~]# unset i j ;i=1;let j=++i;echo "i=$i,j=$j"i=2,j=2[root@localhost ~]# # i++ 与 ++i的区别:i++ 先赋值再运算++i 先运算再赋值
实例:鸡兔同笼问题
[root@localhost ~]# vim shell/chicken.sh#!/bin/bashHEAD=35FOOT=94RABBIT=$(((FOOT-HEAD-HEAD)/2))CHOOK=$[35-RABBIT]echo "兔子的数量为:"$RABBITecho "鸡的数量为: "$CHOOK[root@localhost ~]# chmod +x shell/chicken.sh [root@localhost ~]# shell/chicken.sh 兔子的数量为::12鸡的数量为:23[root@localhost ~]# # 在脚本中写入变量,让用户在命令行写入需要计算的数值[root@localhost ~]# vim shell/chicken.sh#!/bin/bashHEAD=$1FOOT=$2RABBIT=$(((FOOT-HEAD-HEAD)/2))CHOOK=$[35-RABBIT]echo "兔子的数量为:"$RABBITecho "鸡的数量为: "$CHOOK[root@ansible-salve1 ~]# ./chicken.sh 30 80兔子的数量为:10鸡的数量为: 25[root@ansible-salve1 ~]#
面试题:计算出所有人的年龄总和
[root@ansible-salve1 shell]# vim nianling.txt[root@ansible-salve1 shell]# cat nianling.txta=20b=18c=22[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt 201822[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc60[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt 201822[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt | tr '\n' +| grep -Eo ".*[0-9]" | bc60
3.逻辑运算
True用数字表示1,False用数字表示0
-
与:&
1 与 1 = 1 1 与 0 = 00 与 1 = 00 与 0 = 0
-
或:|
1 或 1 = 11 或 0 = 10 或 1 = 10 或 0 = 0
-
非:!
!1 = 0 !True=False!0 = 1 !False=True
-
异或:^
#异或的两个值,相同为假,不同为真1 ^ 1 =01 ^ 0 =10 ^ 1 =10 ^ 0 =0
4.短路运算
-
短路与
CMD1 短路与 CMD2--第一个CMD1结果为0(假),总的结果必定为0,所以不需要执行CMD2--第二个CMD1结果为1(真),第二个CMD2必须要参与计算,才能得到最终的结果
-
短路或
CMD1 短路或 CMD2--第一个CMD1结果为1(真),总的结果必定为1,因此不需要执行CMD2--第一个CMD1结果为0(假),第二个CMD2必须要参与运算,才能得到最终的结果
5.条件测试命令
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便在条件性环境下进行执行。
-
若真,则状态码变量$?返回0
-
若假,则状态码变量$?返回1
扩展: [ ] 与 [[ ]] 的区别
`区别1:[ ]是符合POSIX标准的测试语句,兼容性强,几乎可以运行在所有的Shell解释器中[[ ]]仅可运行在特定的几个Shell解释器中(如Bash)`区别2:> < 可以在[[ ]]中使用ASCII码顺序进行排序,而[]不支持`区别3:在[ ]中使用-a 和 -o 表示逻辑与和逻辑或,[[ ]]使用&& 和 || 表示,[[ ]]不支持-a`区别4:在[ ]中==是字符匹配,在[[ ]]中==是模式匹配`区别5:[ ]不支持正则匹配,[[ ]]支持用=~进行正则匹配`区别6:[ ]仅在部分Shell中支持用()进行分组,[[ ]]均支持`区别7:在[ ]中如果变量没有定义,那么需要用双引号引起来,在[[ ]]中不需要
条件测试命令及其语法
语法1:test <测试表达式> 说明:test命令和<测试表达式>之间至少有一个空格
# 在shell中,大于用 -gt 表示,小于用 -lt 表示,大于或等于用 -ge 表示,小于或等于用 -le表示 ,不相等用-ne 表示[root@ansible-salve1 ~]# test 1 -lt 2[root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]# test 2 -lt 1[root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]#
语法2:[<测试表达式>] 说明:该方法和test命令的用法一样,[]的两边和内容之间至少有一个空格
[root@ansible-salve1 ~]# [ 1 -gt 3 ][root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# [ 1 -lt 3 ][root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]#
语法3:[[ <测试表达式> ]] 说明:比test和[]更新的语法格式。[[]]的边界和内容之间至少有一个空格。[[]]中可以使用通配符等进行模式匹配
[root@ansible-salve1 ~]# [[ 1 > 3 ]][root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# [[ 1 < 3 ]][root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]#
语法4:((<测试表达式>)) 说明:一般用于if语句里,双小括号两端不需要有空格,测试对象只能是整数
[root@ansible-salve1 ~]# ((1>2))[root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# ((1<2))[root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]#
变量测试
语法规则:-v VAR 变量var是否被定义
示例:判断NAME变量是否被定义
[root@ansible-salve1 shell]# [[ -v NAME ]][root@ansible-salve1 shell]# echo $?1[root@ansible-salve1 shell]# NAME=1[root@ansible-salve1 shell]# [[ -v NAME ]][root@ansible-salve1 shell]# echo $?0[root@ansible-salve1 shell]#
语法规则: -R VAR 变量VAR是否被引用
示例:判断NAME变量是否被引用
[root@ansible-salve1 shell]# NAME=10[root@ansible-salve1 shell]# test -v NAME[root@ansible-salve1 shell]# echo $?0[root@ansible-salve1 shell]# test -R NAME[root@ansible-salve1 shell]# echo $?1[root@ansible-salve1 shell]#
文件测试表达式
常用的文件测试操作符 | 说明 |
---|---|
-a/-e 文件 | 文件是否存在 |
-d 文件 | 文件存在且为目录则为真,即测试表达式成立 |
-f 文件 | 文件存在且为普通文件则为真,即测试表达式成立 |
-r 文件 | 文件存在且可读为真 |
-w 文件 | 文件存在且可写为真 |
-x 文件 | 文件存在且可执行则为真 |
测试文件
[root@ansible-salve1 ~]# test -a test.txt[root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# test -d shell[root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]# touch test.txt[root@ansible-salve1 ~]# test -w test.txt &&echo "true"true[root@ansible-salve1 ~]# test -r test.txt &&echo "true"true[root@ansible-salve1 ~]# test -x test.txt &&echo "true"[root@ansible-salve1 ~]#
字符串测试表达式
常用字符串测试操作符 | 说明 |
---|---|
-n ”字符串“ | 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为nozero |
-z ”字符串“ | 若字符串的长度为0,则为真,z可以理解为zero |
> | Ascii码是否大于Ascii码 |
“字符串1” == ”字符串2“ | 若字符串1长度等于字符串2长度,则为真 |
“字符串1” != ”字符串2“ | 若字符串1长度不等于字符串2长度,则为真 |
“字符串1” =~ “字符串2” | 左侧字符串是否能被右侧的PATTERN所匹配。注意:此表达式用于[[ ]]中:扩展的正则表达式 |
测试字符串
[root@ansible-salve1 ~]# var_test='Mike' [root@ansible-salve1 ~]# echo $var_test Mike[root@ansible-salve1 ~]# [[ -n var_test ]] && echo "True"True# 通配符[root@ansible-salve1 ~]# FILE=test.log[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"True[root@ansible-salve1 ~]# FILE=test.txt[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"[root@ansible-salve1 ~]# [[ "$FILE" != *.log ]] && echo "True"True[root@ansible-salve1 ~]# # 扩展的正则表达式[root@ansible-salve1 ~]# FILE=test.log[root@ansible-salve1 ~]# [[ "$FILE" =~ \.log$ ]] && echo "True"True[root@ansible-salve1 ~]# N=100[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"True[root@ansible-salve1 ~]# N=A10[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"[root@ansible-salve1 ~]# IP=1.2.3.4[root@ansible-salve1 ~]# [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]][root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]# # 使用正则表达式判断IP是否合法[root@ansible-salve1 ~]# IP=1.2.3.333[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]][root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# IP=255.255.255.255[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]][root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]#
整数测试表达式
在[ ] 或 test中使用的比较符号 | 在(()) 或 [[ ]]中使用的比较符号(不用这个做数字比较) | 说明 |
---|---|---|
-eq | == 或 = | 相等,equal |
-ne | != | 不相等,not equal |
-gt | > | 大于,greater than |
-ge | > = | 大于等于,greater equal |
-lt | < | 小于,less than |
-le | < = | 小于等于,less equal |
逻辑操作符
在[ ] 中使用的操作符 | 在test, [[ ]] , (( ))中使用的逻辑操作符 | 说明 |
---|---|---|
-a | && | and,与,两边都为真,则结果为真 |
-o | || | or,或,有真则真,同假则假 |
! | ! | not,非,两端相反,则结果相反 |
示例
[root@ansible-salve1 ~]# var_test=1[root@ansible-salve1 ~]# var_t=2[root@ansible-salve1 ~]# [ $var_test -lt 0 -a $var_t -gt 0 ][root@ansible-salve1 ~]# echo $?1[root@ansible-salve1 ~]# [ $var_test -lt 0 -o $var_t -gt 0 ][root@ansible-salve1 ~]# echo $?0[root@ansible-salve1 ~]#
总结:
在Linux操作系统中的Shell脚本,不仅是命令的集合,更是实现自动化、批处理和系统管理的强大工具。通过编写脚本,第一点能效率提升:将重复性操作(如日志清理、备份)固化到脚本中,避免人工干预,第二点流程标准化:确保关键任务(如服务部署)每次执行逻辑一致。