I often want to get the login name associated with a user ID and because it’s proven to be a common use case, I decided to write a shell function to do this. While I primarily use GNU/Linux distributions, I try to write my scripts to be as portable as possible and to check that what I’m doing is POSIX-compatible.
Parse /etc/passwd
The first approach I tried was to parse /etc/passwd (using awk).
awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
However, the problem with this approach is that the logins may not be local, e.g., user authentication could be via NIS or LDAP.
Use the getent command
Using getent passwd is more portable than parsing /etc/passwd as this also queries non-local NIS or LDAP databases.
getent passwd "$uid" | cut -d: -f1
Unfortunately, the getent utility does not seem to be specified by POSIX.
Use the id command
id is the POSIX-standardised utility for getting data about a user’s identity.
BSD and GNU implementations accept a User ID as an operand:
This means it can be used to print the login name associated with a User ID:
id -nu "$uid"
However, providing User IDs as the operand is not specified in POSIX; it only describes the use of a login name as the operand.
Combining all of the above
I considered combining the above three approaches into something like the following:
get_username(){
uid="$1"
# First try using getent
getent passwd "$uid" | cut -d: -f1 ||
# Next try using the UID as an operand to id.
id -nu "$uid" ||
# As a last resort, parse `/etc/passwd`.
awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
}
However, this is clunky, inelegant and – more importantly – not robust; it exits with a non-zero status if the User ID is invalid or does not exist. Before I write a longer and clunkier shell script that analyses and stores the exit status of each command invocation, I thought I’d ask here:
Is there a more elegant and portable (POSIX-compatible) way of getting the login name associated with a user ID?
getentnoridwill return anything past the first match; the only way to find them all is to enumerate all users, if the user database allows that. (Looking in/etc/passwdworks for users defined there, obviously.) – Stephen Kitt Sep 12 '19 at 15:39/etc/passwdand/etc/shadowto test this scenario and verified that bothidandgetent passwdbehave as you describe. If, at some stage, I end up using a system where a user has multiple names, I'll do the same as these system utilities and simply treat the first occurrence as the canonical name for that user. – Anthony Geoghegan Sep 12 '19 at 17:10setuid(some_id), and there's no requirement thatsome_idmay be part of any user database. With such things as user namespaces on Linux this may turn to be a crippling assumption for your scripts. – Sep 13 '19 at 05:04find / -user 42 2>/dev/null -exec ls -do {} \; |cut -d" " -f 3|head -n 1– Philippos Sep 13 '19 at 07:07getpwuid()function whichlsuses to translate UIDs to login names. Gilles’ answer is a more direct and efficient way of accomplishing this. – Anthony Geoghegan Sep 13 '19 at 12:07