首页 > 编程笔记 > Linux笔记 阅读:18

Shell预定义变量详解($?、$$和$!)

Linux 中,预定义变量不同于之前的用户自定义变量、环境变量、位置参数变量,因为以上这些变量的值都是由用户设置或可以由用户修改的。

预定义变量就像它的名字一样,通常是被预先定义过的,我们通过指定变量名来调用预定义变量的值。

预定义变量如下表所示:

表:预定义变量
预定义变量 作用
$? 最后一次执行的命令的返回状态。如果这个变量的值为 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 文件的操作流程:
在以上通过文字描述的流程当中,其实可以进一步对“查看脚本的进程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

相关文章