如何在Bash中创建一个秒表?

18

我创建了一个简单的秒表(bash函数)用于计时,但现在它显示当前带毫秒的时间。

代码:

function stopwatch() {
    date +%H:%M:%S:%N
    while true; do echo -ne "`date +%H:%M:%S:%N`\r"; done;
}

我尝试按照这个答案中的说明进行更改,但仅适用于Unix纪元后的第二个。

当我使用date格式+%s.%N时,上面的答案减法停止工作,因为bash减法仅采用整数。

我该如何解决这个问题,并拥有一个终端秒表,以以下方式打印时间:

0.000000000
0.123123123
0.435345345
(and so on..)

?

7个回答

22

一个可能的(并且有点hacky的)机制,可以在一天内运作:

$ now=$(date +%s)sec
$ while true; do
     printf "%s\r" $(TZ=UTC date --date now-$now +%H:%M:%S.%N)
     sleep 0.1
  done

奖励:您可以随时按回车键来获取LAP时间。;-)

注意:这是一个快速修复,更好的解决方案应该会提供...

watch的变体(相同逻辑):

$ now=$(date +%s)sec; watch -n0.1 -p TZ=UTC date --date now-$now +%H:%M:%S.%N

纳秒不会总是偏离吗? - 123
不错。纳秒已经启用了,这比我的旧解决方案(在另一个答案中)还要好。太棒了!(尽管我也添加了一些睡眠以减少CPU使用率 :-) ) - lewiatan
@lewiatan 如果你在使用sleep,最好使用毫秒/微秒。 - 123
@123 - 是的,如果它们被“date”支持,我可以这样做,但由于只涉及纳秒,而且我不想进行任何额外的计算,所以我更喜欢较少执行此操作。 - lewiatan
1
这将消耗相当多的资源。添加一个睡眠时间将会改善这个问题。我知道 OP 提到了毫秒级别。假设这是为了人类可读性(和交互),我认为像 sleep 0.1s 这样的小睡眠时间可能是一个不错的折衷方案。 - exhuma
显示剩余3条评论

17

如果您想要一个包括分钟、秒钟和百分之一秒的简单传统秒表,您可以使用sw

sw

安装

wget -q -O - http://git.io/sinister | sh -s -- -u https://raw.githubusercontent.com/coryfklein/sw/master/sw

使用方法

# start a stopwatch from 0, save start time in ~/.sw
sw

# resume the last run stopwatch
sw --resume 

对于在2020年7月之后查看此内容的任何人,该应用程序似乎已经损坏了! - Karthik Nayak
1
我刚在macOS Catalina 10.15.5上进行了全新安装,它完美地运行了。如果需要,请随时在GitHub上提交您的详细信息@KarthikNayak。 - Cory Klein
这里有一个待处理的问题,与此问题相同:https://github.com/coryfklein/sw/issues/2 - Karthik Nayak
1
今天对我有用。 - Brett
由于这个技术上并没有回答 OP 的问题,而且很多人来到这里只是在寻找一个功能性的终端秒表,termdown 拥有更多特性(如果需要的话)。通过 这个 Super User 回答 找到。 - joeljpa

9
time cat

然后按下 Ctrl-cCtrl-d 来停止计时器并显示时间。第一个数字是时间。

我把它进一步改进成了这个bash别名。

alias stopwatch="echo Press Ctrl-c to stop the timer; TIMEFORMAT=%R; time cat; unset TIMEFORMAT"

1
plus1。另一种可能性是使用read -n1而不是cat。然后,任何字符都会终止它。 - anishsane

4

这是我一段时间前获取的一个更好的函数:

function stopwatch() {
    local BEGIN=$(date +%s)
    echo Starting Stopwatch...

    while true; do
        local NOW=$(date +%s)
        local DIFF=$(($NOW - $BEGIN))
        local MINS=$(($DIFF / 60))
        local SECS=$(($DIFF % 60))
        local HOURS=$(($DIFF / 3600))
        local DAYS=$(($DIFF / 86400))

        printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
        sleep 0.5
    done
}

作为回应评论,这里提供一个版本,用户按下“Enter”键后将退出:
function stopwatch_with_cancel() {
    local BEGIN=$(date +%s)
    echo Starting Stopwatch...

    while true; do
        local NOW=$(date +%s)
        local DIFF=$(($NOW - $BEGIN))
        local MINS=$(($DIFF / 60))
        local SECS=$(($DIFF % 60))
        local HOURS=$(($DIFF / 3600))
        local DAYS=$(($DIFF / 86400))

        printf "\r%3d Days, %02d:%02d:%02d" $DAYS $HOURS $MINS $SECS
        read -rsN1 -t1 key
        if [ "$key" == $'\x0a' ] ;then
            # echo -e "\n [Enter] Pressed"
            break
        fi
    done
}

你的 MINS 不需要模 60 吗?否则你的 MINS 可能会显示为 61 或 200。然后,HOURS 也是一样... 需要模 24。 - nonopolarity
这个能修改成当用户按下回车键时停止吗? - leo

2
基于 rawaludin的gist
function stopwatch() {
  local BEGIN=$(date +%s)

  while true; do
    local NOW=$(date +%s)
    local DIFF=$(($NOW - $BEGIN))
    local MINS=$(($DIFF / 60 % 60))
    local SECS=$(($DIFF % 60))
    local HOURS=$(($DIFF / 3600 % 24))
    local DAYS=$(($DIFF / 86400))
    local DAYS_UNIT
    [ "$DAYS" == 1 ] && DAYS_UNIT="Day" || DAYS_UNIT="Days"

    printf "\r  %d %s, %02d:%02d:%02d  " $DAYS $DAYS_UNIT $HOURS $MINS $SECS
    sleep 0.25
  done
}

对于不熟悉此规则的人:在英语中,只有当数字为1时我们使用单数——Day。当数字为0、2、3、4、5等时,我们使用复数“Days”,因此请注意这里是0 Days


1
这是另一种基于bash的秒表实现,借鉴了本帖其他答案的思路。与其他版本不同之处包括:
  • 这个版本使用bash算术而不是调用bc,我发现(通过计时)它的cpu时间要少得多。
  • 我解决了某人指出的25小时限制问题,通过为每天经过的小时部分添加24小时。(所以现在我猜它是第31天的限制。)
  • 我将光标留在输出的右侧,与被接受答案中的版本不同。这样你可以通过按回车键轻松测量圈数(或更普遍地标记重要事件时间),它会将计时器移动到下一行,保留按键时的时间可见。
#!/bin/bash

start_time=$(date +%s)

while true; do
  current_time=$(date +%s)
  seconds_elapsed=$(( $current_time - $start_time ))
  timestamp=$(date -d"@$seconds_elapsed" -u +%-d:%-H:%-M:%-S)

  IFS=':' read -r day hour minute second <<< "$timestamp"
  hour="$(( $hour+24*($day-1) ))"

  printf "\r%02d:%02d:%02d" $hour $minute $second
  sleep 0.5
done;

这是运行stopwatch(作为PATH中的可执行脚本)并在第7秒和18秒按回车键,以及大约9分钟后按下Ctrl-C所得到的样例输出:

$ stopwatch
00:00:07
00:00:18
00:09:03^C
$

注:

  • 我使用+%-d:%-H:%-M:%-S输出格式来表示日期(这些破折号意味着“请省略任何前导零”),因为printf似乎会将带有前导零的数字字符串解释为八进制,并最终抱怨无效值。
  • 我去掉了纳秒,因为对于我的目的,我不需要超过1秒的精度。因此,我调整了sleep持续时间以节省计算资源。

0

对于减法,您应该使用 bc(一种任意精度计算器语言)。

这是一个满足您要求的示例代码:

function stopwatch() {
    date1=`date +%s.%N`
    while true; do
        curr_date=`date +%s.%N`
        subtr=`echo "$curr_date - $date1" | bc`
        echo -ne "$subtr\r";
        sleep 0.03
    done;
}

为了降低 CPU 使用率,我们添加了额外的 sleep(在我的机器上,如果没有它,CPU 使用率几乎达到了 15%,但是有了这个 sleep 后,它降低到了 1%)。


1
你可以稍微改进一下:如果直接使用subtr=$(date "+%s.%N-$date1" | bc),就不需要curr_date变量了。这样每次迭代都可以省去一个子shell!你还可以将bc放在循环外面,然后使用另一个循环将bc\n输出替换为\r - gniourf_gniourf
1
计时器() { 本地 a date1=$(date +%s.%N) while :; do date "+%s.%N-$date1" sleep 0.03 done | bc | while read a; do printf '%s\r' "$a" done; } 使用 printf 您也可以使用另一种格式,例如:printf '%.3f\r' "$a" - gniourf_gniourf

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