Shell预定义变量详解($?、$$和$!)
Linux 中,预定义变量不同于之前的用户自定义变量、环境变量、位置参数变量,因为以上这些变量的值都是由用户设置或可以由用户修改的。
预定义变量就像它的名字一样,通常是被预先定义过的,我们通过指定变量名来调用预定义变量的值。
预定义变量如下表所示:
至于具体除 0 外的数字都有什么含义,这是由编写代码的相关人员决定的。除 0 外,其余数字可用来表示在代码执行过程中的错误退出状态,通常默认 0 表示代码执行正确退出。
接下来举例说明:
同样,如果将写入脚本,那么脚本在子 shell 执行时,会输出脚本的进程 PID。但是,只在脚本中写 $$ 也是没有任何意义的,因为这样一来脚本不会在极短的时间内执行完成,我们无法通过进程 PID 来查看进程,例如:
例如,将 sleep 命令写在脚本中,就可以让脚本进程以睡眠的状态运行更长的时间,结果如下:
如果我们将进程 PID 输出到文件中,那么只要用户有权限对文件进行查看,就能查看进程 PID。系统中很多服务等进程会将运行进程的进程 PID 保存在 /var/run/ 目录下。
因此,我们也可以选择使用文件保存脚本进程的进程 PID。
现在,我们除了查看进程状态和进程占用资源,还可以使用 kill 命令给进程发送信号,执行方式如下:
随后,切换终端查看脚本 /root/test1.sh 运行的进程 PID,使用 kill 命令给进程发送结束进程的信号。
现在,我们再次切换回执行 /root/test1.sh 脚本的终端进行查看:
现在总结一下截至目前的脚本执行及对执行后的脚本输出进程 PID 文件的操作流程:
在以上通过文字描述的流程当中,其实可以进一步对“查看脚本的进程PID”操作进行优化,使其更加易于执行,例如:
上面我们使用了两种查看进程 PID 文件的方式,直接使用 cat 命令查看是最常见的方式,而另一种是将查看命令写入 $(),再使用 echo 命令将其输出到当前终端。
$() 在 Bash 中表示,所有在 $() 中的字符串都会被当作命令执行。这样一来,无须人为查看进程 PID,只要将查看命令写入 $() 即可,因此查看进程或结束进程的命令可修改为:
我们也可以将 kill 命令写进脚本,这样,一个用于结束 test1.sh 脚本文件就被创建出来了。
预定义变量就像它的名字一样,通常是被预先定义过的,我们通过指定变量名来调用预定义变量的值。
预定义变量如下表所示:
预定义变量 | 作用 |
---|---|
$? | 最后一次执行的命令的返回状态。如果这个变量的值为 0,就证明上一条命令正确执行;如果这个变量的值为非 0(具体是哪个数由编程人员决定),就证明上一条命令执行错误 |
$$ | 当前进程的进程 PID |
$! | 后台运行的最后一个进程的进程 PID |
Linux $?
我们先看到的就是 $?,它可以用来检查我们执行的“上一条”命令是否正确。通常,如果上一条命令执行正确,$? 的返回结果就为 0;如果命令执行错误,返回结果(执行失败)就为非 0。至于具体除 0 外的数字都有什么含义,这是由编写代码的相关人员决定的。除 0 外,其余数字可用来表示在代码执行过程中的错误退出状态,通常默认 0 表示代码执行正确退出。
接下来举例说明:
[root@localhost ~]# ls /opt/ #执行ls命令查看/opt/目录,命令执行成功 [root@localhost ~]# echo $? 0 #在ls命令执行成功后输出$?的值,返回结果为0 [root@localhost ~]# ls /Panigale ls: cannot access '/Panigale': No such file or directory #执行ls命令查看/Panigale,因为没有/Panigale,所以ls命令执行失败 [root@localhost ~]# echo $? 2 #失败返回结果 2我们可以明确地看到,当命令执行成功时,$? 的返回结果是 0;当命令执行失败时,命令返回结果是非 0。不同类型的命令执行错误,返回的数值并不相同,要想确定错误数值的具体含义,还需要阅读源代码。
Linux $$
$$ 可以输出当前进程的进程 PID。如果我们直接在命令行中输入 echo $$,看到的就是当前终端的 bash 进程 PID。[root@localhost ~]# echo $$ 1021 [root@localhost ~]# ps -p 1021 PID TTY TIME CMD 1021 pts/1 00:00:00 bash #PID为1021的进程是bash命令解释器在用户登录系统后,根据用户在 /etc/passwd 文件中的记录,运行一个相应的解释器。当前在使用 root 用户登录系统时,root 用户默认 shell 类型是 Bash。因此,无论是否执行命令,系统都运行了bash进程,准备对我们输入的命令进行解析并交给内核执行。
同样,如果将写入脚本,那么脚本在子 shell 执行时,会输出脚本的进程 PID。但是,只在脚本中写 $$ 也是没有任何意义的,因为这样一来脚本不会在极短的时间内执行完成,我们无法通过进程 PID 来查看进程,例如:
[root@localhost ~]# cat /root/test1.sh #!/bin/bash echo "$$" #只在脚本中输出$$的值 [root@localhost ~]# /root/test1.sh 1005 #脚本执行输出进程PID [root@localhost ~]# ps -p 1005 PID TTY TIME CMD 1005 pts/1 00:00:00 test1.sh #执行结果为空,PID为1005的进程不存在在查找进程 PID 时,发现根本找不到相应的脚本进程。这是因为脚本从执行开始到执行结束的周期极短,脚本进程在一瞬间就结束了,所以要想看到脚本进程存在,我们还需要在脚本中加入一些能够让脚本持久运行的命令。
例如,将 sleep 命令写在脚本中,就可以让脚本进程以睡眠的状态运行更长的时间,结果如下:
[root@localhost ~]# cat /root/test1.sh #!/bin/bash echo "$$" sleep 30 #在脚本中加入sleep命令 [root@localhost ~]# /root/test1.sh [root@localhost ~]# ps aux | grep 1041 PID TTY TIME CMD 1041 pts/1 00:00:00 test1.sh #在另一个终端查看PID为1041的进程其实对于 $$ 来说,我们可以选择把它输出到屏幕上,也可以选择把它输出到某个文件中。而且,将进程 PID 输出到某个文件中更有利于多用户查看进程 PID,因为我们在当前终端输出的进程 PID 只能在当前终端查看,在不同的终端是看不到的。
如果我们将进程 PID 输出到文件中,那么只要用户有权限对文件进行查看,就能查看进程 PID。系统中很多服务等进程会将运行进程的进程 PID 保存在 /var/run/ 目录下。
[root@localhost ~]# ls /var/run/*.pid /var/run/atd.pid /var/run/auditd.pid /var/run/crond.pid /var/run/rsyslogd.pid /var/run/sshd.pid #查看任意文件,会发现文件中保存了对应进程的进程PID
因此,我们也可以选择使用文件保存脚本进程的进程 PID。
[root@localhost ~]# cat /root/test1.sh #!/bin/bash echo "$$">/var/run/test.pid #将进程PID通过重定向的方式写入文件 sleep 30 [root@localhost ~]# /root/test1.sh #运行脚本,脚本不会有任何输出切换终端后:
[root@localhost ~]# cat /var/run/test.pid 10851 #切换终端,查看保存进程PID的文件 [root@localhost ~]# ps aux | grep 10851 PID TTY TIME CMD 10851 pts/1 00:00:00 test1.sh #根据进程PID查找进程我们使用重定向的方式保存脚本运行过程中的进程 PID,需要注意,这里我们选择了覆盖式重定向(>)的方式,因为如果使用追加式重定向(>>)保存进程 PID,我们就会发现无论进程正在执行还是已经执行结束,进程 PID 都会被不断地保存到 /var/run/test.pid文件中。然而,通常不需要对已经结束的进程执行进一步操作,因此使用覆盖的方式只保存当前正在运行的进程PID。
现在,我们除了查看进程状态和进程占用资源,还可以使用 kill 命令给进程发送信号,执行方式如下:
[root@localhost ~]# /root/test1.sh #执行/root/test1.sh脚本
随后,切换终端查看脚本 /root/test1.sh 运行的进程 PID,使用 kill 命令给进程发送结束进程的信号。
[root@localhost ~]# cat /var/run/test.pid 10899 #切换终端查看/var/run/test.pid文件 [root@localhost ~]# kill 10899 #查看进程PID,结束进程(kill命令执行时默认为结束进程信号)
现在,我们再次切换回执行 /root/test1.sh 脚本的终端进行查看:
[root@localhost ~]# /root/test1.sh 已终止 #命令行中会出现“已终止”的提示 #如果sleep 30秒时间不足,未及时切换终端执行kill命令,就可以适当调整sleep数值
现在总结一下截至目前的脚本执行及对执行后的脚本输出进程 PID 文件的操作流程:
- 先执行 /root/test1.sh 脚本,执行后会生成当前进程的进程 PID,并且将进程 PID 以覆盖的方式写入 /var/run/test.pid 文件;
- 随后切换终端,根据 /var/run/test1.pid 文件确定正在运行的脚本的进程 PID,根据进程 PID 完成结束进程或查看进程等操作。
在以上通过文字描述的流程当中,其实可以进一步对“查看脚本的进程PID”操作进行优化,使其更加易于执行,例如:
[root@localhost ~]# cat /var/run/test.pid 10955 #查看/var/run/test.pid文件 [root@localhost ~]# echo $(cat /var/run/test.pid) 10955 #将查看命令写入$() [root@localhost ~]# ps -p $(cat /var/run/test.pid) PID TTY TIME CMD 10955 pts/0 00:00:00 test1.sh #执行ps命令,通过$!调用当前终端最后一个放入后台的进程PID如果我们将进程 PID 输出到文件中,那么只要用户有权限对文件进行查看,就能查看进程 PID。系统中很多服务等进程会将运行进程的进程 PID 保存在 /var/run/ 目录下。
上面我们使用了两种查看进程 PID 文件的方式,直接使用 cat 命令查看是最常见的方式,而另一种是将查看命令写入 $(),再使用 echo 命令将其输出到当前终端。
$() 在 Bash 中表示,所有在 $() 中的字符串都会被当作命令执行。这样一来,无须人为查看进程 PID,只要将查看命令写入 $() 即可,因此查看进程或结束进程的命令可修改为:
[root@localhost ~]# ps -p $(cat /var/run/test.pid) #查看进程 [root@localhost ~]# kill $(cat /var/run/test.pid) #结束进程
我们也可以将 kill 命令写进脚本,这样,一个用于结束 test1.sh 脚本文件就被创建出来了。
Linux $!$! 可用来输出当前终端放入后台的最后一个进程的进程 PID。如果想要查看当前终端最后一个放入后台的进程,或者想要给当前终端最后一个放入后台的进程发送某些信号,就可以使用 $! 获取进程 PID,例如:
[root@localhost ~]# vim /etc/issue & [1]1010 #使用 vim 打开文件并放入后台,输出结果中[]内的数字表示是在当前终端中第几个放入后台的进程,1010为进程 PID [root@localhost ~]# echo $! 1010 #输出当前终端最后一个放入后台的进程 PID [root@localhost ~]# ps -p $! PID TTY TIME CMD 1010 pts/0 00:00:00 vim #执行 ps 命令,通过 $! 调用当前终端最后一个放入后台的进程 PID