#!/bin/sh # A command line Stopwatch # License: LGPLv2 # Author: # http://www.pixelbeat.org/ # Notes: # This script starts a few processes per lap, in addition to # the shell loop processing, so the assumption is made that # this takes an insignificant amount of time compared to # the response time of humans (~.1s) (or the keyboard # interrupt rate (~.05s)). # '?' for splits must be entered twice if characters # (erroneously) entered before it (on the same line). # '?' since not generating a signal may be slightly delayed # on heavily loaded systems. # Lap timings on ubuntu may be slightly delayed due to: # https://bugs.launchpad.net/bugs/62511 # Changes: # V1.0, 23 Aug 2005, Initial release # V1.1, 26 Jul 2007, Allow both splits and laps from single invocation. # Only start timer after a key is pressed. # Indicate lap number # Cache programs at startup so there is less error # due to startup delays. # V1.2, 01 Aug 2007, Work around `date` commands that don't have nanoseconds. # Use stty to change interrupt keys to space for laps etc. # Ignore other input as it causes problems. # V1.3, 01 Aug 2007, Testing release. # V1.4, 02 Aug 2007, Various tweaks to get working under ubuntu and Mac OS X. # V1.5, 27 Jun 2008, set LANG=C as got vague bug report about it. # V1.6, 19 Mar 2015 http://github.com/pixelb/scripts/commits/master/scripts/sw export LANG=C ulimit -c 0 #no cores from SIGQUIT trap '' TSTP #ignore Ctrl-Z just in case global save_tty := $[stty -g] && trap "stty $save_tty" EXIT #restore tty on exit stty quit ' ' #space for laps rather than Ctrl-\ stty eof '?' #? for splits rather than Ctrl-D stty -echo #don't echo input proc cache_progs { stty > /dev/null date > /dev/null grep . < /dev/null shell {echo "import time" | python} 2> /dev/null bc < /dev/null sed '' < /dev/null printf '1' > /dev/null /usr/bin/time false !2 > /dev/null cat < /dev/null } cache_progs #to minimise startup delay date +%s.%N | grep -qF 'N' && global use_python := '1' #if `date` doesn't have nanoseconds proc now { if test $use_python { echo "import time; print time.time()" !2 >/dev/null | python } else { printf "%.2f" $[date +%s.%N] } } proc fmt_seconds { global seconds := $1 global mins := $[echo $seconds/60 | bc] if test $mins != "0" { global seconds := $[echo "$seconds - ($mins*60)" | bc] echo "$mins:$seconds" } else { echo $seconds } } proc total { global end := $[now] global total := $[echo "$end - $start" | bc] fmt_seconds $total } proc stop { test $lapped && lap $laptime "display" total exit } proc lap { global laptime := $[echo $1 | sed -n 's/.*real[^0-9.]*\(.*\)/\1/p] test ! $laptime -o $laptime = "0.00" && return #signals too frequent global laptotal := $[echo $laptime+0$laptotal | bc] if test $2 = "display" { global lapcount := $[echo 0$lapcount+1 | bc] global laptime := $[fmt_seconds $laptotal] echo $laptime "($lapcount)" global lapped := '"true'" global laptotal := '"0'" } } printf "Space for lap | ? for split | Ctrl-C to stop | Space to start..."> !2 while true { trap true INT QUIT #set signal handlers global laptime := $[/usr/bin/time -p 2>&1 cat!2 > !1 cat >/dev/null] global ret := $Status trap '' INT QUIT #ignore signals within this script if test $ret -eq 1 -o $ret -eq 2 -o $ret -eq 130 { #SIGINT = stop test ! $start && do { echo > !2; exit; } stop } elif test $ret -eq 3 -o $ret -eq 131 { #SIGQUIT = lap if test ! $start { global start := $[now] || exit 1 echo > !2 continue } lap $laptime "display" } else { #eof = split test ! $start && continue total lap $laptime #update laptotal } } (CommandList children: [ (C {(export)} {(Lit_VarLike "LANG=") (C)}) (C {(ulimit)} {(-c)} {(0)}) (C {(trap)} {(SQ )} {(TSTP)}) (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:save_tty) op: Equal rhs: { (CommandSubPart command_list: (CommandList children:[(C {(stty)} {(-g)})]) left_token: spids: [122 126] ) } spids: [121] ) ] spids: [121] ) (C {(trap)} {(DQ ("stty ") ($ VSub_Name "$save_tty"))} {(EXIT)}) ] op_id: Op_DAmp ) (C {(stty)} {(quit)} {(SQ <" ">)}) (C {(stty)} {(eof)} {(SQ <"?">)}) (C {(stty)} {(-echo)}) (FuncDef name: cache_progs body: (BraceGroup children: [ (SimpleCommand words: [{(stty)}] redirects: [(Redir op_id:Redir_Great fd:-1 arg_word:{(/dev/null)} spids:[181])] ) (SimpleCommand words: [{(date)}] redirects: [(Redir op_id:Redir_Great fd:-1 arg_word:{(/dev/null)} spids:[188])] ) (SimpleCommand words: [{(grep)} {(.)}] redirects: [(Redir op_id:Redir_Less fd:-1 arg_word:{(/dev/null)} spids:[197])] ) (Subshell child: (Pipeline children: [(C {(echo)} {(DQ ("import time"))}) (C {(python)})] negated: False ) redirects: [(Redir op_id:Redir_Great fd:2 arg_word:{(/dev/null)} spids:[214])] spids: [202 212] ) (SimpleCommand words: [{(bc)}] redirects: [(Redir op_id:Redir_Less fd:-1 arg_word:{(/dev/null)} spids:[221])] ) (SimpleCommand words: [{(sed)} {(SQ )}] redirects: [(Redir op_id:Redir_Less fd:-1 arg_word:{(/dev/null)} spids:[231])] ) (SimpleCommand words: [{(printf)} {(SQ <1>)}] redirects: [(Redir op_id:Redir_Great fd:-1 arg_word:{(/dev/null)} spids:[242])] ) (SimpleCommand words: [{(/usr/bin/time)} {(false)}] redirects: [(Redir op_id:Redir_Great fd:2 arg_word:{(/dev/null)} spids:[251])] ) (SimpleCommand words: [{(cat)}] redirects: [(Redir op_id:Redir_Less fd:-1 arg_word:{(/dev/null)} spids:[258])] ) ] spids: [176] ) spids: [172 175] ) (C {(cache_progs)}) (AndOr children: [ (Pipeline children: [ (C {(date)} {(Lit_Other "+") (Lit_Other "%") (s.) (Lit_Other "%") (N)}) (C {(grep)} {(-qF)} {(SQ )}) ] negated: False ) (Assignment keyword: Assign_None pairs: [(assign_pair lhs:(LhsName name:use_python) op:Equal rhs:{(1)} spids:[290])] spids: [290] ) ] op_id: Op_DAmp ) (FuncDef name: now body: (BraceGroup children: [ (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$use_python"))} {(Lit_Other "]")}) terminator: ) ] action: [ (Pipeline children: [ (SimpleCommand words: [{(echo)} {(DQ ("import time; print time.time()"))}] redirects: [ (Redir op_id: Redir_Great fd: 2 arg_word: {(/dev/null)} spids: [323] ) ] ) (C {(python)}) ] negated: False ) ] spids: [-1 314] ) ] else_action: [ (C {(printf)} {(DQ ("%.2f"))} { (CommandSubPart command_list: (CommandList children: [ (C {(date)} {(Lit_Other "+") (Lit_Other "%") (s.) (Lit_Other "%") (N)}) ] ) left_token: spids: [340 348] ) } ) ] spids: [331 351] ) ] spids: [300] ) spids: [296 299] ) (FuncDef name: fmt_seconds body: (BraceGroup children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:seconds) op: Equal rhs: {($ VSub_Number "$1")} spids: [363] ) ] spids: [363] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:mins) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [(C {(echo)} {($ VSub_Name "$seconds") (/60)}) (C {(bc)})] negated: False ) ] ) left_token: spids: [368 377] ) } spids: [367] ) ] spids: [367] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$mins"))} {(KW_Bang "!") (Lit_Other "=")} {(DQ (0))} {(Lit_Other "]")} ) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:seconds) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} { (DQ ($ VSub_Name "$seconds") (" - (") ($ VSub_Name "$mins") ("*60)") ) } ) (C {(bc)}) ] negated: False ) ] ) left_token: spids: [402 415] ) } spids: [401] ) ] spids: [401] ) (C {(echo)} {(DQ ($ VSub_Name "$mins") (":") ($ VSub_Name "$seconds"))}) ] spids: [-1 398] ) ] else_action: [(C {(echo)} {(DQ ($ VSub_Name "$seconds"))})] spids: [427 437] ) ] spids: [360] ) spids: [356 359] ) (FuncDef name: total body: (BraceGroup children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:end) op: Equal rhs: { (CommandSubPart command_list: (CommandList children:[(C {(now)})]) left_token: spids: [450 452] ) } spids: [449] ) ] spids: [449] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:total) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {(DQ ($ VSub_Name "$end") (" - ") ($ VSub_Name "$start"))}) (C {(bc)}) ] negated: False ) ] ) left_token: spids: [456 468] ) } spids: [455] ) ] spids: [455] ) (C {(fmt_seconds)} {($ VSub_Name "$total")}) ] spids: [446] ) spids: [442 445] ) (FuncDef name: stop body: (BraceGroup children: [ (AndOr children: [ (C {(Lit_Other "[")} {(DQ ($ VSub_Name "$lapped"))} {(Lit_Other "]")}) (C {(lap)} {(DQ ($ VSub_Name "$laptime"))} {(DQ (display))}) ] op_id: Op_DAmp ) (C {(total)}) (C {(exit)}) ] spids: [482] ) spids: [478 481] ) (FuncDef name: lap body: (BraceGroup children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:laptime) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {(DQ ($ VSub_Number "$1"))}) (C {(sed)} {(-n)} {(SQ <"s/.*real[^0-9.]*\\(.*\\)/\\1/p">)}) ] negated: False ) ] ) left_token: spids: [522 538] ) } spids: [521] ) ] spids: [521] ) (AndOr children: [ (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$laptime"))} {(-o)} {(DQ ($ VSub_Name "$laptime"))} {(Lit_Other "=")} {(DQ (0.00))} {(Lit_Other "]")} ) (ControlFlow token:) ] op_id: Op_DAmp ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:laptotal) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {($ VSub_Name "$laptime") (Lit_Other "+") (0) ($ VSub_Name "$laptotal") } ) (C {(bc)}) ] negated: False ) ] ) left_token: spids: [572 583] ) } spids: [571] ) ] spids: [571] ) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(DQ ($ VSub_Number "$2"))} {(Lit_Other "=")} {(DQ (display))} {(Lit_Other "]")} ) terminator: ) ] action: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:lapcount) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (Pipeline children: [ (C {(echo)} {(0) ($ VSub_Name "$lapcount") (Lit_Other "+") (1)} ) (C {(bc)}) ] negated: False ) ] ) left_token: spids: [607 618] ) } spids: [606] ) ] spids: [606] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:laptime) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [(C {(fmt_seconds)} {($ VSub_Name "$laptotal")})] ) left_token: spids: [622 626] ) } spids: [621] ) ] spids: [621] ) (C {(echo)} {($ VSub_Name "$laptime")} {(DQ ("(") ($ VSub_Name "$lapcount") (")"))}) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:lapped) op: Equal rhs: {(DQ (true))} spids: [640] ) ] spids: [640] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:laptotal) op: Equal rhs: {(DQ (0))} spids: [646] ) ] spids: [646] ) ] spids: [-1 603] ) ] spids: [-1 652] ) ] spids: [518] ) spids: [514 517] ) (SimpleCommand words: [{(printf)} {(DQ ("Space for lap | ? for split | Ctrl-C to stop | Space to start..."))}] redirects: [(Redir op_id:Redir_GreatAnd fd:-1 arg_word:{(2)} spids:[662])] ) (While cond: [(Sentence child:(C {(true)}) terminator:)] body: (DoGroup children: [ (C {(trap)} {(true)} {(INT)} {(QUIT)}) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:laptime) op: Equal rhs: { (CommandSubPart command_list: (CommandList children: [ (SimpleCommand words: [{(/usr/bin/time)} {(-p)} {(cat)}] redirects: [ (Redir op_id: Redir_GreatAnd fd: 2 arg_word: {(1)} spids: [692] ) (Redir op_id: Redir_Great fd: -1 arg_word: {(/dev/null)} spids: [697] ) ] ) ] ) left_token: spids: [687 699] ) } spids: [686] ) ] spids: [686] ) (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:ret) op: Equal rhs: {($ VSub_QMark "$?")} spids: [702] ) ] spids: [702] ) (C {(trap)} {(SQ )} {(INT)} {(QUIT)}) (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {($ VSub_Name "$ret")} {(-eq)} {(1)} {(-o)} {($ VSub_Name "$ret")} {(-eq)} {(2)} {(-o)} {($ VSub_Name "$ret")} {(-eq)} {(130)} {(Lit_Other "]")} ) terminator: ) ] action: [ (AndOr children: [ (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$start"))} {(Lit_Other "]")} ) (BraceGroup children: [ (Sentence child: (SimpleCommand words: [{(echo)}] redirects: [ (Redir op_id: Redir_GreatAnd fd: -1 arg_word: {(2)} spids: [770] ) ] ) terminator: ) (Sentence child: (C {(exit)}) terminator: ) ] spids: [766] ) ] op_id: Op_DAmp ) (C {(stop)}) ] spids: [-1 748] ) (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {($ VSub_Name "$ret")} {(-eq)} {(3)} {(-o)} {($ VSub_Name "$ret")} {(-eq)} {(131)} {(Lit_Other "]")} ) terminator: ) ] action: [ (If arms: [ (if_arm cond: [ (Sentence child: (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$start"))} {(Lit_Other "]")} ) terminator: ) ] action: [ (AndOr children: [ (Assignment keyword: Assign_None pairs: [ (assign_pair lhs: (LhsName name:start) op: Equal rhs: { (CommandSubPart command_list: (CommandList children:[(C {(now)})]) left_token: spids: [827 829] ) } spids: [826] ) ] spids: [826] ) (C {(exit)} {(1)}) ] op_id: Op_DPipe ) (SimpleCommand words: [{(echo)}] redirects: [ (Redir op_id: Redir_GreatAnd fd: -1 arg_word: {(2)} spids: [840] ) ] ) (ControlFlow token: ) ] spids: [-1 823] ) ] spids: [-1 847] ) (C {(lap)} {(DQ ($ VSub_Name "$laptime"))} {(DQ (display))}) ] spids: [783 804] ) ] else_action: [ (AndOr children: [ (C {(Lit_Other "[")} {(KW_Bang "!")} {(DQ ($ VSub_Name "$start"))} {(Lit_Other "]")}) (ControlFlow token:) ] op_id: Op_DAmp ) (C {(total)}) (C {(lap)} {(DQ ($ VSub_Name "$laptime"))}) ] spids: [861 895] ) ] spids: [671 897] ) ) ] )