使用printf内置函数打印当前时间的毫秒或纳秒

8
我们可以使用内置的printf函数,输出当前时间,而不需要调用外部命令如date,例如:
printf '%(%Y-%m-%d:%H:%M:%S)T %s\n' -1
# sample output: 2019-03-30:17:39:36,846

我们如何使printf函数能够输出毫秒或纳秒?在格式字符串中使用%3N%N并不能实现此目的。
printf '%(%Y-%m-%d:%H:%M:%S,%3N)T %s\n' -1 # outputs 2019-03-30:17:38:16,%3N
printf '%(%Y-%m-%d:%H:%M:%S,%N)T %s\n' -1  # outputs 2019-03-30:17:38:16,%N

然而,date命令可以正常工作:

date +%Y-%m-%d:%H:%M:%S,%3N # gives 2019-03-30:17:39:36,846
date +%Y-%m-%d:%H:%M:%S,%N  # gives 2019-03-30:17:39:36,160643077

这是在Red Hat Linux版本7.3上的操作。


如果需要,只需使用“date”即可。 - Shawn
1
是的,date命令可以使用。不过,我更喜欢使用内置的printf命令而不是使用外部命令比如date - codeforester
2
如果你想要高于秒的时间分辨率,就不能使用bash中的printf命令,因为它(以及用于%(foo)的底层调用strftime(3))不支持这样的功能。 - Shawn
2
@codeforester 这不是纳秒,这是微秒!要使用纳秒,您必须使用*/proc/timerlist。请参见如何在bash中玩弄纳秒!(第二部分,我介绍了高精度睡眠*)。 - F. Hauri - Give Up GitHub
2个回答

6

v5 和 $EPOCHREALTIME

  • EPOCHREALTIME 是具有微秒精度的浮点数值

  • EPOCHSECONDS 是自 Unix 纪元以来的秒数

正确方式

简单地说:

IFS=. read ESEC NSEC <<<$EPOCHREALTIME
printf '%(%F:%T)T.%06.0f\n' $ESEC $NSEC

或者如果您确实不需要存储值:

printf '%(%F:%T)T.%06.0f\n' ${EPOCHREALTIME/./ }

1. 关于 $EPOCHREALTIME

请注意:

    EPOCHREALTIME
         Each time this parameter is referenced, it expands to the number
         of seconds since the Unix Epoch  (see  time(3))  as  a  floating
         point  value  with  micro-second  granularity.
所以,如果我在同一行中两次请求相同的变量:
echo $EPOCHREALTIME...  $EPOCHREALTIME 
1572000683.886830... 1572000683.886840

或更明确地说:
printf "%s\n" ${EPOCHREALTIME#*.} ${EPOCHREALTIME#*.}
761893
761925

echo $((  -10#${EPOCHREALTIME#*.} + 10#${EPOCHREALTIME#*.} ))
37

我的树莓派上也一样:

printf "%s\n" ${EPOCHREALTIME#*.} ${EPOCHREALTIME#*.}
801459
801694

echo $((  -10#${EPOCHREALTIME#*.} + 10#${EPOCHREALTIME#*.} ))
246

因此,对于构建整数部分和小数部分进行两次查询会导致问题:(在同一行中第一次访问$ EPOCHREALTIME 可能会给出:NNN1.999995 ,然后是下一个:NNN2.000002 。结果将变为:NNN1.000002 ,有1000000微秒的误差)

2. 警告!关于混合使用$EPOCHSECONDS$EPOCHREALTIME

同时使用两者不仅会导致上述错误!

$EPOCHSECONDS使用调用time(),该函数不会持续更新,而$EPOCHREALTIME使用调用gettimeofday()!因此结果可能会有很大的差异:

我发现这个回答解释了time()和gettimeofday()返回不同的秒数

如果我在我的主机上尝试:

epochVariableDiff () {
    local errcnt=0 lasterrcnt v1 v2 v3 us vals line
    while ((errcnt==0)) || ((errcnt>lasterrcnt)); do
        lasterrcnt=$errcnt
        printf -v vals '%(%s)T %s %s' -1 $EPOCHSECONDS $EPOCHREALTIME
        IFS=$' .' read v1 v2 v3 us <<<"$vals"
        [ "$v1" = "$v2" ] && [ "$v2" = "$v3" ] || ((errcnt++))
        [ $errcnt -eq 1 ] && echo "$line"
        printf -v line '%3d %s - %s - %s . %s' $errcnt $v1 $v2 $v3 $us
        printf "%s\r" "$line"
        ((errcnt)) && echo "$line"
        read -t ${1:-.0002}
    done
}

(
注:我使用read -t而不是sleep,因为sleep不是内置的
注2:您可以通过更改函数参数来玩转读取超时值(睡眠)

这可能会呈现出类似于:

$ epochVariableDiff .0002
  0 1586851573 - 1586851573 - 1586851573 . 999894
  1 1586851573 - 1586851573 - 1586851574 . 000277
  2 1586851573 - 1586851573 - 1586851574 . 000686
  3 1586851573 - 1586851573 - 1586851574 . 001087
  4 1586851573 - 1586851573 - 1586851574 . 001502
  5 1586851573 - 1586851573 - 1586851574 . 001910
  6 1586851573 - 1586851573 - 1586851574 . 002309
  7 1586851573 - 1586851573 - 1586851574 . 002701
  8 1586851573 - 1586851573 - 1586851574 . 003108
  9 1586851573 - 1586851573 - 1586851574 . 003495
 10 1586851573 - 1586851573 - 1586851574 . 003899
 11 1586851573 - 1586851573 - 1586851574 . 004400
 12 1586851573 - 1586851573 - 1586851574 . 004898
 13 1586851573 - 1586851573 - 1586851574 . 005324
 14 1586851573 - 1586851573 - 1586851574 . 005720
 15 1586851573 - 1586851573 - 1586851574 . 006113
 16 1586851573 - 1586851573 - 1586851574 . 006526
 17 1586851573 - 1586851573 - 1586851574 . 006932
 18 1586851573 - 1586851573 - 1586851574 . 007324
 19 1586851573 - 1586851573 - 1586851574 . 007733
 19 1586851574 - 1586851574 - 1586851574 . 008144

$EPOCHREALTIME的整数部分可能在$EPOCHSECONDS之前增加超过8000微秒(在我的主机上)。

注:这似乎与某些错误有关,结果可能因不同的主机或重新启动后在同一台主机上而有很大差异,还有其他事情......奇怪的是,我可以在许多不同的主机(Intel Core、Intel Xeon、Amd64等)上重现这些问题,但在树莓派上却没有!?(相同的Debian bash v5.0.3(1)-release),不同的内核版本。

正确:这不是一个错误!混合使用time()gettimeofday()才是错误!

因此避免同时使用它们!!!

3. 关于printf "..%06.0f"

注:我使用%06.0f而不是%d来确保$NSEC被解释为十进制(浮点数)(如果变量以0开头,则防止八进制解释)。

比较:

printf "nn.%06.0f\n" 012345
nn.012345

printf "nn.%06.0f\n" 098765
nn.098765

并且

printf "nn.%d\n" 012345
nn.5349

printf "nn.%d\n" 098765
-bash: printf: 098765: invalid octal number
nn.0

示例运行:显示每秒开始的时间...

小测试:

等待下一秒,然后打印带微秒的当前时间

while ! read -sn 1 -t .$(( 1000000 - 10#${EPOCHREALTIME#*.} )) _; do
    IFS=. read ESEC NSEC <<< $EPOCHREALTIME
    printf '%(%F:%T)T.%06.0f\n' $ESEC $NSEC
done

按下任意键即可结束此测试。

2023-01-06:17:28:51.000135
2023-01-06:17:28:52.000095
2023-01-06:17:28:53.000109
2023-01-06:17:28:54.000108
2023-01-06:17:28:55.000132
2023-01-06:17:28:56.000166
2023-01-06:17:28:57.000099

1
看一下我的函数epochVariableDiff,了解两个变量如何不同! - F. Hauri - Give Up GitHub

2
bash 5 中,您可以从 EPOCHREALTIME 获取微秒精度。然而,printf 本身没有直接访问它的方法,因此您需要自己提取微秒。"Original Answer" 的翻译是 "最初的回答"。
$ echo $EPOCHREALTIME; printf '%(%F:%T)T.%d\n' "$EPOCHSECONDS" "${EPOCHREALTIME#*.}"; echo $EPOCHREALTIME
1554006709.936990
2019-03-31:00:31:49.937048
1554006709.937083

这需要一些时间,但结果似乎精确到约0.05毫秒。"最初的回答"

2
请查看我的答案;至少存在两个问题:1)在$EPOCHREALTIME上询问两次,2)对一个可能包含字符串012345的变量使用printf %d(将被%d解释为八进制)。 - F. Hauri - Give Up GitHub
尝试这个:errcnt=3;while ! read -t .001 foo ;do printf '%(%F:%T)T.%d\n' "$EPOCHSECONDS" "${EPOCHREALTIME#*.}" || ((errcnt--)) || break; done 2>&1 | tail -n20,然后再试一次... - F. Hauri - Give Up GitHub
2
...而且您使用的是EPOCHSECONDSEPOCHREALTIME,它们的舍入方式不同!请尝试在先前的命令中发布的命令或甚至此命令:errcnt=5;v1=$EPOCHSECONDS v2=$EPOCHREALTIME ;echo $v1 ${v2%.*};while [ "$v1" = "${v2%.*}" ] || ((errcnt--));do read -t .0001; v1=$EPOCHSECONDS v2=$EPOCHREALTIME ;echo $v1 ${v2%.*} - ${v2#*.};done - F. Hauri - Give Up GitHub
使用bash 5,这个命令对我来说速度更快,外观更好:printf "%(${TIMESTAMP_FORMAT})T.%s %s\n" ${EPOCHREALTIME/./ } 'some text'其中TIMESTAMP_FORMAT ='%Y-%m-%dT%H:%M:%S'或其他。 - Mikhail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接