Skip to content
Linux Basics: Manage Dedicated Input and Output in Bash

Linux Basics: Manage Dedicated Input and Output in Bash

October 20, 2026

File descriptors are open connections for input and output. Bash starts with standard input (0), standard output (1), and standard error (2). Additional descriptors let a script keep a log open, read a separate file, or temporarily redirect its own output.

These examples were tested on Linux with GNU Bash 5.2.37. Use Bash rather than sh. No administrator access is needed.

Prepare a scratch directory

You need Bash, a text editor, and the cat, mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this scratch directory before running its command. Calling bash explicitly avoids changing executable permissions.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Keep a separate log open

Save this as descriptor-log.sh:

#!/usr/bin/env bash
exec {log_fd}>descriptor.log || exit 1
printf 'Preparing report\n'
printf 'step=prepare\n' >&"$log_fd"
printf 'step=finish\n' >&"$log_fd"
exec {log_fd}>&-
printf 'Saved log:\n'
cat -- descriptor.log

Run it:

bash descriptor-log.sh

Output:

Preparing report
Saved log:
step=prepare
step=finish

exec without a command changes descriptors in this script. {log_fd} asks Bash to allocate a descriptor and store its number in the variable. >&"$log_fd" routes one command’s output there; {log_fd}>&- closes it. Opening with > truncates an existing destination, so use a fresh scratch file or intentionally choose >> to append.

2. Read a file without consuming standard input

Save this as descriptor-input.sh:

#!/usr/bin/env bash
printf '%s\n' 'build docs' 'run tests' > queue.txt
exec {queue_fd}<queue.txt || exit 1
IFS= read -r request || exit 1
IFS= read -r -u "$queue_fd" first || exit 1
IFS= read -r -u "$queue_fd" second || exit 1
exec {queue_fd}<&-
printf 'Request: %s\n' "$request"
printf 'Queued: %s; %s\n' "$first" "$second"

Run it:

printf '%s\n' status | bash descriptor-input.sh

Output:

Request: status
Queued: build docs; run tests

The unqualified read consumes the request from standard input. The -u reads use the separate descriptor and advance through the queue file instead. Closing the input descriptor uses <&-. The example creates exactly two newline-terminated queue records; real code should decide how to handle unexpected EOF.

3. Save and restore standard output

Save this as descriptor-restore.sh:

#!/usr/bin/env bash
exec {original_stdout}>&1 || exit 1
exec > captured-output.txt || exit 1
printf 'Written while redirected\n'
printf 'Visible through saved output\n' >&"$original_stdout"
exec 1>&"$original_stdout"
exec {original_stdout}>&-
printf 'Standard output restored\n'
cat -- captured-output.txt

Run it:

bash descriptor-restore.sh

Output:

Visible through saved output
Standard output restored
Written while redirected

First duplicate descriptor 1, then change descriptor 1 to the file. The saved descriptor still points to the original output destination. Restoring from it lets later commands print normally again. Run this as a separate Bash script, as shown; sourcing it would change descriptors in your current shell.

Clean up

In the same terminal, remove only the files created for these examples, return to your original directory, and remove the empty scratch directory:

rm -- descriptor-log.sh descriptor-input.sh descriptor-restore.sh descriptor.log queue.txt captured-output.txt
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on