Linux-数据展示和信号
背景
在shell中,数据的展示方式有两种,一种是在终端上,还有一种是记录在文件中,这些在Linux中都是借助标准文件描述符实现,本文会对这些标准文件描述符做简要记录。同时在Linux中,是通过信号与进程中运行的程序进行交互,本文也会对此做简要记述。全文参考《Linux命令行与Shell编程大全》
内容
数据展示
在shell中文件描述符是一个非负数整数,并且在一个进程中最多可以有9个文件描述符,分别对应于:0~8
,但是由于一些原因,在bash shell中保留了前三个文件描述符:0
、1
、2
,分别对应于:标准输入(STDIN
)、标准输出(STDOUT
)、标准错误输出(STDERR
)。
STDIN
可以理解为我们常见的shell输入,比如我们直接在shell终端输入,或者通过文件读取。STDOUT
可以理解为shell命令的输出,STDERR
可以理解为命令运行报错时的输出,STDOUT
和STDERR
都是一样的输出,但是在Linux中,将其区分开对待。
对于STDIN
,默认情况下是通过终端进行输入,但是也可以通过<
、<<
进行修改,比如:
1 | 则cat的内容就是直接通过文件读取内容获得 |
对于STDOUT
,默认情况下是通过终端输出,但是也可以通过>
、>>
进行修改,比如:
1 | 这种就将命令的输出重定向到了file文件中 |
而对于STDERR
,默认情况下也是通过终端输出,如果要修改输出对象,则必须通过:2>
,比如:
1 | 2和>彼此不可分割 |
那么如果将STDERR
和STDOUT
同时使用呢?也是可以的,比如:
1 | 0、1、2和>不可分割 |
如果想将STDERR
和STDOUT
都重定向输出到同一个文件,也可以使用单独的命令:&>
,比如:
1 | ls shuai.csv wahahah &> file |
但是执行之后,你会获得类似这样STDERR错误一直位于文件顶部
的结果:
1 | ls: wahaha: No such file or directory |
这个是因为bash shell
自动赋予STDERR
更高的优先级,所以将STDERR
放在了文件的顶端便于查看。
如果在shell脚本中,我们需要将不同的输出发送给不同的文件描述符时,这个时候就会讲到临时重定向和永久重定向的概念了,对于临时重定向,比如我们想将一个正常的输出传递给STDERR
,则可以通过在文件描述符前加上数字2: &2
1 | >&2不可分割 |
这种方式可以帮助脚本中输出运行错误的日志。同样,也可以修改shell脚本的STDERR
为永久重定向,则需要借助命令: exec
:
1 | 格式: |
但是这种方式一旦修改以后,再想修改回去就非常麻烦,解决办法是借助其他文件描述符先保存原始数据,待到使用完毕后再修改回去,比如:
1 | 4>&1不可分割 |
同样,还可以修改STDIN
的重定向,这样输入的内容就变成了直接从文件中读取:
1 | exec 6<&0 |
与&>
符号类似,也可以直接通过<>
实现对同一个文件的读写,不过需要注意的是:在shell中,读写文件是通过文件指针读写的,因此当读取文件时,文件指针发生了变化,则再次写入的时候会从变化后的文件指针处开始写入
,比如:
1 | exec 3<> file |
则此时会将new line
写到file
文件的第二行。如下内容:
1 | shuai |
如果对于自定的文件描述符,我们不再想用了,则可以将其删除
1 | 格式 |
在使用文件描述符的过程中,有时需要查询当前shell到底使用了哪些文件描述符,此时就可以借助命令:lsof
:
1 | -p:指定进程ID |
执行后得到如下结果:
1 | COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME |
其中的含义如下(这个我也不是很懂,不过FD可以查看):
1 | COMMAND:正在运行的命令的前9个字符 |
有时候我们并不想查看命令的报错输出,则可以使用Linux下的黑洞文件:/dev/null
,它可以将一切写入进去的内容全部丢弃,同时从该文件只能获取空内容,比如:
1 | echo "content will be drop" > /dev/null |
然后就得说在shell编程中常见一个使用临时文件保存数据的概念,有时候我们需要将一些内容输出到临时文件中,我们可以自主创建,但同时也可以使用命令:mktemp
,它会在当前文件夹下创建一个随机命令的临时文件,如下:
1 | 注意X是大写的,shell会使用随机字符串替代X的部分,X的数量随自己而定 |
如果需要在shell脚本中使用,则需要记录这个随机创建的名字
1 | file=$( mktemp devni.XXXX ) |
另外,Linux中一个特殊的文件夹/tmp
,它是临时文件夹,随着系统重启,则该文件夹中的内容就会被删除。我们同样可以利用mktemp
在文件夹下创建文件,不过需要借助参数-t
:
1 | 它会返回文件绝对路径 |
然后还可以在/tmp
下创建临时文件夹,但是需要借助参数:-d
1 | mktemp -t -d devin.XXXX |
假如我们既需要将STDOUT
输出到命令行窗口,又要将内容输出到文件中去,此时就可以借助命令:tee
和管道|
,比如:
1 | 它不仅会将结果输出到控制台,同时还将内容输出到了file文件中 |
信号
以前我们讲过命令kill -9 PID
用户杀死进程,其中-9
一直不得其意,其实它表示就是Linux中的信号,在Linux中,进程之间的通信都是借助于信号,Linux系统和应用程序可以生成超过30个信号,常见如下:
信号 | 值 | 描述 |
---|---|---|
1 | SIGHUP | 挂起进程 |
2 | SIGINT | 终止进程 |
3 | SIGQUIT | 停止进程 |
9 | SIGKILL | 无条件终止进程 |
15 | SIGTERM | 尽可能终止进程 |
17 | SIGSTOP | 无条件停止进程,但不是终止进程 |
18 | SIGTSTP | 停止或暂停进程,但不是终止进程 |
19 | SIGCONT | 继续运行停止的进程 |
默认情况下,bash shell忽略信号3和15,但接受1和2。
然后就说到常见的两种信号的生成方式:终止进程(2)、暂停进程(18)
1 | 终止正在运行的进程:对应于SIGINT |
如果想要查看当前后台运行了多少作业,则可以使用ps
命令
1 | ps -l |
当后台有运行的shell时,此时退出终端,则会出现如下提示:
1 | 如果一定要退出,则再次执行一遍exit命令即可 |
在用户没有设定的情况下,传递给shell脚本的信号就会由shell自己进行执行,不过有时候我们希望由该shell脚本针对信号的不同执行不同的命令,此时就需要借助命令:trap
,它的格式如下:
1 | signals:是信号的值,或者是对应的数字皆可 |
举个例子:
1 | trap "echo now i catch this signal SIGINT" SIGINT |
还有就是有时我们希望shell脚本运行结束后能够执行一些命令,这个时候可以将signals
部分改为EXIT
即可,则其会在shell脚本运行结束后,执行trap
后的命令,如下:
1 | trap "echo 'this shell script is run over'" EXIT |
如果在shell脚本的不同阶段,针对同一个signals要做出不同的响应,则直接在不同的位置针对同样的信号重写trap
命令即可。但是如果希望删除用户自定的signals响应,则需要追加参数--
:
1 | signals:是信号的值,或者是对应的数字皆可 |
当前所描述的方式都是占用当前shell的窗口的,如果我们想将shell脚本的运行进程放到后台,则可以在尾部追加&
:
1 | 比如: |
运行后,一般会得到类似如下的内容:
1 | 2标识作业编号,是shell给定的唯一编号,689是该作业的进程ID |
这里有一点需要注意,就是创建的作业是和终端会话绑定在 一起,如果终端会话结束,则创建的作业也会结束。那如果希望即便是退出终端也能够保证shell脚本的正常运行,则我们需要借助nohup
命令,方式如下:
1 | 当我们退出终端的时候,nohup会直接无视终端发来的SIGHUP信号 |
nohup运行的命令不会在当前终端中输出内容,它会将STDOUT
和STDERR
内容输出到一个独立的文件: nohup.out
,所以可以直接查看该文件的内容即可。
那么如果我们想要查看后台一共运行了多少个作业,则可以借助jobs
命令
1 | 最简单的就是: |
运行以后,应该会得出类似如下的输出:
1 | shuai@baqi:~$ jobs -l |
其中有两个带有+
和-
,其中待+
是当前的默认作业,而带-
是默认作业结束后的下一个作业。
在bash shell中,可以将已停止的作业作为后台进程/前台进程进行重启,这个需要借助命令:bg
、fg
。其中bg
可以将后台已经停止的作业作为后台进程进行重启,格式如下:
1 | bg 作业号 |
同样,fg
可以将后台已经停止的作业作为前台进程进行重启,并且接管它运行的终端shell,格式如下:
1 | fg 作业号 |
不管是bg
还是fg
,在重启后,都会接管该作业的终端shell。
在多任务的操作系统中,内核负责将CPU时间分配给系统上运行的进程,调度优先级
则决定了每个进程占用CPU时间的多少,而在Linux系统中,调度优先级是一个整数值:-20~19
,值越小,则调度优先级
越高。在Linux中,默认每个进程的调度优先级
是一样的,值为0,如果需要修改进程的调度优先级
,则可以借助命令:nice
:
1 | -n:指定修改的调度优先级的值 |
nice
只是修改即将运行的进程的调度优先级
,如果需要修改已经运行的进程的调度优先级
,则可以借助命令:renice
:
1 | -p:指定修改优先级的进程号 |
不过,对于nice
和renice
而言,如果是降低进程的调度优先级,则不需要root用户权限
,如果是提高进程的调度优先级则需要root用户权限。但是我本地进行实验,却发现无法将进度的优先级调低到0以下,但是可以调到0以上
在Linux中,如果需要在某个指定的时间执行一个定时任务,则可以借助与命令:at
,它的格式:
1 | -f:指定在指定时间需要执行的任务的shell脚本 |
对于at
命令中的time
则可以由多种表达方式:
1 | hour:min |
但是对于at
命令调用的shell脚本,他们都会被加入作业队列中,作业队列用a~z和A~Z
表示优先级,字母排序越高则该shell进程的优先级越高。并且该命令的输出不再是STDOUT
、STDERR
,而是将这些输出直接通过邮件系统发送给运行该shell脚本的用户,如果系统上没有安装send mail程序,则输出将丢失。为了解决这类问题,有两个方案:
添加参数:
-M
1
at -M -f ./run.sh teatime
在脚本直接重定向输出
1
2!/bin/bash
echo "out to the file" > file
那么,如果想查看正在等待的at
命令呢?可以借助命令atq
1 | atq |
同时也可以删除正在等待的at
命令,需要借助:atrm
1 | atrm [作业ID] |
at
命令对于特定时间执行一次命令的需求可以很好的适应,但是对于周期性的任务则无法满足,为此又出现了命令cron
,cron是基于时间表的
,类似于jenkins,格式如下:
1 | min hour day_of_month month day_of_week cmd |
其中day_of_week可以使用0~6
表示周日到周一,其中0
表示周日,6
表示周六,如下:
1 | 每周一的10:18分执行任务 |
同时还可以用:mon、tue、wed、thu、fri、sat、sun
来指定周日到周一:
1 | 上面的例子还可以写成如下 |
对于那些已经构建好的cron时间表,如果需要查看,可以借助命令:crontab -l
1 | crontab -l |
但是执行之后,可能会提示:
1 | baqi@qwer1234:~/Desktop$ crontab -l |
这个是因为默认情况下,用户的cron时间表并不存在,如果需要添加任务列表,则需要借助命令:cron
1 | 执行后,会提示选择编辑器,编辑选择后,就会出现文本,在文本的最后追加内容即可 |
当然,如果对运行脚本的时间精确度要求不是很高的话,则可以使用cron
预设的cron
脚本目录更加方便,这些目录可以通过命令查看:
1 | ls /etc/cron.*ly |
But,cron
也有有缺陷的地方,如果系统异常关机,则cron
对于关机期间的任务就不会再去执行,为此又引入了一个命令:anacron
,它的格式如下:
1 | period:定义作业多久运行一次,以天为单位 |
此处有一点需要说明,那就是anacron
命令不会执行/etc/cron.hourly
目录下的shell脚本,这个是因为anacron
执行的基本单位是天。