{"id":724,"date":"2026-05-22T19:31:16","date_gmt":"2026-05-23T03:31:16","guid":{"rendered":"https:\/\/salemdata.net\/johnpress\/?p=724"},"modified":"2026-05-22T19:31:16","modified_gmt":"2026-05-23T03:31:16","slug":"a-better-way-to-record-timed-linux-terminal-sessions","status":"publish","type":"post","link":"https:\/\/salemdata.net\/johnpress\/?p=724","title":{"rendered":"A Better Way to Record Timed Linux Terminal Sessions"},"content":{"rendered":"<h2>Timed Terminal Captures with <code>script<\/code>, <code>timeout<\/code>, and a Future Launch Time<\/h2>\n<p>I recently needed to run the same command on two separate Raspberry Pi Zero 2W systems at almost exactly the same time. Both machines were disciplined to the same local stratum-1 time server, so their clocks were already close enough, e.g. less than 1 second off, for the experiment.<\/p>\n<p>The real problem was not merely starting the commands at the same time. The problem was capturing the terminal sessions cleanly.<\/p>\n<p>I wanted the captured <code>script(1)<\/code> output and timing log to contain only the command I cared about, not the setup, not the countdown, not the time it took me to move from one SSH window to another, and not any manual cleanup afterward.<\/p>\n<p>The solution was simple once the pieces were put in the right order.\u00a0 Here&#8217;s the command I used:<\/p>\n<pre><code class=\"language-bash\">script -q -f -e --log-timing timing.log -c 'timeout -k 5s 60s .\/program' output.script\r\n<\/code><\/pre>\n<p>The important trick is that the waiting happens <strong>outside<\/strong> <code>script<\/code>, and only the actual timed command runs <strong>inside<\/strong> <code>script -c<\/code>.<\/p>\n<p>That means:<\/p>\n<pre><code class=\"language-text\">wait until target time  -&gt;  start script capture  -&gt;  run timeout command  -&gt;  program exits  -&gt;  script exits\r\n<\/code><\/pre>\n<p>No manual <code>exit<\/code> is needed. No extra shell prompt is captured after the program finishes. The <code>timeout<\/code> command terminates the program, and then <code>script -c<\/code> exits because its child command has completed.<\/p>\n<p>This produces two useful files:<\/p>\n<pre><code class=\"language-text\">output.script\r\ntiming.log\r\n<\/code><\/pre>\n<p>The first contains the terminal output. The second contains the byte timing information needed by <code>scriptreplay<\/code> or by custom replay tools.<\/p>\n<p>For a synchronized two-machine run, the workflow becomes something like this.<\/p>\n<p>On the first machine:<\/p>\n<pre><code class=\"language-bash\">.\/makescript_at.sh little_boy_blue 18:45:00 60s -- .\/little_boy_blue\r\n<\/code><\/pre>\n<p>On the second machine:<\/p>\n<pre><code class=\"language-bash\">.\/makescript_at.sh childrens_hour 18:45:00 60s -- .\/childrens_hour\r\n<\/code><\/pre>\n<p>Each shell can be prepared ahead of time. Each wrapper waits until the specified launch time. The terminal capture starts only at the launch moment. The command runs for the requested duration. The capture then closes automatically.<\/p>\n<p>For experiments, demonstrations, debugging sessions, and blog-ready terminal captures, this is a very clean pattern.<\/p>\n<p>The broader lesson is this:<\/p>\n<pre><code class=\"language-text\">Do not start script and then wait.\r\nWait first, then start script around the exact command of interest.\r\n<\/code><\/pre>\n<p>That one ordering decision keeps the capture clean, repeatable, and easy to replay.<\/p>\n<p>Here is an example of two script sending poems to each other with a 10 second pause after the transfer is completed and then the transfer is started all over again.<\/p>\n<div class=\"reticulum-replay-centered\"><iframe src=\"https:\/\/salemdata.us\/dev\/paired_ble_poetry_replay_20260522_Fri_193030.html\"><\/iframe><\/div>\n<div>I&#8217;ll post the Perl script that processes both sets of script output, i.e. script and script.timing, to my Forgejo.<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Timed Terminal Captures with script, timeout, and a Future Launch Time I recently needed to run the same command on two separate Raspberry Pi Zero 2W systems at almost exactly the same time. Both machines were disciplined to the same local stratum-1 time server, so their clocks were already close enough, e.g. less than 1 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":730,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[18,129],"class_list":["post-724","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-linux","tag-script"],"_links":{"self":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/724","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=724"}],"version-history":[{"count":4,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/724\/revisions"}],"predecessor-version":[{"id":731,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/posts\/724\/revisions\/731"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=\/wp\/v2\/media\/730"}],"wp:attachment":[{"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=724"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=724"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/salemdata.net\/johnpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=724"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}