Record a Terminal Session with script
Something broke in a terminal and nobody believes you without the actual bytes, which is exactly what script is for. It writes a plain text record of a session, plus an optional timing file that scriptreplay can use to play it back. The examples use the locally installed util-linux command. Expect about 10 minutes for a basic capture, or 20 if you also want 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 ready and a directory where the log can 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 actually resolves to a separate util-linux 2.42.4 executable earlier in PATH. Check your own path and version before you rely on a detail from here inside an automated workflow:
$ command -v script
/home/linuxbrew/.linuxbrew/bin/script
$ script --version
script from util-linux 2.42.4
Recording your own session needs no elevated privileges. Do not put a log in a shared directory unless its permissions and contents are fine for other users to see.
1. Record an interactive session
Give the log an explicit name, then run the command from the directory you want captured. script starts an interactive shell and writes terminal output to that file:
$ script /tmp/terminal-session.log
Script started, file is /tmp/terminal-session.log
$ printf 'build started\n'
build started
$ exit
Script done.
The recording ends when the child shell exits: use exit or your shell's usual 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 treat it as a terminal transcript rather than a clean command report.
Checkpoint
Verify the file exists and holds 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. It avoids leaving an interactive shell running, and it suits programs whose output changes once standard output is no longer 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, and choose your outer quotes carefully if it contains shell metacharacters: the inner shell that script uses still interprets the command string. The command's exit status is not normally what script itself returns. Add --return when something downstream needs the child's real 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 in a separate file:
$ 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 expecting an exact timestamp or delay. The classic format holds an elapsed delay plus an output character count. Feed both files to scriptreplay to play it back:
$ scriptreplay --timing=/tmp/replay-session.timing /tmp/replay-session.log
Want input and output together? Use --log-io with --log-timing. That selects the advanced multi-stream timing format, which lets scriptreplay --summary describe the recorded streams afterwards.
4. Protect passwords and other secrets
Warning
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 captures 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 got captured: stop using the file, delete it the normal way, and rotate the exposed credential. Deleting a log is irreversible unless you have a separate protected backup. Before sending a transcript on, search it rather than displaying the whole thing 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 either.
Common traps
- No filename means
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 plain output file is replaced, so copy a log first if it holds evidence you need.--flushwrites output after every write. Handy for a second process watching the log live, at some cost to performance.- Screen-oriented programs such as
vileave backspaces and control sequences in the typescript. The command is built for sessions that read like a hardcopy transcript. - Do not pipe into an interactive
scriptsession and expect it to exit cleanly. Its inner shell is interactive and can sit waiting for input. Use--commandfor anything bounded.
Checkpoint
The capture is usable once the output file holds the expected text, the timing file exists if you need replay, 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.