Je souhaite souvent obtenir le nom de connexion associé à un ID utilisateur et comme il s'est avéré qu'il s'agit d'un cas d'utilisation courant, j'ai décidé d'écrire une fonction Shell pour ce faire. Bien que j'utilise principalement des distributions GNU/Linux, j'essaie d'écrire mes scripts pour être aussi portables que possible et de vérifier que ce que je fais est compatible POSIX.
/etc/passwd
La première approche que j'ai essayée était d'analyser /etc/passwd
(en utilisant awk
).
awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
Cependant, le problème avec cette approche est que les connexions peuvent ne pas être locales, par exemple, l'authentification des utilisateurs pourrait être via NIS ou LDAP.
getent
En utilisant getent passwd
est plus portable que l'analyse /etc/passwd
car cela interroge également les bases de données NIS ou LDAP non locales.
getent passwd "$uid" | cut -d: -f1
Malheureusement, l'utilitaire getent
ne semble pas être spécifié par POSIX.
id
id
est l'utilitaire normalisé POSIX pour obtenir des données sur l'identité d'un utilisateur.
Les implémentations BSD et GNU acceptent un ID utilisateur comme opérande:
Cela signifie qu'il peut être utilisé pour imprimer le nom de connexion associé à un ID utilisateur:
id -nu "$uid"
Cependant, fournir des ID utilisateur en tant qu'opérande n'est pas spécifié dans POSIX; il décrit uniquement l'utilisation d'un nom de connexion comme opérande.
J'ai envisagé de combiner les trois approches ci-dessus en quelque chose comme ce qui suit:
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
}
Cependant, c'est maladroit, inélégant et - plus important encore - pas robuste; il se termine avec un état différent de zéro si l'ID utilisateur n'est pas valide ou n'existe pas. Avant d'écrire un script Shell plus long et plus lourd qui analyse et stocke l'état de sortie de chaque appel de commande, je pensais demander ici:
Existe-t-il un moyen plus élégant et portable (compatible POSIX) d'obtenir le nom de connexion associé à un ID utilisateur?
Une façon courante de le faire est de tester si le programme souhaité existe et est disponible depuis votre PATH
. Par exemple:
get_username(){
uid="$1"
# First try using getent
if command -v getent > /dev/null 2>&1; then
getent passwd "$uid" | cut -d: -f1
# Next try using the UID as an operand to id.
Elif command -v id > /dev/null 2>&1 && \
id -nu "$uid" > /dev/null 2>&1; then
id -nu "$uid"
# Next try Perl - Perl's getpwuid just calls the system's C library getpwuid
Elif command -v Perl >/dev/null 2>&1; then
Perl -e '@u=getpwuid($ARGV[0]);
if ($u[0]) {print $u[0]} else {exit 2}' "$uid"
# As a last resort, parse `/etc/passwd`.
else
awk -v uid="$uid" -F: '
BEGIN {ec=2};
$3 == uid {print $1; ec=0; exit 0};
END {exit ec}' /etc/passwd
fi
}
Comme POSIX id
ne prend pas en charge les arguments UID, la clause Elif
pour id
doit tester non seulement si id
est dans le CHEMIN, mais également si il fonctionnera sans erreur. Cela signifie qu'il peut exécuter id
deux fois, ce qui, heureusement, n'aura pas d'impact notable sur les performances. Il est également possible que id
et awk
soient exécutés, avec les mêmes performances négligeables.
BTW, avec cette méthode, il n'est pas nécessaire de stocker la sortie. Un seul d'entre eux sera exécuté, donc un seul imprimera la sortie pour la fonction à retourner.
Il n'y a rien dans POSIX qui pourrait aider autre que id
. Essayer id
et revenir à l'analyse /etc/passwd
est probablement aussi portable que possible dans la pratique.
id
de BusyBox n'accepte pas les ID utilisateur, mais les systèmes avec BusyBox sont généralement des systèmes intégrés autonomes où l'analyse /etc/passwd
est assez.
Si vous rencontrez un système non GNU où id
n'accepte pas les ID utilisateur, vous pouvez également essayer d'appeler getpwuid
via Perl, au cas où il serait disponible :
username=$(Perl -e 'print((getpwuid($ARGV[0]))[0])) 2>/dev/null
if [ -n "$username" ]; then echo "$username"; return; fi
Ou Python:
if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi
POSIX spécifie getpwuid
comme fonction C standard pour rechercher dans la base de données utilisateur un ID utilisateur permettant de traduire l'ID en nom de connexion. J'ai téléchargé le code source pour GNU coreutils et je peux voir cette fonction utilisée dans leur implémentation d'utilitaires tels que id
et ls
.
Comme exercice d'apprentissage, j'ai écrit ce programme C rapide et sale pour simplement agir comme un wrapper pour cette fonction. Gardez à l'esprit que je n'ai pas programmé en C depuis l'université (il y a de nombreuses années) et je n'ai pas l'intention de l'utiliser en production mais j'ai pensé que je le posterais ici comme preuve de concept (si quelqu'un veut le modifier) , N'hésitez pas):
#include <stdio.h>
#include <stdlib.h> /* atoi */
#include <pwd.h>
int main( int argc, char *argv[] ) {
uid_t uid;
if ( argc >= 2 ) {
/* NB: atoi returns 0 (super-user ID) if argument is not a number) */
uid = atoi(argv[1]);
}
/* Ignore any other arguments after the first one. */
else {
fprintf(stderr, "One numeric argument must be supplied.\n");
return 1;
}
struct passwd *pwd;
pwd = getpwuid(uid);
if (pwd) {
printf("The login name for %d is: %s\n", uid, pwd->pw_name);
return 0;
}
else {
fprintf(stderr, "Invalid user ID: %d\n", uid);
return 1;
}
}
Je n'ai pas eu la chance de le tester avec NIS/LDAP mais j'ai remarqué que s'il y a plusieurs entrées pour le même utilisateur dans /etc/passwd
, il ignore tout sauf le premier.
Exemple d'utilisation:
$ ./get_user ""
The login name for 0 is: root
$ ./get_user 99
Invalid user ID: 99
En général, je déconseille de faire cela. Le mappage des noms d'utilisateurs aux uids n'est pas un à un, et les hypothèses de codage que vous pouvez convertir retour de un uid pour obtenir un nom d'utilisateur vont casser les choses. Par exemple, j'exécute souvent des conteneurs d'espace de noms d'utilisateurs totalement exempts de racine en créant les fichiers passwd
et group
dans le conteneur mappant tous les noms d'utilisateurs et de groupes vers l'id 0; cela permet d'installer les packages sans chown
échouer. Mais si quelque chose essaie de reconvertir 0 en uid et n'obtient pas ce qu'il attend, il se cassera gratuitement. Donc, dans cet exemple, au lieu de reconvertir et de comparer les noms d'utilisateur, vous devez convertir en uids et comparer dans cet espace.
Si vous avez vraiment besoin de faire cette opération, il peut être possible de le faire de manière semi-portative si vous êtes root, en créant un fichier temporaire, chown
en le mettant à l'uid, puis en utilisant ls
pour relire et analyser le nom du propriétaire. Mais j'utiliserais simplement une approche bien connue qui n'est pas standardisée mais "portable dans la pratique", comme l'une de celles que vous avez déjà trouvées.
Mais encore une fois, ne faites pas ça. Parfois, il est difficile de vous envoyer un message.