Python's fileinput
Module
You think you know Python, and then you stumble across the fileinput module:
[fileinput] iterates over the lines of all files listed insys.argv[1:]
, defaulting tosys.stdin
if the list is empty. If a filename is'-'
, it is also replaced bysys.stdin
...
This is so helpful, how have I never heard of this module before?
I've always just iterated over sys.argv[1:]
directly and opened all the files manually, not even handling -
because it would be
another condition I didn't feel like testing. This little bit of boilerplate reduction makes quick file IO much nicer IMO.
You can even modify files inplace by simply printing to stdout (assuming you set the inplace
keyword).
This made me think, what CLI tasks could this be used for?
cat
clone
Recreating cat
is trivial:
import fileinput as f
for x in f.input():
print(x, end="")
Lets create some test data:
$ echo "abc123" > a
$ echo "bbbbbb" > b
$ echo "c45678" > c
Then run our cat.py
program:
$ python3 cat.py a b c
abc123
bbbbbb
c45678
What about stdin?
$ echo "hello" | python3 cat.py -
hello
Amazing!
grep
clone
Recreating grep
is also pretty trivial since fileinput
is line based and keeps track of the current file:
import sys
import re
import fileinput
PATTERN = re.compile(sys.argv.pop(1))
for line in fileinput.input():
if PATTERN.search(line):
print(fileinput.filename(), line, sep=":", end="")
Running:
$ python3 grep.py c a b c
a:abc123
c:c45678
Our basic grep.py
program mimics grep
pretty well, except all the missing flags,
and of course, the color.
grep
clone (with color)
I'm getting a bit carried away now, but adding color support to our little grep
clone is easy enough:
import fileinput
import os
import re
import sys
PATTERN = re.compile(sys.argv.pop(1))
GREP_COLORS = os.getenv("GREP_COLORS", "ms=01;31:mc=01;31:sl=:cx=:fn=1;34:ln=32:bn=32:se=0")
COLORS = dict(x.split("=") for x in GREP_COLORS.split(":"))
RESET = "\x1b[0m"
for line in fileinput.input():
if PATTERN.search(line):
filename = f"\x1b[{COLORS['fn']}m{fileinput.filename()}{RESET}"
line = PATTERN.sub(f"\x1b[{COLORS['mc']}m\\1{RESET}", line)
print(filename, line, sep=":", end="")
Running the same command as above gives us the following colorized output:
a:abc123
c:c45678
Fin
That's all I got for now. The fileinput
module is pretty useful for line based file operations,
though if you need to read whole files at once, you're probably better off with iterating over sys.argv[1:]
like usual.