Shell信号发送与捕捉
信号(Signal):是一种在软件层面模拟的中断机制,在系统中当一个进程接收到一种特定的信号时会触发相应的处理函数。
Linux借助信号实现了不同进程之间的通信机制。另外一种方式是利用信号控制shell脚本的执行流程。
进程可以通过三种方式来响应一个信号:
对于不处理信号的部分进行筛选时,默认会对所有输入样本进行归一化处理。然而,在某些特殊情况下(如仅考虑关键节点时),我们可能会选择性地对某些节点进行标记而不对其余节点施加影响。
2)捕捉信号。
3)执行缺省操作,Linux对每种信号都规定了默认操作。
Linux支持两种信号:
一种是标准类型的标记符(标记符),编号为1至31(即共包含31个不同类型的标记符),被称为非实时的不可靠标记符(non-realtime, non-reliable)。这些标记符不具备队列功能(queue)。当发送多个相同的标记符时(例如连续发送三次同一个标识),接收端程仅能捕获一个(即首次成功接收者)。若前一个未被处理完成,则后续的将被丢弃。
另外一种是扩展信号,在编号范围为32至64时被称为可靠信号(实时),它支持队列操作,并在发射了多少进程的情况下被接收了多少次。
| 编号 | 信号名称 | 缺省动作 | 描述 |
|---|---|---|---|
| 1 | SIGHUP | 终止 | 终止进程,挂起 |
| 2 | SIGINT | 终止 | 键盘输入中断命令,一般是CTRL+C |
| 3 | SIGQUIT | CoreDump | 键盘输入退出命令,一般是CTRL+| |
| 4 | SIGILL | CoreDump | 非法指令 |
| 5 | SIGTRAP | CoreDump | trap指令发出,一般调试用 |
| 6 | SIGABRT | CoreDump | abort(3)发出的终止信号 |
| 7 | SIGBUS | CoreDump | 非法地址 |
| 8 | SIGFPE | CoreDump | 浮点数异常 |
| 9 | SIGKILL | 终止 | 立即停止进程,不能捕获,不能忽略 |
| 10 | SIGUSR1 | 终止 | 用户自定义信号1,像Nginx就支持USR1信号,用于重载配置,重新打开日志 |
| 11 | SIGSEGV | CoreDump | 无效内存引用 |
| 12 | SIGUSR2 | 终止 | 用户自定义信号2 |
| 13 | SIGPIPE | 终止 | 管道不能访问 |
| 14 | SIGALRM | 终止 | 时钟信号,alrm(2)发出的终止信号 |
| 15 | SIGTERM | 终止 | 终止信号,进程会先关闭正在运行的任务或打开的文件再终止,有时间进程在有运行的任务而忽略此信号。不能捕捉 |
| 16 | SIGSTKFLT | 终止 | 处理器栈错误 |
| 17 | SIGCHLD | 可忽略 | 子进程结束时,父进程收到的信号 |
| 18 | SIGCONT | 可忽略 | 让终止的进程继续执行 |
| 19 | SIGSTOP | 停止 | 停止进程,不能忽略,不能捕获 |
| 20 | SIGSTP | 停止 | 停止进程,一般是CTRL+Z |
| 21 | SIGTTIN | 停止 | 后台进程从终端读数据 |
| 22 | SIGTTOU | 停止 | 后台进程从终端写数据 |
| 23 | SIGURG | 可忽略 | 紧急数组是否到达socket |
| 24 | SIGXCPU | CoreDump | 超出CPU占用资源限制 |
| 25 | SIGXFSZ | CoreDump | 超出文件大小资源限制 |
| 26 | SIGVTALRM | 终止 | 虚拟时钟信号,类似于SIGALRM,但计算的是进程占用的时间 |
| 27 | SIGPROF | 终止 | 类似与SIGALRM,但计算的是进程占用CPU的时间 |
| 28 | SIGWINCH | 可忽略 | 窗口大小改变发出的信号 |
| 29 | SIGIO | 终止 | 文件描述符准备就绪,可以输入/输出操作了 |
| 30 | SIGPWR | 终止 | 电源失败 |
| 31 | SIGSYS | CoreDump | 非法系统调用 |
stty(设置tty)命令用于查询和调整当前注册终端的通信参数。
在UNIX系统中为键盘输入和终端输出提供了重要控制功能;通过stty命令可对指定终端或通信线路设定相关参数选项。
可通过stty -a命令查询当前注册终端的具体配置信息。

捕捉信号
trap命令 定义shell脚本在运行时根据接收的信号做相应的处理。
命令格式:trap: usage: trap [-lp] [[arg] signal_spec ...]
-l #打印编号1-64编号信号名称
arg # 捕获信号后执行的命令或者函数
signal_spec # 信号名或编号
示例:
#!/bin/bash
trap "echo ByeBye~" EXIT
count=1
while [ count -le 5 ] do echo "Loop #count"
sleep 1
count=[ count + 1 ]
done
执行脚本的结果:

trap "" 2 ##信号屏蔽
trap : 2 (trap - 2) ##恢复信号
一般捕捉信号后,做以下几个动作:
1)清除临时文件
2)忽略该信号
3)询问用户是否终止脚本执行
示例1:按CTRL+C不退出循环
#!/bin/bash
trap "" 2 ##屏蔽第2个陷阱
number=1
while (number -le 5)
do
echo "循环次数:number"
sleep 1
number=$((number + 1))
done
运行脚本的结果:

示例2:循环打印数字,按CTRL+C退出,并打印退出提示
#!/bin/bash
trap "echo 'exit...';exit" 2
count=1
while [ count -le 5 ] do echo "Loop #count"
sleep 1
count=[ count + 1 ]
done
运行脚本的结果:

示例3:让用户选择是否终止循环
#!/bin/bash
trap "fun" 2
function fun(){
read -p "Terminate the process?(Y/N): "char
if [ char == "Y" ];then exit fi } for i in `seq 5` do echo i
sleep 1
done
运行脚本的结果:

xargs命令
什么是xargs
该xargs命令能够通过管道接收输入字符串, 并利用空格将其分解为多个独立的参数(通常情况下是基于空格分割), 这些参数随后被传递给后续的指令作为各自指令的运行参数。
为何选择使用xargs呢?因为我们在处理Linux命令时发现其强大的功能特性能够有效满足需求
还有许多程序并未设计处理标准输入的功能。如kill和rm这类程序,在命令行参数未指定处理对象时,默认不会从标准输入读取数据。即上述说明可理解为:
echo '516' | kill 这种命令是不能执行的
echo 'test' | rm -f 这种也是没有效果的
这些命令仅允许在命令行参数中限定所需处理的内容,并不使用标准输入获取数据
然而,在某些脚本中我们有时需要执行类似echo '516' | kill的操作。例如,在grep 'ddd'之后运行ps -ef并执行kill命令即可实现类似的效果:从而筛选出符合条件的进程PID并终止它们。这一需求对于所有人来说都是理所当然且极为常见的——那么我们应该如何达成这一目标呢?有几个解决方案:
终止进程 ps -ef | grep 'ddd' 这种操作实际上等同于将字符串拼接后的变量并使用 kill 命令进行处理的方式运行后会生成一个新进程
<2> ps -ef | grep 'ddd' | xargs kill
与管道有什么不同
示例:
[root@server day03]# echo '--help' | cat

输出: --help
该命令的输出结果等同于执行echo指令产生的内容。这等价于将echo生成的内容通过管道传递给cat程序作为输入。实际上就是将echo命令的输出结果经过管道连接后传递给cat程序作为其标准输入。随后,cat程序会从其标准输入端口读取并接收待处理的文本内容。
[root@server day03]# echo '--help' | xargs cat
等同于 cat --help 的结果,在这个命令中 xargs 将其接收的字符串 --help 作为单个命令参数传递给 cat 命令;同样地, 使用 echo 'test.c test.cpp' | xargs cat 相当于直接执行 cat test.c test.cpp;此时会将 test.c 和 test.cpp 的内容全部展示出来。

练习:
在$tmp目录中创建命名为westos_2019-01-02-10-52-57的文件,并借助ctrl+C删除这些具有实时标记时间后缀的文件。
#!/bin/bash
启动一个陷阱任务"搜索所有文件名为westos_的/tmp文件 | 使用xargs删除并退出"间隔2秒
使用无限循环执行以下操作
生成包含当前日期和时间的变量westos_(date +%F-%H-%M-%S)并创建/tmp/westos_(date +%F-%H-%M-%S)目录
等待2秒
列出所有westos*文件的信息
结束循环

执行脚本的结果如下:

被/v/var/log/secure中的连接失败主机所扫描并记录次数,当连续出现故障的连接事件达到4次及以上时,将此主机纳入系统禁止访问列表,并将相关的配置信息存储至$HOME/.ssh/config中
#!/bin/bash
cat /var/log/secure | AWK='/Failed/{print (NF-3)}' | sort | uniq -c | echo '{print "2"="1"}' > /tmp/blacklist
declare -i MAXCOUNT=3
for each IP in the list of blacklisted IPs do
echo "IP" | AWK='-F= '{print 1}'' gives us the domain name
echo "IP" | AWK='-F= '{print 2}'' gives us the number of failed attempts
if [ "{#2}" -ge MAXCOUNT ]; then
check if IP exists in /etc/hosts.deny by running grep "IP" /etc/hosts.deny > /dev/null
if this command returns a non-zero status:
append "sshd:$IP" to /etc/hosts.deny with a successful redirect to denied hosts file
fi
fi
done

脚本执行结果:

此时再在主机172.25.254.81和主机172.25.254.200上尝试ssh到测试主机(172.25.254.100)会报错

练习:
(1)使用循环在mariadb数据库中分别建立tom harry natasha三个数据库
(2)将所有库备份成 ‘库名称_年-月-日.sql.gz‘ 的形式
(3)在每个数据库中建立一个表,并向表中插入一组数据
#!/bin/bash
Myuser=root
Mypass=zzz
Mycmd="mysql -uMyuser -pMypass"
Mydump="mysqldump -u Myuser -pMypass"
DBpath=/home/backup
#######建立数据库##########
for dbname in tom harry natasha
do
Mycmd -e "create database dbname;"
done
######在数据库中建立表,并插入数据######
for dbname in tom harry natasha
do
Mycmd -e "use dbname;create table {dbname}test(id int,username varchar(20),passwd varchar(20));insert into {dbname}test values (1,'westos','123')"
done
######备份所有数据库########
[ ! -d DBpath ] && mkdir -p DBpath
for dbname inmysql -uroot -pzzz -e "show databases;" | sed '1,2d' | egrep -v "mysql|schema"
do
Mydump dbname | gzip > DBpath/{dbname}_(date +%F).sql.gz done ######显示每个数据库中所插入表格的内容###### for dbname in tom harry natasha do echo ======={dbname}.{dbname}test======== Mycmd -e "use dbname;select * from {dbname}test;"
done

脚本运行结果如下:

练习:
数据迁移过程中,请运行scriptname.sh $db_passwd以完成所有库的复制工作。所有备份文件将被存储至/mnt/mysqldump目录中,并命名为'库名称.sql'的形式。当该文件存在时切换至交互模式,并请求确认操作:输入's'则可跳过本次备份;输入'b'时指定备份名为'库名称_backup.sql';输入'o'则将覆盖原有文件;输入'e'则退出当前操作。
#!/bin/bash
mkdir "/mnt/mysqldump"
DATABASE=mysql -uroot -pzzz -e "show databases;" | sed "-n '1,2p'" | egrep -v "mysql|schema"
for dbname in DATABASE; do
if [[ -e "{/mnt/mysqldump}/{dbname}.sql"} ]];then
read -p "dbname has been dumped! Skip Backup Overwrite Exit Please input the action:" Action
case Action in
s|S)
;;
b|B)
mysqldump -uroot -p1 dbname > "{/mnt/mysqldump}/{dbname}_backup.sql}"
;;
o|O)
mysqldump -uroot -p1 dbname > "{/mnt/mysqldump}/{dbname}.sql}"
;;
e|E)
echo "Bye~" continue
esac
else
mysqldump -uroot -p1 dbname > "{/mnt/mysqldump}/{dbname}.sql}"
echo "[dbname] is backed up!"
fi
done

执行脚本的结果如下:

练习:shell实现跳板机
跳板机本质上是一台专用服务器,在系统管理中负责提供服务给其他设备。开发或运维人员在执行维护任务时必须首先通过统一入口访问该服务器,并随后通过该服务器连接至目标设备完成必要的维护和操作。
在众多大型企业中,服务器通常不允许直接进行远程登录操作。相反,在这种情况下只能通过专门的中间设备(如跳板机)来完成访问请求。跳板机配置严格限制了用户对系统资源的操作权限,在其内部仅支持有限数量的基本操作(例如SSH连接)。实际上这一设计目标就是对用户的输入行为进行过滤管理,在shell界面与原始操作环境之间建立了一道安全屏障。而脚本的作用则在于根据用户的输入行为动态地控制权限范围——脚本程序越长就意味着它需要处理和判断的操作指令越多;但为了提高效率管理效果,在实际应用中我们只需要根据不同的指令类型编写一次对应的处理逻辑即可。
如果只实现ssh命令,那就是跳板机了。
实验环境:
1.ip:
跳板机IP:172.25.254.100
Server1IP:172.25.254.200
Server2IP:172.25.254.81
2.用户:使用所有主机上都存在的非超级用户student
3.跳板机配置:
< 1>配置免密码登录
(1)在跳板机上生成公钥和私钥

(2)将公钥发到server1和server2服务器
此指令的作用是传输该公钥至IP地址172.25.254.81,并分配给名为 student 的用户;若无指定用户名,默认由当前 shell 确定。


(3)测试:可以在跳板机上免密登陆其他两台服务器


**< 2>配置跳板机脚本1 **
#!/bin/bash
function trapper() {
trap "" INT EXIT TSTP TERM HUP
}
function main() {
while true
do
trapper
clear
cat <<menu
1)host1 172.25.254.200
2)host2 172.25.254.81
3)exit
menu
read -p "Please input a number:" num
case $num in
1)
echo "login in 172.25.254.200..."
ssh 172.25.254.200 ##不加用户的话默认按照当前shell的用户登陆服务器
;;
2)
echo "login in 172.25.254.81..."
ssh 172.25.254.81
;;
3)
exit
;;
esac
done
}main

< 3>配置跳板机脚本2
创建一个名为Jiaobanji的脚本,并将其放置在开机启动目录中;启动Jiaobanji脚本;除root用户外;非root用户的开机将自动执行跳板机脚本。
[root@server day04]# 切换至/etc/profile.d目录
[root@server profile.d]# 编辑user_choice.sh脚本
#!/bin/bash
如果$UID不等于零,则执行以下操作:sh /mnt/tiaobanji.sh

< 4>测试
本机切换到非root用户:

其他测试主机通过ssh服务连接到跳板机:


