Standard Input/Output β stdin/stdout Redirection
After completing this topic, you will be able to:
Explain the roles of stdin, stdout, and stderr, and connect programs' input and output using redirection (>, <) and pipes (|) in the terminal.
The Three Channels
When a program runs, the operating system automatically opens three channels:
| Name | Direction | Purpose | Python |
|---|---|---|---|
| stdin (standard input) | External β Program | Keyboard input | input(), sys.stdin |
| stdout (standard output) | Program β External | Normal results | print(), sys.stdout |
| stderr (standard error) | Program β External | Errors/warnings | sys.stderr |
By default, stdin is connected to the keyboard, and stdout and stderr are connected to the screen (terminal).
print() Writes to stdout
# hello.pyprint("μλ
νμΈμ") # Outputs to stdout$ python hello.pyμλ
νμΈμprint() internally calls sys.stdout.write(). You can also use it directly:
import sys
sys.stdout.write("Output to stdout\n")sys.stderr.write("Output to stderr\n")Both lines will appear on the screen, but through different channels. This difference becomes apparent with redirection.
Redirection β Directing Channels to Files
You can change the direction of input and output using > and < in the terminal.
Redirecting stdout to a File (>)
$ python hello.py > output.txtNothing will appear on the screen. The result of print() will be written to the output.txt file.
$ cat output.txtμλ
νμΈμ>> appends to the existing content:
$ python hello.py >> output.txt # Appends to existing contentTaking stdin from a File (<)
# count.pyimport syslines = sys.stdin.readlines()print(f"Total {len(lines)} lines")$ python count.py < data.txtTotal 42 linesThe content of data.txt is fed into stdin instead of keyboard input.
Separating stderr Only
# process.pyimport sysprint("Processing result: Success")sys.stderr.write("Warning: Some data is missing\n")$ python process.py > result.txt 2> error.txt> redirects only stdout, and 2> redirects only stderr. This allows you to separate results and errors into different files.
Pipes β Connecting Programs
| (pipe) connects the stdout of one program to the stdin of the next program:
$ cat data.csv | python process.py | python report.py > final.txtcat β (stdout) β | β (stdin) β process.py β (stdout) β | β (stdin) β report.py β final.txtEach program receives data from its input, processes it, and passes the result to the next. This is the core of the Unix philosophy of combining small programs to perform complex tasks.
Reading stdin Line by Line in Python
# upper.py β Converts input to uppercaseimport sys
for line in sys.stdin: print(line.strip().upper())$ echo "hello world" | python upper.pyHELLO WORLD
$ cat names.txt | python upper.pyALICEBOBCHARLIEsys.stdin is iterable. Using a for loop to read line by line is memory-efficient.
Relationship between input() and stdin
name = input("Name: ") # Prompt β stderr? No, stdoutinput() internally:
- Prints the prompt string to stdout
- Reads a line from stdin and returns it
Therefore, caution is needed when using it with pipes:
$ echo "μ² μ" | python -c "name = input(); print(f'Hello, {name}')"Hello, μ² μThe prompt is meaningless in a pipe. When creating CLI tools, it is better to read sys.stdin directly instead of using input().
Practical Usage Examples
# csv_filter.py β Outputs only rows that meet specific conditionsimport sysimport csv
reader = csv.reader(sys.stdin)header = next(reader)print(",".join(header))
for row in reader: if float(row[2]) > 100: # 3rd column is greater than 100 print(",".join(row))$ cat sales.csv | python csv_filter.py > filtered.csvUsing stdin/stdout eliminates the need to hardcode file names. Any file can be connected via a pipe, increasing reusability.
Pipe Chaining β UNIX Philosophy
# Find only ERRORs in the log and sort them by frequency$ cat server.log | grep "ERROR" | sort | uniq -c | sort -rn | head -5Each program does only one thing well, and they are connected by pipes. Python scripts can also be part of this chain:
# word_count.py β Calculates word frequencyimport sysfrom collections import Counter
words = []for line in sys.stdin: words.extend(line.strip().split())
for word, count in Counter(words).most_common(10): print(f"{count:>5} {word}")$ cat article.txt | python word_count.py 42 the 31 and 28 toOutputting Progress to stderr
import sys
total = 1000for i in range(total): # Processing logic... if i % 100 == 0: print(f"Progress: {i}/{total}", file=sys.stderr)
print(f"Result: {i * 2}") # Actual output β stdout$ python process.py > results.txtProgress: 0/1000 β Appears on the screen (stderr)Progress: 100/1000...stdout is redirected to the results.txt file, while stderr still appears on the screen. This is a practical pattern for separating progress messages and data output.
Key Summary
| Syntax | Meaning |
|---|---|
> | Redirect stdout to a file (overwrite) |
>> | Redirect stdout to a file (append) |
< | Take a file as stdin |
2> | Redirect stderr to a file |
| ` | ` |
2>&1 | Merge stderr into stdout |
/dev/null β Discarding Output
# Discard stdout (only see errors)$ python noisy_script.py > /dev/null
# Discard stderr (only see results)$ python noisy_script.py 2> /dev/null
# Discard both (completely silent)$ python noisy_script.py > /dev/null 2>&1/dev/null is a "garbage can." It is used to suppress unnecessary output in automated scripts.
# The same pattern in Pythonimport os, sys
if os.environ.get("QUIET"): sys.stdout = open(os.devnull, "w")subprocess β Building Pipes in Python
import subprocess
# Execute an external command + capture stdoutresult = subprocess.run( ["ls", "-la"], capture_output=True, text=True)print(result.stdout)print(result.stderr)
# Pipe chainingp1 = subprocess.Popen(["cat", "data.txt"], stdout=subprocess.PIPE)p2 = subprocess.Popen(["grep", "ERROR"], stdin=p1.stdout, stdout=subprocess.PIPE)output = p2.communicate()[0].decode()You can programmatically connect stdin/stdout of shell commands within Python.
print() writes to stdout, and error messages go to stderr. Understanding this separation makes redirection and pipes more natural.