当前位置: 首页 > Linux

bash中的作业控制机制

时间:2023-04-06 01:50:13 Linux

作业控制可以在shell中通过命令&创建后台作业,通过jobs-l命令可以查看当前shell中维护的作业列表,包括它们的作业号、进程号、运行状态。作业号(jobID或JOB_SPEC)是作业在当前shell中的唯一标识符。作业和进程作业是比进程更高级别的调度单元。它们的定位类似于进程组,但与进程组不同的是,作业只维护其初始进程。一旦所有初始进程退出就意味着作业执行完成,未完成的子进程将不会被作业跟踪,如下例:#!/usr/bin/bash(sleep20&sleep2)&jobs-lpstree-apg$$ps-ejfH|awk'$10~/^sleep/{printf"%s%s--PPID:%s,PGID:%s\n",$10,$11,$3,$4}'sleep2echojobs-lpstree-apg$$ps-ejfH|awk'$10~/^sleep/{printf"%s%s--PPID:%s,PGID:%s\n",$10,$11,$3,$4}'pkillsleep运行这个例子得到以下输出,可以看到整个job最初只有一个bash进程19318没有wait。运行2秒后,19321退出,19318退出,job结束。此时19319由于父进程退出成为孤儿进程,但其进程组号不变:[1]+19318Running(sleep20&sleep2)&bash,19317,19317pgroup.sh├─bash,19318,19317pgroup.sh#作业初始进程│├─sleep,19319,1931720#作业运行过程中创建的子进程Process│└─sleep,19321,193172#作业运行过程中创建的子进程└─pstree,19320,19317-apg19317sleep20--PPID:19318,PGID:19317sleep2--PPID:19318,PGID:19317#process19321exitedduetonowait导致整个作业退出[1]+19318Done(sleep20&sleep2)bash,19317,19317pgroup.sh└─pstree,19328,19317-apg19317sleep20--PPID:1,PGID:19317#Orphan进程但进程组号不变。Job的存在是为了方便shell对前后台进程(进程组)的管理。一个shell进程只能同时有一个前台作业,但可以有多个后台作业。每个作业可以包含一个或多个进程,具体如下:作业包含单个进程>sleep50&[1]20536#作业号为1,包含的进程号为20536>jobs-l[1]+20536runningsleep50&Thejobcontainsprocessesandtheirchildprocesses>(sleep20&sleep10&wait)&[1]21111>pstree-ap$$bash,21023├─bash,21111#作业的初始进程│├─sleep,2111220│└─sleep,2111310└─pstree,21116-ap21023作业包含多个进程>sleep10|sleep8&#管道的两边运行在一个子shell[1]20689>jobs-l#作业包含两个Initialprocess[1]+20688Runningsleep1020689|sleep8&前台作业管理在shell会话中,直接输入命令执行的作业会在前台运行,前台作业会一直阻塞shell,直到执行完毕或挂起。前台作业可以优先传递给进程组的信号,优先占用输入文件描述符。以外部命令cat命令为例。启动后,shell会将当前的前台进程组指定为新创建的cat进程组,这样cat就接管了整个shell的标准输入、标准输出和标准错误文件描述符。命令的组合也可以作为前台任务执行,例如:whiletrue;回声“你好世界”;睡10;done要退出上面的死循环,需要给进程发送一个中断信号。为了方便,我们一般使用Ctrl+C直接向整个进程组发送SIGINT信号。但是如果我们想在不退出前台作业的情况下取回终端控制,我们需要向作业发送一个SIGTSTP或SIGSTOP信号来暂时挂起它。>ping127..0.0.1^Z#PressCtrl+Z[1]+Stoppedping127.0.0.1通过Ctrl+Z可以在shell中向前台作业发送SIGTSTP,所以ping命令暂时停止,并且我们重新获得了shell的控制权,可以通过psT命令查看当前shell终端关联的所有进程:>psTPIDTTYSTATTIMECOMMAND4731pts/1Ss0:00/bin/bash4738pts/1T0:00ping127.0.0.14891pts/1R+0:00psT可以看到ping命令没有退出,状态变成了T,查看manps可以知道T状态的意思该进程被作业控制信号停止。此外,您可以使用内置命令suspend挂起当前shell。后台作业管理除了可以挂起前台作业转为后台作业,还可以通过command&方法让命令直接在后台运行。我们在刚刚暂停的ping的基础上再增加两个Runningbackgroundjobs:>xeyes&>xload&可以通过jobs-l查看当前shell中的后台作业:>jobs-l[1]+6528Stoppedping127.0.0.1[2]7101Runningxeyes&[3]-7104Runningxload&第一列[1]表示作业号JOB_SPEC,后面加+表示是当前作业,或者或最近调到前台的job,而-表示当前job的前一个job,我们可以在命令中通过%JOB_SPEC%+%-:>fg%2#或fg2来访问这些job,因为fgOnly对jobs有效,所以可以省略百分号xeyes^Z#Suspended[2]+Stoppedxeyes>jobs#没有l选项,不会显示进程号[1]-Stoppedping127.0.0.1#减号表示上一个作业[2]+Stoppedxeyes#加号表示当前作业[3]Runningxload&>fg#相当于fg+orfg%+orfg%%第二列数字6528是初始进程job中包含的number,initial可以有多个进程,当所有initial进程都执行完,job就结束了。第三列表示job的状态,包括如下:>sleep5&>kill%+#相当于kill%2百分号不能省略>jobs[1]+Stoppedping127.0.0.1#Stopped[2]Terminatedxeyes#Terminated[3]-Runningxload&#Runningstatus[4]Donesleep5#成功执行退出,只显示一次[5]Hangupping127.0.0.1>/dev/null#Hangup不做特殊处理,然后退出最后一列是作业的运行命令文本。在命令控制任务中,可以使用%s和%?s通过匹配作业文本来指定作业:>kill-sint%ping#向作业文本以ping开头的作业发送SIGINT>fg%?eye#将job文本中包含eye的job调到前台我们也可以使用bg命令让挂起的后台job直接运行,不需要调到前台,用法类似fg:>sleep100^Z#Suspend[1]+Stoppedsleep100>bg1#相当于bg%1[1]+sleep100&>jobs[1]+runningsleep100&但是要小心,为了防止后台作业竞争对于输入资源的前台作业,只要后台作业执行到需要读取输入的代码段,作业就会被挂起,此时bg命令无效。>whiletrue;读行;done&[1]20780>bg1[1]+whiletrue;doreadline;done&#被输入为挂起并仍处于停止状态[1]+Stoppedwhiletrue;doreadline;done作业控制命令及符号作业控制命令及其用法可概括为下表:listkill%1%s作业列表中名称以字符串s开头的作业kill%xe%?s作业列表中名称包含字符串s的作业jobs%?ping%%or%+表示当前作业kill%%kill%+%-表示当前作业的上一个作业bg%-Ctrl+Z暂停或停止作业kill-sstop%pingjobs-l列出所有作业jobs-ljobs-r列出所有正在运行的作业jobs-rjobs-s列出所有挂起的作业jobs-sbg让作业在后台运行bg%%fg把作业拉起来到前台fg%apt-get处理SIGHUP信号。无论是前台作业还是后台作业,都以树状结构依附于shell进程。shell终端退出时,如果有后台作业,会提示Therearestoppedjobs.,如果忽略这个提示,继续退出,shell会向所有作业发送SIGHUP信号,进行清理。如果我们需要一些后台进程在终端退出时继续运行,我们需要处理SIGHUP信号。外部命令nohup使用nohup命令启动一个作业可以使这个作业忽略SIGHUP,使用方法如下:>nohupping127.0.0.1&[1]21222nohup:ignoringinputandappendingoutputto'nohup.out'#execping127.0.0.1&也可以达到类似的效果#但是nohup会自动处理输出重定向让你退出当前终端并在另一个终端执行搜索ping过程:>ps-ef|awk'$8~/^ping/{print"PID:"$2",PPID:"$3}'PID:21222,PPID:1#成为孤儿进程继续运行为了防止后台作业阻塞,nohup会默认情况下让作业忽略输入并将所有输出重定向到~/nohup.out文件。我们可以手动重定向输出,它会自动将标准错误重定向到标准输出:>nohupping127.0.0.1>outfile&[1]22505ignoringinputandredirectingstderrtostdout>nohupping127.0.0.1&>outfile&[2]22523但是要小心,nohup命令不能通过(...)以同样的方式在子shell中执行命令,这会导致解释器将nohup当作一个函数来处理而导致语法错误:>nohup(sleep120;echo"jobdone")&#这样会报错>nohupbash-c'sleep120;echo"jobdone"'&#可以正常执行[1]25758>pstree-ap$$bash,23897├─bash,25758-csleep120;echo"jobdone"│└─sleep,25759120└─pstree,25764-ap23897内置命令disownnohup的缺点是必须在程序运行前指定是否忽略SIGHUP信号。如果我们想修改正在运行的作业,可以使用内置命令disown。用法如下:disown%n:把编号为n的job从列表中剥离,此时job的所有输出都消失了,并且没有作业控制。>ping127.0.0.1>/dev/null&[1]29748>disown%1>jobs#Nooutput>ps-f29748UIDPIDPPIDCSTIMETTYSTATTIMECMDremilia2974828309015:10pts/3S0:00ping127.0.0.1#退出终端,在另一个终端查看进程>ps-f29748#孤儿进程继续运行UIDPIDPPIDCSTIMETTYSTATTIMECMDremilia297481015:10?S0:00ping127.0.0.1disown-h%n:让作业号n忽略退出时产生的SIGHUP,这种方法不会从作业列表中删除作业,所以可以继续使用作业控制命令进行管理.disown-r:从作业列表中删除所有正在运行的作业。disown-a:从作业列表中删除所有作业。此命令的缺点是它不会自动重定向输出。如果我们需要保存作业的输出,我们可以在程序运行时使用gdb修改文件描述符的指向。>python3logerr.py&>sudogdb-p`pgreppython3`(gdb)pclose(2)#删除标准错误$1=0(gdb)pcreat("/tmp/pyout",0600)#创建自动连接到标准错误的文件$2=2(gdb)q#选择是>sudols-l/proc/31586/fd/2l-wx------1remiliaremilia64Mar2215:36/proc/31586/fd/2->/tmp/pyoutoptionhuponexithuponexit是bash中的一个选项,其用法如下:>shopthuponexit#检查该选项是否启用>shopt-shuponexit#启用该选项以启用此功能选项将使shell会话中的所有后台作业忽略会话退出时执行exit产生的SIGHUP,其他方式传递的SIGHUP不会被忽略。SIGHUP的传播过程如下:session--SIGHUP-->bash(huponexit)--SIGHUP-->Job如果在bash中开启了huponexit,那么当huponexit启动时,SIGHUP信号不会分发给子进程(job)会话退出。在其他方面,我们也可以让后台作业成为孤儿进程。实现忽略父shell传递的SIGHUP信号:#子shell不等待自己的后台任务,所以提前退出#该方法可以自定义输出文件的重定向,但不能进行作业管理>(ping127.0.0.1>/dev/null&)>ps-f`pgrepping`UIDPIDPPIDCSTIMETTYSTATTIMECMDremilia8471015:49pts/4S0:00ping127.0.0.1另外还有screen等工具,tmux、dtach等实现更多高级功能。参考内容AdvancedBashScriptingGuideHowToUseBash'sJobControlRedirectingOutputfromarunningProcess