在自动化备份过程或生成计划报告时,在 shell 脚本中执行日期和时间操作是一项相当常见的任务。不幸的是,在 shell 中处理日期和时间操作或尝试格式化日期从来都不是一件简单的事情,因为 Linux date 和 macOS date 命令行行为可能略有不同。
本文提供了如何在Linux和macOS上使用date和awk命令行以及printfbashshell 命令内置功能执行日期操作和格式化日期的示例。
👉 本文中介绍的所有 Linux 选项都与在 Windows 10 上使用 WSL 部署的所有 Linux 发行版完全兼容。通过我的博文 使用 WSL 在 Windows 10 上使用 Ubuntu 运行 Bash 脚本 了解更多关于 Windows 上的 Linux 的信息。
日期和时间简介
处理日期和时间的复杂性超出了工具或编程问题的范畴。通常,需要考虑时区、夏令时、闰年、闰秒、标准格式,甚至有时还需要考虑日历类型。
有哪些不同的日历类型?
日历是一种通过为特定时期(如天、周、月和年)命名来组织天数的方式。日期是日历中单一天的表示。至今仍在使用许多日历系统,有些基于月亮的运动(阴历)或感知的季节(阳历)。
公历是当今使用最广泛的日历系统,通常被视为国际民用标准。它是一种阳历,每年 365 天,分为 12 个月,每月 28 到 31 天不等,闰年除外(每 4 年在 2 月增加一天(闰日))。
除非另有说明,以下文章假定使用公历。
什么是 UTC 和时区?
协调世界时(UTC),也称为Coordinated Universal Time,是始于 1960 年 1 月 1 日的全球时间和频率协调。它是当前计时的基础,也是所有本地时间的基线。UTC 的时区偏移为 0,用字母Z表示。UTC 经常与格林威治标准时间(GMT)互换使用,但这并不正确,因为 GMT 是一个时区(用于一些欧洲和非洲国家),而 UTC 是一个时间标准,因此 UTC 从不用作本地时间。
UTC 带来了时区,即全球各地为日常目的而采用标准时间的区域。时区与本地时间相关,并且与 UTC 有偏移。UTC 的时区偏移并不总是以整数小时表示,例如,印度标准时间(IST)是UTC+5:30。时区可以用中的时区标识符来标识,如America/Los_Angeles(也称为 tzdata、tz 或 zoneinfo)。注意,IANA 的时区数据库包含关键的历史信息,这在处理给定时间的本地日期和时间表示时是必需的。
某些本地时间可能在一年中的部分时间切换到包含夏令时(DST)概念的时区。
什么是 Unix 时间?
Unix 时间是一个时间点,描述了自 1970 年 1 月 1 日(UTC/GMT 午夜)以来经过的秒数,不包括闰秒。它是 POSIX 规范的一部分。Unix 时间的其他常见名称包括Epoch time、POSIX time、seconds since the Epoch或UNIX Epoch time。Unix 时间中的每一天都假定为 86,400 秒。它不是 UTC 的真实表示。
👉 通过我们的博文 为闰秒做好准备 了解更多关于闰秒的信息,以及 2012 年导致半个互联网宕机的闰秒错误。
Unix 时间从零(0)开始,即1970 年 1 月 1 日 00:00:00 UTC。
⚠️ 某些系统仍将此值存储为有符号 32 位整数,这可能在 2038 年 1 月 19 日达到整数上限时导致重大错误。这被称为 Y2038 问题(Year 2038)。
什么是 ISO 8601 标准?
ISO 8601是公历中日期和时间表示的国际标准,以确保跨系统的一致表示。它是一种基于字段的格式,其中日期和时间被标识(2020-05-09T03:44:25Z),与 Unix 时间(1547092066)形成对比。
该标准使用下表中的说明符来表示日期和时间。注意,该标准要求年份至少为 4 位数字,以避免 Y2K 问题(Year 2000)重演。
| 说明符/标记 | 含义 |
|---|
| YYYY | 四位年份 |
| MM | 两位月份(01=一月,依此类推) |
| ww | 从 1 到 53 的周数 |
| D | 从 1 到 7 的星期几,从星期一开始 |
| DD | 两位月份中的日期(01 到 31) |
| hh | 两位小时(00 到 23)(不允许 am/pm) |
| mm | 两位分钟(00 到 59) |
| ss | 两位秒(00 到 59) |
| s | 一位或多位数字,表示秒的小数部分 |
| Z 或 +hh:mm 或 -hh:mm | 时区指示符 |
| / | 用于表示两个时间点之间的时间间隔 |
示例:
从 shell 命令行使用日历
使用 cal 和 ncal 查看儒略历和公历
Linux 和 macOS 上的命令行工具cal和ncal提供了一种方便的方式,可以从 shell 显示儒略历或公历日历信息。两者之间的主要区别在于默认的日历视图:cal将每周的每一天显示为一列,而ncal命令默认将星期几显示在不同的行上(垂直格式或“横向”)。尽管如此,ncal使用-C选项也可以像cal一样将星期几显示为列,而cal本身使用-N选项也可以显示垂直视图。
cal 工具以传统格式显示简单日历,ncal 提供替代布局、更多选项和复活节日期。
这两个工具都会高亮显示当前日期。
[me@linux ~]$ cal May 2020 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [me@linux ~]$ ncal May 2020 Mo 4 11 18 25 Tu 5 12 19 26 We 6 13 20 27 Th 7 14 21 28 Fr 1 8 15 22 29 Sa 2 9 16 23 30 Su 3 10 17 24 31 [me@linux ~]$ cal 2020 2020 January February March Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 1 2 3 4 1 1 2 3 4 5 6 7 5 6 7 8 9 10 11 2 3 4 5 6 7 8 8 9 10 11 12 13 14 12 13 14 15 16 17 18 9 10 11 12 13 14 15 15 16 17 18 19 20 21 19 20 21 22 23 24 25 16 17 18 19 20 21 22 22 23 24 25 26 27 28 26 27 28 29 30 31 23 24 25 26 27 28 29 29 30 31 ...
要使用cal(或ncal)获取特定年份和月份的日历视图,可以使用-d选项,后跟年份和月份。
[me@linux ~]$ cal -d 1991-11 November 1991 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
要使用cal和ncal显示儒略日(从 1 月 1 日到 12 月 31 日的编号天数)日历,可以使用-j选项。例如,下面可以看到 2020 年有 366 天,是闰年,一年的最后一天将是星期四。
[me@linux ~]$ cal -j -m 12 December 2020 Su Mo Tu We Th Fr Sa 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
说实话,除了从命令行快速查看日历之外,我从未发现这两个工具在任何自动化或生产系统中被合理使用。这更多是为了趣味性或提供信息。
在 Linux 上使用 calendar 命令行查看节假日和事件
Linuxcalendar命令行被定义为提醒服务,可以发送通知并检查指定目录中的日历定义,以显示以给定日期开头的行。这对于列出某个国家的常用节假日或创建自定义的定期事件列表非常方便。该工具在 Linux 上可用,并且需要cpp二进制文件来编译日历文件。
[me@linux ~]$ calendar -f /usr/share/calendar/calendar.usholiday -l 30 -B 30Apr 15 Income Tax Day, USA.Apr 28* Arbor Day, USA (varies from state to state)May 10* Mother's Day (2nd Sunday of May)May 16* Armed Forces in USA Day (3rd Saturday of May)May 25* Memorial Day in USA (Last Monday of May)
使用 shell date 命令行显示和格式化日期
在 Linux 和 Unix/macOS 中操作日期最广泛使用的 shell 命令行实用程序无疑是 shelldate命令。请记住,macOS 基于 Unix,将附带 Unix 版本的dateshell 命令。不过,需要注意一些重要的陷阱,因为 Linux 和 Unix 之间的实现差异很大。
dateshell 命令使用date [options] +FORMAT,其中 format 是一系列格式说明符,用于定义日期和时间的输出格式。选项和格式说明符可能因平台而异。下面的日期格式示例将在 Unix/macOS 和 Linux 平台上都能工作。
[me@linux ~]$ date '+Today is %A, %B %d, %Y.'Today is Saturday, May 09, 2020.
如何确保 Linux 和 macOS 之间的一致性?
保证 Linux 和 macOS 之间行为一致的一种方法是始终使用GNU date版本的 date 命令行实用程序。它默认预装在大多数 Linux 发行版上,如果没有,你需要安装 coreutils 包。在 Ubuntu/Debian 上,你可以运行apt install coreutils。
GNU coreutils 可以使用 homebrew 在 macOS 上安装:brew install coreutils。然后GNU date命令行在 macOS 上将作为gdate可用。
通过使用,你可以定义一个小的包装器来测试是否安装了 GNUdate,否则回退到默认版本。这是在 Mac 上的.bashrc中添加的一个方便包装器。
[me@mac ~]$ type -p date/bin/date[me@mac ~]$ date() { if type -t gdate &>/dev/null; then gdate "$@"; else date "$@"; fi }[me@mac ~]$ type -p date[me@mac ~]$ type datedate is a functiondate () { if type -p gdate > /dev/null; then gdate "$@"; else date "$@"; fi}
Linux 和 Unix/macOS 之间有什么区别?
重要的是要理解POSIX标准(POSIX.2)仅指定了shell date实用程序的-u选项和操作数+format。你可以在中找到完整规范。这意味着dateshell 实用程序中的任何其他选项或操作数都是系统特定的实现,可能无法跨平台或环境移植。
Unix 和 macOSdateshell 实用程序兼容POSIX.2标准,-d、-f、-j、-n、-R、-r、-t和-v选项都是对标准的扩展。
Linuxdateshell 实用程序是GNU dateshell 版本,也兼容POSIX.2标准,并且设置了大量选项,这些都是对标准的扩展。Linux 版本的date在不同平台上安装时有时被称为gdate。
⚠️ 非标准选项不保证在 Linux(GNU 版本)和 Unix/macOS 之间具有相同的行为。一个典型的例子是 -d 选项,在 Linux GNU 版本中它显示由字符串描述的时间,而在 macOS 上它用于设置内核的夏令时值。
-u选项将日期设置为 UTC,而不是使用本地时区。由于它是 POSIX.2 规范的一部分,Linux 和 Unix/macOS 的date版本对于命令date -u都会显示相同的输出。
在 Linux 上使用 GNU date 命令行格式化日期
在 bash 版本 5 之前,通常使用 Linux date 命令获取当前 Unix 时间,例如date '+%s'。现在应该避免这样做,因为你可以直接使用$EPOCHSECONDSshell 变量。请参阅。
[me@linux ~]$ date '+%s' && echo $EPOCHSECONDS15890538401589053840
另一种变体是使用date获取微秒级的实时时间,不过GNU date返回纳秒,而 Bash 直接提供$EPOCHREALTIME(微秒级)。因此,根据情况,你可能需要在来获得正确的值。下面的示例可以轻松移植以获得毫秒。完整的格式化选项列表请参见man date(或man gdate)页面。
[me@linux ~]$ echo $((`date '+%s%N'`/1000)) && echo $EPOCHREALTIME15890538409167661589053840.918266# 获取相同微秒格式化的额外技巧[me@linux ~]$ m=$((`date '+%s%N'`/1000)) && echo ${m:0:${#m}-6}.${m:${#m}-6} && echo $EPOCHREALTIME1589053840.9182661589053840.918266# 获取毫秒级时间并用点分隔的示例[me@linux ~]$ m=$((`date '+%s%N'`/1000000)) && ${m:0:${#m}-3}.${m:${#m}-3}1589054941.851
GNU date另一个广泛使用的选项是-d选项,它使处理日期变得非常容易,因为你可以使用人类可读的单词来定义日期。该选项允许你轻松地对日期进行运算,这是GNU date实用程序的一大优势。
[me@linux ~]$ date -d 'last year'Thu May 9 13:01:43 PDT 2019[me@linux ~]$ date -d '90 days ago'Sun Feb 9 12:03:34 PST 2020[me@linux ~]$ date -d 'Feb 12 + 3 day' -uSat Feb 15 00:00:00 UTC 2020[me@linux ~]$ date -d 'Feb 12 + 13 minutes' -uWed Feb 12 00:13:00 UTC 2020
在常见问题解答中查看更多示例。
在 Unix/macOS 上使用 BSD date 命令行格式化日期
macOSdateshell 命令比GNU版本受限得多。不过,你仍然可以将它用于基本格式化。完整的格式化选项列表请参见man date页面。你可以通过使用-j(不要尝试设置日期,只显示)和-v(调整日期)选项来获得与GNU date-d选项相同的行为。
[me@macos ~]$ date '+%Y/%m/%d %H:%M:%S'2020/05/09 13:20:2[me@macos ~]$ date -j -v +3d -f "%b%d" "Feb 10"Thu Feb 13 12:47:10 PST 2020
使用 GNU awk 命令行显示和格式化日期
GNU awk通常用于高效地解析和处理日志,幸运的是,它在处理日期方面也非常强大。同样,这些功能大多是扩展功能,并非由POSIX标准指定。
| |
|---|
| mktime | 将 datespec(YYYY MM DD HH MM SS)转换为时间戳 |
| strftime | 按照 strftime() 定义的给定格式格式化指定时间 |
| systime | 返回 Unix 时间戳(Epoch) |
[me@macos ~]$ gawk 'BEGIN { print "Seconds since Epoch: " mktime("2020 05 09 14 47 27") }'Seconds since Epoch: 1589060689[me@macos ~]$ gawk 'BEGIN { print "Seconds since Epoch: " systime() }'Seconds since Epoch: 1589060689[me@macos ~]$ gawk 'BEGIN { print strftime("Today is %A, %B %d, %Y.", systime()) }'Today is Saturday, May 09, 2020.
下面来自gawk的示例展示了 awk 重现date命令行为和格式化的示例。
#! /bin/sh## date --- 近似 POSIX 'date' 命令case $1 in-u) TZ=UTC0 # 使用 UTC export TZ shift ;;esacgawk 'BEGIN { format = PROCINFO["strftime"] exitval = 0 if (ARGC > 2) exitval = 1 else if (ARGC == 2) { format = ARGV[1] if (format ~ /^\+/) format = substr(format, 2) # 删除前导 + } print strftime(format) exit exitval}' "$@"
使用 printf Bash 内置命令显示和格式化日期
Bashprintf内置命令通过%(datefmt)T支持一些日期格式,其中 datefmt 是传递给strftime的格式字符串,相应的参数是 Epoch 时间。
[me@linux ~]$ TZ=UTC printf "%(%F)T\n" 15890538402020-05-09[me@linux ~]$ TZ=UTC printf "This date occurred in week %(%W)T of the year %(%Y)T\n" $EPOCHSECONDSThis date occurred in week 18 of the year 2020
使用printf使微秒格式化比使用date命令更简单。
[me@linux ~]$ printf "%.3f\n" $EPOCHREALTIME1589054941.851[me@linux ~]$ printf "%.3f\n" $EPOCHREALTIME | tr -d .1589054941851
详细示例与常见问题解答
如何在 shell 中设置日期?
date命令行允许你在 Linux 和 Unix 系统上设置操作系统日期和时间。不过:
在 Unix 和 macOS 上使用date命令行在 shell 中设置日期时,不需要任何选项。默认情况下,日期时间格式需要为[[[mm]dd]HH]MM[[cc]yy][.ss]。示例:date 021211072020.32。你也可以使用-f选项提供特定格式。示例:date -f "%b %d" "Feb 12" +%F。
在 Linux 上使用 GNU 版本的date命令行在 shell 中设置日期时,使用-s或--set选项。示例:date -s ""。
⚠️ 你应该优先设置系统使用 ntp 协议来同步时钟。手动设置系统日期和时间不可靠,因为时间延迟会很快引入。在 Ubuntu 20.04 上,你可以使用 chronyd -q 命令强制一次性同步,使用 chronyd -Q 只检查日期和时间差异。
如何解决 “date: settimeofday (timeval): Operation not permitted”?
如果你遇到错误date: settimeofday (timeval): Operation not permitted,你可能是在没有管理员权限的情况下尝试设置系统日期。使用sudo运行相同的命令,例如sudo date ...。
如何解决 “date: illegal time format”?
如果你遇到错误date: illegal time format,你可能没有遵循 Linux、Unix 或 Macdate命令的正确格式。请仔细检查你的语法是否正确,以及在 Mac 上使用-f时是否缺少-j等选项。
如何在 Bash 中格式化日期?
以下示例使用 shelldate命令行。date命令使用区分大小写的格式说明符,因此格式中的大写或小写字母会导致不同的结果。在下面的示例中,使用%M而不是%m表示分钟而不是月份。
Bash 日期格式 MM-DD-YYYY
要以 MM-DD-YYYY 格式格式化日期,使用命令date +%m-%d-%Y或printf "%(%m-%d-%Y)T\n" $EPOCHSECONDS。
Bash 日期格式 DD-MM-YYYY
要以 DD-MM-YYYY 格式格式化日期,使用命令date +%d-%m-%Y或printf "%(%d-%m-%Y)T\n" $EPOCHSECONDS。
Bash 日期格式 YYYY-MM-DD
要以 YYYY-MM-DD 格式格式化日期,使用命令date +%F或printf "%(%F)T\n" $EPOCHSECONDS。%F选项是%Y-%m-%d的别名。
此格式是 ISO 8601 格式。
Bash 日期格式选项
下面列出了常见的日期格式选项及示例输出。它适用于 Linux date 命令行和 Mac/Unix date 命令行。
| 日期格式选项 | 含义 | 示例输出 |
|---|
date +%c | 区域设置的日期时间 | Sat May 9 11:49:47 2020 |
date +%x | 区域设置的日期 | 05/09/20 |
date +%X | 区域设置的时间 | 11:49:47 |
date +%A | 区域设置的完整星期几名称 | Saturday |
date +%B | 区域设置的完整月份名称 | May |
date +%m-%d-%Y | MM-DD-YYYY 日期格式 | 05-09-2020 |
date +%D | MM/DD/YY 日期格式 | 05/09/20 |
date +%F | YYYY-MM-DD 日期格式 | 2020-05-09 |
date +%T | HH:MM:SS 时间格式 | 11:44:15 |
date +%u | 星期几(1-7) | 6 |
date +%U | 以周日为一周第一天的周数 | 18 |
date +%V | 以周一为一周第一天的 ISO 周数 | 19 |
date +%j | 一年中的第几天 | 130 |
date +%Z | 时区 | PDT |
date +%m | 一年中的月份(MM) | 05 |
date +%d | 一个月中的日期(DD) | 09 |
date +%Y | 年份(YYYY) | 2020 |
date +%H | 小时(HH) | 11 |
date +%H | 24 小时制的小时 | 11 |
date +%I | 12 小时制的小时 | 11 |
date +%p | 区域设置的 AM 或 PM 等效项 | AM |
date +%P | 与 %p 相同但小写 | am |
你可以使用man date或man gdate命令获取完整的支持日期格式说明符列表。
如何在 shell 中给日期添加一天?
要在 bash shell 中获取日期并添加一天,你可以使用GNU date命令,如date -d 'Feb 12 +1 day' +%F,这将输出2020-02-13。有关-d选项的更多示例,请参见上面关于 GNU date 的部分。
在带有 Unixdate命令的 Mac 上,你需要使用-v选项调整日期,并使用-j确保工具不尝试设置日期。示例:date -j -v +1d -f "%b %d" "Feb 12" +%F。
如何在 shell 中获取昨天的日期?
要在 bash shell 中获取昨天的日期,你可以使用 Linux GNUdate版本,使用date -d yesterday,或使用 Unix/macOSdate版本,使用date -j -v -1d。
如何在 bash 中进行日期比较?
要在 bash 中比较两个日期,你可以使用date命令行或printf方法获取两个日期的 Unix 时间戳值,然后使用配合,或者通过使用双括号条件结构[[..]]对两个日期字符串进行。
# 示例:使用 ((...)) 进行算术扩展比较 Unix 时间戳[me@linux ~]$ firstDate=$(date -d 'Feb 12 UTC' +%s)[me@linux ~]$ secondDate=$(date -d 'Feb 13 UTC' +%s)[me@linux ~]$ if (($secondDate > $firstDate)); then echo "Second Date is more recent than First Date"; fiSecond Date is more recent than First Date# 示例:使用 [[...]] 进行字典序比较日期字符串[me@linux ~]$ firstDate=$(date -d 'Feb 12 UTC' +"%F %T")[me@linux ~]$ secondDate=$(date -d 'Feb 13 UTC' +"%F %T")[me@linux ~]$ if [[ $secondDate > $firstDate ]]; then echo "Second Date is more recent than First Date"; fiSecond Date is more recent than First Date
如何按降序对日期列表进行排序?
你可以使用上一个示例中的日期比较、冒泡排序算法和结构轻松地对日期数组进行排序。请参见中的详细示例。
如何生成日期序列?
此示例使用 bash for 循环 和 GNU date 命令对日期进行加法操作,同时使用花括号展开 生成数字序列。然后将结果赋值给 bash 数组。[me@linux ~]$ start_date="2020-09-01"[me@linux ~]$ date_seq=($(for i in {1..7}; do echo `date +"%Y/%m/%d" -d "${start_date} +${i} day"`; done))[me@linux ~]$ printf "%s\n" ${date_seq[@]}2020/09/022020/09/032020/09/042020/09/052020/09/062020/09/072020/09/08
如何在 shell 中计算两个日期之间的差值?
有一种简单的方法可以轻松完成:将两个日期的 Unix 时间戳相减,然后根据需要将秒转换为小时或天。有关 Bash 中的数学运算复习,请参见我的博文。使用 GNUdate命令行,我们可以使用+%s格式说明符获取日期的 Unix 时间戳。
注意,如果日期差预计在同一年时间范围内,date命令行可以直接进行减法,尽管输出可能有点令人困惑,因为完整的 24 小时会被表示为两个不同的日期。
[me@linux ~]$ firstDate=$(date -d 'Feb 12 2020 UTC' +%s)[me@linux ~]$ secondDate=$(date -d 'Feb 13 2020 UTC' +%s)[me@linux ~]$ echo $firstDate $secondDate1581494400 1581552000[me@linux ~]$ echo "The difference is $(( (secondDate-firstDate) / 3600 )) hours"The difference is 24 hours# 在同一年时间范围内计算日期差值的替代选项[me@linux ~]$ date -d "2020-01-01 $secondDate sec - $firstDate sec" +"%j days %H hours %M minutes and %S seconds"002 days 00 hours 00 minutes and 00 seconds
⚠️ 使用此方法时,请确保始终使用 UTC 标准来表示日期,以避免夏令时问题,并可能导致 invalid date 错误。你可以使用 -u 选项 将日期转换为 UTC。另外,请注意,这仍然是一种非常朴素的比较两个日期的方法,它不考虑需要支持闰秒或闰年的更复杂场景。
如何在 Bash 中计算经过的时间?
最简单的选项是使用特殊 shell 变量$SECONDS来获取经过的秒数。此特殊变量返回自 shell 或脚本运行以来的整秒数。该变量可以重置为零或任何其他整数。
[me@linux ~]$ SECONDS=0; sleep 2; echo "Time elapsed since \$SECONDS was reset: $SECONDS seconds";Time elapsed since $SECONDS was reset: 2 seconds
或者,你可以按照部分所述计算两个日期之间的差值。
如何将日期转换为 UTC?
你可以使用带有-u选项的date命令行工具将日期转换为 UTC。-u选项是 POSIX 标准的一部分,在所有平台上都可用,请参见前面关于的部分。
[me@linux ~]$ date && date -uWed Feb 12 13:42:17 PST 2020Wed Feb 12 21:42:17 UTC 2020
date 命令行的替代方案是使用 Unix 时间戳和 printf,请参见下一个问题 如何在 shell 中将 Unix 时间戳转换为 UTC?(即 Unix 转日期格式)如何在 shell 中将 Unix 时间戳转换为 UTC?(即 Unix 转日期格式)
将 Unix 时间戳转换为 UTC 的最简单方法是使用 GNUdate的@功能和-u选项。
[me@linux ~]$ date -d @1547092066 -uThu Jan 10 03:47:46 UTC 2019
另一种选择是使用 bashprintf内置命令,并将 TZ设置为UTC。%+说明符使用与date命令行相同的日期和时间格式。
[me@linux ~]$ printf "%(%+)T\n" 1547092066Wed Jan 9 19:47:46 PST 2019[me@linux ~]$ TZ=UTC printf "%(%+)T\n" 1547092066Thu Jan 10 03:47:46 UTC 2019
如何正确地将 shell date 输出赋值给变量?
在 Linux 或 Mac/Unix 上使用date命令行时,不建议连续多次调用它来为变量赋值,因为命令会在不同的时间执行,并表示不同的日期和时间。如果脚本在午夜前几毫秒运行,第一个命令返回一天,而第二个调用返回第二天,这可能导致 shell 脚本中的重大错误。
不要使用month=$(date +%m); day=$(date +%d); year=$(date +%Y),相反,你可以:
获取日期并将其赋值给bash 数组
将日期作为时间戳获取,然后使用printf或date应用不同的日期格式
对date或printf命令的输出使用eval
👉 一般来说,我倾向于不惜一切代价避免使用 eval,因为它可能导致 shell 脚本中的重大安全漏洞。有了 bash 数组提供的可能性,没有理由再使用 eval。通常有更好、更安全的替代方案,请参见下面的几个示例。
# 使用 date 和 bash 数组的示例[me@linux ~]$ declare -A myDate=$(date +'([month]=%m [day]=%d [year]=%Y)')[me@linux ~]$ echo "Month=${myDate[month]}, Day=${myDate[day]}, Year=${myDate[year]}"Month=06, Day=12, Year=2020# 生成固定的 Unix 时间戳[me@linux ~]$ today=$(date +"%s")[me@linux ~]$ today=$EPOCHSECONDS # Bash 5 及以上# 使用 printf 变量赋值格式化时间戳[me@linux ~]$ printf -v month '%(%m)T' $today; printf -v day '%(%d)T' $today; [me@linux ~]$ echo $month $day06 12# 使用 Linux date 命令行格式化时间戳[me@linux ~]$ month=$(date -d @$EPOCHSECONDS +"%m"); day=$(date -d @$EPOCHSECONDS +"%d");[me@linux ~]$ echo $month $day06 12# 对 date 输出运行 eval 以赋值给各个变量 / 不必要[me@linux ~]$ eval "$(date +'month=%m day=%d')"[me@linux ~]$ echo $month $day06 12
什么是时区环境变量 TZ?
$TZ包含时区信息,内部库和实用程序使用它来覆盖默认时区信息。通常不需要设置$TZ变量,除非你故意在执行命令时覆盖当前环境的时区信息。
$TZ变量可以有两种形式:TZ=:或TZ=stdoffset[dst[offset][,start[/time],end[/time]]]。
当使用冒号:格式时,TZ 以实现定义的方式处理。例如,GNU C 库通常默认使用值TZ=:/etc/localtime(或TZ=:/usr/local/etc/localtime),其他库可能定义不同的默认值和处理此格式的方式。
第二种格式(不以冒号:开头)可用于描述时区,通常用于从命令行覆盖 TZ 信息时。例如:TZ=UTC date。语法stdoffset[dst[offset][,start[/time],end[/time]]]中的格式属性在下表中详细说明。
stdoffset表示两个字段:std和offset,这些字段之间没有空格分隔。
| |
|---|
std | std 字符串指定时区名称。长度不少于三个字符,不能包含前导冒号、嵌入式数字、逗号,也不能包含加号和减号。 |
offset | 这是时区偏移量,对应于为了获得 UTC 值而添加到本地时间的时间值。格式为 `[+ |
dst[offset][,start[/time],end[/time]] | 语法的 dst 部分用于指定夏令时信息。dst 是夏令时名称,offset 是对应于夏令时的偏移量,默认值为标准时间前一个小时。剩余部分用于定义夏令时 start 和 end 的时间。 |
start 和 end | start 和 end 日期可以有三种不同的格式。1) 儒略日 Jn,其中 1 <= n <= 365,不应包括闰日。2) 基于零的儒略日 n,其中 0 <= n <= 365,必须包括闰日。3) 日历日 Mm.n.d,其中 d 是月份 m 的第 n 周中的第 d 天(0-6)。其中 1 <= n <= 5,1 <= m <= 12,第 5 周是月份的最后一周,星期日是每周的第 0 天。 |
例如,北美东部标准时间(EST),用于纽约(NY,USA),使用名为 Eastern Daylight Time(EDT)的夏令时。与UTC时区的标准偏移量为 5 小时。它位于本初子午线以西,所以符号为正+。夏令时从 3 月(Month 3)的第二个(Week 2)星期日(Day 0)凌晨 2:00 开始,到 11 月(Month 11)的第一个星期日(Week 1)凌晨 2:00 结束。完整的TZ格式为EST+5EDT,M3.2.0/2,M11.1.0/2