Capture a Reproducible Terminal Session with script
You will finish with a plain text record of a terminal session, and, when needed, a timing file that scriptreplay can use to reproduce it. The examples use the locally installed util-linux command. Expect about 10 minutes for a basic capture, or 20 minutes if you need timing data and diagnostics.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
Have a shell and a directory where the log may be stored. The local manpage comes from Ubuntu package bsdutils version 1:2.39.3-9ubuntu6.6, and identifies its implementation as util-linux 2.39.3. On this machine, script resolves to a separate util-linux 2.42.4 executable earlier in PATH. Check your own path and version before relying on a detail in an automated workflow:
$ command -v script
/home/linuxbrew/.linuxbrew/bin/script
$ script --version
script from util-linux 2.42.4
No elevated privileges are needed to record your own session. Do not put a log in a shared directory unless its permissions and contents are acceptable to other users.
1. Record an interactive session
Give the log an explicit name, then run the command from the directory you want to capture. script starts an interactive shell and writes terminal output to the named file:
$ script /tmp/terminal-session.log
Script started, file is /tmp/terminal-session.log
$ printf 'build started\n'
build started
$ exit
Script done.
The command ends when the child shell exits. Use exit or the shell's normal end-of-file keystroke. The log includes the start and done messages unless you add --quiet. It also records control characters, line feeds and backspaces, so it is a terminal transcript rather than a clean command report.
Checkpoint
Verify that the file exists and contains the expected text before sharing it:
$ wc -c /tmp/terminal-session.log
$ sed -n '1,20p' /tmp/terminal-session.log
2. Capture one command without opening a shell
For a bounded capture, use --command. This avoids leaving an interactive shell running and is useful for programs whose output changes when standard output is not a terminal:
$ script --quiet --command 'printf "alpha\nbeta\n"' /tmp/command-session.log
$ sed -n '1,10p' /tmp/command-session.log
alpha
beta
Quote the command as one shell argument. If it contains shell metacharacters, choose the outer quotes carefully; the inner shell used by script still interprets the command string. The command's exit status is not normally the status returned by script. Add --return when a script must receive the child's status:
$ script --quiet --return --command 'sh -c "exit 7"' /tmp/status-session.log
$ printf 'script status: %s\n' "$?"
script status: 7
3. Add timing data for replay
A typescript stores terminal data, not the delays between writes. Add --log-timing to store those delays separately:
$ script --quiet --command 'printf "first\n"; sleep 1; printf "second\n"' \
--log-out /tmp/replay-session.log \
--log-timing /tmp/replay-session.timing
$ sed -n '1,5p' /tmp/replay-session.timing
0.000000 6
1.00 7
The exact timing values depend on the machine, so check that the file is non-empty rather than matching a timestamp or delay exactly. The classic timing format contains an elapsed delay and an output character count. Use scriptreplay with the output and timing files to play it back:
$ scriptreplay --timing=/tmp/replay-session.timing /tmp/replay-session.log
For input and output together, use --log-io and --log-timing. That selects the advanced multi-stream timing format, which allows scriptreplay --summary to describe the recorded streams.
4. Protect passwords and other secrets
Do not use --log-in or --log-io around a login, token prompt or private command unless recording that input is deliberate and the log is protected. Input logging records keystrokes even when the terminal disables echo for a password. Ordinary output logging is safer, but commands can still print credentials, environment values or private file contents.
If a sensitive value was captured, stop using the file, remove it with the normal file deletion procedure, and rotate the exposed credential. Deleting a log is irreversible unless you have a separate protected backup. For a quick review before sending a transcript, search it without displaying the whole file in a shared terminal:
$ grep -nE 'password|token|secret|BEGIN .* PRIVATE KEY' /tmp/terminal-session.log
A match is a reason to inspect and redact the log, not proof that it is safe. Do not rely on the terminal's password masking to protect an input log.
5. Avoid the defaults that cause surprises
- If you omit the filename, the output is written to
typescriptin the current directory. Name the file explicitly so an unrelated file is not created or appended by mistake. --appendkeeps the existing log and adds the new session. Without it, an existing regular output file is replaced. Copy a log first if it contains evidence you may need.--flushwrites output after each write, which helps a second process watch a log in real time but can reduce performance.- Screen-oriented programs such as
vimay leave backspaces and control sequences in the typescript. The command is designed for terminal sessions that resemble a hardcopy transcript. - Do not feed an ordinary pipe into an interactive
scriptsession and assume it will exit cleanly. Its inner shell is interactive and can wait for input. Use--commandfor a bounded operation.
Checkpoint
The capture is usable when the output file contains the expected text, the timing file exists if replay is required, and no secret input was recorded.
Done means
- You named the output file instead of relying on
typescript. - You used
--commandfor a finite, non-interactive capture. - You added
--returnwhen the child's exit status mattered. - You added
--log-timingwhen playback timing mattered. - You checked the log for sensitive data before sharing it.