6 脚本相关工具
6.1 信号捕捉trap
一个二进制文件, 运行启动为进程后, 我们可以向进程发送信号, 来完成对该进程的操作
Linux信号种类:
[root@demo-c8 ~]# kill -L
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
[root@demo-c8 ~]# trap -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
范例: ping命令运行过程中, 执行ctrl + c, 发送2信号, SIGINT
ping命令默认是一个死循环, 我们可以输入ctrl + c, 向ping进程发送2信号, SIGINT
[root@demo-c8 ~]# ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.056 ms
^C
--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 64ms
rtt min/avg/max/mdev = 0.056/0.058/0.060/0.002 ms
一个信号可以用三种方法描述:
2) SIGINT
#1. 数字编号: 2
#2. 信号全称: SIGINT
#3. 信号简称: INT
trap '触发指令' 信号: 进程收到信号
后, 执行触发命令
进程收到系统发出的指定信号后, 将执行自定义指令, 而不会执行原操作
trap '' 信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行finish函数, finish函数就是脚本退出时, 自动执行的一段代码, 可以自定义
这个finish函数名可以自定义, 只要前后一致即可, 但一般起名为finish, 代表脚本结束退出
范例:
[23:17:18 root@centos8 /opt]#vim trap1.sh
#!/bin/bash
# ctrl + c 和 ctrl + \ 都可以退出正在执行的命令, 具体看命令
trap 'echo "Press ctrl+c or ctrl+\\"' int quit # 脚本执行过程中, 如果接收到了int, 2信号, 或者 quit 3信号, 就触发执行命令echo "Press ctrl+c or ctrl+\\"
trap -p # 打印自定义的信号操作
for((i=0;i<=10;i++));do # 打印1-10, 在这期间, trap 'echo "Press ctrl+c or ctrl+\\"' int quit会生效, 如果执行了ctrl+c或者ctrl+\\, 那么会打印echo "Press ctrl+c or ctrl+\\, 而不会真正退出脚本
sleep 1
echo $i
done # 1-10打印完毕
trap '' int # 自定义第二个信号, 如果发送2信号, 那么不做任何操作
trap -p
for((i=11;i<=20;i++));do # 11-20正常打印, 在此期间, 执行ctrl+c是没用的
sleep 1
echo $i
done
trap '-' int # 接收到2信号, 正常执行2信号的操作, 退出脚本
trap -p
for((i=21;i<=30;i++));do # 21-30打印期间, 一旦执行了ctrl+c就退出脚本
sleep 1
echo $i
done
[root@demo-c8 opt]# bash trap.sh
trap -- 'echo "Press ctrl+c or ctrl+\\"' SIGINT
trap -- 'echo "Press ctrl+c or ctrl+\\"' SIGQUIT
0
1
^CPress ctrl+c or ctrl+\
2
3
4
^CPress ctrl+c or ctrl+\
5
6
7
8
9
10
trap -- '' SIGINT
trap -- 'echo "Press ctrl+c or ctrl+\\"' SIGQUIT
11
12
^C13
^C^C^C14
15
16
17
18
19
20
trap -- 'echo "Press ctrl+c or ctrl+\\"' SIGQUIT
21
22
^C
范例: finish函数使用
无论哪种方法退出的程序, 都可以触发finish函数
1. ctrl+c
2. kill命令
3. 将正在执行命令的终端窗口关闭
[23:18:12 root@centos8 /opt]#vim trap2.sh
#!/bin/bash
finish(){
echo finish| tee -a /root/finish.log # tee命令既能把管道前的命令结果, 输出在屏幕, 也能把管道接收到的数据, 重定向到文件
}
trap finish exit
while true ;do
echo running
sleep 1
done
[root@demo-c8 opt]# bash trap2.sh
running
running
running
running
^Cfinish
[root@demo-c8 opt]# cat ~/finish.log
finish
[root@demo-c8 opt]#
6.2 创建临时文件mktemp
临时创建的文件/文件夹有可能与系统其他文件冲突, 比如, 多个用户, 同时运行了一个程序, 同时创建了一个同名文件或目录, 此时就会产生冲突
mktemp 命令用于创建并显示临时文件/文件夹,可避免冲突, 因为mktemp创建的文件和文件夹名字都是随机生成的
格式:
mktemp [OPTION]... [TEMPLATE]
说明:
TEMPLATE(文件名模板): filenameXXX,X至少要出现三个, X的个数决定了生成的随机字符串的个数. XXX可以放在前面, 也可以放在后面
filename为自定义的文件名, XXX会由mktemp随机生成, 确保每次生成的文件名不一样
常见选项:
-d 创建临时目录
-p DIR或--tmpdir=DIR 指明临时文件所存放目录位置
范例: mktemp生成临时文件
[root@demo-c8 ~]# mktemp /data/tmpfileXX
mktemp: too few X's in template ‘/data/tmpfileXX’ # 至少2个X
[root@demo-c8 ~]# mktemp /data/tmpfileXXX
/data/tmpfilehPC # 生成的临时文件, 随机字符串, hPC
[root@demo-c8 ~]# ll /data/tmpfilehPC
-rw-------. 1 root root 0 Sep 15 21:19 /data/tmpfilehPC
[root@demo-c8 ~]# mktemp /data/XXXXtmpfileXXX
/data/XXXXtmpfileIeM
[root@demo-c8 ~]# TMPFILE=`mktemp /data/XXXXtmpfile`
[root@demo-c8 ~]# echo $TMPFILE
/data/8dNStmpfile
[root@demo-c8 ~]# ll /data/8dNStmpfile
-rw-------. 1 root root 0 Sep 15 21:20 /data/8dNStmpfile
范例: 存放文件的路径默认会存放到当前工作目录
[root@demo-c8 ~]# mktemp file1XXX
file1MR4
[root@demo-c8 ~]# ll file1MR4
-rw-------. 1 root root 0 Sep 15 21:22 file1MR4
范例: 如果不指定文件名, 那么mktemp会默认在/tmp目录下. 创建临时文件
[root@demo-c8 ~]# mktemp
/tmp/tmp.p9dkev6HY0
[root@demo-c8 ~]# ll /tmp/tmp.p9dkev6HY0
-rw-------. 1 root root 0 Sep 15 21:23 /tmp/tmp.p9dkev6HY0
范例: mktemp -d, 生成临时文件夹
[root@demo-c8 ~]# mktemp -d /data/tempdirXXX
/data/tempdirQx6
[root@demo-c8 ~]# ll /data/tempdirQx6/ -d
drwx------. 2 root root 6 Sep 15 21:25 /data/tempdirQx6/
范例: rm删除脚本
[root@demo-c8 opt]# vim rm.sh
#!/bin/bash
DIR=`mktemp -d /tmp/$(date "+%F_%T"_XXXXX)` &> /dev/null
#echo $DIR
mv $* ${DIR} && echo $* moved to ${DIR} || echo "文件删除失败"
[root@demo-c8 opt]# alias rm=/opt/rm.sh
[root@demo-c8 opt]# PATH+=/opt
[root@demo-c8 opt]# chmod +x rm.sh
[root@demo-c8 opt]# rm trap2.sh trap.sh
trap2.sh trap.sh moved to /tmp/2022-09-15_21:30:13_5Xofb
[root@demo-c8 opt]# ll /tmp/2022-09-15_21\:30\:13_5Xofb/
total 8
-rw-r--r--. 1 root root 126 Sep 15 21:08 trap2.sh
-rw-r--r--. 1 root root 1147 Sep 15 20:51 trap.sh
6.3 安装复制文件install
install 功能相当于 cp,mkdir, chmod,chown,chgrp 等相关工具的集合, 尤其适用于软件安装过程
通过install一条命令, 可以针对文件做一些列的操作
install命令格式:
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录
选项:
-m MODE,默认755
-o OWNER
-g GROUP
-d DIRNAME 目录
范例: 创建/root/anaconda-ks.cfg复制到/data目录, 改名为a.cfg, 并且a.cfg的属主为wang, 属组为bin, 权限为600
[23:24:11 root@centos8 /opt]#useradd wang
[23:24:18 root@centos8 /opt]#install -m 600 -o wang -g bin ~/anaconda-ks.cfg /data/a.cfg
[23:24:40 root@centos8 /opt]#ll /data/a.cfg
-rw------- 1 wang bin 1387 Sep 14 23:24 /data/a.cfg
范例: install创建文件夹
[23:24:47 root@centos8 /opt]#useradd ning
[23:25:08 root@centos8 /opt]#install -m 700 -o ning -g daemon -d /data/testdir
[23:25:16 root@centos8 /opt]#ll -d /data/testdir/
drwx------ 2 ning daemon 6 Sep 14 23:25 /data/testdir/
6.4 交互式转化批处理工具expect
expect 是由Don Libes基于 Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作
的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
范例: 交互式命令举例
[root@demo-c8 opt]# ssh [email protected]
The authenticity of host '10.0.0.108 (10.0.0.108)' can't be established.
ECDSA key fingerprint is SHA256:oK1lzhvBFfBY6F367+6w1GbkkVarP94RW7+JwcALr6c.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.108' (ECDSA) to the list of known hosts.
[email protected]'s password:
[root@demo-c8 opt]# fdisk /dev/sda
Welcome to fdisk (util-linux 2.32.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): ^C
范例: expect命令需要单独安装
[root@demo-c8 opt]# yum -y install expect
expect 语法:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
常见选项:
-c:从命令行执行expect脚本,默认expect是交互地执行的
-d:可以输出调试信息
范例:
expect -c 'expect "\n" {send "pressed enter\n"}'
expect -d ssh.exp
expect中相关命令
- spawn 启动新的进程
- expect 从进程接收字符串
- send 用于向进程发送字符串
- interact 允许用户交互
- exp_continue 匹配多个字符串, 在执行动作后加此命令
1. 通过spawn命令, 让expect启动一个交互式程序, 如ssh, scp等
启动后, 通过expect的expect命令去监听捕获交互式程序返回的字符串
2. 通过expect命令监听并捕获交互式命令执行过程中产生的信息, 比如一旦捕获ssh命令返回的password
关键字就知道需要输入密码了
3. 通过send命令, 替代人, 向交互式程序, 发送提前写好的password口令
interact命令: 返回交互式
exp_continue命令: 当需要多次匹配字符串时, 要用exp_continue
4. expect本身就是一套工具, 不同于Shell语法, 所以不能用bash执行, 执行expect脚本, 需要给expect脚本添加x权限, 然后指定相对或者绝对路径执行
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:匹配一次即退出expect
[root@centos8 test]#expect
expect1.1> expect "hi" {send "You said hi\n"}
hahahixixi # 输入hahahixixi后, 按Enter, 就会打印You said hi, 因为expect会捕捉字符串"hi"
You said hi
匹配到hi后,会输出“you said hi”,并换行
多分支模式语法:匹配几个条件之一, 但只匹配一次, 匹配成功就退出
[root@centos8 ~]#expect
expect1.1> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"} "bye" { send "Good bye\n" }
hehe
Hehe yourself
expect1.2> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"} "bye" { send "Good bye\n" }
bye
Good bye
expect1.3> expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n"} "bye" { send "Good bye\n" }
hi
You said hi
expect1.4>
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send " Good bye\n"}
}
[root@centos8 ~]#expect
expect1.1> expect {
+> "hi" { send "You said hi\n"}
+> "hehe" { send "Hehe yourself\n"}
+> "bye" { send "Good bye\n"}
+> }
bye
Good bye
expect1.2>
范例1: 利用expect, 执行scp远程拷贝文件
[root@demo-c8 opt]# vim expect1
#!/usr/bin/expect
spawn scp /etc/fstab [email protected]:/opt
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "000000\n" }
}
expect eof
[root@demo-c8 opt]# chmod +x expect1
[root@demo-c8 opt]# ./expect1
spawn scp /etc/fstab [email protected]:/opt
[email protected]'s password:
fstab 100% 709 744.2KB/s 00:00
[12:17:23 root@CentOS-8-8 ~]#hostname -I
10.0.0.88
[13:00:16 root@CentOS-8-8 ~]#ll /opt
total 4
-rw-r--r-- 1 root root 709 Sep 16 12:59 fstab
范例2: 利用expect, 执行ssh远程登录
通过expect登录后, 需要执行interact, 返回交互式环境, 否则登录到远程服务器后, 无法执行命令
[root@demo-c8 opt]# vim expect2
#!/usr/bin/expect
spawn ssh [email protected]
expect {
"yes/no" { send "yes\n"; exp_continue}
"password" { send "000000\n" }
}
interact
[root@demo-c8 opt]# chmod +x expect2
[root@demo-c8 opt]# ./expect2
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 12:17:23 2022 from 10.0.0.1
[13:01:47 root@CentOS-8-8 ~]#hostname -I
10.0.0.88
范例3: expect支持变量
[root@demo-c8 opt]# vim expect3
#!/usr/bin/expect
set IP 10.0.0.187
set USER root
spawn ssh $USER@$IP
expect {
"yes/no" { send "yes\n" ; exp_continue }
"password" { send "000000\n" }
}
interact
[root@demo-c8 opt]# chmod +x expect3
[root@demo-c8 opt]# ./expect3
spawn ssh [email protected]
The authenticity of host '10.0.0.187 (10.0.0.187)' can't be established.
ECDSA key fingerprint is SHA256:COyZn2dhR2KD/ZA+18iAKNNAs2nBGwmdV7XfreXCKJ0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.187' (ECDSA) to the list of known hosts.
[email protected]'s password:
Last login: Fri Sep 16 12:17:28 2022 from 10.0.0.1
[13:17:01 root@centos-7-6 ~]#hostname -I
10.0.0.187
范例4: expect支持位置参数
[root@demo-c8 opt]# vim expect4
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n"; exp_continue }
"password" { send "$password\n" }
}
interact
[root@demo-c8 opt]# chmod +x expect4.sh
[root@demo-c8 opt]# ./expect4 10.0.0.109 root 000000
spawn ssh [email protected]
The authenticity of host '10.0.0.109 (10.0.0.109)' can't be established.
ECDSA key fingerprint is SHA256:NxFQQehES7mVvRwJ6IGdnpmrfdaA67v+76SA2ym47jM.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.109' (ECDSA) to the list of known hosts.
[email protected]'s password:
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-76-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Fri Sep 16 13:21:10 CST 2022
System load: 0.1 Processes: 164
Usage of /: 2.1% of 91.17GB Users logged in: 1
Memory usage: 5% IP address for ens33: 10.0.0.109
Swap usage: 0%
* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.
https://ubuntu.com/blog/microk8s-memory-optimisation
243 packages can be updated.
178 updates are security updates.
New release '20.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Fri Sep 16 12:17:18 2022 from 10.0.0.1
root@u18:~#
范例5: expect执行多个命令, 先ssh远程登录, 再创建用户并且设置密码
执行spawn后, 屏幕上所有返回的内容都可以被expect捕获, 即使是命令提示符中的字符串或者是#
也可以被捕获, 一旦捕获到#
就说明ssh登录成功了
[root@demo-c8 opt]# vim expect5
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n"; exp_continue }
"password" { send "$password\n" }
}
expect "#" { send "useradd test\n" }
expect "#" { send "echo 000000 | passwd --stdin test\n" }
send "exit\n"
expect eof
[root@demo-c8 opt]# chmod +x expect5
[root@demo-c8 opt]# ./expect5 10.0.0.88 root 000000
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:45:22 2022 from 10.0.0.108
[13:45:36 root@CentOS-8-8 ~]#useradd test
[13:45:36 root@CentOS-8-8 ~]#echo 000000 | passwd --stdin test
Changing password for user test.
passwd: all authentication tokens updated successfully.
[13:45:36 root@CentOS-8-8 ~]#exit
logout
Connection to 10.0.0.88 closed.
[root@demo-c8 opt]#
范例6: Shell脚本调用expect, 因为expect支持标准输入重定向, 所以可以在Shell脚本中, 把expect脚本的内容, 通过标准输入重定向传给expect命令执行
[root@demo-c8 opt]# vim expect6.sh
#!/bin/bash
ip=$1
user=$2
password=$3
expect <
[root@demo-c8 opt]# bash expect6.sh 10.0.0.88 root 000000
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:47:28 2022 from 10.0.0.108
[13:48:05 root@CentOS-8-8 ~]#useradd hehehe
[13:48:05 root@CentOS-8-8 ~]#echo 000000 | passwd --stdin hehehe
Changing password for user hehehe.
passwd: all authentication tokens updated successfully.
[13:48:05 root@CentOS-8-8 ~]#exit
logout
Connection to 10.0.0.88 closed.
[root@demo-c8 opt]#
范例7: Shell脚本利用循环调用expect在CentOS和Ubuntu上批量创建同一个用户
[root@demo-c8 opt]# vim expect7.sh
#!/bin/bash
NET=10.0.0
user=root
password=000000 # 服务器密码需要一致
# for循环定义ip地址
for ID in 109 88 187;do
ip=$NET.$ID
#echo $ip
expect <
[root@demo-c8 opt]# bash expect7.sh
spawn ssh [email protected]
[email protected]'s password:
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-76-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Fri Sep 16 13:50:10 CST 2022
System load: 0.06 Processes: 163
Usage of /: 2.1% of 91.17GB Users logged in: 1
Memory usage: 5% IP address for ens33: 10.0.0.109
Swap usage: 0%
* Super-optimized for small spaces - read how we shrank the memory
footprint of MicroK8s to make it the smallest full K8s around.
https://ubuntu.com/blog/microk8s-memory-optimisation
243 packages can be updated.
178 updates are security updates.
New release '20.04.5 LTS' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Fri Sep 16 13:21:11 2022 from 10.0.0.108
root@u18:~# useradd hahahaha
root@u18:~# exit
logout
Connection to 10.0.0.109 closed.
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:48:05 2022 from 10.0.0.108
[13:50:11 root@CentOS-8-8 ~]#useradd hahahaha
[13:50:11 root@CentOS-8-8 ~]#exit
logout
Connection to 10.0.0.88 closed.
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:18:09 2022 from 10.0.0.108
[13:50:12 root@centos-7-6 ~]#useradd hahahaha
[13:50:12 root@centos-7-6 ~]#exit
logout
Connection to 10.0.0.187 closed.
[root@demo-c8 opt]#
范例8: 批量禁用SElinux
[root@demo-c8 opt]# vim expect8.sh
#!/bin/bash
NET=10.0.0
user=root
password=000000
for ID in 88 187 ;do
ip=$NET.$ID
expect <
[root@demo-c8 opt]# bash expect8.sh
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:50:10 2022 from 10.0.0.108
[13:54:09 root@CentOS-8-8 ~]#sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
[13:54:09 root@CentOS-8-8 ~]#setenforce 0
setenforce: SELinux is disabled
[13:54:09 root@CentOS-8-8 ~]#exit
logout
Connection to 10.0.0.88 closed.
spawn ssh [email protected]
[email protected]'s password:
Last login: Fri Sep 16 13:50:12 2022 from 10.0.0.108
[13:54:09 root@centos-7-6 ~]#sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
[13:54:09 root@centos-7-6 ~]#setenforce 0
setenforce: SELinux is disabled
[13:54:09 root@centos-7-6 ~]#exit
logout
Connection to 10.0.0.187 closed.
[root@demo-c8 opt]#
7 数组array
7.1 数组介绍
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引(下标)
- 索引的编号从0开始,属于数值索引
- 索引支持使用自定义的格式,不仅仅是数值格式,即为关联索引,bash4.0版本之后开始支持
[root@demo-c8 ~]# bash --version
GNU bash, version 4.4.19(1)-release (x86_64-redhat-linux-gnu)
- bash的数组支持稀疏格式(索引不连续)
定义数组时, 都是按照连续的内存空间, 存放数组的每一个元素. 一旦, 后期删除了某一个元素, 那么该元素所占据的内存空间位置, 就会处于空白状态, 其对应的索引也不会指向其他元素, 其后面的元素, 也不会向前移动
7.2 声明数组
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换
7.3 数组赋值
数组元素的赋值
- 一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
范例:
weekdays[0]="Sunday"
weekdays[4]="Thursday"
[root@demo-c8 ~]# weekdays[0]="Sunday"
[root@demo-c8 ~]# weekdays[4]="Thursday"
[root@demo-c8 ~]# echo ${weekdays[0]}
Sunday
[root@demo-c8 ~]# echo ${weekdays[4]}
Thursday
[root@demo-c8 ~]# echo ${weekdays[1]} # 没有赋值的索引位, 返回空
[root@demo-c8 ~]# echo ${weekdays[2]}
[root@demo-c8 ~]# echo ${weekdays[3]}
[root@demo-c8 ~]# echo ${weekdays} # 如果省略[INDEX]表示引用下标为0的元素
Sunday
[root@demo-c8 ~]# echo ${weekdays[*]} # [*]和[@]返回所有值
Sunday Thursday
[root@demo-c8 ~]# echo ${weekdays[@]}
Sunday Thursday
- 一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
范例:
title=("ceo" "coo" "cto") # 数组的每个元素用空格分开
num=({0..10})
alpha=({a..g})
file=( *.sh ) # 数组还支持通配符, 直接匹配目录内的文件名, 然后生成数组
[root@demo-c8 ~]# title=("ceo" "coo" "cto")
[root@demo-c8 ~]# echo ${title[*]}
ceo coo cto
[root@demo-c8 ~]# echo {1..10}
1 2 3 4 5 6 7 8 9 10
[root@demo-c8 ~]# num=({1..10})
[root@demo-c8 ~]# echo ${num[*]}
1 2 3 4 5 6 7 8 9 10
[root@demo-c8 ~]# alpha=({a..g})
[root@demo-c8 ~]# echo ${alpha[*]}
a b c d e f g
[root@demo-c8 ~]# cd /opt
[root@demo-c8 opt]# touch file{1..10}.sh
[root@demo-c8 opt]# file=( *.sh )
[root@demo-c8 opt]# echo ${file[*]}
file10.sh file1.sh file2.sh file3.sh file4.sh file5.sh file6.sh file7.sh file8.sh file9.sh
- 只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
- 交互式对数组赋值
read -a ARRAY
7.4 显示所有数组
显示所有数组:
declare -a
范例:
[root@centos8 ~]#declare -a
declare -a BASH_ARGC=()
declare -a BASH_ARGV=()
declare -a BASH_COMPLETION_VERSINFO=([0]="2" [1]="7")
declare -a BASH_LINENO=()
declare -ar BASH_REMATCH=()
declare -a BASH_SOURCE=()
declare -ar BASH_VERSINFO=([0]="4" [1]="4" [2]="19" [3]="1" [4]="release"
[5]="x86_64-redhat-linux-gnu")
declare -a DIRSTACK=()
declare -a FUNCNAME
declare -a GROUPS=()
declare -a PIPESTATUS=([0]="0")
7.5 引用数组
引用数组元素
${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素
范例:
[root@centos8 ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@centos8 ~]#echo ${title[1]}
coo
[root@centos8 ~]#echo ${title}
ceo
[root@centos8 ~]#echo ${title[2]}
cto
[root@centos8 ~]#echo ${title[3]}
引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
范例:
[root@centos8 ~]#echo ${title[@]}
ceo coo cto
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
数组的长度, 即数组中元素的个数
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
范例:
[root@centos8 ~]#echo ${#title[*]}
3
7.6 删除数组
删除数组中的某元素,会导致稀疏格式
unset ARRAY[INDEX]
[root@centos8 ~]#echo ${title[*]}
ceo coo cto
[root@centos8 ~]#unset title[1]
[root@centos8 ~]#echo ${title[*]}
ceo cto
删除整个数组
unset ARRAY
范例:
[root@centos8 ~]#unset title
[root@centos8 ~]#echo ${title[*]}
[root@centos8 ~]#
7.7 数组数据处理
数组切片:
${ARRAY[@/*]:offset:number}
offset #要跳过的元素个数, offset=2, 表示从索引位0开始, 跳过2个元素, 从第三个元素开始取
number #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@/*]:offset}
范例:
[root@centos8 ~]#num=({0..10})
[root@centos8 ~]#echo ${num[*]:2:3} # 跳过0和1, 从2开始取, 取3个元素, 即2,3,4
2 3 4
[root@centos8 ~]#echo ${num[*]:6} # 跳过6个元素, 即0-5, 从6开始, 取到最后10
6 7 8 9 10
范例: 也可以针对数组中, 某一个元素进行切片
[root@demo-c8 opt]# title=(ceo cto coo)
[root@demo-c8 opt]# echo ${title[0]:1:1} # 针对0号位索引的值, 进行切片
e
[root@demo-c8 opt]# echo ${title[0]:1:2}
eo
[root@demo-c8 opt]# echo ${title[0]:2:1}
o
向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value # 把当前数组中, 元素的个数, 作为新追加的元素的索引下标, 因为索引是从0开始, 所以在没有稀疏索引的情况下, 元素个数, 就是追加的元素的索引下标
ARRAY[${#ARRAY[@]}]=value
范例:
[root@centos8 ~]#num[${#num[@]}]=11
[root@centos8 ~]#echo ${#num[@]}
12
[root@centos8 ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11
7.8 关联数组
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用, 并且关联数组不能和普通数组互相转换
范例:
[root@centos8 ~]#declare -A course
[root@centos8 ~]#declare -a course
-bash: declare: course: cannot convert associative to indexed array
[root@centos8 ~]#file=( *.sh )
[root@centos8 ~]#declare -A file
-bash: declare: file: cannot convert indexed to associative array
关联数组类似字典, 给值取一个有意思的索引名称
范例: 关联数组必须先声明, 后赋值
[root@demo-c8 opt]# name[ceo]=zhao
[root@demo-c8 opt]# name[cto]=qian
[root@demo-c8 opt]# name[coo]=sun
[root@demo-c8 opt]# echo ${name[ceo]}
sun
[root@demo-c8 opt]# echo ${name[cto]}
sun
[root@demo-c8 opt]# echo ${name[coo]}
sun
[root@demo-c8 opt]# echo ${name}
sun
[root@demo-c8 opt]# declare -A name
-bash: declare: name: cannot convert indexed to associative array
[root@demo-c8 opt]# unset name
[root@demo-c8 opt]# declare -A name
[root@demo-c8 opt]# name[ceo]=zhao
[root@demo-c8 opt]# name[cto]=qian
[root@demo-c8 opt]# name[coo]=sun
[root@demo-c8 opt]# echo ${name[cto]}
qian
[root@demo-c8 opt]# echo ${name[ceo]}
zhao
[root@demo-c8 opt]# echo ${name[coo]}
sun
[root@demo-c8 opt]# echo ${name[*]}
zhao qian sun
[root@demo-c8 opt]# echo ${name} # 对关联数组取值, 如果不加索引名称, 那么返回空, 因为关联数组不是按照数字索引构建的, 所以无法返回索引为0的值
[root@demo-c8 opt]#
范例: for循环返回关联数组索引值
[root@demo-c8 opt]# declare -A student
[root@demo-c8 opt]# student[name1]=name1
[root@demo-c8 opt]# student[name2]=name2
[root@demo-c8 opt]# student[name3]=name3
[root@demo-c8 opt]# student[name4]=name4
[root@demo-c8 opt]# student[name5]=name5
[root@demo-c8 opt]# student[name6]=name6
[root@demo-c8 opt]# student[name7]=name7
[root@demo-c8 opt]# student[name8]=name8
[root@demo-c8 opt]# student[name9]=name9
[root@demo-c8 opt]# student[name10]=name10
[root@demo-c8 opt]# for i in {1..10};do echo student[name$i]=${student[name$i]}; done
student[name1]=name1
student[name2]=name2
student[name3]=name3
student[name4]=name4
student[name5]=name5
student[name6]=name6
student[name7]=name7
student[name8]=name8
student[name9]=name9
student[name10]=name10
范例:生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min
范例:编写脚本,定义一个数组,数组中的元素对应的值是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和
#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
fi
done
echo "Lines: $lines"
练习:
- 输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序
- 将下图所示,实现转置矩阵 matrix.sh
1 2 3 1 4 7
4 5 6 ===> 2 5 8
7 8 9 3 6 9
- 打印杨辉三角形
8 字符串处理
将字符串存到变量中, 再对变量值, 进行字符串处理, 而变量值本身是不变的
8.1 字符串切片
基于偏移量取字符串
#返回字符串变量var的长度
${#var}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var:offset:number}
#取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var: -length}
#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}
#先从最右侧向左取, 取到第length个字符作为开始,再向右取, 取到距离最右侧offset个字符之间的内容,注意:-length前有空格
${var: -length:-offset}
范例:
[root@centos8 script40]#str=abcdef我你他
[root@centos8 script40]#echo ${#str}
9
[root@centos8 script40]#echo ${str:2} # offset=2, 那么从第三个字符开始取, 取到最后
cdef我你他
[root@centos8 script40]#echo ${str:2:3} # offset=2, 那么从第三个字符开始取, 取3个字符, 即为cde
cde
[root@centos8 script40]#echo ${str:-3} # -length前必须有空格, 否则会取整个字符串
abcdef我你他
[root@centos8 script40]#echo ${str: -3} # -3, 取字符串最右侧的三个字符串, 显示顺序为从左显示
我你他
${var:offset:-length}: 掐头去尾
[root@centos8 script40]#echo ${str:2:-3} # 2:-3, 去掉左侧两个字符, 去掉右侧三个字符, 保留中间字符串, 掐头去尾
cdef
${var: -length:-offset}: length的值必须大于offset的值, 都是从右边开始取
[root@centos8 script40]#echo ${str: -2:-3}
-bash: -3: substring expression < 0
[root@centos8 script40]#echo ${str: -3:-2} # 从右侧开始, 取到第三个字符串, 再从尤其开始, 取到2个字符, 保留中间部分
我
[root@centos8 script40]#echo ${str:-3:-2} # -length前面要有空格
abcdef我你他
[root@centos8 script40]#echo ${str: -3:-2}
我
[root@centos8 script40]#echo ${str: -5:-2}
ef我
基于模式取子串
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var#*word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}
范例:
[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file#*/}
log/messages
[root@centos8 ~]#echo ${file##*/}
messages
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}
范例: 处理路径
[root@centos8 ~]#file="var/log/messages"
[root@centos8 ~]#echo ${file%/*}
var/log
[root@centos8 ~]#echo ${file%%/*}
var
范例: 取url的端口号和协议
[root@centos8 ~]#url=http://www.baidu.com:8080
[root@centos8 ~]#echo ${url##*:}
8080
[root@centos8 ~]#echo ${url%%:*}
http
8.2 查找替换
#查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
[root@demo-c8 opt]# getent passwd root
root:x:0:0:root:/root:/bin/bash
[root@demo-c8 opt]# line=`getent passwd root`
[root@demo-c8 opt]# echo $line
root:x:0:0:root:/root:/bin/bash
[root@demo-c8 opt]# echo ${line/root/ROOT}
ROOT:x:0:0:root:/root:/bin/bash
[root@demo-c8 opt]# echo ${line//root/ROOT}
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
# 变量本身是不变的
[root@demo-c8 opt]# echo ${line}
root:x:0:0:root:/root:/bin/bash
[root@demo-c8 opt]# sed -n '/^UUID/p' /etc/fstab
UUID=b1ab1ace-2582-4afd-8693-39bd9855041c / xfs defaults 0 0
UUID=d5131695-82b3-4a23-bc28-5c8a4bf381a0 /boot ext4 defaults 1 2
UUID=bdd66510-e510-4fe7-ba71-e2a35e6dc492 /data xfs defaults 0 0
UUID=05c944fb-d6f9-4544-ba10-8b7bf3cc8fed swap swap defaults 0 0
[root@demo-c8 opt]# sed -n '/^UUID/p' /etc/fstab | sed -n "1p"
UUID=b1ab1ace-2582-4afd-8693-39bd9855041c / xfs defaults 0 0
[root@demo-c8 opt]# fstab=`sed -n '/^UUID/p' /etc/fstab | sed -n "1p"`
[root@demo-c8 opt]# echo $fstab
UUID=b1ab1ace-2582-4afd-8693-39bd9855041c / xfs defaults 0 0
[root@demo-c8 opt]# echo ${fstab/#UUID/\#UUID}
#UUID=b1ab1ace-2582-4afd-8693-39bd9855041c / xfs defaults 0 0
[root@demo-c8 opt]# line=`getent passwd root`
[root@demo-c8 opt]# echo ${line}
root:x:0:0:root:/root:/bin/bash
[root@demo-c8 opt]# echo ${line/%\/bin\/bash/\/sbin\/nologin}
root:x:0:0:root:/root:/sbin/nologin
8.3 查找并删除
#删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}
删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}
删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
8.4 字符大小写转换
#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
9 高级变量
9.1 高级变量赋值
变量的三种状态:
- 正常赋值
[root@demo-c8 ~]# name=admin
- 赋了一个空串
[root@demo-c8 ~]# name=""
[root@demo-c8 ~]# name=""
- 被删除, 也就是不存在此变量
[root@demo-c8 ~]# unset name
高级变量赋值就是基于var1的状态, 给var2赋值
范例: 变量配置方式1
变量title参考变量name取值 title=${name-ceo}
如果变量name是空串, title也为空串
[root@demo-c8 ~]# unset name;name=""; title=${name-ceo}; echo $title
[root@demo-c8 ~]# unset name;name=; title=${name-ceo}; echo $title
如果变量name不存在, 那么title按照expr取值
[root@demo-c8 ~]# unset name; title=${name-ceo}; echo $title
ceo
如果变量name被赋值了, 那么title按照name的值赋值
[root@demo-c8 ~]# unset name;name=admin; title=${name-ceo}; echo $title
admin
9.2 高级变量用法-有类型变量
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
选项:
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare -l var=UPPER, 即使赋值的是大写, 也会存成小写...
. .
-u 声明变量为大写字母 declare -u var=lower
9.3 变量间接引用
默认情况下, 如果命令中包含变量, 那么会直接把变量替换, 然后返回, 并不会先替换变量, 再执行命令
[root@demo-c8 ~]# n=10
[root@demo-c8 ~]# echo {0..$n} # $n替换为10, 直接被echo返回{0..10}
{0..10}
9.3.1 eval命令
eval命令将会首先扫描命令行进行所有的变量置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令会对变量进行两次扫描
eval跟的一定是个可执行命令
范例: eval直接存在变量里的命令
[root@demo-c8 ~]# CMD=whoami
[root@demo-c8 ~]# eval $CMD # 先把$CMD替换为whoami, 然后执行whoami命令
whoami
[root@demo-c8 ~]# eval $CMD
root
范例: for循环配合eval
[root@demo-c8 ~]# n=10
[root@demo-c8 ~]# for i in {0..$n}; do echo $i; done
{0..10}
[root@demo-c8 ~]# for i in `eval echo {0..$n}`; do echo $i; done
0
1
2
3
4
5
6
7
8
9
10
范例:
[root@centos8 ~]# CMD=whoami
[root@centos8 ~]# echo $CMD
whoami
[root@centos8 ~]# eval $CMD
root
[root@centos8 ~]# n=10
[root@centos8 ~]# echo {0..$n}
{0..10}
[root@centos8 ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
[root@centos8 ~]#for i in `eval echo {1..$n}` ;do echo i=$i ;done
i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9 i=10
[root@centos8 ~]#i=1
[root@centos8 ~]#j=a
[root@centos8 ~]#$j$i=hello
-bash: a1=hello: command not found
[root@centos8 ~]#eval $j$i=hello
[root@centos8 ~]#echo $j$i
a1
9.3.2 间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用. variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为
名 值
variable1=variable2
variable2=value
Bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1 # 先将$variable1替换为variable1的值, 然后通过$再取variable2的值. \$需要转义, 表示$符号本身. 因为$$是系统环境变量, 表示当前进程的PID
tempvar=${!variable1}
范例:
[root@demo-c8 ~]# var1=var2
[root@demo-c8 ~]# var2=root
[root@demo-c8 ~]# eval temvar=\$$var1
[root@demo-c8 ~]# echo $temvar
root
[root@demo-c8 ~]# temvar=${!var1}
[root@demo-c8 ~]# echo $temvar
root