A friend recently told me of a small problem he was asked to resolve:
«Find all duplicate UIDs in your /etc/passwd file. Print them along with the associated usernames. You may use shell, perl or python».
My first, almost instant, response was the following
awk -F: '{print $1,$3}' /etc/passwd | \
sort | uniq -c | \
awk '{if ($1 > 1) {print $3, $2}}'
But using the passwd sample below, I found a bug.
root:x:0:0:root:/root:/bin/bash root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh unbound:x:1001:1001:Un Bound,,,:/home/unbound:/bin/bash nsd:x:1002:1002:,,,:/home/nsd:/bin/bash nsd:x:1002:1002:,,,:/home/nsd:/bin/bash nsd:x:1002:1002:,,,:/home/nsd:/bin/bash insd:x:1002:1002:,,,:/home/nsd:/bin/bash
It misses the «insd» username, because it only prints out lines that contain the same uid *and* username.
So my second attempt was using perl
#!/usr/bin/perl
open(F, "</etc/passwd");
while (<F>) {
@line = split(/:/, $_);
$accounts{$line[2]} .= "$line[0] "; # build a hash, with uid as the key
}
close(F);
for $uid ( keys %accounts ) {
@usernames = split(/ /, $accounts{$uid});
if ($#usernames > 0) {
print "$uid @usernames\n";
}
}
Now that’s more like it :-)
cut -d: -f3 /etc/passwd | sort | uniq -d
:^)
You don’t get all the points, your solution only prints the duplicate uid, not the usernames :-p
You may try again :-)
Ah, I didn’t see that bit. Fine then. Here’s the relevant addition:
cut -d: -f3 /etc/passwd | sort | uniq -d | sed ‘s/.*/:x:&:/’ | egrep -f – /etc/passwd | cut -d: -f1,3
And if you want to avoid duplicates in the final output (like multiple instances of nsd, in your example):
cut -d: -f3 /etc/passwd | sort | uniq -d | sed ‘s/.*/:x:&:/’ | egrep -f – /etc/passwd | cut -d: -f1,3 | sort -t: -k2 -n | uniq
At this point I’d probably prefer perl too, but this is more fun. ;^)
Yeah! I went for readability too..
Here is a perl one-liner that also does the same thing (found in Linux Gazette #85) !
A solution in Python:
accounts = {} with open('asfd.txt') as f1: for line in f1: (username, dont_care, uid, *dont_care2) = line.split(':') if uid in accounts: accounts[uid].append(username) else: accounts[uid] = list() accounts[uid].append(username) duplicateUIDs = {} for uid in accounts: if len(accounts[uid]) > 1: duplicateUIDs[uid] = set(accounts[uid]), len(accounts[uid]) print(duplicateUIDs)A similar python version was what I came up with too:
#!/usr/bin/env python accounts = {} for u in file('/etc/passwd').readlines(): (name, _, uid) = u.split(':')[0:3] nlist = accounts.get(uid, []) nlist.append(name) accounts[uid] = nlist for uid in [u for u in accounts if len(accounts[u]) > 1]: print uid, " ".join(accounts[uid])This one prints on my laptop: