Input, Output, and Redirection

Input, Output, and Redirection

Bitnesia • Aug 30, 2026 100 •ID

Every script discussed from Chapter 1 to Chapter 4 still runs in one direction: we write the logic, and the output simply appears on the screen without any interaction with the person executing it. In reality, a useful script almost always needs two-way communication, whether receiving input via the keyboard, displaying clean and readable output, separating error messages from standard output, or saving execution results into log files for Sysadmin review later. This chapter covers the three primary builtins for basic interaction (echo, printf, and read), the concept of the three standard data streams for every process (stdin, stdout, stderr), redirection techniques to point these streams to files, and pipes to chain one command output into another command input, concluding with a case study combining all these concepts into a production-ready script.

5.1 echo, printf, and read for User Interaction

This section covers the three most frequently used builtins for communicating with the script user: echo and printf for displaying messages, and read for capturing typed input.

5.1.1 Displaying Text with echo

echo is the simplest builtin for printing text to the screen. This command has appeared frequently in previous chapters without deep technical breakdown.

echo "Server setup started"

By default, Bash does not interpret escape characters such as \n or \t inside arguments to echo; both are displayed literally as text. This point frequently causes confusion for beginners coming from other programming languages.

echo "Line one\nLine two"

The command above prints Line one\nLine two exactly as written, rather than two separate lines, because Bash treats \n as two standard characters.

5.1.2 The -n and -e Options in echo

The two most common echo options are -n to suppress the trailing newline, and -e to enable the interpretation of escape sequences that were previously ignored.

echo -n "Checking connection... "
echo -e "Status:\n- OK\n- Ready for use"

echo -n is useful when displaying progress indicators without breaking to a new line, while echo -e enables escape sequences such as \n (newline), \t (tab), and \\ (literal backslash). Note that default echo behavior without -e is specific to Bash; some other shells, such as POSIX sh implementation on certain systems, interpret escape sequences by default. Do not assume identical behavior when running scripts under non-Bash shells.

5.1.3 Precision Formatting with printf

printf is the more appropriate builtin when precise control over output formatting is required, such as aligning numerical columns or enforcing specific decimal places. Its syntax follows the standard format string pattern established in the C programming language: a format string followed by a list of arguments corresponding to each format specifier.

printf "Name: %s, Age: %d years\n" "Budi" 30

The specifier %s inserts a string, %d inserts an integer, and %f inserts a floating-point number. Unlike echo, printf never appends a newline automatically, so \n must be declared explicitly when needed.

printf "Disk usage: %.1f%%\n" 78.456

The snippet above prints Disk usage: 78.5% because %.1f rounds the float to one decimal place, and %% is used to output a literal percent sign since a single percent sign acts as a specifier prefix. A powerful feature of printf often overlooked by beginners is that if more arguments are supplied than specifiers in the format string, printf automatically cycles through the format string until all arguments are consumed. This is useful for printing repeating datasets with a structured layout.

printf "%s: %s\n" "web1" "active" "db1" "inactive"

5.1.4 Reading User Input with read

read is the builtin used to capture a single line of input from standard input (stdin), typically from the keyboard, and assign it to a variable. The concept of stdin is explained in detail in section 5.2.

echo "Enter server name:"
read server_name
echo "Selected server: $server_name"

If read is called without target variable names, Bash automatically stores the input in a built-in variable named REPLY. If multiple variable names are passed, read splits the input line based on the internal field separator (IFS), filling each variable sequentially, with any remaining words dumped into the last variable.

read user host
# input: admin server1.example.com
echo "User: $user, Host: $host"

5.1.5 Essential Options for read

The following options for read represent common production usage. The -p option displays a prompt text directly without requiring a preceding echo command.

read -p "Enter server name: " server_name

Note that prompt text defined via -p is only displayed if stdin is connected to an interactive terminal session. When executing a script through pipes or file redirection (covered in section 5.3), the prompt will not be printed, though read will still attempt to consume incoming data.

The -s (silent) option suppresses typed input from displaying on screen, which is required when handling sensitive input like database passwords.

read -sp "Enter database password: " db_pass
echo

The empty echo command after read -s ensures a newline is printed, as pressing Enter in silent mode does not emit a newline, preventing subsequent prompts from rendering on the same terminal line.

The -a option stores the incoming words into an indexed array, splitting input words into array elements.

read -a port_list -p "Enter list of ports separated by space: "
echo "First port: ${port_list[0]}"

The -n option forces read to return immediately after capturing a specified number of characters without waiting for the Enter key, ideal for single-character confirmations.

read -n 1 -p "Continue process? (y/n) " answer
echo

The -t option specifies a timeout in seconds. If the user provides no input before the timeout expires, read exits with a non-zero status code. This prevents automated scripts, such as non-interactive jobs managed via cron (discussed in Chapter 18), from hanging indefinitely.

read -t 10 -p "Type something within 10 seconds: " response

5.1.6 Troubleshooting: Calling read Without Option -r

A common mistake in shell scripting is using read without the -r flag. Without -r, read treats backslashes as escape characters, removing them from the input value instead of preserving them literally.

read path
# typed input: C:\Users\budi
echo "$path"
C:Usersbudi

Notice that backslashes are missing from the output because read interpreted them as escape indicators for the following characters. As recommended by static analysis tools like ShellCheck, always use read -r by default unless explicit backslash escaping is required.

read -r path
# typed input: C:\Users\budi
echo "$path"
C:\Users\budi

5.2 Standard Input, Output, and Error

Having covered input capture and display mechanics, this section explains the core architecture behind them: the three standard data streams provisioned for every process running on Unix-like operating systems, including Linux and macOS.

5.2.1 The Three Default File Descriptors

Whenever a process is spawned, the operating system assigns three communication streams referenced by integer identifiers called standard file descriptors. These file descriptors are defined by the POSIX standard and exist automatically for every process.

NumberNameFunctionDefault Target
0stdin (standard input)Inbound data stream for commandsKeyboard
1stdout (standard output)Outbound normal operational dataTerminal screen
2stderr (standard error)Outbound error messages and diagnosticsTerminal screen

The read builtin reads from stdin (fd 0), while echo and printf write to stdout (fd 1). By default, both stdout and stderr render to the same terminal console, making them visually indistinguishable, even though they operate as separate streams at the operating system level.

5.2.2 Why stderr is Separated from stdout

Decoupling stdout and stderr ensures normal functional payload (data meant for processing or downstream consumption) does not get contaminated by error messages, warnings, or debug logs. Constructing commands with echo "error message" >&2 allows scripts to explicitly output messages to stderr, maintaining a clean operational pipeline.

echo "Starting database backup..."
echo "Error: Database credentials not found" >&2

The >&2 syntax is a redirection form detailed in section 5.3. Stream separation allows operators to store normal data logs to one destination while redirecting errors to distinct log storage or system alerts.

5.2.3 Viewing Active File Descriptors of a Process

On Linux systems, active file descriptors for a specific process can be inspected via the /proc pseudo-filesystem, which exposes real-time kernel process statistics. The command below inspects file descriptors associated with the current shell process using the $$ variable (representing the current shell PID).

ls -l /proc/$$/fd

The output displays symbolic links labeled 0, 1, and 2 mapped to the current terminal device file. Note that /proc is specific to Linux systems. BSD-derived platforms like macOS do not utilize the /proc filesystem, requiring alternative utilities such as lsof -p for process inspection (covered in Chapter 17).

5.3 Redirection and Pipes

This section explores the core mechanics of redirecting standard streams away from default terminal endpoints to external targets like files or secondary processes.

5.3.1 Output Redirection with > and >>

Redirection overrides default stream routing. The > operator redirects stdout from a command into a target file instead of the terminal display.

echo "Backup completed at $(date)" > status.log

The > operator truncates the target file if it exists, overwriting existing content without warning. Verify destination paths carefully before using stdout redirection against critical files. To append data to an existing file without truncating it, use the >> operator.

echo "Backup completed at $(date)" >> status.log

The >> operator is standard for continuous logging, preserving execution history across multiple script invocations.

5.3.2 Input Redirection with <

The < operator redirects standard input (stdin) so that a command reads content directly from a file rather than waiting for keyboard input.

read server_name < configuration.txt
echo "Server loaded from file: $server_name"

The command causes read to fetch its first line of input directly from configuration.txt.

5.3.3 Redirecting stderr with 2> and Combined Redirection &>

Since stderr uses file descriptor 2, redirecting standard error requires using the syntax 2>, with no spaces between the integer and the operator.

ls /non/existent/directory 2> error.log

Standard output displays on screen as normal, but error messages produced by ls are sent to error.log. To capture both stdout and stderr into a single file simultaneously, Bash provides the &> shorthand operator.

ls /etc /non/existent/directory &> full_output.log

The &> operator is a extension supported in Bash and zsh, but it is not part of the strict POSIX shell standard. Scripts targeting strict POSIX shell compliance (such as dash) should use the portable 2>&1 syntax described next.

5.3.4 Redirection Order and the 2>&1 Pattern

The 2>&1 construct is the POSIX standard way to merge stderr into the standard output stream. It translates to: "redirect file descriptor 2 to the current destination of file descriptor 1".

ls /etc /non/existent/directory > full_output.log 2>&1

Order of evaluation is critical when combining stream redirections. Bash evaluates redirections strictly from left to right. In the snippet above, > full_output.log redirects stdout to the file first; then 2>&1 binds stderr to point to the current location of stdout (the file). Reversing the sequence produces a completely different result.

ls /etc /non/existent/directory 2>&1 > full_output.log

Here, 2>&1 binds stderr to stdout's current destination (the terminal screen). The subsequent > full_output.log moves stdout to the file, but stderr remains bound to the terminal screen. Consequently, error messages continue printing on screen while standard output goes into the file. Rule of thumb: position 2>&1 at the end of the command string when merging stderr into stdout.

Another routine pattern used in background execution and cron scheduling is discarding output streams using /dev/null, a pseudo-device file that acts as a data sink for unneeded stream output.

./check_backup.sh > /dev/null 2>&1

This command suppresses all standard output and error output emitted by the script, fitting background processes that handle internal logging independently.

5.3.5 Pipelines | and tee for Stream Processing

A pipe, represented by the | character, connects standard output (stdout) of one command directly into standard input (stdin) of another command without intermediate disk storage. Unlike file redirection, pipes connect active processes in memory.

ps -eo user= | sort | uniq -c

This pipeline feeds process user list data from ps directly to sort, sending sorted results into uniq -c to calculate occurrence frequencies per user. Multiple operations combined in this sequence form a command pipeline.

Important subshell behavior: by default, Bash executes each command of a pipeline inside isolated subshell environments. Consequently, variables declared or updated inside a pipeline stage (such as inside a while read loop consuming piped input) lose their values once the pipeline finishes, as state modifications remain contained within that child subshell. This behavior can be adjusted using settings like lastpipe in supported environments.

The tee utility splits standard input streams, sending matching content to standard output while writing copies to specified log files.

echo "Starting disk check" | tee status.log
df -h | tee -a status.log

Using the -a flag with tee appends incoming stream data to target files rather than truncating existing data.

5.3.6 Common Redirection Pitfalls

By default, > overwrites target file content without user confirmation. Enabling the shell option set -o noclobber (or set -C) prevents > from overwriting existing files, returning an error instead of truncating data.

set -o noclobber
echo "test" > status.log
bash: status.log: cannot overwrite existing file

To force truncation when noclobber is set, use the >| override operator. Another pitfall is assuming 2> logfile handles all output streams, leaving standard output printing to screen. Lastly, incorrect placement of 2>&1 relative to file redirection operators remains a frequent source of logic bugs.

5.4 Practical Case Study: Building Command Pipelines

This practical case study integrates read, file redirection, and processing pipelines into an operational script that outputs user process summary metrics on a target machine.

5.4.1 Scenario and Pipeline Data Flow Design

The script prompts the user via read -p to specify display limits, captures output from ps, sorts process ownership, aggregates occurrences per user account, sorts the list numerically in descending order, limits output rows, prints console output, and logs results using tee. Note that the ps -eo user= syntax uses GNU ps flags common on Linux systems; BSD variants on macOS require syntax adjustments as detailed in Chapter 17.

5.4.2 Writing the Complete Script

Save the script below as process_report.sh.

#!/bin/bash

read -rp "Display top how many users? " row_limit
report_file="user_process_report.txt"

echo "User Process Summary Report - $(date '+%Y-%m-%d %H:%M:%S')" > "$report_file"
echo "===========================================" >> "$report_file"

ps -eo user= | sort | uniq -c | sort -rn | head -n "$row_limit" | tee -a "$report_file"

echo "Complete report saved to: $report_file"

This script combines standard redirection operators alongside process piping. The initial > creates or clears the output file, >> appends section headers, and the execution pipeline ps | sort | uniq -c | sort -rn | head filters process metrics directly into tee -a for joint display and logging.

5.4.3 Executing and Verifying Script Output

Grant execution permissions to the script file and run it:

chmod +x process_report.sh
./process_report.sh

When prompted, enter a value like 5. The stdout output displays process counts grouped by username:

Display top how many users? 5
     42 www-data
     18 root
      6 mysql
      3 budi
      1 sshd

Verify that tee wrote matching results directly into the destination text file:

cat user_process_report.txt

This script can be extended by routing standard error streams into dedicated logs, or by scheduling execution through cron (Chapter 18) to send automated alerting notifications via API webhooks (Chapter 20). Combining interactive read prompts, file logging via redirection, and stream processing with pipes forms a primary architectural pattern applied throughout subsequent chapters, including string parsing workflows using grep, sed, and awk starting in Chapter 9.