Python Basics: Track Line Origins Across Several Files
When combining small reports, keep both the overall row number and the original file location. FileInput streams an explicit file list while exposing those two coordinates.
Prepare a scratch directory
Tested on Linux with CPython 3.12.14. You need Bash, a text editor, the named runtime, and standard mktemp, rm, and rmdir utilities. Run Python without -O so assertions remain enabled. The examples use only controlled local data; generated fixture directories are cleaned automatically.
Keep the same terminal open. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as demonstrate-fileinput.py:
import fileinput
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
first = Path(directory) / "east.txt"
second = Path(directory) / "west.txt"
first.write_text("apple\npear\n", encoding="utf-8")
second.write_text("plum\n", encoding="utf-8")
seen = []
with fileinput.FileInput(files=[first, second], encoding="utf-8") as lines:
for line in lines:
name = Path(lines.filename()).name
value = line.rstrip("\n")
row = (lines.lineno(), name, lines.filelineno(), value)
seen.append(row)
print(*row, sep=" | ")
assert seen == [(1, "east.txt", 1, "apple"),
(2, "east.txt", 2, "pear"),
(3, "west.txt", 1, "plum")]Run it:
python3 -B demonstrate-fileinput.pyObserved output:
1 | east.txt | 1 | apple
2 | east.txt | 2 | pear
3 | west.txt | 1 | plumRead the result
lineno continues across files, while filelineno starts again at one for the second file. Passing files explicitly avoids interpreting the program command line as an input list. The context manager closes the active stream.
Apply it carefully
This reads files without modifying them. Do not enable inplace merely to add labels: it redirects output into replacement files. A dash in the file list means standard input, so validate a user-supplied list if that behavior is unwanted. Removing only the newline preserves meaningful spaces.
The official reference describes the behavior and options. These checks cover the fixture above rather than every input or platform.
Clean up
Remove the saved source, return to the starting directory, then remove the empty practice directory:
rm -- demonstrate-fileinput.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect its remaining contents rather than deleting unknown files.
Acknowledgements
This post was created with assistance from AI.