Given a text file that contains a single mac address per line the goal is to process the file and replace known mac addresses with their equivalent device name using Python.

With the example input of

1 2 3 4
AA:AA:AA BB:BB:BB CC:CC:CC DD:DD:DD

… and with the known mac addresses defined as

1 2 3 4
macs = { 'AA:AA:AA': 'Laptop', 'DD:DD:DD': 'Phone' }

The expected output would be

1 2 3 4
Laptop BB:BB:BB CC:CC:CC Phone

Code

We’re going to be using the fileinput module for our “file manipulation” as opposed to doing it “manually”.

We’ll start by showing the code.

1 2 3 4 5 6 7 8 9 10 11 12
import fileinput filename = 'macs.txt' macs = { 'AA:AA:AA': 'Laptop', 'DD:DD:DD': 'Phone' } for line in fileinput.input(filename): line = line.rstrip('\r\n') print(macs.get(line, line))

… and here is what happens when we run it.

$ python replace-macs.py
Laptop
BB:BB:BB
CC:CC:CC
Phone
$ cat macs.txt
AA:AA:AA
BB:BB:BB
CC:CC:CC
DD:DD:DD

As you can see this code just prints the result and does not “modify” the file.

inplace=True

We can pass inplace=True to fileinput.input() which will instead overwrite the original file with the generated output.

for line in fileinput.input(filename, inplace=True):

Let’s re-run the code after our change.

$ python replace-macs.py
$ cat macs.txt
Laptop
BB:BB:BB
CC:CC:CC
Phone

When using fileinput.input() like this any output generated by print() calls will be written to file.

Let’s say for example we wanted to filter a file and keep only “even-numbered” lines.

>>> import sys
>>> 
>>> sys.stdout.writelines(open('4.txt'))
line 1
line 2
line 3
line 4
>>> 
>>> import fileinput
>>> 
>>> for n, line in enumerate(fileinput.input('4.txt'), start=1):
...     if n % 2 == 0:
...         print(line, end='')
... 
line 2
line 4

We can then use inplace=True to overwrite the original file with our “changes”.

>>> for n, line in enumerate(fileinput.input('4.txt', inplace=True), start=1):
...     if n % 2 == 0:
...         print(line, end='')
... 
>>> sys.stdout.writelines(open('4.txt'))
line 2
line 4

(note: Python 2 users will need to import print_function from __future__)

Back to our original code on line 11 we’re calling rstrip('\r\n')

The reason for this is that our line will contain a “line ending” which we can see here by using repr()

>>> for line in fileinput.input('macs.txt'):
...     print(repr(line))
... 
'AA:AA:AA\n'
'BB:BB:BB\n'
'CC:CC:CC\n'
'DD:DD:DD\n'

So we need to strip off any potential line ending in order to get a successful lookup from our dict.

dict.get()

Next we have macs.get(line, line) which may look odd if you have not used dict.get() before.

What we want to do to solve the problem at hand is to test if we have a “known” mac address then get it’s value else use the address itself.

Perhaps you may write the following to do so.

1 2 3 4
if line in macs: print(macs[line]) else: print(line)

Which is a perfectly fine way to do such a thing.

dict.get() has 2 features of note, the first being that it avoids any possible KeyError

>>> d = { 'foo': 1, 'bar': 2 }
>>> print(d['baz'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'baz'

It does this by instead returning None for non-existent keys.

>>> print(d.get('baz'))
None

We can also change what it returns by supplying a default return value.

>>> print(d.get('baz', 'me instead!!'))
'me instead !!'

This means that by using .get(line, line) if a lookup fails (i.e. it is not a known address) we get back the address itself.

The fileinput module also simplifies the creation of “shell-like” tools in that if you do not supply any filenames to fileinput.input() it will read from stdin or it will read from filenames passed as command-line arguments (i.e. sys.argv[1:]) similar to how commands such as cat or sed work.