shell训练营 | 4. Shell 周边:语言特殊点调试 Debug 方案 vscode 对 shell 的支持

2,359 阅读11分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情

王志远,微医前端技术部

Shell 周边:语言特殊点调试 Debug 方案 vscode 对 shell 的支持

语言特殊点

一门语言的出现必然存在其特殊点,java 的 jvm 和面向对象、js 的解释型、python 的自动化等等,shell 也不例外

  • 脚本控制:优先级控制、捕获信号
  • 计划任务:定时执行任务
  • 延时计划任务:anacontab
  • 计算任务加锁:flock

脚本控制

cpu 和资源的分配

  • nice 和 renice 调整优先级
  • 死循环的控制:cpu 占用和死机

当执行如下命令时,系统会进入假死状态

.()^C|.&}.

捕获信号

捕获语句语法如下

trap "[行为]" [信号编号]
  • kill 默认会发送 15 号信号给应用程序
  • ctrl + c 发送 2 号信息给应用程序
  • -9 号信号不可阻塞
案例一:监听 15 号信号

循环监听 15 信号,当此信号被下发时,打印sig 15

#!/bin/bash
trap "echo sig 15" 15
echo $$ # 打印端口
while :; do
    :
done

启动之后会首先打印端口口,然后我们执行如下命令像指定进程发送 15 信号

kill -15 [pid]
案例二:监听 2 号信号

循环监听 15 信号,当此信号被下发时,打印sig 15

#!/bin/bash
trap "echo sig 2" 2
echo $$ # 打印端口
while :; do
    :
done

启动之后我们ctrl+c实现触发

计划任务

  • 一次性计划任务:at
  • 周期性计划任务:cron
  • 延时计划任务:anacrontab
  • 计划任务加锁 flock

注意事项:

  1. 定时任务是没有终端的,这也就意味这默认没有输出,如果需要查看输出结果就需要用到重定向符号>

一次性计划任务

at [选项] [时间]
  • 录入命令:回车后就会出现at>这种要求输入命令的提示,输入需要在指定时间输入命令

  • 完成记录:输入完成后按下 ctrl + D 即完成了命令的存入

  • 查看记录:可以使用命令atq查看当前一次性任务列表

  • 删除记录:可以使用atrm + [编号]进行删除

注意事项
  1. at 命令是一次性定时计划任务,at 的守护进程 atd 会以后台模式运行,检查作业队列来运行。
  2. 默认情况下,atd 守护进程每 60 秒检查作业队列,有作业时,会检查作业运行时间,如果时间与当前时间匹配,则运行此作业。
  3. at 命令是一次性定时计划任务,执行完一个任务后不再执行此任务了
  4. 在使用 at 命令的时候,一定要保证 atd 进程的启动 , 可以使用相关指令来查看
ps -ef | grep atd //可以检测 atd 是否在运行

画一个示意图

选项

时间定义
支持格式案例补充
接受在当天的 hh:mm(小时:分钟)式的时间指定;假如该时间已过去,那么就放在第二天执行04:00
接受比较模糊的词语来指定时间midnight(深夜),noon(中午),teatime(饮茶时间,一般是下午 4 点)等
采用 12 小时计时制,即在时间后面加上 AM(上午)或 PM(下午)来说明是上午还是下午12pm
指定命令执行的具体日期,指定格式为 month day(月 日)或 mm/dd/yy(月/日/年)或 dd.mm.yy(日.月.年)04:00 2021-05-12指定的日期必须跟在指定时间的后面
使用相对计时法。指定格式为:now + count time-unitsnow + 5 minutesnow 就是当前时间,time-units 是时间单位,这里能够是 minutes(分钟)、hours(小时)、days(天)、weeks(星期)。count 是时间的数量,几天,几小时
直接使用 today(今天)、tomorrow(明天)来指定完成命令的时间。
案例

1 分钟后将在/tmp 目录下创建hello.txt并写入内容hello

at now + 1 minutes
# at>内容框出现后输入如下内容,然后按 ctrl + D
echo hello > /tmp/hello.txt
# 验证
cat /tmp/hello.txt

周期性计划任务:cron

crontab [选项]

crontab 是根据选项执行对应行为的

  • 录入命令:crontab -e ,回车后打开一个 vim 的编辑界面,输入格式为【分钟 小时 日期 月份 星期 执行的命令】,*代表所有,比如小时位是*,代表每小时;比如* * * * * /usr/bin/date >> /tmp/deepinout.com.txt
  • 查看计划执行记录:可以执行tail -f /var/log/cron查看定时任务执行记录
  • 查看用户的计划:crontab -l,其实就等同于cat /var/spool/cron/和用户同名的文件,只是做了个文件读取到标准输出上操作而已
  • 删除记录:可以使用crontab -r [编号]进行删除
注意事项
  1. 最小的设置时间单位是分钟(可以用第三方软件包实现秒)
  2. 注意命令的路径问题

每个用户都有自己的周期性计划任务配置文件,保存在/var/spool/cron/下面,以用户名作为文件名。

crontab 示例

每分钟将日期保存在指定文件中

* * * * * /usr/bin/date >> /tmp/deepinout.com.txt
固定星期几执行计划任务

周一每分钟执行

* * * * 1 /usr/bin/date >> /tmp/deepinout.com.txt

周五每分钟执行

* * * * 5 /usr/bin/date >> /tmp/deepinout.com.txt

周一和周五每分钟执行

* * * * 1,5 /usr/bin/date >> /tmp/deepinout.com.txt

周一至周五每分钟执行

* * * * 1-5 /usr/bin/date >> /tmp/deepinout.com.txt
同时满足指定日期和星期才执行任务

7 月 8 日且是周一至周五,每分钟执行

* * 8 7 1-5 /usr/bin/date >> /tmp/deepinout.com.txt

延时计划任务:anacontab

为了缓解 cron 中一瞬间大量任务并发执行而导致系统压力过大的问题。相关文件

  • /etc/cron.d/0hourly:存储着运行任务的信息
  • /etc/anacontab:
# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days   delay in minutes   job-identifier   command
1       5       cron.daily              nice run-parts /etc/cron.daily
7       25      cron.weekly             nice run-parts /etc/cron.weekly
@monthly 45     cron.monthly            nice run-parts /etc/cron.monthly
环境变量部分
  • RANDOME_DELAY=45:表示 anacron 在执行任务前先延时一段随记的时间再执行,这段随机的时间为 0-45 分钟之内的随机数。

  • START_HOURS_RANGE=3-22:指定了只有在凌晨 3 点到晚上 22 点这个时间段内才允许执行任务。

执行计划部分

由四部分组成:

  • period in days:轮回天数,表示任务多少天执行一次。
  • delay in minutes:表示启动 Anacron 和运行作业时间之间的延迟,单位为分钟. 当然前提是自最后一次运行之后所经过的时间超出了轮回天数。 但是它并不是作业真正运行的时间,真正运行的时间还需要加上 RANDOME_DELAY 中设置的随机分钟数。
  • job-identifier:作业的标识符。anacron 在执行任务时会将日期写入 /var/spool/anacron/$job-identifier 文件中
  • command:实际运行的命令。这里的 run-parts 是一个运行指定目录中所有程序与脚本的命令,可以通过 man run-parts 来查看它的说明
特点如下
  • anacron 只运行每天/周/月的任务
  • 如果一项任务在预定的时间机器处于关机状态,那么在下次开机的时候会执行;
  • anacron 自己没有守护进程运行,需要依赖于外部工具(例如 crond);
  • 可以设置一个延时时间,当到指定时间时,等待一个延时时间再执行任务;

计划任务加锁:flock

为什么出现

在使用 crontab 管理定时脚本时,如果设定的脚本执行时间间隔较短,例如 5 分钟执行一次,正常情况下,脚本执行耗时 1 分钟,在非正常情况下(如服务器压力较大的情况下,或数据量突然增大),脚本执行时间超过 5 分钟,这时就会造成多个脚本同时执行,严重时甚至拖垮服务器,影响服务器上的其它服务。

当多个进程可能会执行同一个脚本,这些进程需要保证其它进程没有在操作,以免重复执行,这就是 flock 的作用。

怎么做到的

通常,这样的进程会使用一个锁文件,也就是建立一个文件来告诉别的进程自己在运行,如果检测到那个文件存在则认为有操作同样数据的进程在工作。

如何使用
flock -h

Usage:
 flock [options] <file|directory> <command> [command args]
 flock [options] <file|directory> -c <command>
 flock [options] <file descriptor number>

Options:
-s, --shared:    获得一个共享锁
-x, --exclusive: 获得一个独占锁
-u, --unlock:    移除一个锁,通常是不需要的,脚本执行完会自动丢弃锁
-n, --nonblock:  如果没有立即获得锁,直接失败而不是等待
-w, --timeout:   如果没有立即获得锁,等待指定时间
-o, --close:     在运行命令前关闭文件的描述符号。用于如果命令产生子进程时会不受锁的管控
-c, --command:   在 shell 中运行一个单独的命令
-h, --help       显示帮助
-V, --version:   显示版本
使用案例
flock -xn "/tmp/f.lock" -c "/root/a.sh"

下图是两个终端同时运行这条命令,后执行者会因为抢不到锁而得不到执行,马上退出

重点概念:管道

当我们单纯的运行多个程序而不需要它们彼此间有互通时,存在如下方式

多命令执行符格式作用案例
;命令 1;命令 2多个命令执行,命令之间没有任何逻辑联系echo 1;echo 2;
&&命令 1&&命令 2逻辑与 当命令 1 正确执行,则命令 2 才会执行 当命令 1 执行不正确,则命令 2 不会执行echo 1&&echo 2;
\命令 1\ 命令 2逻辑或 当命令 1 执行不正确,则命令 2 才会执行 当命令 1 正确执行,则命令 2 不会执行echo 1\echo 2;
echo 1;echo 2;
echo 1&&echo 2;
echo 1||echo 2;

而如果需要互通,比如第一个命令的返回传递给第二个命令,就需要用到管道了,管道的本质就是将多个程序进行了一个连接,和信号一样,也是进程通信的方式之一

  • 管道与管道符(即匿名管道,|,作用是将前一个命令的结果传递给后面的命令,如ls -l | more
  • 子进程与子 shell
ls /etc/ | more
netstat -an | grep ESTABLISHED | wc -l

注意:因为管道是以子进程方式进行执行的,所以内建命令的执行不会传递给父进程。

重点概念:重定向

重定向的本质就是将文件和输入、输出(包含标准输出、错误输出)进行了一个连接

  • 输入重定向符号:<
  • 输出重定向符号:
    • >:覆盖写入文件
    • >>:追加写入文件
    • 2>:错误输出写入文件
    • &>:正确和错误输出统一写入到文件中

多行内容写入

cat > [文件名] << EOF
xxxx 内容
EOF

具体整理

设备设备文件名文件描述符类型
键盘/dev/stdin0标准输入
显示器/dev/stdout1标准输出
显示器/dev/stderr2标准错误输出
类型符号作用
标准输出重定向命令 > 文件以覆盖的方式,把命令的正确输出输出到指定的文件或设备当中
标准输出重定向命令 >> 文件以追加的方式,把命令的正确输出输出到指定的文件或设备当中
错误输出重定向命令 2>文件以覆盖的方式,把命令的错误输出输出到指定的文件或设备当中
错误输出重定向命令 2>>文件以追加的方式,把命令的错误输出输出到指定的文件或设备当中
正确输出和错误输出同时保存命令>文件 2>&1以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存命令>文件 2>>&1以追加的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存命令&>文件(类似 2>&1)以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存命令&>>文件以追加的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存命令>>文件 1 2>文件 2以覆盖的方式,正确的输出追加到文件 1 中,把错误输出追加到文件 2 中

案例

# 创建 a.txt 并写入 123
echo '123' > a.txt
# 将文件内容输入进变量
read a < a.txt
# 将 a 变量内容追加入 a.txt
echo $a >> a.txt
# 将命令执行错误的提示内容写入 error.log 中
nocmd 2> error.log

shell 之调试

说了这么多,最后还是要落到写的程度来,【纸上得来终觉浅,绝知此事要躬行】,写自然免不了有问题,当有问题的时候我们就需要一些手段去调试我们的代码了。怎么调呢?对我个人而言经历了三个阶段

  • log 时期:变量使用 echo 输出,个人通过 vscode 插件优化至一键生成日志语句
  • 自动输出时期:set命令用来修改 Shell 环境的运行参数,也就是可以定制环境,这里就可以做到自动输出日志
  • 运行时处理时期:也就是我们常说的 debugger 了,没错,我们可以和 debugger js 一样去可视化处理 shell 脚本(cool~);个人通过 vscode 插件优化至一键完成搭建动作【如需试用可以搜索:weiyi-tools】

逐一分享啦

log 时期

这个很简单,唯一要注意的就是 shell 变量类型导致的奇葩输出,在 shell 中默认变量是字符串类型,而其他类型的输出则有不同

  • string:echo $变量名
  • array:echo 变量名[@],日后直接echo{变量名[@]} ,日后直接 echo 变量名会只打印第一个字符
  • function:无法打印函数体(个人没找到),echo 变量名会打印函数名,而且函数的引用不能加$

这些就是🐢的臀部--规定了,踩坑良久,献于诸君。我们来看看我说的那个 vscode 插件支持【一键输出打印语句】,目前支持 js 和 shell。【如需试用可以搜索:weiyi-tools】

自动输出时期:set

set命令用来修改 Shell 环境的运行参数,也就是可以定制环境。一共有十几个参数可以定制,官方手册有完整清单。

使用方式很简单,在脚本的顶部放上 set 命令+对应配置即可

set -[...options]

具体配置如下

配置不加时情况加上后补充
u如果遇到不存在的变量,Bash 默认忽略它。遇到不存在的变量就会报错,并停止执行。另一种写法-o nounset,两者是等价的。
x屏幕只显示运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。运行结果之前,先输出执行的那一行命令。还有另一种写法-o xtrace。
e有运行失败的命令(返回值非 0),Bash 默认会继续执行后面的命令。只要发生错误,就终止执行等价-o errexit;这个一定要注意!!!因为我们的函数入出参是用非 0 实现的,如果加了这个就会中止了
o pipefail-e有一个例外情况,就是不适用于管道命令。管道符链接的,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。

顺便提一下,如果命令行下不带任何参数,直接运行set,会显示所有的环境变量和 Shell 函数。

常见使用方式

将下面内容放在脚本顶部可以做到

  • 只要发生错误,就终止执行 (建议不用)
  • 遇到不存在的变量就会报错,并停止执行。
  • 运行结果之前,先输出执行的那一行命令。
  • 管道符链接的,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
set -euxo pipefail

补充:其实本人还是喜欢下面这样就好啦,因为我会封装很多函数,如果加上了 e 就会导致中止

set -ux

运行时处理时期:debugger

在这,会发现自动化时期其实并没有做到随时随地查看自己想看的运行状态,甚至还会打印很多很多不需要看的内容。有没有一种办法,可以直接图形化的查看当前调试状态下参数的值、卡住程序快照、逐步运行呢?聪明的小伙伴肯定想到了,这不就是 debugger 调试嘛。

js 我们肯定是可以的,shell 呢?其实也可以,只是有几个小要求

  1. 系统 bash 版本 > 4.x (mac 自带版本为 3.2,需要手动升级):参考 Mac 升级最新版 bash
  2. 需要安装 vsode 的插件:bash debug
  3. 完成 bash debug 插件配置文件,即 launch.json 文件

这几步的动作原博主写的很赞,我就不赘述了,可见:liushiming.cn/article/deb… program 替换成${file}(原文复制下面的也可以),这代表要调试当前打开的 shell 文件。

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "bashdb",
            "request": "launch",
            "name": "Bash-Debug (simplest configuration)",
            "program": "${file}"
        }
    ]
}

最后,我们来看下效果

关于个人的优化:一键完成搭建动作

直接看效果【如需试用可以搜索:weiyi-tools】

至此,shell 之调试篇,完成。

自实现 vscode 插件对 shell 的支持

除此之外的内容

  • vscode 插件支持常见的 shell snippet
  • vscode 插件支持【属性.xxx】形式使用函数库

写不动了,到时候另起一文,看下效果吧,对 vscode 插件开发想了解的同学也可以前往我的专栏查看:juejin.cn/column/7078…

尾声

救命,内容实在是太多了,而且还有非常非常多踩的坑没有分享,开始以为能一文结束,写着写着才发现 shell 的世界浩如烟海,我只是抓住了一个角而已,还好,名字是带你走进这个世界,本文只能作为一个脉络文了,后续会根据本文进行拆分,输出一个专栏,希望同学们有遇到 shell 中有趣的知识点也能够留言和我分享。

老规矩,鸡汤一下

当你埋头苦读的时候,阿拉斯加的鳕鱼正跃出水面,当你伏案写作的时候,南太平洋的海鸥正掠过海岸,当你认真工作的时候,地球的极圈正五彩斑斓,但梦要你亲自实现,那些你觉得看不到的人,和遇不到的风景,都终将在你生命里出现

每到一个新世界,都是一片新的美好风景,luck!

常用文档