python workout: exercise 19
problem
Write a function,
passwd_to_dict, that reads from a Unix-style “password file”, commonly stored as/etc/passwd, and returns a dict. The function should return a dict based on/etc/passwdin which the dict’s keys are usernames and the values are the users’ IDs (i.e the first and third fields in the comma-separated fields of an/etc/passwdentry).
attempts
This should be straightforward. We can split each line on colons and extract the first and third fields:
etc_passwd = "files/sample_passwd"
def passwd_to_dict(filename: str) -> dict[str,str]:
usernames_to_ids = {}
for line in open(etc_passwd):
fields = line.strip().split(':')
username = fields[0]
user_id = fields[2]
usernames_to_ids[username] = user_id
return usernames_to_ids
print(passwd_to_dict(etc_passwd))
{'root': '0', 'daemon': '1', 'bin': '2', 'sys': '3', 'sync': '4', 'games': '5', 'man': '6', 'lp': '7', 'mail': '8', 'news': '9', 'uucp': '10', 'proxy': '13', 'www-data': '33', 'backup': '34', 'list': '38', 'irc': '39', 'gnats': '41', 'nobody': '65534', 'systemd-coredump': '999', 'docker': '998', 'alice': '1001', 'bob': '1002', 'charlie': '1003', 'diana': '1004', 'eve': '1005', 'frank': '1006', 'grace': '1007', 'henry': '1008', 'isabel': '1009', 'james': '1010'}
solution
The book’s solution addresses the potential inconsistencies in formatting it mentioned. Specifically, the potential for empty lines and comment lines starting wtih ‘#’. (I wasn’t clear on whether our solution needed to do this too…)
It uses startswith() for these checks, in a way I wasn’t aware of: it passes a
tuple as an argument allowing the method to return True if the strings starts
with either element of the tuple:
def passwd_to_dict(filename):
users = {}
with open(filename) as passwd:
for line in passwd:
if not line.startswith(('#', '\n')):
user_info = line.split(':')
users[user_info[0]] = int(user_info[2])
return users
print(passwd_to_dict("files/sample_passwd"))
{'root': 0, 'daemon': 1, 'bin': 2, 'sys': 3, 'sync': 4, 'games': 5, 'man': 6, 'lp': 7, 'mail': 8, 'news': 9, 'uucp': 10, 'proxy': 13, 'www-data': 33, 'backup': 34, 'list': 38, 'irc': 39, 'gnats': 41, 'nobody': 65534, 'systemd-coredump': 999, 'docker': 998, 'alice': 1001, 'bob': 1002, 'charlie': 1003, 'diana': 1004, 'eve': 1005, 'frank': 1006, 'grace': 1007, 'henry': 1008, 'isabel': 1009, 'james': 1010}
It also makes sure to convert the user id to an integer, which we don’t do.
beyond the exercise
users and their login shells
-
problem
Read through
/etc/passwd, creating a dict in which user login shells (the final field on each line) are the keys. Each value will be a list of the users for whom that shell is defined as their login shell.
-
attempts
We can take the book’s solution as a starting point and just use different indices to access the fields relevant to this exercise. We can pair that with a
defaultdict(list)to directly append user names to the relevant login shell list. Something like:from collections import defaultdict filename = "files/sample_passwd" shells = defaultdict(list) with open(filename) as passwd: for line in passwd: if not line.startswith(('#', '\n')): user_info = line.strip().split(':') shells[user_info[-1]].append(user_info[0]) print(shells)defaultdict(<class 'list'>, {'/bin/bash': ['root', 'www-data', 'gnats', 'grace'], '/usr/sbin/nologin': ['daemon', 'bin', 'sys', 'nobody', 'systemd-coredump'], '/bin/sync': ['sync'], '/usr/bin/fish': ['games', 'proxy', 'docker', 'diana', 'henry'], '/usr/bin/zsh': ['man', 'frank'], '/bin/sh': ['lp', 'uucp', 'list', 'eve', 'james'], '/bin/dash': ['mail', 'charlie'], '/usr/bin/bash': ['news', 'bob'], '/bin/zsh': ['backup', 'alice', 'isabel'], '/usr/bin/dash': ['irc']})
factors of user ints
-
problem
Ask the user to enter integers, separated by spaces. From this input, create a dict whose keys are the factors for each number, and the values are lists containing those of the users’ integers that are multiples of those factors.
-
attempts
The real challenge here is user handling and finding factors. We won’t deal with input validation here. We’ll just assume we get back the expected space-separated list of integers.
We can then split them and focus on finding factors. There must be a standard library prime factorisation function. Maybe in
math?…
Turns out the
sympypackage has afactorintpackage. All that’s left is figuring out how to construct our dict.I think reusing
defaultdictis the simplest approach:from collections import defaultdict from sympy import factorint factors = defaultdict(list) user_input = "8 65 35 1323 78945" for n in user_input.split(): for factor in factorint(int(n)): factors[factor].append(int(n)) print(factors)defaultdict(<class 'list'>, {2: [8], 5: [65, 35, 78945], 13: [65], 7: [35, 1323], 3: [1323, 78945], 19: [78945], 277: [78945]})
dict of dicts
-
problem
From
/etc/passwd, create a dict in which the keys are the usernames (as in the main exercise) and the values are themselves dicts with keys (and appropriate values) for user ID, home directory, and shell.
-
attempts
This should be straightforward. We want a dict of dicts that outlines the fields in each row. Retrieving the user ID, home directory, and shell fields should just be a question of proper indexing.
filename = "files/sample_passwd" users = {} i = 0 for line in open(filename): if i == 5: # output is too long for entirety of file break if not line.startswith(('#', '\n')): fields = line.strip().split(':') username = fields[0] user_info = { "id": fields[2], "home directory": fields[-2], "shell": fields[-1] } users[username] = user_info i += 1 print(users){'root': {'id': '0', 'home directory': '/root', 'shell': '/bin/bash'}, 'daemon': {'id': '1', 'home directory': '/usr/sbin', 'shell': '/usr/sbin/nologin'}, 'bin': {'id': '2', 'home directory': '/bin', 'shell': '/usr/sbin/nologin'}, 'sys': {'id': '3', 'home directory': '/dev', 'shell': '/usr/sbin/nologin'}, 'sync': {'id': '4', 'home directory': '/bin', 'shell': '/bin/sync'}}