Shell脚本:Linux Shell脚本学习指南(第三部分Shell高级)三

十三、如何检测子Shell和子进程?

上节我们说了子 Shell 和子进程的区别,这节就来看一下如何检测它们。

我们都知道使用 $ 变量可以获取当前进程的 ID,我在父 Shell 和子 Shell 中都输出 $ 的值,只要它们不一样,不就是创建了一个新的进程吗?那我们就来试一下吧。

[mozhiyan@localhost ~]$ echo $$ #父Shell PID

3299

[mozhiyan@localhost ~]$ (echo $$) #组命令形式的子Shell PID

3299

[mozhiyan@localhost ~]$ echo "http://c.biancheng.net" | { echo $$; } #管道形式的子Shell PID

3299

[mozhiyan@localhost ~]$ read < <(echo $$) #进程替换形式的子Shell PID [mozhiyan@localhost ~]$ echo $REPLY

3299

你看,子 Shell 和父 Shell 的 ID 都是一样的,哪有产生新进程了?作者你是不是骗人呢?

其实不是我骗人,而是你掉坑里了,因为 $ 变量在子 Shell 中无效!Base 官方文档说,在普通的子进程中,$ 确实被展开为子进程的 ID;但是在子 Shell 中,$ 却被展开成父进程的 ID。

除了 $,Bash 还提供了另外两个环境变量——SHLVL 和 BASH_SUBSHELL,用它们来检测子 Shell 非常方便。

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,每次进入一层普通的子进程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(sub shell)嵌套深度的累加器,每次进入一层子 Shell,BASH_SUBSHELL 的值就加 1。

1) 我们还是用实例来说话吧,先说 SHLVL。创建一个脚本文件,命名为 test.sh,内容如下:

#!/bin/bash

echo "$SHLVL $BASH_SUBSHELL"

然后打开 Shell 窗口,依次执行下面的命令:

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

2 0

[mozhiyan@localhost ~]$ bash #执行bash命令开启一个新的Shell会话 [mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

3 0

[mozhiyan@localhost ~]$ bash ./test.sh #通过bash命令运行脚本

4 0

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

3 0

[mozhiyan@localhost ~]$ chmod +x ./test.sh #给脚本增加执行权限

[mozhiyan@localhost ~]$ ./test.sh

4 0

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

3 0

[mozhiyan@localhost ~]$ exit #退出内层Shell

exit

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

2 0

SHLVL 和 BASH_SUBSHELL 的初始值都是 0,但是输出结果中 SHLVL 的值从 2 开始,我猜测 Bash 在初始化阶段可能创建了子进程,我们暂时不用理会它,将关注点放在值的变化上。

仔细观察的读者应该会发现,使用 bash 命令开启新的会话后,需要使用 exit 命令退出才能回到上一级 Shell 会话。

bash ./test.shchmod +x ./test.sh; ./test.sh这两种运行脚本的方式,在脚本运行期间会开启一个子进程,运行结束后立即退出子进程。

2) 再说一下 BASH_SUBSHELL,请看下面的命令:

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

2 0

[mozhiyan@localhost ~]$ (echo "$SHLVL $BASH_SUBSHELL") #组命令

2 1

[mozhiyan@localhost ~]$ echo "hello" | { echo "$SHLVL $BASH_SUBSHELL"; } #管道

2 1

[mozhiyan@localhost ~]$ var=$(echo "$SHLVL $BASH_SUBSHELL") #命令替换 [mozhiyan@localhost ~]$ echo $var

2 1

[mozhiyan@localhost ~]$ ( ( ( (echo "$SHLVL $BASH_SUBSHELL") ) ) ) #四层组命令

2 4

你看,组命令、管道、命令替换这几种写法都会进入子 Shell。

注意,“进程替换”看起来好像产生了一个子 Shell,其实只是玩了一个障眼法而已。进程替换只是借助文件在()内部和外部的命令之间传递数据,但是它并没有创建子 Shell;换句话说,()内部和外部的命令是在一个进程(也就是当前进程)中执行的。

我们不妨来实际检测一下:

[mozhiyan@localhost ~]$ echo "$SHLVL $BASH_SUBSHELL"

2 0

[mozhiyan@localhost ~]$ echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL")

2 0

SHLVL 和 BASH_SUBSHELL 变量的值都没有发生改变,说明进程替换既没有进入子进程,也没有进入子 Shell。

十四、Linux中的信号是什么?

在 Linux 中,理解信号的概念是非常重要的。这是因为,信号被用于通过 Linux 命令行所做的一些常见活动中。例如,每当你按 Ctrl+C 组合键来从命令行终结一个命令的执行,你就使用了信号。每当你使用如下命令来结束一个进程时,你就使用了信号:

kill -9 [PID]

所以,至少知道信号的基本原理是非常有用的。

1、Linux中的信号

在 Linux 系统(以及其他类Unix操作系统)中,信号被用于进程间的通信。信号是一个发送到某个进程或同一进程中的特定线程的异步通知,用于通知发生的一个事件。从 1970 年贝尔实验室的 Unix 面世便有了信号的概念,而现在它已经被定义在了 POSIX 标准中。

对于在 Linux 环境进行编程的用户或系统管理员来说,较好地理解信号的概念和机制是很重要的,在某些情况下可以帮助我们更高效地编写程序。对于一个程序来说,如果每条指令都运行正常的话,它会连续地执行。但如果在程序执行时,出现了一个错误或任何异常,内核就可以使用信号来通知相应的进程。

信号同样被用于通信、同步进程和简化进程间通信,在 Linux 中,信号在处理异常和中断方面,扮演了极其重要的角色。信号巳经在没有任何较大修改的情况下被使用了将近 30 年。

当一个事件发生时,会产生一个信号,然后内核会将事件传递到接收的进程。有时,进程可以发送一个信号到其他进程。除了进程到进程的信号外,还有很多种情况,内核会产生一个信号,比如文件大小达到限额、一个 I/O 设备就绪或用户发送了一个类似于 Ctrl+C 或 Ctrl+Z 的终端中断等。

运行在用户模式下的进程会接收信号。如果接收的进程正运行在内核模式,那么信号的执行只有在该进程返回到用户模式时才会开始。

发送到非运行进程的信号一定是由内核保存,直到进程重新执行为止。休眠的进程可以是可中断的,也可以是不可中断的。如果一个在可中断休眠状态的进程(例如,等待终端输入的进程)收到了一个信号,那么内核会唤醒这个进程来处理信号。如果一个在不可中断休眠状态的进程收到了一个信号,那么内核会拖延此信号,直到该事件完成为止。

当进程收到一个信号时,可能会发生以下 3 种情况:

  • 进程可能会忽略此信号。有些信号不能被忽略,而有些没有默认行为的信号,默认会被忽略。
  • 进程可能会捕获此信号,并执行一个被称为信号处理器的特殊函数。
  • 进程可能会执行信号的默认行为。例如,信号 15(SIGTERM) 的默认行为是结束进程。

当一个进程执行信号处理时,如果还有其他信号到达,那么新的信号会被阻断直到处理器返冋为止。

2、信号的名称和值

每个信号都有以SIG开头的名称,并定义为唯一的正整数。在 Shell 命令行提示符 下,输入kill -l命令,将显示所有信号的信号值和相应的信号名,类似如下所示:

[c.biancheng.net]$ kill -l
1) SIGHUP        2) SIGINT          3) SIGQUIT         4) SIGILL         5) SIGTRAP
6) SIGABRT       7) SIGBUS          8) SIGFPE          9) SIGKILL       10) SIGUSR1
11) SIGSEGV      12) SIGUSR2        13) SIGPIPE        14) SIGALRM       15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD        18) SIGCONT        19) SIGSTOP       20) SIGTSTP
21) SIGTTIN      22) SIGTTOU        23) SIGURG         24) SIGXCPU       25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF        28) SIGWINCH       29) SIGIO         30) SIGPWR
31) SIGSYS       34) SIGRTMIN       35) SIGRTMIN+1     36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5     40) SIGRTMIN+6     41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12   47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13   52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10    55) SIGRTMAX-9     56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5     60) SIGRTMAX-4     61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

信号值被定义在文件 /usr/include/bits/signum.h 中,其源文件是 /usr/src/linux/kernel/signal.c。

在 Linux 下,可以查看 signal(7) 手册页来查阅信号名列表、信号值、默认的行为和它们是否可以被捕获。其命令如下所示:

man 7 signal

下标所列出的信号是 POSIX 标准的一部分,它们通常被缩写成不带SIG前缀,例如,SIGHUP 通常被简单地称为 HUP。

信 号默认行为描 述信号值
SIGABRT生成 core 文件然后终止进程这个信号告诉进程终止操作。ABRT 通常由进程本身发送,即当进程调用 abort() 函数发出一个非正常终止信号时6
SIGALRM终止警告时钟14
SIGBUS生成 core 文件然后终止进程当进程引起一个总线错误时,BUS 信号将被发送到进程。例如,访问了一部分未定义的内存对象10
SIGCHLD忽略当了进程结束、被中断或是在被中断之后重新恢复时,CHLD 信号会被发送到进程20
SIGCONT继续进程CONT 信号指不操作系统重新开始先前被 STOP 或 TSTP 暂停的进程19
SIGFPE生成 core 文件然后终止进程当一个进程执行一个错误的算术运算时,FPE 信号会被发送到进程8
SIGHUP终止当进程的控制终端关闭时,HUP 信号会被发送到进程1
SIGILL生成 core 文件然后终止进程当一个进程尝试执行一个非法指令时,ILL 信号会被发送到进程4
SIGINT终止当用户想要中断进程时,INT 信号被进程的控制终端发送到进程2
SIGKILL终止发送到进程的 KILL 信号会使进程立即终止。KILL 信号不能被捕获或忽略9
SIGPIPE终止当一个进程尝试向一个没有连接到其他目标的管道写入时,PIPE 信号会被发送到进程13
SIGQUIT终止当用户要求进程执行 core dump 时,QUIT 信号由进程的控制终端发送到进程3
SIGSEGV生成 core 文件然后终止进程当进程生成了一个无效的内存引用时,SEGV 信号会被发送到进程11
SIGSTOP停止进程STOP 信号指示操作系统停止进程的执行17
SIGTERM终止发送到进程的 TERM 信号用于要求进程终止15
SIGTSTP停止进程TSTP 信号由进程的控制终端发送到进程来要求它立即终止18
SIGTTIN停止进程后台进程尝试读取时,TTIN 信号会被发送到进程21
SIGTTOU停止进程后台进程尝试输出时,TTOU 信号会被发送到进程22
SIGUSR1终止发送到进程的 USR1 信号用于指示用户定义的条件30
SIGUSR2终止同上31
SIGPOLL终止当一个异步输入/输出时间事件发生时,POLL  信号会被发送到进程23
SIGPROF终止当仿形计时器过期时,PROF 信号会被发送到进程27
SIGSYS生成 core 文件然后终止进程发生有错的系统调用时,SYS 信号会被发送到进程12
SIGTRAP生成 core 文件然后终止进程追踪捕获/断点捕获时,会产生 TRAP 信号。5
SIGURG忽略当侖一个 socket 有紧急的或是带外数据可被读取时,URG 信号会被发送到进程16
SIGVTALRM终止当进程使用的虚拟计时器过期时,VTALRM 信号会被发送到进程26
SIGXCPU终止当进程使用的 CPU 时间超出限制时,XCPU 信号会被发送到进程24
SIGXFSZ生成 core 文件然后终止进程当文件大小超过限制时,会产生 XFSZ 信号25

十五、Bash Shell中的信号简述

当没有任何捕获时,一个交互式 Bash Shell 会忽略 SIGTERM 和 SIGQUIT 信号。由 Bash 运行的非内部命令会使用 Shell 从其父进程继承的信号处理程序。如果没有启用作业控制,异步执行的命令会忽略除了有这些信号处理程序之外的 SIGINT 和 SIGQUIT 信号。由于命令替换而运行的命令会忽略键盘产生的作业控制信号SIGTTIN、SIGTTOU 和 SIGTSTP。

默认情况下,Shell 接收到 SIGHUP 信号后会退出。在退出之前,一个交互式的 Shell 会向所有的作业,不管是正在运行的还是已停止的,重新发送 SIGHUP 信号。对已停止的作业,Shell 还会发送 SIGCONT 信号以确保它能够接收到 SIGHUP 信号。

若要阻止 Shell 向某个特定的作业发送 SIGHUP 信号,可以使用内部命令 disown 将它从作业表中移除,或是用“disown -h”命令阻止 Shell 向特定的作业发送 SIGHUP 信号,但并不会将特定的作业从作业表中移除。

我们通过如下实例,来了解一下 disown 命令的作用

#将 sleep 命令放在后台执行,休眠30秒

[c.biancheng.net]$ sleep 30 &

[1] 8052

#列出当前 Shell 下所有作业的信息

[c.biancheng.net]$ jobs -l

[1]+ 8052 Running sleep 30 &

#将作业1从作业表中移除

[c.biancheng.net]$ disown %1

#再次列出当前 Shell 下所有作业的信息

[c.biancheng.net]$ jobs -l

#查找 sleep 进程

[c.biancheng.net]$ ps -ef | grep sleep

mozhiyan 8052 8092 cons1 11:28:21 /usr/bin/sleep

#打印当前 Shell 的进程号

[c.biancheng.net]$ echo $$

8092

在上述实例中,我们首先将命令“sleep 30”放在后台运行,此时,我们使用命令“jobs -l”可以看到作业表中有一个正在运行的作业,然后,我们使用命令“disown %1”将作业1从作业表中移除,再使用命令“jobs -l”会看到作业表中已经没有了作业,但是我们发现其实“sleep 30”这个命令的进程仍然存在。此时,Shell 若接收到 SIGHUP 信号,它就不会向作业1重新发送 SIGHUP 信号,此时如果我们退出 Shell,这个作业仍将继续运行,而不会被终止。

我们再来看一下命令“disown -h”的用途

#将 sleep 命令放在后台执行,休眠30秒

[c.biancheng.net]$ sleep 30 &

[1] 3184

#列出当前 Shell 下所有作业的信息

[c.biancheng.net]$ jobs -l

[1]+ 3184 Running           sleep 30 &

#阻止 Shell 向作业1发送 SIGHUP 信号

[c.biancheng.net]$ disown -h %1

[c.biancheng.net]$ jobs -l

[1]+ 3184 Running              sleep 30 &

我们看到,在执行了命令“disown -h %1”后,作业1并没有从作业表中移除,但它己经被标记,所以即使 Shell 收到 SIGHUP 信号也不会向此作业发送 SIGHUP 信号。因此, 如果此时我们退出 Shell,这个作业也仍将继续运行,而不会被终止。

注意:如果使用内部命令 shopt 打开了 Shell 的 huponexit 选项,当一个交互式的登录 Shell 退出时,会向所有的作业发送 SIGHUP 信号。

十六、Linux进程简明教程

进程是 Linux 操作系统中最重要的基本概念之一,这一节我们将了解学习 Linux 进程的一些基础知识。

进程是运行在 Linux 中的程序的一个实例。这是一个你之前就可能已经听说过的基本定义。

当你在 Linux 系统中执行一个程序时,系统会为这个程序创建特定的环境。这个环境包含系统运行这个程序所需的任何东西。

每当你在 Linux 中执行一个命令,它都会创建,或启动一个新的进程。比如,当你尝试运行命令“ls -l”来列出目录的内容时,你就启动了一个进程。如果有两个终端窗口显示在屏幕上,那么你可能运行了两次同样的终端程序,这时会有两个终端进程。

每个终端窗门可能都运行了一个 Shell,每个运行的 Shell 都分别是一个进程。当你从 Shell 调用一个命令时,对应的程序就会在一个新进程中执行,当这个程序的进程执行完成后,Shell 的进程将恢复运行。

操作系统通过被称为 PID 或进程 ID 的数字编码来追踪进程。系统中的每一个进程都有一个唯一的 PID。

现在我们通过一个实例来了解 Linux 中的进程。我们在 Shell 命令行下执行如下命令:

[c.biancheng.net]$ sleep 10 &

[1] 3324

因为程序会等待 10 秒,所以我们快速地在当前 Shell 上查找任何进程名为 sleep 的进程:

[c.biancheng.net]$ ps -ef | grep sleep

mozhiyan   3324    5712   cons1   17:11:46   /usr/bin/sleep

我们看到进程名为 /usr/bin/sleep 的进程正运行在系统中(其 PID 与我们在上一命令中得到的 PID 相同)。

现在,我们尝试并行地从 3 个不同的终端窗口运行上述的 sleep 命令,上述命令的输出将类似如下所示:

[c.biancheng.net]$ ps -ef | grep sleep

mozhiyan   896     5712   cons1   17:16:51   /usr/bin/sleep

mozhiyan   5924   5712   cons1   17:16:52   /usr/bin/sleep

mozhiyan   2424   5712   cons1   17:16:50    /usr/bin/sleep

我们看到 sleep 程序的每一个实例都创建了一个单独的进程。

每个 Linux 进程还有另一个 ID 号码,即父进程的 ID(ppid)。系统中的每一个用户进程都有一个父进程。

命令“ps -f”就会列出进程的 PID 和 PPID。此命令的输出类似如下所示:

[c.biancheng.net]$ ps -f

      UID         PID  PPID      TTY        STIME      COMMAND

mozhiyan   4124   228      cons0      21:37:09      /usr/bin/ps

mozhiyan     228     1        cons0      21:32:23     /usr/bin/bash

你在 Shell 命令行提示符下运行的命令都把当前 Shell 的进程作为父进程。例如,你在 Shell 命令行提示符下输入 ls 命令,Shell 将执行 ls 命令,此时 Linux 内核会复制 Shell 的内存页,然后执行 ls 命令。

在 Unix 中,每一个进程是使用 fork 和 exec 方法创建的。然而,这种方法会导致系统资源的损耗。

在 Linux 中,fork 方法是使用写时拷贝内存页实现的,所以它导致的仅是时间和复制父进程的内存页表所需的内存的损失,并且会为子进程创建一个唯一的任务结构。

写时拷贝模式在创建新进程时避免了创建不必要的结构拷贝。例如,用户在 Shell 命令行提示符下输出 ls 命令,Linux 内核将会创建一个 Shell 的子进程,即 Shell 的进程是父进程,而 ls 命令的进程是子进程,ls 命令的进程会指向与此 Shell 相同的内存页,然后子进程使用写时拷贝技术执行 ls 命令。

1、前台进程和后台进程

当你启动一个进程时(运行一个命令),可以如下两种方式运行该进程:

  • 前台进程
  • 后台进程

默认情况下,你启动的每一个进程都是运行在前台的。它从键盘获取输入并发送它的输出到屏幕。

当一个进程运行在前台时,我们不能在同一命令行提示符下运行任何其他命令(启动任何其他进程),因为在程序结束它的进程之前命令行提示符不可用。

启动一个后台进程最简单的方法是添加一个控制操作符“&”到命令的结尾。例如,如下命令将启动一个后台进程:

[c.biancheng.net]$ sleep 10 &

[1] 5720

现在 sleep 命令被放在后台运行。当 Bash 在后台启动一个作业时,它会打印一行内容显示作业编号([1])和进程号(PID-5720)。当作业完成时,作业会发送类似如下的信息到终端程序,来显示此作业已完成,其内容类似如下所示:

[1]+ Done    sleep 10

将进程放在后台运行的好处是:你可以继续运行其他命令,而不需要等待此进程运行完成再运行其他命令。

2、进程的状态

每个 Linux 进程都有它自己的生命周期,比如,创建、执行、结束和清除。每个进程也都有各自的状态,显示进程中当前正发生什么。

进程可以有如下几种状态:

  • D(不可中断休眠状态)——进程正在休眠并且不能恢复,直到一个事件发生为止。
  • R(运行状态)——进程正在运行。
  • S(休眠状态)——进程没有在运行,而在等待一个事件或是信号。
  • T(停止状态)——进程被信号停止,比如,信号 SIGINT 或 SIGSTOP。
  • Z(僵死状态)——标记为 <defunct> 的进程是僵死的进程,它们之所以残留是因为它们的父进程适当地销毁它们。如果父进程退出,这些进程将被 init 进程销毁。

若要查看指定进程的状态,可以使用如下命令:

ps -C processName -o pid=,cmd,stat

例如:

[c.biancheng.net]$ ps -C sleep -o pid=,cmd,stat

             CMD                      STAT

9434     sleep       20             S

十七、Linux使用什么命令查看进程

通过前面章节的一些实例的学习,想必你已经知道了使用 ps 命令可以查看进程的信息,但除了 ps 命令,我们还可以使用 pstree 命令和 pgrep 命令查看当前进程的信息。

使用 ps 命令,可以查看当前的进程。默认情况下,ps 命令只会输出当前用户并且是当前终端(比如,当前Shell)下调用的进程的信息。其输出将类似如下所示:

[c.biancheng.net]$ ps

PID       TTY         TIME          CMD

4380    pts/0      00:00:00        bash

4414    pts/0      00:00:00          ps

我们从上面的输出中可以看到,默认情况下,ps 命令会显示进程 ID(PID)、与进程关联的终端(TTY)、格式为“[dd-]hh:mm:ss”的进程累积 CPU 时间(TIME),以及可执行文件的名称(CMD)。并且,输出内容默认是不排序的。

使用标准语法显示系统中的每个进程:

[c.biancheng.net]$ ps -ef | head -2

UID     PID    PPID   C       STIME    TTY     TIME          CMD

root       1          0      0        Janl4         ?   00:00:02       init [5]

使用 BSD 语法显示系统中的每个进程:

[c.biancheng.net]$ ps aux | head -2

USER    PID    %CPU    %MEM     VSZ       RSS   TTY   STAT  START   TIME   COMMAND

root          1         0.0          0.0       2160        648      ?       Ss       Janl4     0:02      init [5]

使用 BSD 样式选项会增加进程状态(STAT)等信息作为默认显示,你也可以使用 PS_FORMAT 环境变量重写默认的输出格式。

查看系统中 httpd 进程的信息:

ps aux | grep httpd

使用 pstree 命令,可以显示进程树的信息:

[c.biancheng.net]$ pstree
init-+-acpid
     |-atd
     |-auditd-+-audispd---{audispd}
     |        `-{auditd}
     |-automount---4*[{automount}]
     |-avahi-daemon---avahi-daemon
     |-crond---5*[crond-+-mj.sh]
     |                  `-sendmail]
     |-cupsd
     |-dbus-daemon---{dbus-daemon}
     |-events/0
     |-events/1
     |-gam_server
     |-gpm
     |-hald---hald-runner-+-hald-addon-acpi
     |                    |-hald-addon-keyb
     |                    `-hald-addon-stor
     |-hcid
     |-hidd
     |-hpiod
     |-java-+-java---17*[{java}]
     |      `-14*[{java}]
     |-java-+-java---29*[{java}]
     |      `-14*[{java}]
     |-java-+-java---34*[{java}]
     |      `-14*[{java}]
     |-java---20*[{java}]
     |-java---292*[{java}]
     |-khelper
     |-klogd
     |-krfcommd
     |-ksoftirqd/0
     |-ksoftirqd/1
     |-kthread-+-aio/0
     |         |-aio/1
     |         |-ata/0
     |         |-ata/1
     |         |-ata_aux
     |         |-cqueue/0
     |         |-cqueue/1
     |         |-hd-audio0
     |         |-kacpid
     |         |-kauditd
     |         |-kblockd/0
     |         |-kblockd/1
     |         |-khubd
     |         |-khungtaskd
     |         |-2*[kjournald]
     |         |-kmpath_handlerd
     |         |-kmpathd/0
     |         |-kmpathd/1
     |         |-kondemand/0
     |         |-kondemand/1
     |         |-kpsmoused
     |         |-kseriod
     |         |-ksnapd
     |         |-kstriped
     |         |-kswapd0
     |         |-2*[pdflush]
     |         |-rpciod/0
     |         |-rpciod/1
     |         |-scsi_eh_0
     |         |-scsi_eh_1
     |         |-scsi_eh_2
     |         |-scsi_eh_3
     |         |-scsi_eh_4
     |         `-scsi_eh_5
     |-loop0
     |-mcstransd
     |-migration/0
     |-migration/1
     |-6*[mingetty]
     |-mj.sh---make---java---11*[{java}]
     |-ntpd
     |-pcscd---{pcscd}
     |-portmap
     |-python
     |-restorecond
     |-rpc.idmapd
     |-rpc.statd
     |-screen---bash---update.sh---cvs
     |-sendmail---2*[sendmail]
     |-sendmail
     |-setroubleshootd---2*[{setroubleshootd}]
     |-smartd
     |-sshd-+-sshd---bash---update_and_rest---cvs
     |      |-sshd---bash---pstree
     |      `-sshd---bash
     |-start_derby.sh---java---45*[{java}]
     |-surf---8*[{surf}]
     |-syslogd
     |-tomcat---sleep
     |-udevd
     |-watchdog/0
     |-watchdog/1
     |-xfs
     |-xinetd
     `-yum-updatesd

pstree 命令以树形结构的形式显示系统中所有当前运行的进程的信息。此树形结构以指定的 PID 为根,若没有指定 PID,则以 init 进程为根。下面,我们看一个显示指定 PID 的进程树的例子:

[c.biancheng.net]$ pstree 4578

httpd-11*[httpd]

上述输出内容的含义是,PID 是 4578 的 httpd 进程下有 11 个 httpd 子进程。在显示时,pstree 命令会将一样的分支合并到一个方括号中,并在方括号前显示重复的次数。

如果 pstree 命令指定的参数是用户名,那么就会显示以此用户的进程为根的所有进程树的信息。其显示内容将类似如下所示:

[c.biancheng.net]$ pstree mozhiyan
Xvnc
dbus-daemon
dbus-launch
dcopserver
gconfd-2
kded
kdeinit-+-bt-applet
        |-esc-+-esc---9*[{esc}]
        |  `-esc---6*[{esc}]
        |-2*[kio_file]
        |-kio—media
        |-klauncher
        `-kwin
kdesktop
kicker
klipper
ksmserver
bash---pstree
start_kdeinit
xstartup---startkde---kwrapper

使用 pgrep 命令,可以基于名称或其他属性查找进程。

pgrep 命令会检查当前运行的进程,并列出与选择标准相匹配的进程的 ID。例如,查看 root 用户的 sshd 进程的 PID:

[c.biancheng.net]$ pgrep -u root sshd

2877

6572

18563

列出所有者是 root 和 daemon 的进程的 PID:

pgrep -u root,daemon

十八、Shell向进程发送信号(kill、pkill和killall命令)

我们可以使用键盘或 pkill 命令、kill 命令和 killall 命令向进程发送各种信号。

1、使用键盘发送信号

在 Bash Shell 下,我们可以使用键盘发送信号,如下表所示。

可以发送信号的组合键

组合键含 义
Ctrl+C中断信号,发送 SIGINT 信号到运行在前台的进程。
Ctrl+Y延时挂起信号,使运行的进程在尝试从终端读取输入时停止。控制权返回给 Shell,使用户可以将进程放在前台或后台,或杀掉该进程。
Ctrl+Z挂起信号,发送 SIGTSTP 信号到运行的进程,由此将其停止,并将控制权返回给 Shell。

1、kill 命令发送信号

大多数主流的 Shell,包括 Bash,都有内置的 kill 命令。Linux 系统中,也有 kill 命令,即 /bin/kill。如果使用 /bin/kill,则系统可能会激活一些额外的选项,比如,杀掉不是你自己的进程,或指定进程名作为参数,类似于 pgrep 和 pkill 命令。不过两种 kill 命令默认都是发送 SIGTERM 信号。

当准备杀掉一个进程或一连串的进程时,我们的常识是从尝试发送最安全的信号开始,即 SIGTERM 信号。以这种方式,关心正常停止运行的程序,当它收到 SIGTERM 信号时,有机会按照已经设计好的流程执行,比如,清理和关闭打开的文件。

如果你发送一个 SIGKILL 信号到进程,你将消除进程先清理而后关闭的机会,而这可能会导致不幸的结果。但如果一个有序地终结不管用,那么发送 SIGINT 或 SIGKILL 信号就可能是唯一的方法了。例如,当一个前台进程使用 Ctrl+C 组合键杀不掉时,那最好就使用命令“kill -9 PID” 了。

在前面的学习中我们已经了解,kill 命令可以发送多种信号到进程。特别有用的信号包括:

  • SIGHUP (1)
  • SIGINT (2)
  • SIGKILL (9)
  • SIGCONT (18)
  • SIGSTOP (19)

在 Bash Shell 中,信号名或信号值都可作为 kill 命令的选项,而作业号或进程号则作为 kill 命令的参数。

(1)实例1

发送 SIGKILL 信号到 PID 是 123 的进程。

kill -9 123

或是:

kill -KILL 123

也可以是:

kill -SIGKILL 123

(2)实例2

使用 kill 命令终结一个作业。

#将 sleep 命令放在后台执行,休眠30秒

[c.biancheng.net]$ sleep 30 &

[1] 20551

#列出当前 Shell 下所有作业的信息

[c.biancheng.net]$ jobs -l

[1]+   20551   Running    sleep 30 &

#终结作业1

[c.biancheng.net]$ kill %1

[1]+   20551   Terminated    sleep 30

#查看当前 Shell 下的作业的信息

[c.biancheng.net]$ jobs -l

2、killall 命令发送信号

killall 命令会发送信号到运行任何指定命令的所有进程。所以,当一个进程启动了多个实例时,使用 killall 命令来杀掉这些进程会更方便些。

注意:在生产环境中,若没有经验,使用 killall 命令之前请先测试该命令,因为在一些商业 Unix 系统中,它可能不像所期望的那样工作。

如果没有指定信号名,killall 命令会默认发送 SIGTERM 信号。例如,使用 killall 命令杀掉所有 firefox 进程:

killall firefox

发送 KILL 信号到 firefox 的进程:

killall -s SIGKILL firefox

3、pkill 命令发送信号

使用 pkill 命令,可以通过指定进程名、用户名、组名、终端、UID、EUID 和 GID 等属性来杀掉相应的进程。pkill 命令默认也是发送 SIGTERM 信号到进程。

(1)实例1

使用 pkill 命令杀掉所有用户的 firefox 进程。

pkill firefox

(2)实例2

强制杀掉用户 mozhiyan 的 firefox 进程。

pkill -KILL -u mozhiyan firefox

(3)实例3

让 sshd 守护进程重新加载它的配置文件。

pkill -HUP sshd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值