Find duplicate UIDs in passwd

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 :-)

6 Replies to “Find duplicate UIDs in passwd”

  1. 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. ;^)

  2. 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)
    
    1. 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:

      $ python foo.py
      0 root toor
      $

Αφήστε απάντηση στον/στην Sotiris Tsimbonis Ακύρωση απάντησης