Linux学习笔记——脚本执行的过程控制
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习笔记——脚本执行的过程控制相关的知识,希望对你有一定的参考价值。
一、 脚本执行过程中的控制
之前的内容中,运行编写好的脚本时都是在命令行上直接确定运行的,并且运行的脚本是实时的,这并不是脚本唯一的运行方式,下面的内容是脚本的其他运行方式。例如在Linux系统中如何控制脚本的执行过程,想在脚本运行过程中对运行中的脚本执行流程进行控制,或者控制脚本的运行时机等等,这些都是通过信号来实现的。
15.1 Linux信号
在Linux系统中,Linux是通过信号和运行在系统上的进程实现通信的。信号就是一个很短的信息,可以发送给一个或多个进程。在前面的内容中讲了如何通过信号启动、停止或终止一个进程的运行。当然可以通过这些信号来控制Bash脚本的运行,如终止一个进程或将运行的脚本挂起(暂停执行)。
15.1.1 常用的信号
在Linux中,系统和Shell进程中进行通信的信号有几十个,可以通过下面的命令在控制台显示这些信号的值和对应的信号名称。
控制台显示:
但是常用到的系统信号如下表中列出的。
Linux系统中常用到的信号以及对应的信号名称和描述信息:
编号 | 信号名称 | 描述 | 默认状态 |
1 | SIGHUP | 终止终端或进程 | 进程终止 |
2 | SIGINT | 来自键盘的中断,【Ctrl+C】 | 进程终止 |
3 | SIGQUIT | 来自键盘的退出,【Ctrl+\】 | 进程流产 |
9 | SIGKILL | 强迫进程终止,不可以屏蔽 | 进程终止 |
15 | SIGTERM | 使进程终止 | 进程终止 |
17 | SIGCHLD | 使子进程停止或终止 | 忽略 |
18 | SIGCONT | 进程继续运行,与SIGSTOP结合使用 | 忽略 |
19 | SIGSTOP | 进程暂停执行 | 进程暂停 |
20 | SIGTSTP | 来自键盘的进程挂起,【Ctrl+Z】 | 进程暂停 |
在Linux系统中使用信号的目的主要有两个:
1. 让运行的进程感知特定的事件并作出反应。
2. 在脚本进程执行代码过程中处理相关信号的代码块。
在Linux系统中,信号的传递过程中主要分为两个阶段,第一个阶段是信号的发送阶段,第二个阶段是进程对信号的接收阶段。在第一个阶段中系统修改目标进程的状态描述符表示一个新的信号已经发送。第二个阶段系统强制进程做出反应并改变进程的运行状态,或者执行一段特定的代码,这段代码是脚本中捕获信号后执行的特定代码。
另外需要注意的是信号只被当前正在运行的进程接收,并且进程可以屏蔽特定的信号,相同的信号除第一个被进程接收后其他的会被屏蔽。下面会通过例子详细说明。
通常情况下,运行一个脚本会启动一个新的Shell进程,在这个Shell进程中运行脚本,当脚本执行结束后对应的Shell进程也会终止。特殊情况下,如果使用source或点命令启动脚本不会启动一个新的Shell进程,而是在当前的Shell进程中运行脚本,这就会使结果有些不同,下面会通过例子说明。当Shell进程接收到挂起或终止信号时,Shell进程会终止运行,但Shell进程终止前会将接收到的信号发送到本Shell进程中运行的所有的子进程,包括运行的脚本进程或某个命令进程。
15.1.2 SIGINT和SIGTSTP
挂起一个进程和终止一个进程是不同的,当一个进程被挂起时,进程的运行状态会保存到内存,只是系统将不会在分配CPU的时间片给挂起的进程,当通过命令启动被挂起的进程时,进程会从断点开始继续执行。而终止一个进程是将进程的运行状态从内存中清除并且不能在继续执行。
在Linux系统中,使用组合键可以生成两个基本的Linux信号,它们是【Ctrl+C】终止进程信号和【Ctrl+Z】挂起一个进程。使用这两个组合键可以将运行的进程终止或挂起,下面通过简单的例子说明。
使用【Ctrl+C】终止一个命令行上的命令进程,sleep命令可以程序运行过程中暂停运行指定的秒数。使用这个命令会更加直观,其他命令很快就会执行结束。
当sleep命令执行开始,会等待60秒后才会显示命令行提示符$,当采用组合键【Ctrl+C】可以终止这个进程,注意:终止的是当前运行的进程,可以是一个或多个进程。
通过ps命令查看sleep命令执行时和终止sleep命令进程的进程信息。
可以看到sleep命令没有执行结束时的进程号是5099,执行终止命令后进程被清除。
采用【Ctrl+Z】组合键可以挂起一个进程,下面还用sleep命令演示。
通过组合键挂起了命令行进程,通过ps命令查看进程状态。
可以看到sleep命令进程的STAT字段由S+变为了T,从运行状态转变为挂起状态。挂起的进程状态保存到了内存可以通过命令jobs查看当前挂起的进程。
对于挂起的进程,可以使用fg命令恢复进程的运行,如下所示。
当通过exit命令退出Shell进程时,如果Shell进程中存在被挂起的进程,Shell进程会给出提示信息,如果再次执行exit命令Shell进程将会终止并退出,暂停运行的进程也会被终止。
如果想终止一个暂停的进程,可以使用kill命令向暂停的进程发送SIGKILL信号将进程终止。
15.2 在脚本中捕捉信号
默认情况下,脚本会忽略这些Linux系统信号,发送的信号会被Shell进程接收并处理,例如,当终止或挂起一个进程(或多个进程)时,接收到信号的Shell进程会被终止或者被挂起,运行在Shell进程中的脚本也同时被终止或被挂起。但是,也可以让脚本不忽略这些信号,在信号出现时拦截并执行相应的代码块,完成特定的任务或者释放资源。需要注意的是如果信号在脚本中被拦截,Shell进程将不再接收并处理这些信号,而是在拦截信号的脚本中被处理。在脚本中捕捉信号是非常有意义,当脚本在运行过程中遇到不可预知的异常情况下,可以采用这种方式做一些系统的善后工作,如断电等异常情况下造成的进程终止。在脚本中可以使用trap命令来拦截Linux系统发出的信号,命令格式如下:
在脚本执行过程中,如果出现trap命令列出的信号类型列表中的其中一个信号,这个信号将被脚本拦截,并且暂停脚本的继续执行,进而会执行trap命令指定的命令。当trap命令指定的命令被执行后,脚本会继续接着暂停执行的断点继续向下执行,直到脚本执行结束或再次出现指定的信号,拦截、执行指定的命令、继续执行脚本。
15.2.1 trap命令
在脚本执行过程中可以通过trap命令拦截指定的系统信号,并且执行指定的命令。下面通过几个简单的例子说明在脚本中如何通过trap命令拦截信号和拦截信号后脚本的执行流程。第一个例子是当脚本运行过程中不允许通过键盘组合键终止脚本的执行。
例:trapTest01.sh
控制台显示:
^C --Process can not be terminated! ^C --Process can not be terminated! |
可以看到,当从控制台通过【Ctrl+C】发出终止进程的信号时,被脚本中的trap命令拦截,并在控制台输出信息,当指定的命令执行结束后,脚本继续向下执行并没有被终止。trap命令指定的命令可以是一个脚本,在脚本中执行相关的代码。修改上面的例子,将输出的信息放置在脚本中。
例:trapTest02.sh
例:trapTest02_1.sh
控制台显示:
^C --Process can not be terminated! ^C --Process can not be terminated! |
第二种方式可以在脚本中执行相关的代码块,如释放资源等等。需要注意的是每一次拦截到指定的信号后都会执行一次指定的命令或脚本。
15.2.2 trap命令捕捉Shell进程退出
启动一个脚本可以有两种方式,第一种是在新的Shell进程中运行脚本,另一种方式是在当前Shell进程中运行脚本。启动脚本的方式不同将会产生不同的结果,如下例子。
例:trapTest03.sh
# 当脚本运行结束,对应的Shell进程也同样会结束,当Shell进程终止时会被拦截 |
第一种启动脚本的方式,启动一个新的Shell进程运行脚本,在这种情况下,脚本执行结束时会被拦截,因为脚本执行结束后运行脚本的Shell进程也同时会终止,拦截的是Shell进程的终止。
控制台显示:
第二种启动脚本的方式是,在当前Shell进程中运行脚本,这种情况下,当脚本运行结束后,运行脚本的Shell进程并不会终止,所以不会在控制台输出相关的信息。注意:拦截的是Shell进程的终止信号,不是脚本运行结束的信号。
控制台显示:
通过source命令或点命令启动脚本时,不会启动一个新的Shell进程,而是在当前的Shell进程中运行脚本,要注意。同样,如果在脚本运行过程中终止脚本的运行,也同样会被拦截。下面通过组合键【Ctrl+C】终止脚本的运行,同样分为两种情况。
控制台显示:
|
上面通过两种方式运行脚本,第一种方式,当从键盘终止进程时会被拦截,因为脚本是在新的Shell进程中运行,而第二种方式运行脚本时是在当前的Shell进程中,所以当脚本运行结束后当前Shell进程并没有终止,所以没有Shell进程终止信号。
15.2.3 移除捕捉信号
在脚本中可以使用trap命令加单破折号来移除指定要捕捉的信号,一旦捕捉信号被移除,相应的信号将不会再被捕捉。命令格式如下。
trap - 指定捕捉的信号 |
通过trap命令可以移除指定要捕捉的信号,当捕捉信号被移除后,脚本将不会在拦截这样的信号,信号将会被Shell进程接收并处理,下面通过例子说明。
例:trapTest04.sh
trap "echo ‘--OK! end of execution!‘" EXIT
|
控制台显示:
|
拦截信号没有移除时,终止信号被拦截控制台输出相关信息。当拦截信号移除后,终止信号会终止脚本进程但不会被脚本拦截。
例:trapTest05.sh
trap "echo ‘ --Process can not be terminated!‘" SIGINT
|
控制台显示:
一旦捕捉信号被移除后,脚本将会忽略该信号。但是在移除捕捉信号前,脚本还是会拦截指定的信号。
15.3 系统后台运行模式
之前运行脚本时都是在命令行直接运行,这种运行脚本的方式是在系统前台的运行模式。这种运行脚本的方式在某些情况下不是最方便的方法,如有的脚本运行时间很长,在这段时间内,只能等待脚本运行结束而不能进行其他的工作。或者某些服务在系统生命周期内都在运行,这样的服务脚本如果运行在系统前台,用户将无法工作。
通过ps命令可以将系统运行的进程显示到控制台,但不是所有的进程都运行在终端上的,如下所示。
不是运行在终端内的进程我们称为后台进程,这些进程运行在后台模式下,通常都是一些服务类进程,当然,用户也可以将自己的脚本在后台运行,这种运行方式不会占用终端资源。对于用户来说,后台运行的进程是透明的,用户可以不用关心后台运行的进程。
15.3.1 在系统后台运行脚本
在Linux系统中,以后台模式运行自己编写的脚本同样要在终端命令行启动脚本进程,与在前台启动脚本不同的是,需要在命令后面加一个&符号,&符号会将命令或脚本与Shell分离开,并将命令或脚本作为独立的进程在系统后台运行,命令格式如下。
通过终端命令行比较前台模式运行和后台模式运行的区别,还以sleep命令演示,如下。
上面的例子中依次执行了三条sleep命令,三条命令都是在后台执行,当执行了第一条sleep命令后,光标没有停顿等待暂停的秒数,而是在控制台显示了一行信息,方括号中的是当前Shell进程中的正在运行的进程的作业号,第一个作业是1,第二个作业的作业号是2,以此类推,作业号后面的的数字是进程ID号(进程ID是唯一的)。命令实际上是在后台运行,控制台只显示作业号和进程ID。第二条命令暂停2秒,显示了两行信息,第一行是作业号和进程ID。第二行显示的是第一条命令在后台执行结束后的信息,作业号、进程状态是完成和执行的命令名称。第三条命令和第二条命令显示的内容相同,作业号、进程ID和第二条命令执行结束后的信息。
一旦在控制台显示了作业号和进程ID,新的命令行提示符会重新出现,这时用户可以在命令行执行其他的命令或脚本,而之前执行的命令正在后台模式下运行。前台进程和后台进程平行运行,如下例子。
上面第一条sleep命令在后台运行,第二条命令是在前台运行,两条命令是平行运行的,当第三条命令执行时,后台运行的sleep命令执行结束。
运行脚本也可以在后台运行,方式与命令在后台执行是相同的,如下例子。
例:process01.sh
控制台显示:
|
上面例子中,脚本后台运行时任然会将标准输出输出到控制台。通常情况下可以将输出重定向到日志文件。
15.3.2 运行多个后台作业
在终端命令行可以同时启动多个后台作业。
例:process02_1.sh
|
例:process02_2.sh
|
例:process02_3.sh
|
例:process02_4.sh
|
控制台显示:
|
每启动一个新的作业,Linux系统都会为其分配一个作业号和一个唯一的进程ID号,通过ps命令可以查看到运行的进程。
通过ps命令可以查看到所有启动的进程,包括后台进程和前台进程。即使是后台运行的进程如果有输出,默认情况下也同样会输出到控制台。在终端中启动后台进程时,每个后台进程都会绑定到启动后台进程的终端上,上面的例子中,所有的后台进程都绑定到了pts/6终端上(启动的第六个终端),如果pts/6终端退出,绑定在终端上的后台进程也可能会退出(不同的Linux版本略有不同)。需要注意的是有的Linux版本的终端模拟器会提示有后台进程在运行,有的终端不会提示,这就需要注意了,如果在当前终端启动了后台进程,在后台进程没有结束前,不能将终端退出运行。如果想将后台进程和控制台终端分离,需要使用其他的方式。
15.4 在非控制台下运行脚本进程
在终端命令行上启动脚本,让脚本一直运行在后台,即使终端退出运行,脚本进程也不会退出运行,也就是后台进程不绑定特定的终端。这种运行脚本的方式使用nohup命令可以实现。命令格式如下。
先通过sleep命令做一个演示再进行说明,分为两种情况,执行命令的终端运行时和退出运行两种情况。
控制台显示:
上面的命令启动命令进程后使用ps命令查看进程,可以看到当终端没有退出运行时,sleep命令进程是绑定在pts/16终端上的,当终端退出运行后通过ps命令可以看到sleep命令进程没有绑定任何终端。就是说不管启动进程的终端是否运行,命令进程都会在后台独立运行,直到进程结束。实现的原理是由nohup命令启动进程时,nohup命令会运行另一个命令进程来阻断所有发送给当前进程的SIGHUP信号,这样在终端退出运行时进程也不会退出。终端退出运行时进程被阻止退出。
使用nohup命令启动后台进程和普通方式启动后台进程相同的是,Shell进程会为启动的进程分配作业号和进程ID,不同的是当使用nohup命令启动的进程在启动进程的终端退出运行时,进程会忽略终端发送过来的SIGHUP信号,不会终止进程,会继续在后台运行进程,直到进程运行结束。
在使用nohup命令启动后台进程时,在控制台可以看到一行输出“忽略输入并把输出追加到"nohup.out"”,这是因为nohup命令会从终端解除与进程的关联,进程会失去与标准输出STDOUT和STDERR的链接,为了保存进程运行过程中的输出,nohup命令会自动将进程运行过程中的STDOUT和STDERR信息重新定向到当前目录下的nohup.out文件中,nohup.out文件保存了所有输出到控制台的信息,相当于进程运行过程中的日志文件,可以通过cat命令显示文件内容。需要注意的是如果在当前目录使用nohup命令启动多个后台运行的进程,多个进程的输出都会输出到nohup.out文件中。
下面的例子采用nohup命令在后台启动多个脚本进程并通过ps命令查看后台进程的运行状态。采用的脚本还是上面例子的脚本process02_*.sh。
在控制台使用nohup命令启动脚本:
$ nohup process02_1.sh & [1] 3797 $ nohup: 忽略输入并把输出追加到"nohup.out"
$ nohup process02_2.sh & [2] 3800 $ nohup: 忽略输入并把输出追加到"nohup.out"
$ nohup process02_3.sh & [3] 3804 $ nohup: 忽略输入并把输出追加到"nohup.out"
$ nohup process02_4.sh & [4] 3807 $ nohup: 忽略输入并把输出追加到"nohup.out"
$ |
通过ps命令查看后台进程的运行状态,分为两种情况,执行命令的终端运行时和退出运行两种情况。
$ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 3659 3638 0 11:55 pts/1 00:00:00 bash yarn 3774 3638 0 12:05 pts/2 00:00:00 bash yarn 3797 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_1.sh yarn 3798 3797 0 12:06 pts/2 00:00:00 sleep 50 yarn 3800 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_2.sh yarn 3801 3800 0 12:06 pts/2 00:00:00 sleep 30 yarn 3804 3774 0 12:06 pts/2 00:00:00 /bin/bash ./process02_3.sh yarn 3805 3804 0 12:06 pts/2 00:00:00 sleep 30 yarn 3807 3774 1 12:06 pts/2 00:00:00 /bin/bash ./process02_4.sh yarn 3808 3807 0 12:06 pts/2 00:00:00 sleep 30 yarn 3809 3659 0 12:07 pts/1 00:00:00 ps -ef $ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 3659 3638 0 11:55 pts/1 00:00:00 bash yarn 3797 1 0 12:06 ? 00:00:00 /bin/bash ./process02_1.sh yarn 3798 3797 0 12:06 ? 00:00:00 sleep 50 yarn 3800 1 0 12:06 ? 00:00:00 /bin/bash ./process02_2.sh yarn 3801 3800 0 12:06 ? 00:00:00 sleep 30 yarn 3804 1 0 12:06 ? 00:00:00 /bin/bash ./process02_3.sh yarn 3805 3804 0 12:06 ? 00:00:00 sleep 30 yarn 3807 1 0 12:06 ? 00:00:00 /bin/bash ./process02_4.sh yarn 3808 3807 0 12:06 ? 00:00:00 sleep 30 yarn 3812 3659 0 12:07 pts/1 00:00:00 ps -ef $ |
终端退出运行后,后台运行的进程就没有绑定到启动进程的终端了,并且进程继续运行直到运行结束。
查看nohup.out文件的内容。四个脚本进程将所有的输出都输出到nohup.out文件。
$ cat nohup.out This is first process! This is second process! This is third process! This is fourth process! $ |
15.5 进程控制
对于运行中的进程我们可以采用组合键将其终止或者挂起,使用kill命令向挂起的进程发送SIGCONT信号可以使挂起的进程重新启动。通常情况下,我们将启动一个进程、挂起一个进程、终止和重新启动进程这些操作称做进程控制,通过进程控制我们可以对运行在Shell进程中的所有进程进行控制。
15.5.1 查看Shell进程中正在运行的进程
通过jobs命令可以查看当前Shell进程中挂起的进程,需要注意的是jobs命令只能查看当前Shell进程中的进程信息,如果在不同的Shell进程中启动的进程,只能在启动进程的Shell中可以查看到,如下所示。
注:启动一个命令进程 $ sleep 50 注:让进程挂起 ^Z [1]+ Stopped sleep 50 注:使用jobs命令查看当前Shell进程中正在运行的进程 $ jobs [1]+ Stopped sleep 50 注:通过bash命令重新启动一个新的子Shell进程(对于当前Shell进程) $ bash 注:再通过jobs命令查看子Shell进程中正在运行的进程,没有运行的进程 $ jobs 注:退出并终止当前子Shell进程并返回到父Shell进程 $ exit exit 注:再通过jobs命令查看当前Shell进程中运行的进程,可以看到挂起的进程 $ jobs [1]+ Stopped sleep 50 $ |
下面的例子采用的脚本的方式
例:process03.sh
#!/bin/bash # 控制台显示,变量$$保存当前脚本进程的进程号 echo "This is a process! Process number is $$" # 控制循环变量 count=0
while [ $count -le 5 ] do echo "count = $count" count=$[ $count + 1 ] sleep 30 done
echo "End of the test process!" |
注:启动一个进程并通过键盘进进程挂起 $ process03.sh This is a process! Process number is 4708 count = 0 count = 1 ^Z [1]+ Stopped process03.sh 注:启动一个后台进程 $ process03.sh & [2] 4712 $ This is a process! Process number is 4712 count = 0 # 通过jobs命令可以看到两个进程,一个暂停运行,一个正在运行 $ jobs [1]+ Stopped process03.sh [2]- Running process03.sh & $ |
通过jobs命令可以查看到当前Shell进程中正在运行或者暂停运行的进程,可以看到输出的信息中一条信息带有加号,一条信息带有一个减号。带有加号的进程是当前Shell进程中默认的进程,带有减号的进程是当默认进程结束后将变为默认进程。不管当前Shell进程中有多少个进程,只有一个进程带有加号,一个进程带有减号。默认进程是当使用进程控制命令时没有指定进程,那么操作的就是默认进程。
上面的例子中在使用jobs命令时没有带任何参数,下面表格中是jobs命令常用到的参数。
参数 | 描述 |
-l | 显示进程的进程ID号 |
-n | 只列出状态改变的进程 |
-p | 只列出进程的PID |
-r | 只列出运行中的进程 |
-s | 只列出已停止的进程 |
下面通过一个例子说明jobs命令的参数的用法。
注:启动一个进程并将进程挂起 $ process03.sh This is a process! Process number is 4948 count = 0 ^Z [1]+ Stopped process03.sh 注:在后台启动一个进程 $ process03.sh & [2] 4953 $ This is a process! Process number is 4953 count = 0 注:启动第三个进程并将进程挂起 $ process03.sh This is a process! Process number is 4956 count = 0 ^Z [3]+ Stopped process03.sh 注:使用jobs命令的参数-l,显示进程调度ID号 $ jobs -l [1]- 4948 停止 process03.sh [2] 4953 Running process03.sh & [3]+ 4956 停止 process03.sh 注:参数-p只显示进程的ID号 $ jobs -p 4948 4953 4956 注:参数-r只显示运行的进程 $ jobs -r [2] Running process03.sh & 注:参数-s只显示停止运行的进程 $ jobs -s [1]- Stopped process03.sh [3]+ Stopped process03.sh $ jobs -l [1]- 4948 停止 process03.sh [2] 4953 Running process03.sh & [3]+ 4956 停止 process03.sh 注:根据进程号终止进程,注意是默认进程 $ kill -9 4956 注:带减号的进程成了默认进程 $ jobs -l [1]+ 4948 停止 process03.sh [2]- 4953 Running process03.sh & $ |
注意:参数可以连起来使用,如:jobs -ls。显示停止的进程信息,包括进程ID。另外,Shell进程的默认进程是最后启动的进程。当默认进程被终止后,带减号的进程变成了默认进程。
15.5.2 重启停止的作业
在Shell进程中停止的进程可以通过命令重新启动,重启进程的模式有两种,既前台运行模式和后台运行模式。前台模式重启进程的命令格式如下。
fg 进程作业号 |
以前台模式重启停止运行的进程需要注意的是重启后的进程会占用当前启动进程的终端,直到进程运行结束。下面通过例子说明。
$ process03.sh This is a process! Process number is 5451 count = 0 ^Z [1]+ Stopped process03.sh $ jobs [1]+ Stopped process03.sh $ fg 1 process03.sh count = 1 count = 2 count = 3 count = 4 count = 5 End of the test process! $ |
以前台模式启动的进程会一直占用当前的终端,直到进程运行结束。
以后台模式重启进程,进程会在后台执行,命令格式如下。
bg 进程作业号 |
下面通过例子说明。
$ process03.sh This is a process! Process number is 5482 count = 0 ^Z [1]+ Stopped process03.sh $ process03.sh This is a process! Process number is 5488 count = 0 ^Z [2]+ Stopped process03.sh $ jobs -l [1]- 5482 停止 process03.sh [2]+ 5488 停止 process03.sh $ bg 2 [2]+ process03.sh & count = 1 $ jobs -n [2]- Running process03.sh & $ bg 1 [1]+ process03.sh & count = 1 $ jobs -ln [1]- Running process03.sh & $ jobs -r [1]- Running process03.sh & [2]+ Running process03.sh & $ jobs -l [1]- 5482 Running process03.sh & [2]+ 5488 Running process03.sh & … |
通过bg命令加上进程作业号可以在后台重启停止的进程,重启的进程信息后面加上了&符号,说明进程在后台运行。需要说明的是如果bg命令后面不跟进程作业号,会默认重启带加号的进程,也就是当前Shell进程中默认的进程。通过jobs -n可以查看改变进程状态的进程信息。
15.6 进程的谦让度
在Linux系统中,通过ps命令可以看到有多个进程同时在系统中运行,有的在前台运行,有的在后台运行。如下表中的bash进程、ps-ef进程是前台进程,字段TTY为?号的进程是后台进程。
$ ps -ef UID PID PPID C STIME TTY TIME CMD … yarn 2808 1 0 14:04 ? 00:00:04 gnome-terminal yarn 2809 2808 0 14:04 ? 00:00:00 gnome-pty-helper yarn 2810 2808 0 14:04 pts/0 00:00:00 bash root 2895 1 0 14:10 tty2 00:00:00 /sbin/mingetty /dev/tty2 root 3014 2042 0 14:24 ? 00:00:00 tpvmlpd2 yarn 3039 2810 3 14:26 pts/0 00:00:00 ps -ef |
对于在Linux中运行的所有进程,Linux内核会将CPU时间片分配给系统上的每一个进程,在一个特定的时间点只有一个进程占用CPU,所以Linux内核会轮流将CPU的使用权分配给每一个进程。默认情况下,从Shell启动的所有进程在Linux系统中具有相同的调度优先级。调度优先级是系统内核分配给进程的CPU使用权的大小。
在系统中,进程的调度优先级是使用整数值来表示的,取值范围是从-20~+19。-20具有最高的调度优先级,+19具有最低的调度优先级。默认情况下,Bash Shell以调度优先级0来启动所有的进程。调度优先级越高,获得CPU的时间机会越高,调度优先级越低,获得CPU的时间机会越低。要注意,整数值越大调度优先级越低。值越小调度优先级越高。
可以通过命令修改进程的调度优先级,对于运行时间较长的进程可以提高进程的调度优先级(取得CPU时间片的机会更多)或者降低一个进程的调度优先级(取得CPU时间片的机会减少)。
15.6.1 nice命令
在Linux系统中可以通过nice命令在启动进程时指定进程的调度优先级,nice命令后面不跟任何参数将返回当前进程的调度优先级,在Shell进程中启动的脚本进程默认调度优先级为0,下面通过一个例子说明。
例:process04.sh
#!/bin/bash
echo "process begin" # 在控制台显示当前进程的调度优先级值 nice
for (( i = 0;i < 5;i++ )) do echo "i = $i" # 暂停30秒 sleep 30 done |
控制台显示:
$ process04.sh process begin 0 i = 0 … |
可以看到脚本运行过程中的调度优先级值为默认的0。也可以通过ps命令查看进程的调度优先级值,如下。
$ ps -efl F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD … 0 S yarn 2810 2808 0 80 0 - 1739 - 14:04 pts/0 00:00:00 bash 4 S root 2895 1 0 80 0 - 502 ? 14:10 tty2 00:00:00 /sbin/mingetty /dev/tty2 0 S yarn 3290 2808 0 80 0 - 1719 - 15:16 pts/1 00:00:00 bash 4 S postfix 4321 2141 0 80 0 - 3150 ? 17:22 ? 00:00:00 pickup -l -t fifo -u 0 S yarn4493 2810 0 80 0 - 1671 -17:59 pts/0 00:00:00 /bin/bash ./process04.sh 0 S yarn 4504 4493 0 80 0 - 1420 - 18:00 pts/0 00:00:00 sleep 30 0 R yarn 4506 3290 0 80 0 - 1639 - 18:00 pts/1 00:00:00 ps -efl |
从上面控制台显示的信息中可以看到,字段NI显示的内容就是进程的调度优先级,可以看到所有的进程调度优先级默认情况下都是0。
如果nice命令未指定进程的调度优先值,则以缺省值10来调整程序运行优先级,既在当前程序运行优先级基础之上增加10。如下例子。
$ nice process04.sh process begin 10 i = 0 … |
默认情况下,进程的调度优先级是0,上面的例子中在默认值的基础上增加10的优先级。如果调整后的调度优先级高于-20,则就以优先级-20来运行命令行;若调整后的程序运行优先级低于19,则就以优先级19来运行命令行。这里需要注意的是一般用户只能降低进程的调度优先级,如果想要提高进程的调度优先级,只有root用户才具备权限,下面通过一个例子说明。
[[email protected] bash01]$ nice -n -1 process04.sh nice: 无法设置优先级: 权限不够 process begin 0 i = 0 ^C [[email protected] bash01]$ su 密码: [[email protected] bash01]# nice -n -1 process04.sh process begin -1 i = 0 ^C [[email protected] bash01]# nice -n 30 process04.sh process begin 19 i = 0 ^C [[email protected] bash01]# nice -n -30 process04.sh process begin -20 i = 0 ^C [[email protected] bash01]# |
通过上面的例子说明,一般用户只能降低进程的调度优先级,而不能提高进程调度优先级。
15.6.2 renice命令
对已经运行的进程进行调度优先级修改需要使用renice命令,需要指定进程的PID,命令格式如下。
renice 调度优先级值 -p 进程PID |
需要说明的是,设定的值是具体的优先级值。与nice命令不同的是可以使用renice命令多次对运行中的进程进行进程优先级设置,但需要注意的有以下几点:
1. 用户只能对属于自己的进程进行优先级设置。
2. 普通用户只能降低进程的调度优先级而不能提高进程的优先级。
3. root用户可以对如何进程进行优先级的提高和降低操作。
下面通过一个例子进行说明。
$ process04.sh & [1] 4249 … [yarn@YARN bash01]$ renice 10 -p 4249 4249: old priority 0, new priority 10 [[email protected] bash01]$ renice 3 -p 4249 renice: 4249: setpriority: 权限不够 [[email protected] bash01]$ su 密码: [root@YARN bash01]# renice -12 -p 4249 4249: old priority 10, new priority -12 [root@YARN bash01]# renice -16 -p 4249 4249: old priority -12, new priority -16 |
可以看到yarn用户第一次将进程的调度优先级设置成10,第二次设置成3时提示权限不够,优先级从10到3是调度优先级提高了,普通用户只能降低进程的调度优先级。而root用户可以对所有的进程进行优先级的提高或降低,这是要注意的。并且普通用户只能对属于自己的进程进行优先级设置,对于属于其他用户的进程是没有权限进行设置的。所以有时要用到root用户的权限进行操作。
15.7 指定脚本的执行时机
通常情况下执行的脚本是实时的,比如手动启动某个服务进程。但是有些情况下需要在特定的情况下或时间点才需要启动脚本。在Linux系统中提供了这样的方法,用来指定脚本的执行时机或特定的时间执行脚本,本节的内容主要对at命令和cron表的使用方法进行说明。
15.7.1 使用at命令计划执行脚本
在Linux中,可以使用at命令指定何时执行脚本,at命令可以将这个定时启动脚本的作业提交到队列中,指定Shell进程何时启动脚本(需要注意的是,通过at命令指定执行的脚本只执行一次,如果需要定期执行脚本作业,在同一个时间点执行作业需要使用其他的命令)。at命令对应的后台服务进程是atd,Linux不同的版本有一些不同,有的版本中没有预装at工具包,有的版本没有自动启动atd服务。使用at命令需要安装at工具包和启动atd服务,大多数Linux发行版都会在启动时运行此守护进程。可以通过下面的命令检查是否安装at工具包。
# 检查是否安装at工具包 $ rpm -qa | grep at # 检查atd服务是否启动 $ ps -ef | grep atd # 安装at工具包,注意:需要联网 $ yum install at |
注意:安装at工具包需要root用户的权限,
atd服务会定时检查一个特定的目录:/var/spool/at。此目录用来保存at命令提交的作业。默认情况下,atd服务会60秒检查一下这个目录。当有at命令提交的作业时,atd服务会检查作业设置的运行时间。如果设定运行的实际和当前时间匹配,atd服务会启动运行此作业。at命令的格式如下。
at -f 执行的脚本 执行的日期时间 at 执行的日期时间 |
日期格式:
日期格式 | 举例 |
HH:MM | 05:30 |
HH:MM YYYY-MM-DD | 05:30 2016-06-18 |
[am|pm] + 数字 [minutes|hours|days|weeks] | 8am + 3 days 9pm + 2 weeks now + 3 hours now + 5 minutes |
第一种方式是指定在特定的时间执行脚本文件,这种方式较为方便和明确,这种方式可以在脚本中使用,指定特定的时间执行不同的任务。需要说明的是,当作业开始运行时不会绑定任何终端,所以在命令行看不到任何执行过程,所执行的命令和脚本中所有的STDOUT和STDERR都会输出到当前执行at命令的用户的E-mail中。下面通过例子说明。
例:process05.sh
#!/bin/bash # 输出,注意:不会输出到控制台,会输出到当前用户的E-mail中 echo "Test at command!" echo "Script is running" # 切换目录 cd /home/yarn/bash01 # 创建日志目录 mkdir log.d # 进入目录 cd log.d # 创建一个空的日志文件 touch syslog.log # 输出到E-mail中 echo "Create Log file success" |
控制台显示:
[[email protected] bash01]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 6 at 2016-04-08 18:05 [[email protected] bash01]$ atq 6 2016-04-08 18:05 a yarn [[email protected] bash01]$ su 密码: [[email protected] bash01]# cd /var/spool/at [[email protected] at]# ll 总用量 12 -rwx------. 1 yarn yarn 5024 4月 8 18:00 a0000701739bff drwx------. 2 daemon daemon 4096 4月 8 17:57 spool |
设置5分钟后执行process05.sh脚本,并输出了作业的设置时间日期和作业号,这个作业属于哪个用户等。at命令会将作业提交到队列中,在目录/var/spool/at下,这个目录要用root用户的权限才能看到,看到的a0000701739bff文件是队列文件,当作业完成后,此文件会被删除。
脚本执行结束后会提示将信息输出到了E-mail中:
[[email protected] at]# atq You have new mail in /var/spool/mail/yarn |
所有的输出都输出到当前用户的E-mail中:
# cat /var/spool/mail/yarn From [email protected] Fri Apr 8 18:05:01 2016 Return-Path: <[email protected]> X-Original-To: yarn Delivered-To: [email protected] Received: by YARN.localdomain (Postfix, from userid 500) id CB04F408AD; Fri, 8 Apr 2016 18:05:01 +0800 (CST) Subject: Output from your job 6 Message-Id: <[email protected]> Date: Fri, 8 Apr 2016 18:05:01 +0800 (CST) From: [email protected] (yarn)
Test at command! Script is running Create Log file success |
a0000701739bff文件:
#!/bin/sh # atrun uid=500 gid=500 # mail yarn 0 umask 2 ORBIT_SOCKETDIR=/tmp/orbit-yarn; export ORBIT_SOCKETDIR HOSTNAME=YARN; export HOSTNAME USER=yarn; export USER … #!/bin/bash
echo "Test at command!" echo "Script is running"
cd /home/yarn/bash01 mkdir log.d cd log.d touch syslog.log
echo "Create Log file success"
marcinDELIMITER42a52adb |
这个文件是个临时脚本文件,作业执行后此脚本会被删除。可以看到脚本的前面部分有环境变量,在脚本的后面是执行的脚本的代码复制。
脚本执行后创建的目录:
$ ll /home/yarn/bash01/log.d 总用量 0 -rw-rw-r--. 1 yarn yarn 0 4月 8 18:05 syslog.log |
at命令的的另一种使用方式是在命令行直接输入要执行的命令,【Ctrl+d】结束。将上面的例子采用命令行方式设定执行计划。
$ at now + 5 minutes at> echo "Test at command!" at> echo "Script is running" at> cd /home/yarn/bash01 at> mkdir log2.d at> cd log2.d at> touch syslog.log at> echo "Create Log file success" at> <EOT> job 8 at 2016-04-21 17:18 $ |
上面的方式比较脚本而言适合执行不常用的任务,如果采用脚本的方式就不需要每次输入命令而直接指定脚本就可以了。两种方式中采用脚本的方式较为常见。
删除一个未执行的作业可以使用atrm命令,命令格式如下。
atrm 作业号 |
删除一个未执行的作业需要指定作业的作业号,下面通过例子说明。
[[email protected] mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 13 at 2016-04-22 10:41 [[email protected] mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 14 at 2016-04-22 10:41 [[email protected] mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 15 at 2016-04-22 10:41 [[email protected] mail]$ at -f /home/yarn/bash01/process05.sh now + 5 minutes job 16 at 2016-04-22 10:41 [[email protected] mail]$ atq 16 2016-04-22 10:41 a yarn 14 2016-04-22 10:41 a yarn 15 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [[email protected] mail]$ atrm 16 [[email protected] mail]$ atq 14 2016-04-22 10:41 a yarn 15 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [[email protected] mail]$ atrm 15 [[email protected] mail]$ atq 14 2016-04-22 10:41 a yarn 13 2016-04-22 10:41 a yarn [[email protected] mail]$ atrm 13 [[email protected] mail]$ atq 14 2016-04-22 10:41 a yarn [[email protected] mail]$ |
通过atrm命令可以删除未执行的作业,atq命令显示未执行的作业。
15.8 启动时运行脚本或命令
在Linux系统启动时或者用户重新启动一个Shell进程时,执行一个或多个特定功能的脚本,完成特定的功能或启动特定的服务。例如当系统启动时启动WEB服务、数据库服务或重新生成一个系统日志文件等。另外,当用户重新启动一个Shell进程时,也同样可以先执行一个或多个脚本,例如用户专用的环境变量或特定的命令函数等。
15.8.1 系统启动时运行脚本
Linux系统采用特定的顺序在系统启动时运行脚本,不同的Linux版本启动的流程略有不同。了解系统启动流程可以帮助我们在系统启动时运行自己的脚本。下面将系统启动时的顺序进行简要的说明。
1. 开机过程
当开始运行Linux系统时,Linux会将系统内核加载到内存中并运行。首先读取/etc/inittab文件,/etc/inittab文件列出了系统的运行级别,不同的Linux运行级别会启动不同的程序和脚本。
/etc/inittab文件:
# inittab is only used by upstart for the default runlevel. … # Default runlevel. The runlevels used are: # 0 - halt (Do NOT set initdefault to this) # 1 - Single user mode # 2 - Multiuser, without NFS (The same as 3, if you do not have networking) # 3 - Full multiuser mode # 4 - unused # 5 - X11 # 6 - reboot (Do NOT set initdefault to this) # id:5:initdefault: |
以CentOS系统为例,系统的运行级别分为六种,如下表说明。
运行级别 | 说明 |
0 | 关机 |
1 | 单用户模式 |
2 | 多用户模式,通常不支持网络 |
3 | 全功能多用户模式,支持网络 |
4 | 可定义用户 |
5 | 多用户模式,支持网络和图形化桌面系统 |
6 | 重启 |
系统运行级别可以控制启动系统上的那些服务,可以看到/etc/inittab文件默认的运行级别是5,是多用户模式。每个运行级别将决定启动过程运行和停止哪些脚本。这些开机脚本都是Shell脚本,通过提供必要的环境变量来启动应用程序。开机脚本会放置在/etc/rc*.d目录或init.d目录下,*号代表系统运行级别。不同的Linux版本略有不同。
2. 定义自己的开机脚本
不要将用户的启动脚本和系统的启动的脚本放置在同一目录下,例如:将自己的Shell脚本放置在rc5.d目录下,或者在系统启动脚本中添加自己的脚本内容,这样可能会出现不可预测的问题。通常情况下,以CentOS系统为例,用户可以在/etc/rc.d/rc.local文件中添加用户启动时执行的脚本。
在rc.local文件中可以指定要执行的命令或者脚本,但要注意的是执行的脚本需要完整的路径,否则系统找不到执行文件会提示错误信息。如下例子,在系统启动时执行yarn用户目录下的一个脚本文件,注意:需要提供完整路径。
/etc/rc.d/rc.local 文件 :
#!/bin/sh # # This script will be executed *after* all the other init scripts. # You can put your own initialization stuff in here if you don‘t # want to do the full Sys V style init stuff.
touch /var/lock/subsys/local /home/yarn/bash01/init.sh |
配置完成后,每次启动系统,都会执行init.sh脚本,如果创建文件或删除文件等等。
15.8.2 启动新的Shell进程时运行脚本
在前面的章节中讲过,每个Linux系统用户的工作目录下都存在两个文件:.bash_profile文件和.bashrc文件,可以使用这两个文件设置用户环境变量、函数、命令别名和启动用户脚本。
当用户登录系统时启动的Shell进程会执行.bash_profile文件,并且只执行一次,所以可以将用户登录时要执行的脚本设置到这个文件中。而.bashrc文件是通过bash命令或打开一个终端时启动的新的Shell进程时都会执行一次,所以这个文件中也可以设置要运行的脚本,但要注意,每次启动一次新的Shell进程时都会运行一次。
或者在/etc/profile.d目录下创建用户的脚本,当用户登录时,这个脚本会被执行。或者在/etc/profile文件中指定要执行的脚本,需要指定脚本的完整路径。/etc/profile文件只在用户登录时执行一次。另外,可以在/etc/bashrc文件中指定要运行的脚本,但不同之处在于,系统上所有的用户只要通过bash命令或重新打开一个终端时都会执行一次,用户可以根据不同的情况选择不同的文件执行用户的脚本。
本文出自 “kevin.blog” 博客,请务必保留此出处http://dongkevin.blog.51cto.com/12103298/1867442
以上是关于Linux学习笔记——脚本执行的过程控制的主要内容,如果未能解决你的问题,请参考以下文章
Linux学习笔记:bash特性之多命令执行,shell脚本