跳转至

时间相关的命令

时间,对于人类来说是一个十分重要的概念,对于程序来说也是。不管是在写日志的时候需要输出相应的时间,还是为了衡量程序的性能需要测量运行时间等等。因此,这一章我们来介绍几个和时间有关的命令。

获取系统时间的命令——date

这个命令最基础的用法就是直接输出系统时间,输出的时间会根据系统设置的时区来选择。例如,你在中国,那么date默认输出的时间大概率是CST(China Standard Time)。

你可能想说,这个命令也太简单了。但是,实际上,这个命令除了基础用法之外还有很多用法,其中的一些用法非常好用。

格式化输出

我们要介绍的第一个用法,也是最常用的用法,就是对输出的时间进行格式化。默认情况下,date的输出格式也会根据系统所设置的地区的习惯来显示。我们也可以手动设置时间的输出格式,以满足我们的各种需求。

如果你学过C语言,你会发现,date的格式化方式与C语言的printf有点类似,你需要在最后的位置参数上放上一个设置的格式的字符串,并且记号也是使用%开头的。这种%开头的记号的通用格式如下,其中[]中的内容不是必须的。

%[Flag][Width]S
下面我来详细解释一下每个部分的含义: 1. S:这里是这个标记中唯一必须的部分,使用一个约定的记号来表示时间中的某个成分,例如:%Y可以表示4位数字的年份,%m可以表示两位数字的月份。这里可选的记号有很多,你可以根据需要查看date的文档,或者询问AI。 2. Flag,Width: 这个部分是一个可选的参数和一个表示宽度的数字,这两个部分通常配合使用,用于表示当前这个部分的对齐方式或者大小写方式。例如:%06Y,其中Flag的部分是0,Width的部分是6,表示当年份数字不足6位的时候,在前面使用0来补齐6位。对于用于补齐的字符,你可以选择0, 空格或者不补齐。另外还有两个特殊作用的参数^#,它们用于那些原本为字母的成分,例如星期或者月份的名称。请你自己查阅文档去了解他们的作用。 3. 最后,整个格式化字符串必须以+开头。 下面是几个例子:
date +%Y-%m-%dT%H:%M:%S%Z
使用这个例子,你可以得到你所在的时区以ISO 8601格式的输出,可能类似2025-07-23T21:44:09CST
date "+%Y-%m-%dT%H:%M:%S.%3N [INFO]"
你可以用这个例子作为你的脚本程序的日志输出的开头,结果类似与2025-07-23T21:47:16.760 [INFO]

最后,在这一节,我介绍三个比较常见的时间戳格式。

ISO 8601

首先,是刚才提到过的ISO 8601。这是一个国际标准化组织提供的一个有关时间表示的方案。其实从上面举例用的格式化字符串就能看出它的格式规定。“年-月-日T时:分:秒[,纳秒]时区”,其中纳秒部分可以省略。最后的时区部分可以使用时区对应的英文缩写,可以使用+/-加数字来表示,有时也用字母Z表示UTC时间。

date也提供了参数-I/--iso-8601来输出使用ISO 8601格式化的时间,并且参数后面可以紧跟"date", "hours", "minutes", "seconds", "ns"来指定输出时间的精度,默认情况为"date"。例如,

date -Iminutes # 输出ISO 8601格式化的时间,精确到分钟

RFC 5322

RFC是一些计算机网络方面的文件,通常用于提出一些标准化的建议供大家审阅。大部分计算机网络中的标准文档都是脱胎于此的。这里我们涉及到的RFC 5322是一个有关电子邮件格式的文档,其中的3.3节规定了电子邮件的日期和时间戳的规范。下面这三个命令的效果相同,都可以输出符合RFC 5322规定的时间戳,其中-R/--rfc-email是date提供的专门用于输出RFC 5322格式的参数。你可以参考我提供的格式化字符串或者文档去了解这个规范的具体格式。

date "+%a, %d %b %Y %T %z"
date -R
date --rfc-email

RFC 3339

RFC 3339是一个专门规定了互联网上的时间戳格式的RFC文件,它主要基于上面提到过的ISO 8601。你可以使用date提供的参数--rfc-3339=[date/seconds/ns]来输出符合RFC 3339规定格式的时间戳,其中等号后面必须从date, seconds, ns中三选一以选择输出时间的精度。

推算时间

date还可以用于推算时间,也就是根据你给定的一个时间来生成时间戳。你给定的时间不只可以是一个确定描述的绝对时间,也可以是一个相对时间,也就是根据你的当前时间来推算这个相对时间对应的时间戳。这个功能使用--date=参数,等号后面给出你要要生成时间戳的时间。同时你也可以使用上面提到过的格式化方式来选择时间戳的格式化方式。例如

date --date="next Friday" -I
这个例子使用ISO 8601格式输出了以当前时间为基准下一个最近的周五的最开始(也就是0点0分0秒)对应的时间戳。

设置系统时间

date命令还可以用来设置系统时间。使用-s=/--set=后面接上你要设置的时间即可。当然,大部分情况我们并不希望设置一个任意的时间,因为对于很多应用,尤其是使用到互联网,需要与其他计算机通信的应用,有一个精准的时间是非常重要的。因此,我们可以使用nptdate工具来和npt服务器同步时间。

NPT(Network Time Protocol)是一个用于同步计算机时间的协议。互联网上有有一些权威的授时机构会设立NPT服务器供大家同步时间。你可以使用下面这个命令安装nptdate工具。

apt update
apt install nptdate
然后使用下面的命令来同步时间
nptdate ntp.ntsc.ac.cn
这里我使用的是国家授时中心的NPT服务器,你可以把这里的域名换成其他你信任的NPT服务器的域名。

用于休眠的命令——sleep

sleep命令可以让你的当前shell会话休眠指定时间(最高精度为秒)。这个命令的用法很简单,sleep后面跟上你想要休眠的时间长度(整数)即可,默认单位为秒。你也可以使用s/m/h/d中的一个后缀来作为单位。(注意:d表示天)这个命令只会休眠当前shell会话计算机上其他运行的程序都不会受影响,它通常用于脚本中运行命令之间的停顿。

用于限制命令运行时间的命令——timeout

有的时候你会有需要限制程序运行时间的命令,例如一个程序有概率会因为出现错误导致无法停止等。timeout命令给你提供了这种功能。它可以在命令到达指定时间仍然没有结束的时候给程序发送一个信号来尝试终止这个程序。和上面的sleep类似,你需要使用一个位置参数表示超时时间,超过这个时间的时候就会给正在运行的程序发送信号,这个参数的格式和sleep一样。然后你可以再加上一系列位置参数表示你要运行的命令和参数。你可以用下面这个命令来试试它的效果。

timeout 10 sleep 1m
正常情况下,sleep 1m会导致你的shell会话被休眠1分钟,但是timeout会在10秒钟的时候就终止sleep命令。

timeout还有一些其他可选参数,我在下面介绍几个最常用的。

设置发送的信号

默认情况下,timeout会发送SIGTERM信号,让程序终止。但是有的时候你可能需要发送其他信号。这种情况喜下,你可以使用-s=/--signal=参数,在等号后面接上你要发送的信号的名称,这里使用的名称和kill -l列出的名称一致。

发送强制终止

有的时候,一些程序可能会设置信号处理程序,这可能导致你发送的SIGTERM或者其他信号不能按照你的预期终止程序。在这种情况下timeout也提供了参数-k=/--kill-after=,在等号后面接上一个时间t,可以让timeout在超时发送过指定信号后继续等待时间t。如果这个时间之后程序还没有停止就会给它发送一个SIGKILL让它被强制终止(还记得吗,SIGKILL的默认行为是终止并且不能被修改)。这里的时间t格式和sleep的参数也一致。

测量时间的命令——time

测量程序的运行时间也是十分有用的功能,所以shell中当然也有提供,这里我们先介绍要给简单易用的命令。GNU coreutils中提供的time命令,这个命令不需要任何参数,只需要在后面接上你需要测量时间的语句即可。它的运行结果如下:

time sleep 1
# real    0m1.003s
# user    0m0.002s
# sys     0m0.000s
我们可以看到,这个命令的结果中总共包含了三个数据。其中第一个real的时间是这个程序的真正运行时间。

这个命令也支持格式化输出和其他的用法,但是因为time并不是现在大家最常用的测量时间的工具,所以其他细节请大家自己查阅文档。

更好用的测量时间的命令——hyperfine

刚才我们介绍了GNU coreutils中提供的测量时间的工具,并且我们提到time并不是现在大家最常用的工具。这是因为time测量的时间不够精准,并且功能比较少,而且输出的可视化效果不够好。因此这里我向大家介绍一个更好用的测量时间的工具hyperfine。你可以使用你的包管理器来安装,也可以使用cargo(Rust的包管理器)来安装,因为这是一个Rust的项目。虾米那我们来介绍一下hyperfine的用法。

基础用法

hyperfine最基础的用法就是直接测量一个或者多个命令的运行时间。用法也很简单,就是在hyperfine命令的后面接上要测量的命令(通常你需要用引号把你需要测量的命令包裹起来)。如果你同时测量多个命令,它还会比较这个命令的运行效率。下面你可以尝试一下,

hyperfine "sleep 10"
在等待一段时间之后你可以看到测量的结果,应该是一个比较漂亮的形式。同时,你可能会注意到,hyperfine默认情况下会多次运行测试来减少随机性并且你得到的结果包含平均值和标准差(\(\sigma\))。如果你同时测试多个程序,那么你还可以得到它们运行时间的倍数关系。

指定测量次数

上面我看到hyperfine为了测来的准确性会默认运行多次测试,并且测试的次数是不一定的,会根据你的命令的单次运行时间来决定。但是有的时候,我们可能想要指定运行此数,hyperfine当然也提供了这个功能,你可以从下面的参数中选择: 1. -m/--min-runs: 在这个参数后面接一个整数,表示最少运行的次数。 2. -M/--max-runs: 在这个参数后面接一个整数,表示最多运行的次数。 3. -r/--runs: 在这个参数后面接一个整数,表示指定运行的次数。

准备和收尾

在一些更复杂的情况下,我们可能希望在测试运行之前运行一些命令,或者在测试完成之后运行一些脚本。hyperfine也为我们提供了这种功能,你可以从下面的参数中选择; 1. -s/--setup: 后面接你需要运行的命令,这个命令会在每一轮测试开始之前执行,多次重复运行算一轮。这个参数通常可以用来做一些例如,编译你需要测试的程序之类的任务。 2. -p/--prepare: 后面接你需要运行的命令,这个命令会在每一次测试开始之前执行,通常用于做一些测试的准备,例如生成测试数据等。 3. --conclude: 后面接你需要运行的命令,这个命令会在每一轮结束之后执行,同样的,多次重复运行算一轮。 4. -c/--cleanup: 后面接你需要运行的命令,这个命令会在每一次测试结束之后执行,通常用于清理测试产生的数据或者磁盘缓存等。 这些参数都可以分别为每个要测试的命令指定。

另外,有的时候,我们可能想要排除冷启动对于测试结果的影响。这种情况下,我们可以选择先重复运行若干次测试,但是不计算在结果中。我们可以使用hyperfine提供的-w/--warmup参数。后面接上要先重复运行的测试。(这也是为什么这个参数叫warmup

info

什么是冷启动

有的时候一些会经常被运行的程序可能会利用计算机或者操作系统中的一些缓存来提升自己的性能。例如当我们反复读取同一个文件的时候,第一次读取这个文件需要从磁盘中读取,但是第一次读取后,数据就会被操作系统缓存到系统的内存中。由于从内存中读取会比从磁盘中读取快,所以从第二次读取开始,程序的读取速度就会变快。类似这种的情况中,第一次(或者前几次)运行就被成为冷启动,之后的运行就被成为热启动。

其他功能

hyperfine还有很多其他好用的功能,包括但不限于: 1. 指定测试程序的输入输出位置(标准流,文件或者管道)。 2. 依照某种规则遍历所有可能参数进行测试。这个功能对于需要尝试大量参数的情况比较有用,这样就不需要将每一种命令都写一遍了。 3. 忽略测试程序发生的错误。 4. 指定输出格式。(是否包含颜色,或者用某种格式输出,例如json, html, markdown等) 5. 指定测试程序运行的shell。有的时候,你的测试命令可能用到了某些shell的特性,这个功能会比较有用。

如果你需要使用上这些功能,请你自己探索hyperfine的文档。

小结

这一章介绍了一些和时间有关的命令,虽然内容也不少,但是实际上都很简单。并且,这其中很多命令也都是非常常用的。另外一提,你也可以尝试使用我在这一章提到的命令去测量一下上一章最后介绍的“使用tar命令复制”的命令,并且在你的电脑上和cp比一比,看看能得到什么结论。