如何让shell脚本自杀
bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
1.脚本自杀正文
有些时候我们写的shell脚本中有一些后台任务,当脚本的流程已经执行到结尾处或将其kill掉时,这些后台任务会直接挂靠在init/systemd进程下,而不会随着脚本退出而停止。
例如:
[root@mariadb ~]# cat test1.sh
#!/bin/bash
echo $BASHPID
sleep & [root@mariadb ~]# ps -elf | grep slee[p]
S root - hrtime : pts/ :: sleep
从结果中可以看到,脚本退出后,sleep进程的父进程变为了1,也就是挂在了init/systemd进程下。
这时我们可以在脚本中直接使用kill命令杀掉sleep进程。
[root@mariadb ~]# cat test1.sh
#!/bin/bash
echo $BASHPID
sleep &
kill $!
但是,如果这个sleep进程是在循环中(for、while、until均可),那就麻烦了。
例如下面的例子,直接将循环放入后台,杀掉sleep、或者exit、或者杀掉脚本自身进程、或者让脚本自动退出、甚至exec退出当前脚本shell都是无效的。
[root@mariadb ~]# cat test1.sh
#!/bin/bash
echo $BASHPID while true;do
sleep
echo
done & killall sleep
kill $BASHPID
为了分析,新建一个脚本test2.sh:
#!/bin/bash
echo $BASHPID while true;do
sleep
echo
done & sleep
然后在脚本执行的60秒内查看test2.sh进程的信息:
[root@mariadb ~]# pstree -p | grep "test2.sh"
| `-bash()---test2.sh()-+-sleep()
| `-test2.sh()---sleep()
其中pid=2923的test2.sh进程是脚本自身进程,pid=2924的test2.sh进程是while开始运行后为while提供执行环境的子shell进程(为什么会生成这个进程,见我的另一篇文章)。
所以,对于前面的test1.sh进程,杀掉了 $BASHPID 对应的test1.sh进程后,其实还有一个为while提供运行环境的test1.sh进程,且这个进程在 $BASHPID 结束后,会挂在init/systemd下。
[root@mariadb ~]# ./test1.sh ./test1.sh: line : Terminated sleep
Terminated [root@mariadb ~]# pstree -p | grep sleep
|-test1.sh()---sleep()
这就是shell脚本中的一个"疑难杂症",CTRL+C中止了脚本进程,这个脚本却还在后台不断运行,且时不时地输出点信息到终端(我这里是循环中的echo命令输出的)。
除非我们手动杀掉新生成的test1.sh,否则这个脚本将无限循环下去。但是,这不是很麻烦吗?
那么如何实现"脚本自杀"?其实很简单,只要在脚本退出前,使用killall命令杀掉脚本进程即可。
[root@mariadb ~]# cat test1.sh
#!/bin/bash
echo $BASHPID while true;do
sleep
echo
done & killall `basename $`
这样,在脚本退出前,两个test1.sh进程都会被杀掉。
再考虑一个问题,如果脚本已经执行到了while中的后台任务,但在执行到killall命令之前按下了CTRL+C,这时由于没有执行killall,后台任务也将挂在新的脚本进程下。我们的目的是保证脚本终止,其内进程一定终止。所以我们需要对这种情况做出合理的处理。
可以使用trap捕捉ctrl+c信号,捕捉到的时候执行killall命令即可。例如:
[root@mariadb ~]# cat test1.sh
#!/bin/bash trap "killall `basename $0`" SIGINT
echo $BASHPID while true;do
sleep
echo
done & killall `basename $`
这样就能保证脚本终止时,其内一切任务都将终止的目的。
上面的脚本并不健壮,因为 ./test1.sh 和 bash test1.sh 两种执行方式的进程名称不一样,前者的进程名称为test1.sh,后者的进程名称为bash,所以killall没法同时解决这两种情况。为了健壮性,可以加上杀后台进程"$!"的代码,并将killall换成pkill,且通过筛选全路径的方式杀掉进程:
[root@mariadb ~]# cat test1.sh
#!/bin/bash trap "pkill -f `basename $0`" SIGINT
echo $BASHPID while true;do
sleep
echo
done &
pid=$!
kill $pid
pkill -f `basename $`
#!/bin/bash trap "pkill -f $(basename $0);exit 1" SIGINT SIGTERM EXIT ERR while true;do
sleep
echo "hello world!"
done & # do something
sleep
可能写100个shell脚本也遇不到需要一个脚本需要将while/for/until这样的语句放入后台的。但有时候也是有用的。例如,有个需求:每秒去a.txt文件中同步数据到b.txt中,然后每分钟对b.txt文件做处理。
#!/bin/bash while true;do
(a.txt--->b.txt)
sleep
done & while true;do
(b.txt)
sleep
done
此外,对一些比较复杂的需求(我个人遇到过多次),可能也会使用到后台的循环。
本文只是提供一种杀脚本的解决方案。很多情形并非如我这里所描述的,例如不是while循环放后台,而是循环内的sleep放后台,这时(脚本终止时)sleep会挂在init/systemd下,不过这很简单。相信读懂了本文,各位已经了解了一些trap的功能以及处理这类问题的逻辑,也知道其他各种情形如何处理。
最后,有一种更方便更精确的自杀手段:man kill。在该man手册中解释了,如果kill的pid值为0,表示发送信号给当前进程组中所有进程,对shell脚本来说这意味着杀掉脚本中产生的所有进程。方案如下:
#!/bin/bash trap "echo 'signal_handled:';kill 0" SIGINT SIGTERM while true;do
sleep
echo "hello world! hello world!"
done &
sleep
2.补充:bash内置命令的特殊性
为什么上文运行脚本进程,脚本中的后台while会新生成一个脚本进程?在这里补充说明下。
究其原因,是因为while/for/until等是bash内置命令,它们的特殊性在于它们有一个很替它们着想的爹:bash进程。bash进程对他们的孩子非常负责,所有能直接执行的内置命令都不会创建新进程,它们直接在当前bash进程内部调用执行,所以我们用ps/top等工具是捕捉不到cd、let、expr等等内置命令的。但正因为爹太负责,把孩子们宠坏了,这些bash内置命令的执行必须依赖于bash进程才能执行。
内置命令中还有几个比较特殊的关键字:while、for、until、if、case等,它们无法直接执行,需要结合其他关键字(如do/done/then等)才能执行。非后台情况下,它们的爹会直接带它们执行,但当它们放进后台后,它们必须先找个bash爹提供执行环境:
- 如果是在当前shell中放进后台,则这个爹是新生成的bash进程。这个新的bash进程只负责一件事,就是负责这个后台,为它的孩子们提供它们依赖的bash环境。
- 如果是在脚本中放进后台,则这个爹就是脚本进程。由于脚本不是内置命令,它能直接负责这个后台(因为脚本进程也算是bash进程的特殊变体,也相当于一个新的bash进程)。
验证下就知道咯。
目前bash进程信息为:
[root@xuexi ~]# pstree -p | grep bash
|-sshd()-+-sshd()---bash()---mysql()
| `-sshd()-+-bash()
| `-bash()-+-grep()
将for、unitl、while、case、if等语句放进后台。例如:
[root@xuexi ~]# if true;then sleep ;fi &
然后再查bash进程信息:
[root@xuexi ~]# pstree -p | grep bash
|-sshd()-+-sshd()---bash()---mysql()
| `-sshd()-+-bash()---bash()---sleep()
| `-bash()-+-grep()
不难看出,sleep进程之前先生成了一个pid=13295的bash进程。(注:如果这几个特殊关键字不进入后台,则是当前在bash进程下执行的)
无论它们的爹是脚本进程还是新的bash进程,它们都是当前shell下的子shell。如果某个子shell中有后台进程,当杀掉子shell,意味着杀掉了它们的爹。非内置bash命令不依赖于bash,所以直接挂在init/systemd下,而bash内置命令严重依赖于bash爹,没有爹就没法执行,所以在杀掉bash进程(上面pid=7008)的时候,bash爹(pid=13295)会立即带着它下面的进程(sleep)挂在init/systemd下。
再来验证下咯。还是刚才的后台命令。
[root@xuexi ~]# while true;do sleep ;done &
另一个窗口,查看bash进程信息:
[root@xuexi ~]# pstree -p | grep bash
|-sshd()-+-sshd()---bash()---mysql()
| `-sshd()-+-bash()---bash()---sleep()
| `-bash()-+-grep()
杀掉pid=7008的bash进程(为什么不杀pid=13468的bash进程?它是为while提供环境的bash进程,杀了这个相当于杀了while循环结构)。注意,这个bash进程是交互式登陆shell,默认情况下会忽略SIGTERM信号,所以只能使用SIGKILL信号来杀。
[root@xuexi ~]# kill - [root@xuexi ~]# pstree -p | grep bash
|-bash()---sleep()
|-sshd()-+-sshd()---bash()---mysql()
| `-sshd()---bash()-+-grep()
可以看到,新生成了一个bash进程,而且这个bash进程是挂在init/systemd下的,这意味着该bash和终端无关。看下面的状态为"?"。
[root@xuexi ~]# ps aux | grep bas[h]
root 0.0 0.1 pts/ Ss : : -bash
root 0.0 0.1 pts/ Ss : : -bash
root 0.0 0.1 ? S : : -bash
bash进程竟然会挂在init/systemd下?如此奇怪现象,可能你除了这里外永远也不会遇到。
如何让shell脚本自杀的更多相关文章
- shell 脚本学习
Shell简介 概述 Shell是一种具备特殊功能的程序,它提供了用户与内核进行交互操作的一种接口.它接收用户输入的命令,并把它送入内核去执行.内核是Linux系统的心脏,从开机自检就驻留在计算机的内 ...
- 第一个shell脚本
打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好. #!/bin/bash echo "Hello World !" &quo ...
- 使用C#给Linux写Shell脚本
在这个逼格决定人格,鄙视链盛行的年头,尤其是咱们IT界,请问您今天鄙视与被鄙视的次数分别是多少?如果手中没有一点压箱的本事,那就只有看的份了.今天我们也要提升下自己的格调,学习些脑洞大开的东西,学完之 ...
- shell脚本规划化模板
shell脚本规划化模板 Linux运维过程中,shell脚本是不可缺少的工具,但是每个运维人员编程的习惯都不一样,很多时候就是实现某个功能,写出来的脚本都是烂七八糟的.脚本必须规范化,应该从以后几个 ...
- Shell脚本编程30分钟入门
Shell脚本编程30分钟入门 转载地址: Shell脚本编程30分钟入门 什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd ~ mkdir shell_tut cd shell_t ...
- Linux Shell脚本逻辑操作符简介
在写程序时,会用到条件判断,测试条件是否成立.很多时候,判断条件是多个的,这个时候需要用到逻辑操作符.shell脚本中常用的有哪些逻辑操作符呢? 1.逻辑与: -a 格式: conditon1 -a ...
- Linux shell脚本编程(三)
Linux shell脚本编程 流程控制: 循环语句:for,while,until while循环: while CONDITION; do 循环体 done 进入条件:当CONDITION为“真” ...
- Linux shell脚本编程(二)
Linux shell脚本编程(二) 练习:求100以内所有偶数之和; 使用至少三种方法实现; 示例1: #!/bin/bash # declare -i sum=0 #声明一个变量求和,初始值为0 ...
- Linux shell脚本编程(一)
Linux shell脚本编程: 守护进程,服务进程:启动?开机时自动启动: 交互式进程:shell应用程序 广义:GUI,CLI GUI: CLI: 词法分析:命令,选项,参数 内建命令: 外部命令 ...
随机推荐
- Android WebView 缓存机制和模式详解
当我们加载Html时候,会在我们data/应用package下生成database与cache两个文件夹: 我们请求的Url记录是保存在webviewCache.db里,而url的内容是保存在webv ...
- javascript学习笔记02--面向对象学习
js面向对象编程 1. javascript 是一种基于对象的编程 object-based(基于对象):遇到的所有对象都是对象2.javascript没有类class,但是有新的原型对象,习 ...
- javaWeb之自动发送邮件生日祝福(ServletContextListener监听)
在看完本随笔仍然不理解的可以看 javaWeb邮箱发送 :里面有具体的邮箱服务器配置 企业在员工生日当天发送邮箱生日祝福: 一般是用监听器完成: 而合适的监听是ServletContextLis ...
- VS2010编译VS2008工程时,LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt
1.问题 电脑上同时安装了VS2008,VS2010,使用VS2010编译VS2008建立的工程,或者,VS2010创建新的工程.编译时,出现以下链接错误: LINK : fatal error LN ...
- 第二个scala程序
计算昨日收益,读取hdfs文件,使用临时表sqlcontext进行计算,结果保存于mysql中. 之前考虑过将结果存储于Hbase中,由于各种原因及问题,在加上数据量真的很小很小,就改成mysql了. ...
- C#图解教程 第二十四章 反射和特性
反射和特性 元数据和反射Type 类获取Type对象什么是特性应用特性预定义的保留的特性 Obsolete(废弃)特性Conditional特性调用者信息特性DebuggerStepThrough 特 ...
- Maven使用中央仓库下载慢的解决办法
配置Maven仓库的方法 打开Maven配置文件Setting.xml,如下: vim /Users/yuanweipeng/.m2/settings.xml 在配置文件中添加如下配置: <mi ...
- [HNOI2013]游走
题面在这里 题意 从1号点开始等概率选择路径并加上边权,直到到达n号点结束,要求将m条边赋权值1-m使得期望最小 sol 续上文 zsy ycb orz 简单的贪心:求出每条边的期望经过次数,sort ...
- 【learning】凸包
吐槽 计算几何这种东西qwq一开始真的觉得恶心qwq(主要是总觉得为啥画图那么直观的东西非要写一大堆式子来求qwq真的难受qwq) 但其实静下心来学习的话感觉还是很妙的ovo题目思考起来也十分好玩ov ...
- log4j日志输出性能优化-缓存、异步
转载 1.log4j已成为大型系统必不可少的一部分,log4j可以很方便的帮助我们在程序的任何位置输出所要打印的信息,便于我们对系统在调试阶段和正式运行阶段对问题分析和定位.由于日志级别的不同,对系 ...