Input Redirection (<)
Input redirection lets you feed the contents of a file into a command as if you had typed it in by hand at the keyboard. Instead of a command waiting for you to type lines and press Enter, it reads those lines straight from a file you point it at with the < operator. This is one half of the shell’s redirection toolkit — you’ll pair it constantly with > (output redirection) and | (pipes) to build commands and scripts that process data with zero manual typing. Once you understand how < connects a file to a command’s standard input, a lot of shell scripting starts to click into place.
Overview: How Input Redirection Works
Every process on Linux is handed three open file descriptors by the kernel the moment it starts: file descriptor 0 is standard input (stdin), file descriptor 1 is standard output (stdout), and file descriptor 2 is standard error (stderr). Normally, when you run an interactive command, all three are connected to your terminal: stdin reads whatever you type, and stdout/stderr print to your screen.
A file descriptor is just a small integer the kernel uses as a handle for something it has open — a regular file, a terminal, a pipe, a socket. Because Linux treats almost everything as a byte stream accessible through this same descriptor mechanism, a command that reads from stdin doesn’t actually know or care whether fd 0 is connected to your keyboard, a regular file on disk, or the output of another process. It just calls read() on file descriptor 0 and gets bytes back.
This is exactly what the < operator exploits. When you write command < file.txt, the shell does the following before it even runs command: it opens file.txt for reading, gets back a new file descriptor for it, and then uses the dup2() system call to make that file the target of file descriptor 0, closing whatever was there before (normally your terminal). Only after this plumbing is in place does the shell fork() and exec() the command. From the command’s point of view, nothing looks different — it still reads from fd 0 — but fd 0 is now wired to the file instead of your terminal.
This means input redirection only changes anything for commands that actually read from stdin. Commands like sort, grep, wc, cat, tr, and read are designed to consume stdin when no filename argument is given. Commands like ls or mkdir don’t read stdin at all for their normal operation, so redirecting a file into them with < does nothing useful — the file descriptor is open, but the program never looks at it.
Syntax
The general forms of input redirection look like this:
command < input_file
command < input_file > output_file
command <<< "string"
| Form | Meaning |
|---|---|
< |
Redirects the contents of a file to a command’s stdin (fd 0) |
<< |
A heredoc — feeds a multi-line block of literal text, terminated by a delimiter, to stdin |
<<< |
A herestring — feeds a single string (plus a trailing newline) to stdin |
You can combine < with > on the same command line to redirect input and output at once, and the order of redirections generally doesn’t matter to Bash as long as they come after the command name. Heredocs and herestrings are covered in depth in their own lesson, but they’re mentioned here because they’re close relatives of < — all three exist to get bytes onto a command’s stdin without you typing them interactively.
Examples
Example 1: Sorting a file’s contents
Suppose employees.txt contains names in no particular order:
cat employees.txt
Output:
Dana Okafor
Alice Chen
Carol Nguyen
Bob Martinez
Redirecting that file into sort‘s stdin prints it alphabetically, without sort ever needing a filename argument:
sort < employees.txt
Output:
Alice Chen
Bob Martinez
Carol Nguyen
Dana Okafor
Note that sort employees.txt (without <) produces the identical result — sort accepts a filename argument directly. The < form matters most for commands that only know how to read stdin, or when you want to make the data flow explicit in a script.
Example 2: Counting lines without a filename in the output
wc -l < /var/log/app.log
Output:
128
Compare that to running wc with the filename as an argument instead:
wc -l /var/log/app.log
Output:
128 /var/log/app.log
When you give wc a filename, it knows the name and prints it alongside the count. When you redirect the file to stdin with <, wc only ever sees an anonymous stream of bytes on fd 0 — it has no idea what file (if any) that stream came from, so it can’t print a name even if it wanted to. This is a useful trick whenever you want a bare number for use elsewhere, such as capturing it into a variable with count=$(wc -l < /var/log/app.log).
Example 3: Reading a file line by line in a script
A very common pattern is looping over a file’s lines with the read builtin, redirecting the file into the done keyword at the end of the loop:
#!/usr/bin/env bash
set -euo pipefail
while IFS= read -r line; do
echo "Processing: $line"
done < servers.txt
Output:
Processing: web01.internal
Processing: web02.internal
Processing: db01.internal
Here, < servers.txt redirects stdin for the entire while loop, not just the read call. Each iteration of read -r line consumes one more line from that same stdin stream until the file is exhausted. Setting IFS= stops read from trimming leading/trailing whitespace, and -r stops it from interpreting backslashes — both are standard habits for reading files reliably.
How It Works Step by Step
Take tr 'a-z' 'A-Z' < employees.txt > employees_upper.txt as a worked trace:
- The shell parses the command line and finds two redirection operators before it does anything else.
- It opens
employees.txtfor reading and, viadup2(), attaches that file to file descriptor 0. - It opens (or creates)
employees_upper.txtfor writing and attaches it to file descriptor 1, truncating it to zero bytes first because>is the overwrite form. - Only now does the shell
fork()a child process andexec()trinside it, inheriting both redirected descriptors. tritself never opens any file — it just reads bytes from fd 0 and writes translated bytes to fd 1, unaware that both are backed by files instead of a terminal.- When
trreaches end-of-file on fd 0 (the kernel’sread()returns 0 bytes), it exits, and the shell reports its exit status.
This separation — the shell wires up the descriptors, the program just reads and writes them — is why redirection works identically for every command, without each program needing special code to support it.
Common Mistakes
Mistake 1: Redirecting output back into the same file used for input
It’s tempting to “sort a file in place” like this:
sort < employees.txt > employees.txt
This looks reasonable but destroys your data. The shell sets up all redirections before running sort, and > truncates employees.txt to zero bytes immediately. By the time sort actually tries to read from fd 0, the file it’s reading from is already empty — the result is an empty file, not a sorted one. Fix it by writing to a different file and then replacing the original:
sort < employees.txt > employees.sorted.txt
mv employees.sorted.txt employees.txt
Mistake 2: Using < when you meant a herestring
New users sometimes expect < to accept literal text directly:
grep "error" < "This is my error log text"
This fails because < always expects a filename to open — Bash tries to open a file literally named This is my error log text, which doesn’t exist:
grep: This is my error log text: No such file or directory
To feed a literal string to a command’s stdin, use a herestring instead:
grep "error" <<< "This is my error log text"
Mistake 3: Assuming every command reads stdin
Redirecting a file into ls, hoping it will list each directory named inside that file, does nothing:
ls < dirlist.txt
ls simply ignores stdin for its normal directory-listing operation and lists the current directory instead, as if < dirlist.txt weren’t there at all. To actually use each line of a file as an argument to a command, loop over it explicitly:
while IFS= read -r dir; do
ls "$dir"
done < dirlist.txt
Best Practices
- Prefer
command < fileovercat file | commandwhen only one command needs the file — it avoids spawning an extracatprocess for no reason (the “useless use of cat” pattern). - Never use the same filename on both sides of
<and>in one command; write to a temporary file and rename it afterward instead. - Use
while IFS= read -r line; do ... done < filefor line-by-line processing rather than piping into the loop — redirecting with<keeps the loop in the current shell, so variables you set inside it are still visible afterward, unlike piping into a loop, which runs it in a subshell. - Always quote strings used with
<<<to prevent word-splitting and globbing on their contents. - Before relying on
<with a command, confirm it actually reads stdin when no filename is given — check its manual page if you’re unsure. - Quote file paths used with
<in scripts (e.g. via a variable like< "$input_file") so paths containing spaces don’t break.
Practice Exercises
- Create
~/practice/colors.txtwith several color names in random order, one per line. Use input redirection withsortto print them alphabetically without modifying the original file. - Write a script that reads a list of usernames from
users.txt(one per line) using<and awhile readloop, printing"Welcome, <username>!"for each — without using a pipe anywhere. - Use
<<<to test whether the string"connection refused"matches the patternrefusedwithgrep, without creating any file on disk.
Summary
<redirects a file’s contents to a command’s standard input (file descriptor 0), replacing the terminal as the source of input.- The shell sets up all redirections before the command runs, which is why redirecting output to the same file used for input truncates it first and loses the data.
<only affects commands that actually read from stdin when no filename is given; commands likelsignore it entirely.<<<(herestring) feeds a literal string to stdin;<<(heredoc) feeds a multi-line block of text — neither is the same as<, which always expects a filename.while IFS= read -r line; do ... done < fileis the standard, subshell-free way to process a file line by line in Bash.
