特殊状态变量

$?:上一次命令执行状态返回值,0正确,非0失败
$$:当前shell脚本的进程号
$!:获取上一次后台进程的id
$_:取得之前执行的命令最后一个参数
特殊状态变量查找方式: man bash
搜索special parameters

1)$? :脚本执行返回值

脚本控制返回值的用法,这个脚本执行完毕了,会返回一个数值,称呼之为返回值,学习shell函数编程以后才能彻底理解,如下只是演示:

[root@hadoop102 ~]# vim jiujiu.sh 
[root@hadoop102 ~]# cat jiujiu.sh 
#!/bin/bash
# $#:取参数总个数  -ne:不等于  2:满足的条件   &&:与(并且)的意思 []:里面是一个条件判断
[ $# -ne 2 ] && {
    echo  "必须是两个参数"  #如果不是2个参数则执行本行代码输出
    exit 110    #终止程序运行。且返回110状态码,提供给当前shell函数的$?变量,若是在函数里,可以用return 110用法
}
echo "输入参数正确" 
[root@hadoop102 ~]# bash jiujiu.sh 15 20 35
必须是两个参数
[root@hadoop102 ~]# echo $?
110
[root@hadoop102 ~]# bash jiujiu.sh 15 20
输入参数正确
[root@hadoop102 ~]# echo $?
0

2)$!:获取上一次后台执行的程序PID

[root@hadoop102 ~]# nohup ping baiduo.com & 1> /dev/null # 让后台运行ping百度,且日志写入/dev/null 
[1] 3182
[root@hadoop102 ~]# nohup: ignoring input and appending output to ‘nohup.out’

[root@hadoop102 ~]# ps -ef | grep 3182
root       3182   2586  0 19:18 pts/0    00:00:00 ping baiduo.com
root       3184   2586  0 19:19 pts/0    00:00:00 grep --color=auto 3182
[root@hadoop102 ~]# echo $!
3182

3)$$ :获取当前脚本的PID

[root@hadoop102 ~]# vim jiujiu.sh 
[root@hadoop102 ~]# cat jiujiu.sh 
#!/bin/bash
# $#:取参数总个数  -ne:不等于  2:满足的条件   &&:与(并且)的意思 []:里面是一个条件判断
[ $# -ne 2 ] && {
    echo  "必须是两个参数"  #如果不是2个参数则执行本行代码输出
    exit 110    #终止程序运行。且返回110状态码,提供给当前shell函数的$?变量,若是在函数里,可以用return 110用法
}
echo "输入参数正确" 

echo "当前的脚本id是: $$"
[root@hadoop102 ~]# bash jiujiu.sh 12 25
输入参数正确
当前的脚本id是: 3188
注:脚本PID每次执行都会发生改变

4)$_ :获取上次命令的最后一个参数

[root@hadoop102 ~]# bash jiujiu.sh 12 25
输入参数正确
当前的脚本id是: 3200
[root@hadoop102 ~]# echo $_
25

shell子串

bash一些基础的内置命令

echo
eval
exec
export
read
shift

1)echo命令

输出,打印

选项:
-n:不换行输出
-e:解析字符串中的特殊符号

\n :换行
\r :回车
\t :制表符 表示四个空格
\b :退格

#不换行输出打印
[root@hadoop102 ~]# echo 你好
你好
[root@hadoop102 ~]# echo 你好;echo 你真胖  (不加-n,则当成两个命令换行输出)
你好
你真胖
[root@hadoop102 ~]# echo -n 你好;echo 你真胖  (加-n表示不换行输出)
你好你真胖
[root@hadoop102 ~]# echo -n 你好;echo -n 你真胖  (前后命令都加-n)
你好你真胖[root@hadoop102 ~]#   (因为都加了-n,所以命令提示符不换行而跟在输出结果后面

#识别字符串特殊符号
[root@hadoop102 ~]# echo '今晚月色\n真美' (认识别不了特殊符号,当普通字符串输出)
今晚月色\n真美
[root@hadoop102 ~]# echo -e '今晚月色\n真美'  (加上-e,解析字符串特殊符号)
今晚月色
真美

# 打印 printf
#printf自动识别特殊符号,因为没加-n所有没换行输出
[root@hadoop102 ~]# printf '你好\t我是\t吴彦祖'
你好  我是  吴彦祖[root@hadoop102 ~]# ^C
[root@hadoop102 ~]# printf '你好\t我是\t吴彦祖\n'
你好  我是  吴彦祖
[root@hadoop102 ~]# 

2)eval

执行多个命令

# 先执行ls在执行cd命令
[root@hadoop102 ~]# eval ls;cd jiujiu
anaconda-ks.cfg  dir1  fine9  jiujiu  jiujiu.sh  like.sh  nohup.out
[root@hadoop102 jiujiu]# 

3)exec

不创建子进程,执行后续命令,且执行完毕后,自动exit

# 先切入普通用户执行命令,执行完毕后exit退出
[root@hadoop102 ~]# su jiujiu
[jiujiu@hadoop102 root]$ exec date
Tue Mar 28 20:21:13 CST 2023
[root@hadoop102 ~]# 

shell子串的花式用法

shell

1.基础语法

${变量} : 返回变量值

[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${name}
jiujiu

${#变量} :返回变量长度,字符长度

[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${#name}
6

${变量:start} :返回变量start数值之后的字符

每个变量值都是有索引和序号的,
start:表示数字
name=jiujiu
0 1 2 3 4 5 
echo ${name:5} # 表示取变量name值的从索引5开始到后面,包括索引5所在的字符
[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${name:5}
iu

${变量:start:length} 提取start之后的length限制的字符

length :表示限制数字
name=jiujiu
0 1 2 3 4 5 
echo ${name:4} # 表示取变量name值的从索引4开始到后面,包括索引4所在的字符
[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${name:2:4}  设置起点以及元素长度
iuj

${变量#world} 从变量开头删除最短匹配的word子串

# 井号后面跟需删出的字符,从开头开始删除
[root@hadoop102 ~]# name2="abcABCabcABC"
[root@hadoop102 ~]# echo ${name2#a*c}
ABCabcABC

${变量##world} 从变量开头,删除最长匹配的word

[root@hadoop102 ~]# name2="abcABCabcABC"
[root@hadoop102 ~]# echo ${name2##a*c}
ABC

${变量%world} 从变量结尾删除最短的word

# %号后面跟需删出的字符,只不过是从后面开始删除
[root@hadoop102 ~]# name='hello,world'
[root@hadoop102 ~]# echo ${name%w}
hello,

${变量%%world} 从变量结尾开始删除最长匹配的word

${变量} = "Hello world! Goodbye world!"
则通过${变量%%world}操作,将从变量结尾开始删除最长匹配的"world",得到的结果为:"Hello world! Goodbye "

${变量/pattern/string} 用string代替第一个匹配的pattern

[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${name/jiu/999}
999jiu

${变量//pattern/string} 用string代替所以的pattern

[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${name//jiu/999}
999999

字串的实际案列:

shell变量截取字符串通常有两种方式:

从指定位置开始截取和指定字符(子字符串)开始截取。

从指定位置开始截取

这种方式需要两个参数,除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串

既然需要指定起始位置,那么就涉及到计数方向的问题,到底是从字符串左边开始计算,还是从字符串右边开始计算,

答案是:shell同时支持两种计数方式

1)从字符串左边开始计算

#  ${string:start:length}
其中:
string:是要截取的字符串
start:是起始位置(从左边开始,从0开始计数)
length:是要截取的长度(省略的话表示直到字符串的末尾)

计算变量长度的各种方法

多种统计方法
name='hello,world'
#方法一                 最快的统计方式
[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo ${#name}
6
#方法二
[root@hadoop102 ~]# name='jiujiu'
[root@hadoop102 ~]# echo $name | wc -L
6
#方法三
利用数值计算expr(shell中用于数值计算的)
[root@hadoop102 ~]# name='hello,world'
[root@hadoop102 ~]# expr length ${name}
11
#方法四
awk统计长度,length函数
[root@hadoop102 ~]#  echo ${name} | awk '{print length($0)}'
11

统计速度比较:

time命令,统计命令时长
for循环的shell编程
# for 语法
# for  number  in  {1..100}
for [变量] in [循环的序列,比如1~100]
do
    echo number   #你要做什么事
done    
写在一行的写法:[root@hadoop102 ~]# for i in {1..10};do echo $i;done
1
2
3
4
5
6
7
8
9
10

#方法一  ${#变量}
拓展:seq生成序列,默认以空格为分隔符,-s指定分隔符
[root@hadoop102 ~]# time for i in {1..10000};do jj=`seq -s ":" 100`;echo ${#jj} &>/dev/null;done 

real    0m30.010s    #实际运行时间
user    0m17.750s    # 用户态执行的时间
sys 0m11.981s        #内核态执行的时间

#方法二  wc -L
[root@hadoop102 ~]# time for i in {1..10000};do jj=`seq -s ":" 100`;echo ${jj} | wc -L  &>/dev/null;done 

real    1m11.701s
user    0m39.893s
sys 0m31.073s
# 方法三 expr命令的length函数
[root@hadoop102 ~]# time for i in {1..10000};do jj=`seq -s ":" 100`;expr length ’${jj}‘ &>/dev/null;done 

real    1m1.522s
user    0m37.090s
sys 0m23.817s
# 方法四  awk加工处理
[root@hadoop102 ~]# time for i in {1..10000};do jj=`seq -s ":" 100`;echo '${jj}' | awk '{print length($0)}' &>/dev/null;done 

real    1m19.003s
user    0m42.968s
sys 0m35.314s
#总结
shell编程,尽量使用linux内置命令,内置的操作和内置的函数,效率最高,尽可能的减少管道符的操作

字符串的截取

删除匹配到的字符串

[root@hadoop102 ~]# name="I am jiujiu"
[root@hadoop102 ~]# echo ${name:2:3}  
am
[root@hadoop102 ~]# echo ${name:2:5}
am ji
#  #  ##
[root@hadoop102 ~]# name2="abcABCabcABC"
[root@hadoop102 ~]# echo ${name2#a*c}
ABCabcABC
[root@hadoop102 ~]# echo ${name2##a*c}
ABC
#   %    %%
[root@hadoop102 ~]# name2="abcABC123ABCabc"
[root@hadoop102 ~]# echo ${name2%a*c}
abcABC123ABC
[root@hadoop102 ~]# echo ${name2%%a*c}

#因为是从a开始匹配到c结尾,所以整行都给截取了输出为空

替换字符串

# / /
[root@hadoop102 ~]# jj="hello,girl,you Beautiful"
[root@hadoop102 ~]# echo ${jj/girl/boy}
hello,boy,you Beautiful
#全局替换
[root@hadoop102 ~]# jj="hello,girl,you Beautiful"
[root@hadoop102 ~]# echo ${jj//o/O}
hellO,girl,yOu Beautiful
[root@hadoop102 ~]# jj="hello,girl,you Beautiful;hello,boy,you Beautiful"
[root@hadoop102 ~]# echo ${jj//hello/HELLO}
HELLO,girl,you Beautiful;HELLO,boy,you Beautiful

删除文件名案例

# 测试准备
[root@hadoop102 sub_str]# ll
total 0
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.npg
#题:去掉所有文件的like字符信息

解答:

#  思路一:单个文件去掉这个字符  
[root@hadoop102 sub_str]# mv jiujiu_1_like.jpg jiujiu_1.jpg
[root@hadoop102 sub_str]# ll
total 0
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.npg
#  思路二:利用变量的字串功能,去掉后缀
[root@hadoop102 sub_str]# f=jiujiu_1_like.jpg
[root@hadoop102 sub_str]# echo ${f/_like/}
jiujiu_1.jpg
#  思路三:利用反引号的功能
[root@hadoop102 sub_str]# f=jiujiu_1_like.npg
[root@hadoop102 sub_str]# mv $f `echo ${f//_like/}`
[root@hadoop102 sub_str]# ll
total 0
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_1.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_2_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_3_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_4_like.npg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.jpg
-rw-r--r--. 1 root root 0 Mar 30 20:06 jiujiu_5_like.npg
#  思路四:批量文件名替换 只修改所有的jpg文件
[root@hadoop102 sub_str]# ls *like*.jpg
jiujiu_2_like.jpg  jiujiu_3_like.jpg  jiujiu_4_like.jpg  jiujiu_5_like.jpg
[root@hadoop102 sub_str]# for jj in `ls *like*.jpg`;do echo $jj;done
jiujiu_2_like.jpg
jiujiu_3_like.jpg
jiujiu_4_like.jpg
jiujiu_5_like.jpg
[root@hadoop102 sub_str]# for jj in `ls *like*.jpg`;do mv $jj `echo ${jj/_like/}`;done
[root@hadoop102 sub_str]# ls *.jpg
jiujiu_1.jpg  jiujiu_2.jpg  jiujiu_3.jpg  jiujiu_4.jpg  jiujiu_5.jpg

特殊shell扩展变量

变量的处理

这四个扩展变量,都属于对变量的值进行判断丶处理

: -

如果parameter变量值为空,返回word字符串

${parameter:-word}

: =

如果para变量值为空,则word替代变量值,且返回其值

${parameter:=word}

: ?

如果para变量值为空,word当作s't'derr输出,否则输出变量值用于设置变量为空导致错误时,返回的错误信息

${parameter:?word}

: +

如果para为空变量为空,什么都不做,否则word返回

${parameter:+word}

实际案例

: -

作用:判断如果变量值为空,就返回后面的字符信息,可以通过result变量去接收
# 变量值为空的情况下
[root@hadoop102 ~]# echo $jiujiu   # 此变量为空

[root@hadoop102 ~]# result=${jiujiu:-hello}   #定义变量名,
[root@hadoop102 ~]# echo $jiujiu   #变量还是为空

[root@hadoop102 ~]# echo $result    #echo result输出变量值
hello
# 变量值不为空的情况
[root@hadoop102 ~]# jiujiu=125
[root@hadoop102 ~]# result2=${jiujiu:-hello}
[root@hadoop102 ~]# echo $jiujiu
125
[root@hadoop102 ~]# echo $result2   #在变量值不为空的情况下,则:-赋值失效
125

:=

作用:如果变量值为空,后面的值,赋值给接收者,以及变量本身
#   变量值为空的前提下
[root@hadoop102 ~]# echo $jiujiu $result
[root@hadoop102 ~]# result=${jiujiu:=hello}
[root@hadoop102 ~]# echo $jiujiu;echo $result
hello
hello
# 变量值不为空的前提下
[root@hadoop102 ~]# echo $jiujiu
hello
[root@hadoop102 ~]# result2=${jiujiu:=daxigaun}
[root@hadoop102 ~]# echo $jiujiu;echo $result2
hello
hello   # 变量值有值的情况下,:=赋值失效  result2取jiujiu的变量值

:?

作用:当变量值为空时,主动输出一个错误信息
# 变量值为空的前提下
[root@hadoop102 ~]# echo ${new_jiujiu}
[root@hadoop102 ~]# echo ${new_jiujiu:?该变量值为空}
-bash: new_jiujiu: 改变量值为空
[root@hadoop102 ~]# new_jiujiu=hello
[root@hadoop102 ~]# echo ${new_jiujiu:?改变量值为空}
hello

:+

作用:当变量为空,什么都不做,否则字符返回个接收者
# 变量值为空的前提下
[root@hadoop102 ~]# echo $jiujiu

[root@hadoop102 ~]# echo ${jiujiu:+hei}

# 变量值不为空的前提下
[root@hadoop102 ~]# jiujiu=hello
[root@hadoop102 ~]# echo ${jiujiu:+hei}
hei   
[root@hadoop102 ~]# like=${jiujiu:+nihao}
[root@hadoop102 ~]# echo $like
nihao

实践案例

数据备份,删除过期数据的脚本

find xagrs :搜索,且删除
# 删除7天以上的过期文件
find 需要搜索的目录 -name 你要搜索的文件名字 -type 文件类型 -mtime +7 | xargs
rm  -f
[root@hadoop102 ~]# vim del_data.sh
[root@hadoop102 ~]# cat del_data.sh 
#!/bin/bash
#shell语法是否有bug
#希望删除某个数据文件夹的备份文件
#定义变量值:dir_path="/data/mysql_back_data/"
#如果有bug歧义,就会在当前目录,搜索,删除
#find ${dir_path] -name '*.tar.gz' -type f -mtime +7 | rm -rf
#变量名的扩展
find ${dir_path:=/data/mysql_back_data/} -name '*.tar.gz' -type f -mtimr +7 | rm -rf

父子shell

image-20230331180258043

1丶source和点,执行脚本,只在当前的shell环境中执行生效
2丶指定bash sh解释器运行脚本,是开启subshell,开启子shell运行脚本命令
3丶./script,都会指定shebang,通过解释器运行,也是开启subshell运行命令

父shell概念

通过命令pstree看到此状态就表示在父shell
[root@hadoop102 ~]# pstree
systemd─┬─NetworkManager───2*[{NetworkManager}]
        ├─VGAuthService
        ├─agetty
        ├─auditd───{auditd}
        ├─crond
        ├─dbus-daemon───{dbus-daemon}
        ├─lvmetad
        ├─master─┬─pickup
        │        └─qmgr
        ├─polkitd───6*[{polkitd}]
        ├─rsyslogd───2*[{rsyslogd}]
        ├─sshd───sshd───bash───pstree   # shell环境所在
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-udevd
        ├─tuned───4*[{tuned}]
        └─vmtoolsd───2*[{vmtoolsd}]
 # ps进程管理命令查看
 -f:显示UID,PID,PPID
 -e:列出所有进程的信息,如同-A选项
 #通过一条命令,检查父子shell的关系
 ps -ef --forest

image-20230401112717412

子shell概念

image-20230401113401017

image-20230401113422181

#多个子shell概念
[root@hadoop101 ~]#bash
[root@hadoop101 ~]#sh
sh-4.2# bash
[root@hadoop101 ~]#ps -ef --forest
root       1268   1018  0 11:11 ?        00:00:00  \_ sshd: root@pts/0
root       1272   1268  0 11:11 pts/0    00:00:00      \_ -bash
root       1309   1272  0 11:32 pts/0    00:00:00          \_ bash
root       1322   1309  0 11:35 pts/0    00:00:00              \_ bash
root       1333   1322  0 11:35 pts/0    00:00:00                  \_ sh
root       1334   1333  0 11:35 pts/0    00:00:00                      \_ bash
root       1346   1334  0 11:36 pts/0    00:00:00                          \_ ps -ef --forest
root       1019      1  0 11:11 ?        00:00:00 /usr/bin/python2 -Es /usr/sbin/tuned -l -P
#只要输入exit就能退出子shell环境了

image-20230401113955631

创建进程列表(创建子shell)

#需要执行一系列的命令,该如何去操作呢?
#是这样:“ls;pwd;cd /tmp;”嘛?
NO,NO,NO
#所谓进程列表,列表列表,就是指被包括起来的数据
shell的进程列表理念,需要用()小括号,如下执行方式,就称之为进程列表
[root@hadoop101 ~]#(pwd;(ls;(echo $BASH_SUBSHELL)))   //开启小括号,就是开启子shell运行命令
#作用:检测是否在子shell环境当中
linux有个默认有关shell的变量
$BATH_SUBSHELL  # 该变量值的特点,如果是0就是在当前shell环境中执行的;如果非0,就是开辟子shell环境去运行

## 检测是否开启了子shell运行命令
[root@hadoop101 ~]#pwd;ls;cd /opt/;ls;ll;echo $BASH_SUBSHELL
/root
git
total 0
drwxr-xr-x. 3 root root 23 Mar 31 16:26 git
0
#明确开启子shell运行的命令进程列表,并开启子shell运行
[root@hadoop101 ~]#(pwd;ls;cd /opt/;ls;ll;echo $BASH_SUBSHELL) //一个括号表示开启一个子shell去运行
/root
git
total 0
drwxr-xr-x. 3 root root 23 Mar 31 16:26 git
1  # 1表示子shell个数

子shell的意义:

image-20230401134802990

子shell嵌套运行

刚才是一个小括号,开启一个子shell运行命令,还可以嵌套多个

[root@hadoop101 ~]#(pwd;(ls;cd /opt/;(ls;ll;(echo $BASH_SUBSHELL))))
/root
git
total 0
drwxr-xr-x. 3 root root 23 Mar 31 16:26 git
4
#利用括号,开启子shell得到理念,以及检查,在shell脚本开发中,经常会用子shell进行多进程的处理,提高程序并发执行效率

内置命令/外置命令

什么是内置命令?什么外置命令呢

内置命令:在系统启动是就加载入内存,常驻内存,执行效率更高,但是占用资源 (比如cd)
外置命令:用户需要从硬盘中读取程序文件,再读入内存加载 (

外置命令

外置命令,也称之为自己单独下载的文件系统命令,处于bash丶shell之外的程序

#外部命令一般储存在:
/bin
/usr/bin
/sbin
/usr/sbin

#可以通过linux中的命令type去判断命令是否属于内置还是外置
[root@hadoop101 ~]#type cd
cd is a shell builtin  

外置的命令特点:

一定会开启子进程进程
#bash在执行外部命令时,一定会开启一个子进程去执行命令

image-20230401141431543

image-20230401141810678

内置命令

特点:内置命令不会产生子进程

内置命令和shell是为一体的,是shell的一部分,不需要单独去读取某个文件,系统启动后就执行在内存中了

#查看linux系统的内置shell命令
compgen -b

shell脚本开发

内容回顾

1丶shell执行多行命令
[root@hadoop101 ~]#pwd;ls;cd /opt/;ls;ll;echo $BASH_SUBSHELL  //用分号把命令隔开
#我们可以把复杂的命令执行过程,通过逻辑代码,组成一个脚本文件,再去执行该文件就好了

2丶shebang 解释器
#!/bin/bash
#!/bin/perl
#!/bin/python
写什么样的脚本就指定什么shebang

3丶执行脚本的方式
bash
sh
./         //需赋予脚本权限
source
.

4丶echo命令,在linux下的格式化打印
#执行脚本如下
[root@hadoop101 ~]#cat echo.sh 
#!/bin/bash
echo "The time and date are: "
date
echo "Let's see who's logged into the system: "
who
[root@hadoop101 ~]# ./echo.sh
-bash: ./echo.sh: Permission denied
[root@hadoop101 ~]#chmod +x echo.sh 
[root@hadoop101 ~]# ./echo.sh
The time and date are: 
Sat Apr  1 16:11:51 CST 2023
Let's see who's logged into the system: 
root     pts/0        2023-04-01 12:32 (192.168.16.1)
#echo 和转义符的概念
所谓转义符就是还原符号原本的意思,不会被阅读为特殊含义
" "
''
#双引号的结果
[root@hadoop101 ~]#echo "这个手机的价格是: $998"
这个手机的价格是: 98    //不加转义符就默认识别为$9 98
[root@hadoop101 ~]#echo "这个手机的价格是: \$998"
这个手机的价格是: $998
#单引号的结果
[root@hadoop101 ~]#echo '这个手机的价格是: $998'
这个手机的价格是: $998   //原文输出
区别:单引号不做任何语法的翻译,双引号支持语法翻译

5丶变量在脚本中的作用
变量被引用的时候,会赋予其值,脚本中的变量,在shell中执行完毕后就会消失,根据执行的方式决定是否消失
[root@hadoop101 ~]#vim jiu.sh
[root@hadoop101 ~]#cat jiu.sh 
#!/bin/bash
name="jiujiu"
days=10

echo "$name学shell脚本$days天"
[root@hadoop101 ~]#bash jiu.sh 
jiujiu学shell脚本10天
[root@hadoop101 ~]#echo $name

[root@hadoop101 ~]#echo $days

[root@hadoop101 ~]#source jiu.sh 
jiujiu学shell脚本10天
[root@hadoop101 ~]#echo $name
jiujiu
[root@hadoop101 ~]#echo $days
10
#注:当你用不同的方式。执行脚本,产生后的后果也不一样
source和. 是在当前的shell环境中加载变量执行脚本
bash和sh 去执行脚本的时候,是开启子shell运行的,变量也是在子shell环境中加载,子shell退出后,变量也就消失了
记住,对变量名的获取,一定要加上美元符号$,否则就错误

6丶Linux、shell变量的替换引用
shell一大特性,就是可以从命令的执行结果中,再提取结果,因此特别适合编写脚本
$()
``
#拓展:下列符号代表意思
${}:取出变量值
[root@hadoop101 ~]#name=jiujiu
[root@hadoop101 ~]#echo ${name}
jiujiu
$():在括号中执行命令,且拿到命令的执行结果
[root@hadoop101 ~]#$(date)
-bash: Sat: command not found
[root@hadoop101 ~]#echo "当前时间是: $(date)"
当前时间是: Sat Apr  1 16:55:32 CST 2023
``:在括号中执行命令,且拿到命令的执行结果
[root@hadoop101 ~]#`whoami`
-bash: root: command not found
[root@hadoop101 ~]#echo  "当前登录用户: `whoami`"
当前登录用户: root
():开启子shell执行命令结果
[root@hadoop101 ~]#(pwd;ls;echo $BASH_SUBSHELL)
/root
jiu.sh
1
$vars:取出变量值
[root@hadoop101 ~]#name=jiujiu
[root@hadoop101 ~]#echo ${name}
jiujiu

数值计算

运算符号,linux的用于计算的命令

常见的运算符号

算术运算符 意义(*表示常用)
+丶- 加法(或正号)丶减法(或负号)*
*丶/丶% 乘法丶除法丶取余(取模)*
** 幂运算*
++丶-- 增加及减少,可前置也可放在变量结尾*
!丶&&丶|| 逻辑非(取反)丶逻辑与(and)丶逻辑或(or)*
<丶<=丶>丶>= 比较符号(小于,小于等于,大于,大于等于)
==丶!=丶= 比较符号(相等,不相等,对于字符串“=”也可以表示相当于)
<<丶>> 向左移位,向右移位
~丶|丶&丶^ 按位取反,按位异位,按位与,按位或
=丶+=丶-=丶*=丶/=丶%= 赋值运算符,例如a+=1相当于a=a+1,a-=1相当于a=a-1*

常见的运算命令

运算操作符与运算命令 意义
(()) 用于整数运算的常用运算符,效率很高
let 用于整数运算。类似于“(())”
expr 可用于整数运算,但还有很多其他的功能
bc Linux下的下一个计算器程序(适合整数及小数运算)
$[] 用于整数运算
awk awk即可以用于整数运算,也可以用于小数运算
declare 定义变量值和属性吗,-i参数可以用于定义整形变量,做运算

用于数值计算的命令

shell的一些命令,只支持整数的运算,小数的计算需要如bc这样的命令才能支持

双小括号(())

运算操作符和运算命令 意义
((i=i+1)) 此种书写方法为运算后赋值法,即将i+1的运算结果赋值给变量i。注意,不能用“echo ((i=i+1))“的形式输出表达式的值,但可以用echo $((i=i+1))输出其值
i=$((i+1)) 可以在(())前加$符,表示将表达式运算后赋值给i
$((8>7&&5==5)) 可以进行比较操作,还可以加入逻辑与和逻辑或,用于条件判断
echo $((2+1)) 需要直接输出运算表达式的运算结果时,可以在(())前加上$符
#有关逻辑运算, 真,假的区别   真为1 假为0
[root@jiujiu ~]# echo $((10>8)) //10大于8是正确的
1
[root@jiujiu ~]# echo $((10>15)) //10大于15是错误的
0

# 逻辑与的用法:&& //符号两边结果都为真,结果才能为真,输出值才能为1,否则都是0
[root@jiujiu ~]# echo $((10>15&&4<5))
0
[root@jiujiu ~]# echo $((10<15&&4<5))
1
[root@jiujiu ~]# echo $((20>15&&4<5))
1

# 实践双小括括号用法
加减乘除
[root@jiujiu ~]# #加减乘除
[root@jiujiu ~]# echo $((6+4))
10
[root@jiujiu ~]# echo $((6-4))
2
[root@jiujiu ~]# echo $((1-4))
-3
[root@jiujiu ~]# echo $((6*4))
24
[root@jiujiu ~]# echo $((8/4))
2
[root@jiujiu ~]# echo $((7/4))  //除法取商
1
[root@jiujiu ~]# echo $((10/4))
2

#幂运算
[root@jiujiu ~]# echo $((5**2)) //5的二次方
25
[root@jiujiu ~]# echo $((5**3)) //5的三次方
125

#取余数/取模
[root@jiujiu ~]# echo $((5%3)) //5除3得商1余2
2
[root@jiujiu ~]# echo $((6%3)) //6除3得商2余0
0
[root@jiujiu ~]# echo $((10%3)) //10除3得商3余1
1

#结合变量计算
[root@jiujiu ~]# num=1    //赋值为1
[root@jiujiu ~]# ((num*5))  //做计算乘5
[root@jiujiu ~]# echo $num   //直接echo $输出值不变
1
[root@jiujiu ~]# ((num=num*5))   //定义num=num*5
[root@jiujiu ~]#echo $((num=num*5))  // 输出结果为5
5
[root@jiujiu ~]# echo $((num=num*5)) //每重新输出一次,值都会递增
25
[root@jiujiu ~]# echo $((num=num*5)) 
125
    #错误语法
    [root@jiujiu ~]# echo ((num=num*5))  //变量前需加上$符才行
    -bash: syntax error near unexpected token `('
#复杂数学运算
[root@jiujiu ~]# ((a=5+4**2-8%5))  //a=5+16-3 
[root@jiujiu ~]# echo $a
18
[root@jiujiu ~]# b=((5+4**2-8%5))  //报错是因为要取双括号里的值前面得加上$
-bash: syntax error near unexpected token `('
[root@jiujiu ~]# b=$((5+4**2-8%5))
[root@jiujiu ~]# echo $b
18

特殊符号运算

符号 意义
++ 加一
-- 减一

注意事项:

#  ++ 有个坑记得避免
++a 先计算+1,在赋值给a
a++ 先对变量a操作,在进行+1
#例:++a  a++
[root@localhost ~]# a=5
[root@localhost ~]# echo $((++a)) //先进行+1也就是5+1 ,再赋值给a 所以输出等于6
6
[root@localhost ~]# echo $((a++)) //因为上面重新赋值的原因,所有这里a值还是输出为6
6
[root@localhost ~]# echo $a //这里为什么输出结果为7呢?因为a++是先对变量a操作,在进行+1。所有这也就是上面输出还是为6的原因,对a变量操作过后再进行+1,这时我们在输出a值结果就为7
7
脚本开发,复杂的对用户输入判断的脚本开发
    注:该脚本开发,利用了很多后期学习内容,涉及shell函数丶if逻辑判断

例:脚本开发

  • 1.想好脚本的功能丶作用丶以及需求
    • 转换为shell代码
#开发一个,接受用户输入的数字,且对运算符号判断,最终得出结果的一个计算脚本
#1.接受用户输入
#2.对用户输入的是不是数字进行判断
#对输入的运算符号进行判断
#最终进行结果计算,输出打印
#!/bin/bash
#脚本开发
#函数的作用,就是把你写的功能代码,进行打包,封装成一个函数名,绕后调用该函数名,函数就会执行
#函数中的代码就会执行

#涵数的名字
#这个函数作用是,告诉用户,你到底应该输入什么的一个简单 提示函数
print_usage(){
    printf "Please enter on integer\n"
#给脚本的执行结果,赋予一个状态码,退出码
    exit 1

}
#接受用户输入的命令-p参数后面写,给用户考到的提示信息
#read -p "提示信息" 接受用户输入的变量
read -p "Please input your number: "   firstnum

#进行对用户输入判断if 语句 注意语法格式!
#限制用户必须输入纯数字
#if语法必须写一个中括号,而且中括号里面前后必须空格
#-n 是if的语句,对字符串判断,如果字符串为空,条件就不成立。如果字符串不为空,条件成立
#sed的作用是把变量字符串进行替换,把所有的数字替换为空,那么就剩下其他非数字的内容了
if [ -n "`echo $firstnum|sed 's/[0-9]//g'`" ]
then
    print_usage
fi
#上诉代码的意思是如果字符串不为空就去执行函数名。如果为空,则跳过该段代码

#上面的代码都是对用户输入进行判断  //希望用户输入3+5
#此时,对运运算符进行输入
read -p "Please input your operator:"  operator
#对运算符进行判断
#限制在+ - * /四个运算符号
if [ "${operator}" != "+" ] && [ "${operator}" != "-" ] && [ "${operator}" != "*" ] && [ "${operator}" != "/" ]
then
    echo "只允许输入+|-|*|/"
    exit 2
fi

#对第二个变量进行处理
read -p "Pleasr input second number:"  secondnum
if [ -n "`echo $secondnum|sed 's/[0-9]//g'`" ]
then
    print_usage
fi
#最后进行数值计算,利用双小括号计算
echo "${firstnum}${operator}${secondnum}结果是:$((${firstnum}${operator}${secondnum}))"

简单的运算玩法:

#!/bin/bash
echo $(($1))
[root@localhost ~]# cat jisuan.sh 
#!/bin/bash
echo $(($1))
[root@localhost ~]# sh jisuan.sh 6*8
48
[root@localhost ~]# sh jisuan.sh 6 * 8  //shell是以空格来区分命令的
6
[root@localhost ~]# sh jisuan.sh "6 * 8" //可以用双引号引起来使用
48

let命令计算

简单计算:

#let命令的执行,效果等同于双小括号 但是,双小括号效率更高
[root@localhost ~]# num=5
[root@localhost ~]# num=num+5    //此种方式不会重新赋值于num
[root@localhost ~]# echo $num
num+5
[root@localhost ~]# num=$num+5
[root@localhost ~]# echo $num
num+5+5
[root@localhost ~]# unset num
[root@localhost ~]# num=5
[root@localhost ~]# let num=num+5 //使用let计算
[root@localhost ~]# echo $num
10
[root@localhost ~]# num2=5
[root@localhost ~]# echo $((num2=num2+10))
15

实践:复杂的let脚本开发(开发,检测nginx服务是否运行的脚本)

#!/bin/bash

CheckUrl(){
    timeout=5
#相当于一个计算器
    fails=0
    success=0
#循环的检测,循环执行一个命令
    while true
    do
        wget -- timeout=$ {timeout} --tries=1 http:/ /pythonav.cn/ -q -o /dev/null  ##此行填写nginx网站地址
        if [ $? -ne 0 ]
            then
                let fails=fails+1 #失败次数加一
            else
                let success=1
        fi
        #判断当成功次数大于1的时候,就可以得出该网站是正确访问
        if [ ${success} -ge 1 ]
            then
                echo "该网站正确运行"
                #返回一个状态码
                exit 0
        fi
        #当错误次数的大于2的时候报警
        if [ ${fails} -ge 2 ];then
            echo "网站有问题"
            exit 2
        fi
    done
}
CheckUrl   #函数写完一定要写变量

expr

简单的计算执行命令

实践

expr必须是传入参数的形式
语法: expr 参数 运算符号 参数
       expr  5 + 5
#加减乘除
[root@localhost ~]# expr 5 * 5    //expr并不是很好用,因为它是基于空格传入参数,但是在sehll里一些元字符都是有特殊含义的,expr识别不了,只能加上转义字符\ 
expr: syntax error
[root@localhost ~]# expr 5 \* 5
25
#逻辑判断  0为错误 1为正确
[root@localhost ~]# expr 5 \> 6
0
[root@localhost ~]# expr 9 \> 6
1
#求长度
    length内置函数
[root@localhost ~]# expr length shajdfhahrf
11
[root@localhost ~]# expr length 123456789
9

expr模式匹配

expr命令也支持模式匹配功能
expr的两个特殊符号
    :  冒号,计算字符串的字符数量,
    .*  任意的字符重复0此或者多次
[root@localhost ~]# expr 123456789 ":" ".*"
9
#最后的字符可以自定义
[root@localhost ~]# expr 11111er21154564 ":" ".*"
15
[root@localhost ~]# expr 11111er21154564 ":" ".*e"
6
[root@localhost ~]# expr 11111er21154f5564 ":" ".*f"
13
[root@localhost ~]# expr jiujiu.png ":" ".*\.jpg"
0
[root@localhost ~]# expr jiujiu.jpg ":" ".*\.jpg"
10
[root@localhost ~]# expr jiujiu.jpog ":" ".*\.jpg"  
0
[root@localhost ~]# expr jiujiu.jpgggggggggggggg ":" ".*\.jpg" //之后匹配到jpg结尾为止
10

expr命令判断文件名后缀是否合法

需求:执行脚本,传入一个文件名,判断是否为.jpg图片

思路:
1.先明白expr命令的匹配模式功能,字符数匹配上了才能统计长度,匹配不上就返回0,在shell中0为假,非0就为中
对真假条件判断的不同条件执行
    if
    else语句
--------------------------------------------------------------------------
#!/bin/bash
if expr "$1"  ":" ".*\.jpg" &> /dev/null
    then
        echo "这的确是以.jpg结尾的"
    else
        echo "这不是以.jpg结尾的"
fi

#找出长度小于3的单词    -lt=小于   -le=小于等于
#!/bin/bash
for i in hello I am is Mr wang  
do
    if [ `expr length $i` -lt 3 ]
        then
            echo $i
    fi
done

bc

bc命令是当作计算器来使用的,而且是命令行的计算器

#交互式的操作,支持小数点的运算
[root@xbs ~]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
5+5
10
5/5
1
5-5
0
5*5
25
1.1*5
5.5

#bc命令结合管道符来进行计算数学
[root@xbs ~]# echo '4+4'
4+4
[root@xbs ~]# echo '4+4'| bc
8
[root@xbs ~]# echo '4*4'
4*4
[root@xbs ~]# echo '4*4'| bc
16

#bc案例:
计算1-1000的总和
#脚本编写思路:如何生成1+2+3+4+....+999+1000
    可以利用echo{}输出序数
    利用tr替换数值中间的空格为“+”号
#利用bc计算
[root@xbs ~]# num=`echo {1..100} | tr ' ' '+'` | bc
5050
[root@xbs ~]# seq -s "+" 100   //  -s 指定分隔符
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+31+32+33+34+35+36+37+38+39+40+41+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100
[root@xbs ~]# seq -s "+" 100 | bc
5050
#利用双括号来计算
[root@xbs ~]# echo $((`seq -s "+" 100`))
5050
#利用expr命令计算,稍许复杂,expr命令是接受多个参数来计算的
[root@xbs ~]# seq -s "+" 100 | xargs expr   //因为expr计算,数值要与运算符号要有空格
1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23+24+25+26+27+28+29+30+31+32+33+34+35+36+37+38+39+40+41+42+43+44+45+46+47+48+49+50+51+52+53+54+55+56+57+58+59+60+61+62+63+64+65+66+67+68+69+70+71+72+73+74+75+76+77+78+79+80+81+82+83+84+85+86+87+88+89+90+91+92+93+94+95+96+97+98+99+100
[root@xbs ~]# seq -s " + " 100 | xargs expr   
5050
#awk计算数值
[root@xbs ~]# echo '5.5 4.5' | awk '{print $1+$2}'
10
[root@xbs ~]# echo '5.5 4.5' | awk '{print $1*$2}'
24.75
[root@xbs ~]# echo '5.5 4.5' | awk '{print $1+2*$2}'  //遵循数学计算规则,先乘后除
14.5
#中括号计算数值
语法:$[表达式]
[root@xbs ~]# i=5
[root@xbs ~]# num=$[i+5]
[root@xbs ~]# echo $num
10
[root@xbs ~]# num=$[i+10]
[root@xbs ~]# echo $num
15

读取用户输入

read

shell变量除了直接赋值,或者脚本传参,还有就是read命令读取,read也是内置命令

选项:
-p 设置提示信息
-t 等待用户输入超时,timeout
read -p "请输入: " vars

shell条件测试

得出真,假的概念

在shell中具有条件测试的语法

test命令
[ ] 中括号
条件测试语法 说明
语法1:test <测试表达式> 这是利用test命令进行条件测试判断式。test命令和“<测试表达式>”之间至少有一个空格
语法2:[ < 测试表达式> ] 这是通过[](中括号)进行条件判断表达式的方法,和test命令的用法相同,[]的边界和内容之间至少有一个空格
语法3:[[ < 测试表达式> ]] 这是通过[[]] (双中括号)进行条件测试表达式的方法,是比test和[] 更新的语法格式,[[]]的边界和内容之间至少有一个空格
语法4:(( < 测试表达式> )) 这是通过(())(双小括号)进行条件测试表达式的语法,一般用于if语句里,(())(双小括号)两端不需要空格

test条件测试

test命令评估一个表达式,它的结果是真,还是假。如果条件为真,那么命令执行状态码结果就为0,否则就不是为0,通过$?取值

#test命令参数
    -e 判断文件是否存在(普通文件丶目录 存在就为真,否则为假)
#简单演示
[root@xbs2 ~]# ll
total 8
-rw-r--r-- 1 root root  97 Apr 23 21:50 99.sh
-rw-r--r-- 1 root root 190 Apr 23 20:24 ip.sh
[root@xbs2 ~]# test -e 99.sh 
[root@xbs2 ~]# echo $?
0
[root@xbs2 ~]# test -e 99
[root@xbs2 ~]# echo $?
1
#test参数
语法
    #按文件类型:
关于某个文件名的r类型a侦测(存在与否),如test -e filename
-e该文件名8是否存在? (常用) 
-f该文件名a是否为文件(file)? (常用)
-d该文件名J是否为目录(directory)? (常用) 
-b该「文件名J是否为一个block device 装置?
-C该「文件名J是否为一个character device 装置?
-S该”文件名8是否为一个Socket 文件? 
-p该文件名1是否为一个FIFO (pipe) 文件?
-L该「文件名J是否为一个连结档?
    #按文件权限:
2.关于文件的权限侦测,如test -r filename
-r侦测该文件名是否具有r可读a的属性?
-W侦测该文件名是否具有r可写a的属性?
-x侦测该文件名是否具有r可执行a的属性?
-u侦测该文件名是否具有PSUIDa 的属性?
-g侦测该文件名是否具有SGIDg 的属性? 
-k 侦测该文件名是否具有FSticky bita 的属性? 
-s侦测该文件名是否为非空白文件a ?
    #两个文件之间的比较:
-nt (newer than)判断 file1 是否比file2 新
-ot (older than)判断file1 是否比file2 旧
-ef判断file2与file2是否为同一文件,可用在判断hard link 的判定上。 主要意义在判定,
两个文件是否均指向同一个inode 哩!
4.关于两个整数之间的判定,例如test n1 -eq n2
    #针对变量数值的大小比较判断
-eq两数值相等( equal )
-ne两数值不等(not equal)
-gt n1大于n2 ( greater than )
-lt n1小于n2 (less than)
-ge n1大于等于n2 ( greater than or equal )
le n1小于等于n2 (less than or equal)
    #判定字符串的数据
test -Z :判断字符串是否为空,空为真,反之为假
test -n :与-z相反,字符串为空时假,不为空时为真
注:
-n亦可省略
test str1 . str2 判定str1 是否等于str2,
著相等,则回传true
test str1 != str2判定str1 是否不等于str2,若相等,则回传false
    #多重条件判定,例如: test -r filename -a -x filename
-a (and)两状况同时成立!例如test -r file -a -x file, 则file 同时具有r与x权限时,才回传true.
-a (or)两状况任何一个成立!例如test -r file -0 -x file, 则file 具有r或x权限时,就可回传true.
! 反相状态,如test ! -X file,当file不具有x时,回传true

test条件实践

-e:判断文件是否为真,存在就为真,否则就为假
    shell对于真假判断的逻辑,提供了两个符号`"&&":与运算,并且的,并且的两端是递进的关系 "||":或运算 
A条件 && B条件 :当A条件成立,并且执行B条件
A条件 || B条件 :当A条件不成立的时候,才会执行B条件
#例:
语法:test 测试参数 文件名/目录名 对结果进行判断执行的逻辑动作
[root@xbs ~]# ll
total 4
-rw-r--r-- 1 root root 190 Apr 25 14:19 ip.sh
[root@xbs ~]# test -e ip.sh  && echo 这个文件存在
这个文件存在
[root@xbs ~]# test -e xxx.sh  && echo "这个文件存在"
[root@xbs ~]# echo $?
1            #//因为没有xxx,sh这个文件,所以没有输出,状态值也非0
[
[root@xbs ~]# test -e "jiujiu" && echo "这个文件已存在,无法创建" || touch jiujiu   //与和或的运用
这个文件已存在,无法创建
[root@xbs ~]# ll
total 4
-rw-r--r-- 1 root root 190 Apr 25 14:19 ip.sh
-rw-r--r-- 1 root root   0 Apr 27 14:18 jiujiu
[root@xbs ~]# test -e "like" && echo "这个文件已存在,无法创建" || touch hello
[root@xbs ~]# ll
total 4
-rw-r--r-- 1 root root   0 Apr 27 14:21 hello
-rw-r--r-- 1 root root 190 Apr 25 14:19 ip.sh
-rw-r--r-- 1 root root   0 Apr 27 14:18 jiujiu
0