---
id: bash-basics-5-input-and-output-in-bash
slug: bash-basics-5-input-and-output-in-bash
title: "Bash Basics #5: Input and Output in Bash"
excerpt: "Master data streams and I/O redirection in Bash: standard file descriptors (0, 1, 2), pipelines, here-documents, here-strings, tee, and interactive input with read."
date: "2024-10-26T09:00:00+01:00"
updated: "2024-11-10T09:00:00+01:00"
author:
  name: "Sebastian Palencsar"
  handle: "spalencsar"
category: "bash-basics"
tags: ["bash", "linux", "shell", "scripting", "io-redirection", "pipes", "streams", "basics"]
reading_time: 18
toc: true
---

**Welcome to the fifth part of our technical series on Linux and Bash programming!**

After learning how to [create and secure scripts](/en/bash-basics/bash-basics-1-create-your-first-bash-shell-script){.badge-link-text}, work with [variables](/en/bash-basics/bash-basics-2-using-variables-in-bash){.badge-link-text}, use [control structures and loops](/en/bash-basics/bash-basics-3-control-structures-in-bash){.badge-link-text}, and [modularize code into functions](/en/bash-basics/bash-basics-4-functions-in-bash){.badge-link-text} in the previous modules, today we focus on the lifeline of every Linux system: **input and output data streams (I/O)**.

The fundamental Unix principle is: *"Everything is a stream / file"* and *"Write programs that do one thing and do it well. Write programs to work together"*. A script that cannot flexibly ingest, process, redirect, and persistently store data is useless for practical system administration.

In this lesson, you will learn how Linux manages data streams through **file descriptors (0, 1, 2)**, how to deliberately separate standard output and error messages (`2>`, `2>&1`, `&>`), how pipelines (`|`) and `tee` chain data in memory, how to create multi-line configurations via **here-documents**, and how to safely read user input with **`read -r`**.

## File Descriptors: stdin (0), stdout (1), and stderr (2)

Every process that the Linux shell starts automatically receives three open data channels from the kernel (*file descriptors*):

```markdown
┌─────────────────────────────────────────────────────────────┐
│                 STANDARD DATA STREAMS IN LINUX               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  [ Keyboard / File / Stream ]                               │
│         │                                                   │
│         ▼                                                   │
│  0: Standard Input (stdin)                                  │
│         │                                                   │
│         ▼                                                   │
│  ┌──────────────┐                                           │
│  │ Linux Process│                                           │
│  └──────┬───────┘                                           │
│         │                                                   │
│         ├──────────────────────────┐                        │
│         ▼                          ▼                        │
│  1: Standard Output (stdout)  2: Standard Error (stderr)    │
│  Normal program output        Error messages & warnings     │
│         │                          │                        │
│         ▼                          ▼                        │
│  [ Screen / File / Pipe ]     [ Screen / Error Log ]        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### The Three Channels at a Glance:

1. **`0` – `stdin` (*Standard Input*):** The input stream through which programs receive data from the keyboard, from files, or from other programs.
2. **`1` – `stdout` (*Standard Output*):** The output stream for regular program results.
3. **`2` – `stderr` (*Standard Error*):** The separate channel for error messages, status, and warning notices.

<blockquote class="infobox infobox--info">
💡 **Why two separate output channels?**
The separation of `stdout` (1) and `stderr` (2) ensures that a program can forward usable data through a pipe, while warnings or errors remain visible on the screen instead of corrupting the downstream program.
</blockquote>

## I/O Redirection: Controlling Output and Errors

With redirection operators, you redirect data streams into files or pseudo-devices like `/dev/null`:

```markdown
┌─────────────────────────────────────────────────────────────┐
│                 REDIRECTION MATRIX OVERVIEW                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Operator       Meaning & Behavior                          │
│  ─────────────────────────────────────────────────────────  │
│  > file         Redirects stdout to file (overwrites file)  │
│  >> file        Appends stdout to file (append mode)        │
│  2> file        Redirects ONLY stderr (errors) to file      │
│  2>> file       Appends ONLY stderr to file                 │
│  &> file        Redirects stdout AND stderr together        │
│  2>&1           Duplicates stderr to stdout's channel       │
│  < file         Reads stdin from file                       │
│  >/dev/null 2>&1 Discards all output & errors completely    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Practical Examples for Redirections:

```bash
# 1. Save regular output to file (overwriting):
echo "Server initialized" > /var/log/init.log

# 2. Append output to existing log:
echo "[$(date '+%F %T')] Service started" >> /var/log/app.log

# 3. Write only error messages to separate error log:
find /var/ -name "*.conf" 2> /tmp/find_errors.log

# 4. Completely silence error messages (/dev/null):
grep -r "SECRET_KEY" /etc/ 2>/dev/null

# 5. Write stdout and stderr together to the same log file:
./backup.sh &> /var/log/backup.log
# POSIX-compliant alternative:
./backup.sh > /var/log/backup.log 2>&1
```

### Outputting Custom Error Messages to `stderr` (`>&2`)

When your script encounters an error, you should explicitly send the message to `stderr`:

```bash
#!/usr/bin/env bash
set -euo pipefail

if [[ ! -f "/etc/app.conf" ]]; then
    # '> &2' redirects the output of this echo command to stderr (channel 2):
    echo "CRITICAL ERROR: Configuration file /etc/app.conf missing!" >&2
    exit 1
fi
```

## Pipelines (`|`) and Parallel Output with `tee`

A **pipeline** (`|`) connects the standard output (`stdout`) of the left command directly to the standard input (`stdin`) of the right command in memory – without temporary intermediate files.

```bash
# Filter and count all running Nginx processes:
ps aux | grep nginx | grep -v grep | wc -l

# Find the 5 largest files in the home directory:
du -ah ~/ | sort -rh | head -n 5
```

### The `tee` Tool: T-Junction for Data Streams

Often you want to **see a command's output on the screen AND save it to a file simultaneously**. For this, use `tee`:

```markdown
┌─────────────────────────────────────────────────────────────┐
│                 HOW TEE WORKS                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Command ──> [ stdout ] ──► [ tee -a app.log ]              │
│                                   │                         │
│                    ┌──────────────┴─────────────┐           │
│                    ▼                            ▼           │
│             [ Screen ]                [ File app.log ]       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

```bash
# Execute deployment, monitor on screen, and archive in log:
./deploy.sh | tee -a /var/log/deploy.log
```

## Multi-line Text: Here-Documents (`<<EOF`) and Here-Strings (`<<<`)

When you want to generate multi-line configuration files, SQL queries, or emails directly in your script, **here-documents** are the cleanest tool.

### 1. Standard Here-Document with Variable Expansion

```bash
#!/usr/bin/env bash
set -euo pipefail

DOMAIN="example.com"
PORT=8080

# Generate an Nginx configuration file:
cat <<EOF > /tmp/nginx_${DOMAIN}.conf
server {
    listen 80;
    server_name ${DOMAIN};

    location / {
        proxy_pass http://127.0.0.1:${PORT};
        proxy_set_header Host \$host;
    }
}
EOF
```

<blockquote class="infobox infobox--info">
💡 **Prevent variable expansion (`<<'EOF'`):**
If the text contains quotation marks, `$` characters, or Bash code that should not be expanded by the script, enclose the delimiter in quotes (`<<'EOF'`).
</blockquote>

### 2. Tab Indentation with `<<-EOF`

The minus sign (`<<-EOF`) ensures that leading **tab characters** (not spaces!) in the generated text are automatically stripped, so your script code remains cleanly indented.

### 3. Here-Strings (`<<<`)

A **here-string** passes a single variable or a short string directly to the `stdin` of a program:

```bash
IP="192.168.1.1"
# Passes $IP directly to bc or cut without an echo pipe:
cut -d'.' -f1 <<< "$IP"  # Output: 192
```

## Interactive Input with `read -r`

The `read` command reads user input from the keyboard or from data streams.

### Important Parameters of `read`:

| Option | Function | Example |
|---|---|---|
| `-r` | **Mandatory:** Prevents misinterpretation of backslashes | `read -r text` |
| `-p "text"` | Displays an input prompt | `read -r -p "Name: " name` |
| `-s` | **Silent mode:** Hides input (ideal for passwords) | `read -rs -p "Password: " pw` |
| `-t sec` | **Timeout:** Automatically aborts after x seconds | `read -t 10 -p "Continue? (10s): " a` |
| `-n num` | Reads maximum *num* characters (without pressing Enter) | `read -n 1 -p "Y/N? " answer` |
| `-a arr` | Reads words directly into an indexed array | `read -ra words` |

### Practical Example: Secure Password and Confirmation Dialog

```bash
#!/usr/bin/env bash
set -euo pipefail

# 1. Ask for username
read -r -p "Enter username: " USERNAME

# 2. Ask for password silently
read -rs -p "Enter password: " PASSWORD
echo "" # Line break after silent input

# 3. Single-key confirmation (1 character without Enter)
read -n 1 -r -p "Do you want to continue? (y/n): " CONFIRMATION
echo ""

if [[ "$CONFIRMATION" =~ ^[jJyY]$ ]]; then
    echo "Executing action for user '$USERNAME'..."
else
    echo "Operation cancelled."
    exit 0
fi
```

## Safely Reading Files Line by Line

To process log files or CSV files line by line, the combination of `while` and `read -r` is the absolute standard:

```bash
#!/usr/bin/env bash
set -euo pipefail

CSV_FILE="/tmp/users.csv"

# Create example data
cat <<'EOF' > "$CSV_FILE"
max;admin;/home/max
anna;developer;/home/anna
lisa;support;/home/lisa
EOF

# Read line by line with custom delimiter (semicolon):
while IFS=';' read -r name role home_dir || [[ -n "$name" ]]; do
    [[ -z "$name" || "$name" =~ ^# ]] && continue
    echo "User: $name | Role: $role | Directory: $home_dir"
done < "$CSV_FILE"
```

## Exercises & Practice Check

<blockquote class="infobox infobox--practice">
❗ **Practice tasks for lesson #5:**

1. **Task 1 (Structured Log Routing):**
   Write a script `log_router.sh` that writes normal output to `/tmp/output.log` and error messages to `/tmp/error.log`, while the user sees both outputs in color on the terminal.
2. **Task 2 (Configuration Generator):**
   Create a script that asks the user for `DB_NAME`, `DB_USER`, and `DB_PASS` and generates a file `database.env` with permissions `600` (`chmod 600`) via a here-document.
3. **Task 3 (Timeout Query):**
   Write a script that asks for confirmation with `read -t 5` and automatically continues with a default value after 5 seconds if no input is provided.
</blockquote>

### Sample Solution for Task 2:

```bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

read -r -p "Database Name  : " DB_NAME
read -r -p "Database User  : " DB_USER
read -rs -p "Database Pass  : " DB_PASS
echo ""

ENV_FILE="/tmp/database.env"

cat <<EOF > "$ENV_FILE"
# Automatically generated database configuration
DB_NAME="${DB_NAME}"
DB_USER="${DB_USER}"
DB_PASS="${DB_PASS}"
DB_HOST="127.0.0.1"
DB_PORT="3306"
EOF

# Restrict permissions to owner only
chmod 600 "$ENV_FILE"
echo "Configuration successfully created at $ENV_FILE (permissions: 600)."

exit 0
```

## Command Reference (Cheatsheet)

| Syntax / Command | Function & Description |
|---|---|
| `cmd > file` | Redirects stdout to file (overwrites file) |
| `cmd >> file` | Appends stdout to file |
| `cmd 2> file` | Redirects error messages (stderr) to file |
| `cmd &> file` | Redirects stdout AND stderr together to file |
| `echo "msg" >&2` | Explicitly outputs text on the error channel (stderr) |
| `cmd1 \| cmd2` | Connects stdout of cmd1 directly with stdin of cmd2 |
| `cmd \| tee -a file` | Writes stdout to file and displays it on screen simultaneously |
| `cat <<EOF > file` | Creates file from multi-line here-document |
| `read -r -p "prompt" var` | Reads user input with a displayed prompt text |
| `read -rs var` | Reads passwords silently without screen output |
| `while IFS= read -r line; do` | Safely reading files line by line |

## Further Resources

| Resource | Description |
|---|---|
| [GNU Bash Redirections Manual](https://www.gnu.org/software/bash/manual/html_node/Redirections.html){.badge-link-text} | Complete GNU documentation on data streams and descriptors |
| [Bash Basics #4: Functions in Bash](/en/bash-basics/bash-basics-4-functions-in-bash){.badge-link-text} | The previous part: Modularization, parameters, and scopes |
| [Linux Command Line Processor Guide](/en/linux-beginners/command-line-processor-in-linux){.badge-link-text} | Fundamental knowledge on shells, I/O streams, and pipes |
| [chmod and File Permissions](/en/linux-beginners/basics-to-best-practices-all-about-chmod-in-linux){.badge-link-text} | Linux permission concepts in detail |

## Conclusion

Mastering input and output streams, file descriptors, and redirections is the key to seamlessly integrating your scripts into the Linux ecosystem. Through deliberate separation of `stdout` and `stderr`, using `tee` for gapless logging, and robust input dialogs with `read -r`, you make your automations transparent and user-friendly.

<blockquote class="infobox infobox--info">
💡 **Tip:** Always explicitly send error messages in scripts to `stderr` (`echo "Error..." >&2`). This allows other admins and CI/CD pipelines to cleanly filter errors into separate log files via `2>` without contaminating the regular data streams.
</blockquote>

In the next lesson, we make our scripts indestructible and learn professional error handling, signal trapping, and debugging:
👉 **Next up:** [Bash Basics #6: Error Handling and Debugging in Bash](/en/bash-basics/bash-basics-error-handling-and-debugging-in-bash){.badge-link-text}

👉 **Course overview:** [All lessons of the Bash basics course](/en/category/bash-basics){.badge-link-text}
