web-dev-qa-db-fra.com

Moyen compatible POSIX pour obtenir le nom d'utilisateur associé à un ID utilisateur

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.

Analyser /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.

Utilisez la commande 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.

Utilisez la commande 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.

Combiner tout ce qui précède

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?

26
Anthony Geoghegan

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.

14
cas

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
6
Anthony Geoghegan

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, chownen 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.