The simplest way to feed a file into a script that reads standard input is shell redirection: python script.py < data.txt. Your code carries on calling input() or reading sys.stdin and never knows the difference. This guide covers that, pipes, reading stdin properly, and how to redirect from inside Python when you cannot change how the program is launched.
Shell redirection#
python script.py < data.txt
The shell opens the file and connects it to the process’s standard input before Python starts. Nothing in your code changes.
# script.py
import sys
for line in sys.stdin:
print(line.rstrip())
This works identically on Windows in PowerShell and cmd, and on macOS and Linux.
Pipes#
cat data.txt | python script.py
grep ERROR app.log | python count.py
python generate.py | python process.py | sort | uniq -c
A pipe connects one program’s output to the next one’s input. The practical difference from redirection is that a pipe streams — the second program can start working before the first has finished — whereas a file is simply there.
Writing a script that reads stdin and writes stdout makes it composable with every other command-line tool, which is a substantial amount of power for very little effort.
Reading standard input#
import sys
# One line at a time - constant memory
for line in sys.stdin:
process(line.rstrip("\n"))
# Everything at once
text = sys.stdin.read()
# A list of lines
lines = sys.stdin.readlines()
# A single line
first = sys.stdin.readline()
Prefer the loop. A log file of several gigabytes will exhaust memory with read() and stream comfortably with iteration.
input() also reads from stdin, so it works with redirection too:
name = input() # reads the first line of the file
age = input() # reads the second
while True:
try:
line = input()
except EOFError:
break
process(line)
Handling both a file argument and stdin#
Well-behaved command-line tools accept either. The fileinput module does this for you:
import fileinput
for line in fileinput.input():
print(fileinput.filename(), fileinput.lineno(), line.rstrip())
python script.py data.txt # reads the file
python script.py a.txt b.txt # reads both, in order
python script.py < data.txt # reads stdin
cat data.txt | python script.py # also stdin
Doing it by hand with argparse gives more control:
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument("file", nargs="?", type=argparse.FileType("r"),
default=sys.stdin)
args = parser.parse_args()
for line in args.file:
print(line.rstrip())
The nargs="?" makes the argument optional, and the default falls back to standard input.
Detecting where input is coming from#
import sys
if sys.stdin.isatty():
print("Type your input, then press Ctrl+D to finish:", file=sys.stderr)
else:
pass # input is redirected or piped
isatty() is true when a terminal is attached. Use it to show a prompt only when a person is there — and note the prompt goes to stderr, so it does not contaminate piped output.
Redirecting from inside Python#
Useful when you cannot change how the program is launched, and essential for testing:
import sys
with open("data.txt") as f:
original = sys.stdin
sys.stdin = f
try:
run_the_thing() # now reads from the file
finally:
sys.stdin = original
A context manager is cleaner:
import sys
from contextlib import contextmanager
@contextmanager
def stdin_from(path):
original = sys.stdin
with open(path) as f:
sys.stdin = f
try:
yield
finally:
sys.stdin = original
with stdin_from("data.txt"):
run_the_thing()
For tests, feed it a string rather than a file:
import io
import sys
from unittest.mock import patch
def test_reads_two_lines():
fake = io.StringIO("Ana\n34\n")
with patch.object(sys, "stdin", fake):
assert read_person() == ("Ana", 34)
Pytest has this built in:
def test_reads_input(monkeypatch):
monkeypatch.setattr("sys.stdin", io.StringIO("Ana\n34\n"))
assert read_person() == ("Ana", 34)
Feeding input to a subprocess#
import subprocess
# From a string
result = subprocess.run(
["python", "script.py"],
input="line one\nline two\n",
capture_output=True,
text=True,
)
print(result.stdout)
# From a file
with open("data.txt") as f:
subprocess.run(["python", "script.py"], stdin=f)
Binary input#
import sys
data = sys.stdin.buffer.read() # bytes, not text
sys.stdout.buffer.write(data)
Use the .buffer attribute for images, archives and anything that is not text. Reading binary data as text corrupts it, and on Windows the line-ending translation makes that corruption worse.
Encoding#
import sys
print(sys.stdin.encoding)
# Force UTF-8 regardless of the environment
sys.stdin.reconfigure(encoding="utf-8", errors="replace")
The default encoding comes from the environment, which differs between machines. Being explicit prevents a script that works locally from failing on a server with a different locale.
Questions people ask#
Why does my script hang with no input?
It is waiting for you to type. Press Ctrl+D on macOS and Linux, or Ctrl+Z then Enter on Windows, to signal end of input.
What is the difference between redirection and a pipe?
Redirection connects a file to stdin. A pipe connects another program’s output to stdin. Your code cannot tell them apart, which is the point.
Can I read stdin twice?
Not directly — it is a stream, and once consumed it is gone. Read it into a variable first if you need it more than once.
How do I read stdin without blocking?
Use select.select() on Unix, or read on a separate thread. Non-blocking stdin is awkward and cross-platform support is patchy, so consider whether the design can avoid needing it.
Where to go next#
- Python file handling — reading files directly.
- Command line basics — pipes and redirection in the shell.
- Building a complete Python project — where argparse fits.