Linux 命令行与 shell 脚本编程大全 16 控制脚本

介绍如何向脚本发送信号、修改脚本的优先级,以及在脚本运行时切换到运行模式

更多精彩

导览

  1. Linux 可以利用信号控制脚本,这在第 4 章中已经有介绍
  2. trap 命令可以在脚本中拦截 shell 发送到脚本的信号,并进行本地化操作
  3. 可以在脚本的执行语句最后添加 & 符号,来实现脚本的后台运行
    • 但这种后台运行不是静默的,任何没有重定向的 STDOUTSTDERR 都会输出到显示器
    • 同时如果当前 shell 退出,处于该模式下运行的脚本也会退出
  4. 可以再脚本的执行语句最前添加 nohup 以及最后添加 & 符号,来实现脚本的静默后台运行
    • 在这种模式下运行的脚本,任何没有重定向的 STDOUTSTDERR 都会被输出到当前目录下的 nohup.out 文件中
    • 同时即使当前 shell 退出,处于该模式下运行的脚本也不会直接退出
  5. jobs 命令可以查看当前 shell 的作业列表
  6. bg 命令可以将暂停的作业恢复后台运行
  7. fg 命令可以将暂停的作业恢复前台运行
  8. nice 命令可以在脚本执行时设置脚本的调度优先级
  9. renice 命令可以在脚本执行过程中设置脚本的调度优先级
  10. at 命令可以在预设的时间节点执行脚本
  11. atq 命令可以查看当前 shell 等待执行的脚本列表
  12. atrm 命令可以从等待执行的脚本列表中删除指定预设
  13. cron 程序提供了定期运行脚本的接口

16.1 处理信号

16.1.1 重温 Linux 信号

  1. Linux 和程序之间可以生成超过 30 个信号
  2. 在前文的 4.1.3.1 Linux 中进程通信方式 中已经介绍过几种常见的 Linux 信号,如下图
    • 默认情况下,shell 会忽略 QUIT ( 3 )TERM ( 15 ) 信号,因为只有这样,交互式的 shell 才不会意外终止
  3. shell 会处理 HUP ( 1 )INT ( 2 ) 信号,并将这些信号发送给脚本,但脚本的默认行为是忽略这些信号
    • 要避免这种情况,可以在脚本中加入识别信号的代码,并执行命令对信号进行处理
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.1.2 生成信号

  1. 这里的生成信号指的是利用键盘按下组合键来向 shell 发送信号

16.1.2.1 Ctrl + C 中断进程

  1. Ctrl + C 会生成 INT ( 2 ) 信号,并发送给当前在 shell 中运行的进程
  2. 通过在执行一段命令或脚本时,如果想中途退出,按下 Ctrl + C 即可
  3. 但这对 vim 无效

16.1.2.2 Ctrl + Z 暂停进程

  1. Ctrl + Z 会生成 TSTP ( 18 ) 信号,暂停当前在 shell 中运行的进程,但继续保留在内存中
  2. 注意,这里的暂停不是终止进程,暂停的程序会继续保留在内存中,而且能从上次暂停的位置继续运行
  3. 可以做一个简单的尝试,来看一下两个组合键触发的信号有什么区别,如下图
    • 首先是使用 Ctrl + C 中断进程,之后使用 ps 命令查看当前进程,并没有找到该进程,说明进程确实不存在了
    • 然后是使用 Ctrl + Z 暂停进程,shell 直接输出了一句提示信息,方括号中的数字是该进程的作业号
    • 之后使用 ps -l 命令查看当前进程的详细信息,发现该进程还存在,并且在列表的 S 列显示的字母是 T ,这表示该进程被暂停了
    • 想要终止该进程,只需要使用 kill -9 PID 即可( 下图中该进程的 PID 是 25916 )
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.1.3 trap 捕获信号

  1. 使用 trap 命令可以在脚本中捕获 shell 发送给脚本的信号
  2. 如果脚本收到了通过 trap 命令指定的信号,那么该信号就不再由 shell 处理,而是由脚本进行本地处理
  3. trap 命令的完整语法是 trap commands signals ,具体操作如下图
    • commands 是捕获到对应信号想要执行的命令
    • singals 是对应的信号值,可以使用信号对应的数值、名称( 可以是全称、简称 )
      • 比如 Ctrl + C 对应的终止信号,数值是 2 ,全称是 SIGINT ,简称是 INT ,这三个值都可以捕获到该信号
        Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.1.4 捕获脚本退出

  1. 使用 trap 命令时,在最后的信号处添加 EXIT 就可以捕获到脚本的退出信号,如下图
    • 可以看到, 第一次运行时,让脚本运行结束后正常退出,成功触发了 EXIT 信号,并输出了语句
    • 第二次运行时,使用 Ctrl + C 提前退出脚本,也成功触发了 EXIT 信号,并输出了语句
    • 第三次运行时,使用 Ctrl + Z 提前暂停脚本,并没有触发 EXIT 信号,这也说明 暂停和终止是不同的
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.1.5 修改或移除捕获

16.1.5.1 修改捕获

  1. 修改捕获只需要在脚本的另一处对相同的信号输出不同的命令即可,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.1.5.2 移除捕获

  1. 移除捕获只需要在 commands 的位置使用 双破折号( – ) 代替即可,如下图
    • 可以看到,当移除捕获后,按下 Ctrl + C 可以直接退出,没有任何提示
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.2 以后台模式运行脚本

16.2.1 后台运行脚本

  1. 在脚本运行的命令后添加一个 & 符号即可实现脚本的后台运行,如下图
    • 可以看到,脚本在后台运行时,首先会输出一个带作业号的以及 PID 的进程信息,运行结束后,还会再输出一个运行结束的提示信息
  2. 但需要注意到的是,由于在脚本内部的输出语句没有重定向,所以对应的语句还是直接输出到了显示器,这种体验不太友好
  3. 建议对需要后台运行的脚本,将内部的输出语句,不论是 STDOUT ,还是 STDERR ,都应该重定向到对应文件
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.2.2 运行多个后台作业

  1. 其实就是多次执行后台运行的脚本,让这些脚本能够在后台同时运行,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.3 nohup 在非控制台下运行脚本

  1. 当我们使用 & 符号对脚本后台运行时,虽然脚本的运行已经不占用 shell 的交互,但如果此时我们想要退出 shell ,首先 shell 会给予我们当前仍有脚本在运行的提示,如果再次执行退出,则会退出当前 shell ,并终止脚本的运行,如下图
    • 下图中,如果在脚本运行结束之前再执行一次退出,shell 窗口就会直接关闭,并且对应的脚本运行也会直接终止
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  2. 想要让脚本实现完全的静默后台运行,可以脚本运行的语句最前面添加 nohup 命令,当然后最后依旧需要添加 & 符号,如下图
    • 为什么说是静默后台运行,看下图便知,在当前运行的脚本中,我们并没有对输出语句进行重定向
    • 但是脚本在运行时,并没有在显示器输出任何语句,对应的输出语句都被直接保存到了当前目录临时生成的 nohup.out 文件中
    • 这个文件是由 nohup 命令自动生成的
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  3. nouhup 模式下,就算我们退出了当前 shell ,通过 nohup 运行的脚本依旧会正常运行,并且将所有的输出语句保存到 nohup.out
  4. 需要注意的是,nohup.out 文件在同一目录下是通用的,如果在同一目录中有多个使用 nohup 运行的脚本,那么他们的输出语句都被被保存到当前目录下的同一个 nohup.out

16.4 作业控制

  1. 作业控制 是指对脚本作业的启动、暂停、终止以及恢复

16.4.1 jobs 查看作业

  1. 使用 jobs 命令可以查看 shell 当前正在处理的作业,如下图
    • jobs 命令后添加 -l 指令,可以看到更详细的包括作业 PID 的信息
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  2. 更多 jobs 命令的指令可以参考下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本
  3. 当同时运行多个脚本时,使用 jobs 命令就可以看到多个作业,但不同的作业在列表中显示存在一定差异,如下图
    • 带加号的作业是 默认作业 ,如果在使用相关作业控制命令时,没有指定任何作业号,那么该作业就会被作为缺省作业号直接使用
      • 例如后续会介绍到的 fg 命令,如果在下图的环境中直接使用的话,那么就会将这个作业号为 3 的默认作业切换到前台模式运行
    • 在当前 默认作业 运行结束后,带减号的作业就会变成下一个 默认作业
    • 任何使用都只会同时存在一个带加号和一个带减号的作业
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.4.2 重启暂停的作业

16.4.2.1 bg 实现以后台模式重启作业

  1. 使用 bg 命令可以将暂停的作业以后台模式继续运行,如下图
    • 当直接运行脚本后并使用 Ctrl + Z 暂停脚本
    • 使用 jobs -l 命令可以查看到该脚本处于暂停状态( 我运行的环境是 Mac 下的终端,暂停标识是 suspended ,Linux 中一般是 stopped )
    • 直接使用 bg 命令,不添加任何作业号( 因为该作业是默认作业 ),就可以将该作业重启
    • 再次使用 jobs -l 命令可以看到该作业已经恢复运行
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.4.2.2 fg 实现以前台模式重启作业

  1. 使用 fg 命令可以将暂停的作业以前台模式继续运行,如下图
    • 由于作业以前台模式恢复运行,新的命令提示符,必须等脚本运行结束后才会出现
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.5 设置调度优先级( Scheduling Priority )

  1. TIP :这里的原文翻译是调整谦让度,我觉得有点拽词了,没必要
  2. 调度优先级 是 Linux 内核分配给进程的 CPU 时间,由 shell 启动的所有进程的 调度优先级 默认都是相同的
  3. 调度优先级 是个整数值,范围是从 -20 ~ +19-20 是最高优先级,-19 是最低优先级,默认值是 0

16.5.1 nice 设置调度优先级

  1. 使用 nice -n 命令可以设置命令启动时的调度优先级,如下图
    • ps 命令的列表信息中默认是没有显示调度优先级的,但是可以通过 -o ni 来开启该值的显示,为了防止只显示 NI 列,所以最后使用 -o pid,ni,cmd 来丰富列表信息
    • 可以看到,使用 nice -n 10 命令启动的脚本 NI 列的值就是 10
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  2. 脚本启动时,默认的调度优先级是 0 ,小于 0 是调高调度优先级,大于 0 是调低调度优先级
  3. 但普通用户其实是无法将调度优先级调低的,如下图
    • 当非 root 用户试图以 -10 的调度优先级来运行脚本时,系统给予了权限不足的提示
    • 使用 jobs -l 命令可以查看到该脚本仍然是运行成功的
    • 但是使用 ps 命令查看时,发现该脚本虽然运行成功,但调度优先级是默认的,说明调度优先级的修改是失败的
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  4. 如果想要更简单的使用 nice 命令,可以忽略 -n 指令,直接使用 nice -10 就表示将调度优先级修改到 10

16.5.2 renice 设置调度优先级

  1. 使用 renice 命令可以通过指定 PID ,来设置正在运行脚本的调度优先级,如下图
    • 可以看到,一开始通过 nice -10 启动的脚本,调度优先级是 10
    • 之后通过 renice -n 15 重新将脚本的调度优先级设置到 15 也是成功的
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  2. 普通用户在使用 renice 命令设置脚本的调度优先级时,需要注意以下几点
    1. 只能对属于自己的进程执行 renice
    2. 只能使用 renice 降低进程的调度优先级
  3. 只有 root 用户可以使用 renice 所以设置进程的调度优先级

16.5.2.1 renice 的简写和 nice 不一样

  1. 需要注意以下的是,nice 命令可以省略 -n 指令直接使用 nice -10 表示将脚本的调度优先级设置到 10
  2. 但是 renice 命令不一样,如果省略 -n 指令直接使用 renice -15 表示将脚本的调度优先级设置到 -15 ,如下图
    • 如果要使用简写形式将脚本的调度优先级设置到 15 ,应该使用 renice 15
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6 定时运行作业

16.6.1 用 at 命令来计划执行作业

  1. 使用 at 命令会将作业提交到队列中,告知 shell 何时运行该作业
    • 针对不同的优先级,存在 26 种不同的作业队列,通常用 a-z 和 A-Z 来指代
    • 作业队列的字母排序越高,作业运行的优先级就越高
    • 默认情况下,作业会被提交到 a 作业队列
    • 使用 -q 指令可以显式的指定想要的队列
  2. at 命令的守护进程 atd 会以后台模式运行,检查作业队列来运行作业
    • 大多数 Linux 会在启动时运行该守护进程
    • 默认情况下,atd 进程会每隔 60 秒检查 /var/spool/at 目录,来获取 at 命令提交的定时作业
    • 如果检查到当前时间需要运行的作业,就会运行该作业

16.6.1.1 at 命令的格式

  1. at 命令的基本语法是 at -f filename time
  2. time 支持的时间格式非常灵活,比如:
    • 标准的小时、分钟格式,10:15
    • 增加指示符,10:15 PM
    • 特定时间,now 、noon 、midnight 、teatime
    • 标准日期格式,MMDDYY 、MM/DD/YY 、DD.MM.YY
    • 文本日期,Jul 4 、Dec 25

16.6.1.2 获取作业的输出

  1. 默认情况下,Linux 会将提交作业的用户对应的邮件地址作为作业 STDOUTSTDERR 的输出对象
  2. 当脚本运行时,会将脚本的输出结果发送到用户的邮件中
  3. 这听起来就让人觉得体验非常糟糕,而且如果当前 Linux 没有安装 sendmail 程序,就无法获取到任何输出结果
  4. 所以推荐将需要定时运行的脚本内部所有的输出都提前重定向到对应的文件中
  5. 如果不想获取任何输出,可以使用 -M 指令进行输出屏蔽
  6. 写一个例子演示一下,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.1.3 atq 列出等待的作业

  1. 使用 atq 命令可以查看当前有哪些作业在等待执行,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.1.4 atrm 删除作业

  1. 使用 atrm 命令可以删除指定作业号的作业,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.1.5 作业队列的权限

  1. 普通用户只能看到自己的作业队列,也只能删除自己的作业,root 用户可以看到所有的,也能删除所有作业,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.2 cron 安排需要定期执行的脚本

16.6.2.1 cron 时间表

  1. cron 程序 会在后台运行并检查 cron 时间表 ,用来获取已经安排执行的作业
  2. cron 时间表 的格式是 min hour dayofmonth month dayofweek command
    • 格式中的时间节点可以使用具体数字,也可以使用星号作为占位符,例如 15 10 * * * command 就是在每天的 10:15 执行某个命令
    • dayofmonth 是指一个月的哪一天,取值 1 - 31
    • dayofweek 是指一周的哪一天,取值 0 - 6 ,或者 mon / tue / wed / thu / fri / sat / sun

16.6.2.2 crontab 构建 cron 时间表

  1. 每个用户都可以用自己的 cron 时间表 来运行安排好的任务
  2. 使用 crontab -e 可以打开 cron 时间表 的内容编辑器,在其中按照格式编写脚本的运行时间即可,编写好之后输入 :wq 保存并退出即可,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本
    Linux 命令行与 shell 脚本编程大全 16 控制脚本
  3. 使用 crontab -l 可以查看已经规划的 cron 时间表 ,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.2.3 浏览 cron 目录

  1. 如果对脚本的执行时间要求不高,用预配置哈偶的 cron 脚本目录会更方便,如下图
    • cron.hourly 每小时执行,cron.daily 每天执行,cron.weekly 每周执行,cron.monthly 每月执行
    • 将需要执行的脚本复制到以下对应的目录即可
      Linux 命令行与 shell 脚本编程大全 16 控制脚本

16.6.2.4 anacron 程序

  1. cron 程序 的定期基于一个假设:当前 Linux 的系统是 7 * 24 小时不间断运行的
  2. 那么如果当前 Linux 并不是这样运行,而是会在某个时间节点关机,之后再开机,那么本来在这个时间段应该定期执行的脚本就会被错过
  3. 为了解决这个问题,Linux 提供了 anacron 程序
  4. 如果在 Linux 再次开机时, anacron 知道某个作业错过了执行时间,就会尽快执行该作业
  5. anacron 程序只会处理位于 cron 目录的脚本
  6. anacron 用时间戳来决定作业是否在正确的计划间隔内执行了,如下图
    • 每个 cron 目录都有自己的时间戳文件,位于 /var/spool/anacron
    • anacron 也有自己的时间表来检查作业目录,位于 /etc/anacrontab
      Linux 命令行与 shell 脚本编程大全 16 控制脚本
  7. anacron 时间表的格式是 period delay identifier command
    • period 用于定义作业多久运行一次,单位是天
    • delay 会指定系统启动后需要等待多长时间再开始运行错过的脚本,单位是分钟
    • identifier 是特别的非空字符串,我也不知道怎么解释
    • command 包含 run-pars 程序和 cron 脚本的目录名
      • run-parts 是负责运行目录中脚本的程序

16.6.3 使用新 shell 启动脚本

  1. 如果想要在每次开启新 shell 时就立即执行一段脚本,既不需要使用 at 命令,也不需要使用 cron 程序
  2. 回顾 6.6.1 登录式 shell 的内容,我们可以知道 shell 在开启时会首先寻找用户自定义启动文件
  3. 在用户自定义启动文件中一定会被执行的配置文件是 .bashrc 文件
  4. 所以只需要将想要执行的脚本放置在该文件中即可,如下图
    Linux 命令行与 shell 脚本编程大全 16 控制脚本