Shell信号发送与捕捉

信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行相应的处理函数。

linux通过信号来在运行在系统上的进程之间通信,也可以通过信号来控制shell脚本的运行

进程可以通过三种方式来响应一个信号:

1)忽略信号,即对信号不做任何处理,其中有两个信号不能忽略:SIGKILL及SIGSTOP。

2)捕捉信号。

3)执行缺省操作,Linux对每种信号都规定了默认操作。

Linux支持两种信号:

一种是标准信号,编号1-31,称为非可靠信号(非实时),不支持队列,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,如果第一个信号没有处理完,第二个信号将会丢弃。

另一种是扩展信号,编号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(set tty,设置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
count=1
while [ $count -le 5 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 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 这样的效果,例如 ps -ef | grep 'ddd' | kill 这样的效果,筛选出符合某条件的进程pid然后结束。这种需求对于我们来说是理所当然而且是很常见的,那么应该怎样达到这样的效果呢。有几个解决办法:

<1>  kill `ps -ef | grep 'ddd'`     这种形式实际上等同于拼接字符串得到的命令,其效果类似于  kill $pid

<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的一个命令参数来运行cat命令,同样  echo 'test.c test.cpp' | xargs cat 等价于 cat test.c test.cpp 此时会将test.c和test.cpp的内容都显示出来。

练习:

<1>在/tmp目录下建立形如westos_2019-01-02-10-52-57的文件,其中westos_的后缀是实时的时间,并使用ctrl+C删除这些文件。

#!/bin/bash
trap "find /tmp -type f -name "westos_*" | xargs rm -f && exit " 2
while true
do
  touch /tmp/westos_$(date +%F-%H-%M-%S)
  sleep 2
  ls -l /tmp/westos*
done

执行脚本的结果如下:

<2>在/var/log/secure查找连接失败的主机,并统计次数,累计连接失败超过3次,将此主机加入系统黑名单(/etc/hosts.deny)

#!/bin/bash
cat /var/log/secure | awk '/Failed/{print $(NF-3)}' | sort | uniq -c | awk '{print $2"="$1}' > /tmp/blacklist
MAXCOUNT="3"
for i in `cat /tmp/blacklist`
do
   IP=`echo $i | awk -F= '{print $1}'`      ##取出主机IP
   NUM=`echo $i | awk -F= '{print $2}'`     ##取出失败次数
if [ $NUM -ge $MAXCOUNT ];then
   ##屏蔽IP前前确认此IP是否存在
   grep $IP /etc/hosts.deny > /dev/null
   if [ $? -gt 0 ];then
      echo "sshd:$IP" >> /etc/hosts.deny
   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 -u$Myuser -p$Mypass"
Mydump="mysqldump -u $Myuser -p$Mypass"
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 in `mysql -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 $dbpasswd 备份数据库中所有库到/mnt/mysqldump目录中,备份文件名称为“库名称.sql”,当此文件存在时进入交互模式,询问动作,输入“s”跳过备份,输入“b”,备份名称为“库名称_backup.sql”,输入“o”时,覆盖原文件,输入“e”退出

#!/bin/bash
mkdir -p /mnt/mysqldump
DATABASE=`mysql -uroot -pzzz -e "show databases;" | sed '1,2d' | egrep -v "mysql|schema"`
for dbname in $DATABASE
do
   if [ -e /mnt/mysqldump/$dbname.sql ];then
       read -p "$dbname has been dumped!
       [S]kip [B]ackup [O]verwrite [E]xit
       please input the action:" Action

       case $Action in
       s|S)
          ;;
       b|B)
          mysqldump -uroot -p$1 $dbname > /mnt/mysqldump/${dbname}_backup.sql
          ;;
       o|O)
          mysqldump -uroot -p$1 $dbname > /mnt/mysqldump/${dbname}.sql
          ;;
       e|E)
          echo -e "Bye~"
          continue
       esac
   else
       mysqldump -uroot -p$1 $dbname > /mnt/mysqldump/${dbname}.sql
       echo -e "$dbname is backuped!"
   fi
done

执行脚本的结果如下:

练习:shell实现跳板机

什么是跳板机

跳板机就是一台服务器,开发或运维人员在维护过程中首先要统一登录到这台服务器,然后再登录到目标设备进行维护和操作。

为什么用跳板机

很多大公司的服务器都不允许直接登录,而是通过一个跳板机才能登录过去。在跳板机中,通常只能执行几个少数命令(如SSH),而其他命令是不允许执行的,这个需求实质上就是拦截用户的输入,在用户与shell之间加一道门,而脚本的功能,就是根据用户不同的输入采取不同的动作,脚本的长短也就跟需要的命令的多少有关,需要的命令越多,脚本需要判断的就越多,脚本也就越长,但实际并不需要为每个命令都写一个action,只要为每个种类的写一个action就行了,

如果只实现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服务器

[student@server ~]$ ssh-copy-id -i ~/.ssh/id_dsa.pub 172.25.254.81  ####将公钥发送到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脚本,处了root用户以外,其他用户开机就执行跳板机脚本

[root@server day04]# cd /etc/profile.d/
[root@server profile.d]# vim user_choice.sh

#!/bin/bash
[ $UID -ne 0 ] && sh /mnt/tiaobanji.sh

<4>测试

本机切换到非root用户:

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

没有更多推荐了,返回首页