Modo compatibile POSIX per ottenere il nome utente associato a un ID utente


23

Spesso desidero ottenere il nome di accesso associato a un ID utente e poiché è dimostrato che è un caso d'uso comune, ho deciso di scrivere una funzione di shell per farlo. Mentre utilizzo principalmente le distribuzioni GNU / Linux, provo a scrivere i miei script per essere il più portatile possibile e per verificare che ciò che sto facendo sia compatibile con POSIX.

Parse /etc/passwd

Il primo approccio che ho provato è stato di analizzare /etc/passwd(usando awk).

awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd

Tuttavia, il problema con questo approccio è che gli accessi potrebbero non essere locali, ad esempio l'autenticazione dell'utente potrebbe avvenire tramite NIS o LDAP.

Usa il getentcomando

L'utilizzo getent passwdè più portatile dell'analisi in /etc/passwdquanto esegue anche query su database NIS o LDAP non locali.

getent passwd "$uid" | cut -d: -f1

Sfortunatamente, l' getentutilità non sembra essere specificata da POSIX.

Usa il idcomando

id è l'utility standardizzata POSIX per ottenere dati sull'identità di un utente.

Le implementazioni di BSD e GNU accettano un ID utente come operando:

Ciò significa che può essere utilizzato per stampare il nome di accesso associato a un ID utente:

id -nu "$uid"

Tuttavia, fornire ID utente come operando non è specificato in POSIX; descrive solo l'uso di un nome di accesso come operando.

Combinando tutto quanto sopra

Ho considerato di combinare i tre approcci sopra in qualcosa di simile al seguente:

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
}

Tuttavia, questo è goffo, inelegante e - soprattutto - non robusto; esce con uno stato diverso da zero se l'ID utente non è valido o non esiste. Prima di scrivere uno script di shell più lungo e clunkier che analizza e memorizza lo stato di uscita di ogni invocazione di comando, ho pensato di chiedere qui:

Esiste un modo più elegante e portatile (compatibile POSIX) per ottenere il nome di accesso associato a un ID utente?


10
Per ulteriore divertimento, considera che più nomi utente possono essere associati allo stesso ID ...
Stephen Kitt,

Il problema con più nomi utente associati allo stesso ID nel contesto di questa domanda è che né getentidrestituiranno nulla oltre la prima corrispondenza; l'unico modo per trovarli tutti è enumerare tutti gli utenti, se il database degli utenti lo consente. (Cercare /etc/passwdopere per gli utenti definiti lì, ovviamente.)
Stephen Kitt,

1
Grazie @StephenKitt ho creato una voce simile nel mio /etc/passwde /etc/shadowper testare questo scenario e verificato che entrambi ide getent passwdcomportarsi come descritto. Se, a un certo punto, finissi per usare un sistema in cui un utente ha più nomi, farò lo stesso di questi programmi di utilità del sistema e tratterò semplicemente la prima occorrenza come il nome canonico per quell'utente.
Anthony G - giustizia per Monica,

1
Non POSIX richiede un ID utente deve essere associato a un nome utente a tutti ? Qualsiasi programma in esecuzione come root può chiamare setuid(some_id)e non è necessario che some_idfaccia parte di alcun database utente. Con cose come gli spazi dei nomi degli utenti su Linux, questo può diventare un presupposto paralizzante per i tuoi script.
mosvy,

1
@Philippos che sembra un modo costoso di chiamare la getpwuid()funzione che lsutilizza per tradurre gli UID in nomi di login. La risposta di Gilles è un modo più diretto ed efficiente per raggiungere questo obiettivo.
Anthony G - giustizia per Monica,

Risposte:


14

Un modo comune per farlo è verificare se il programma che desideri esiste ed è disponibile dal tuo PATH. Per esempio:

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
}

Poiché POSIX idnon supporta gli argomenti UID, la elifclausola iddeve verificare non solo se si idtrova nel PERCORSO, ma anche se verrà eseguito senza errori. Ciò significa che potrebbe funzionare iddue volte, che fortunatamente non avrà un impatto evidente sulle prestazioni. E 'anche possibile che sia ide awkverrà eseguito, con lo stesso calo di prestazioni trascurabile.

A proposito, con questo metodo, non è necessario memorizzare l'output. Verrà eseguito solo uno di essi, quindi solo uno stamperà l'output per restituire la funzione.


per far fronte alla possibilità che più nomi utente abbiano lo stesso uid, avvolgi tutto da ifa fiin { ... } | head -n 1. cioè rilasciare tutto tranne la prima partita uid. ma ciò significa che dovrai acquisire il codice di uscita di qualunque programma sia stato eseguito.
cas

Grazie per la risposta. Speravo che potesse esserci qualche altra utility che non avevo incontrato ma questo è utile. Poiché non ho accesso a un'implementazione idche non accetta un ID come operando, ho pensato che testare il suo stato di uscita potesse essere problematico: come dire la differenza tra un nome di accesso che non esiste o un UID che non esiste. È possibile che un nome di accesso sia composto solo da caratteri numerici: gnu.org/software/coreutils/manual/html_node/…
Anthony G - Justice for Monica

1
Con ogni modifica, la funzione sta diventando più solida. :) Su quella nota, probabilmente userei if command -v getent >/dev/null;invece if [ -x /usr/bin/getent ] ;per caso che queste utility abbiano un percorso diverso.
Anthony G - giustizia per Monica,

3
Sì. Uso regolarmente command -va questo scopo: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (anche se l'ho mai provato con la dashshell integrata).
Anthony G - giustizia per Monica,

1
@AnthonyGeoghegan Se devi mai lavorare su sistemi antichi, type foo >/dev/null 2>/dev/nullfunziona su ogni sh che io abbia mai visto. commandè relativamente moderno.
Gilles 'SO- smetti di essere malvagio' il

6

In POSIX non c'è nulla che possa aiutare a parte id. Provare ide ricadere nell'analisi /etc/passwdè probabilmente portatile come nella pratica.

BusyBox idnon accetta gli ID utente, ma i sistemi con BusyBox sono in genere sistemi integrati autonomi in cui l'analisi /etc/passwdè sufficiente.

Nel caso in cui incontri un sistema non GNU in cui idnon accetta gli ID utente, puoi anche provare a chiamare getpwuidtramite Perl, nella possibilità che sia disponibile:

username=$(perl -e 'print((getpwuid($ARGV[0]))[0])) 2>/dev/null
if [ -n "$username" ]; then echo "$username"; return; fi

O Python:

if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi

2
L'analisi /etc/passwdnon è affatto portatile e non funzionerà per i backend di file non passwd come LDAP.
R ..

Mi piace, lo ruberò
Cas

1
@R .. Il richiedente è consapevole di ciò, questa risposta non pretende diversamente, quindi qual è il punto del tuo commento?
Gilles 'SO- smetti di essere malvagio' il

Grazie per questa risposta Sono rassicurato che non ci sono altre utilità di cui non ero a conoscenza. Sembra che POSIX specifichi una funzione C standard per tradurre l'UID in un nome di accesso ma non necessariamente un comando corrispondente (diverso da id).
Anthony G - giustizia per Monica,

2
Come ultimo fallback, controlla se c'è un compilatore ac sul sistema, quindi compila un wrapper getpwuid () fornito ...
rackandboneman

5

POSIX specifica getpwuidcome funzione C standard per cercare nel database utenti un ID utente che consenta di tradurre l'ID in un nome di accesso. Ho scaricato il codice sorgente per i coreutils GNU e posso vedere questa funzione utilizzata nella loro implementazione di utility come ide ls.

Come esercizio di apprendimento, ho scritto questo programma C veloce e sporco per agire semplicemente come un wrapper per questa funzione. Ricorda che non ho programmato in C dal college (molti anni fa) e non ho intenzione di usarlo in produzione, ma ho pensato di pubblicarlo qui come prova di concetto (se qualcuno volesse modificarlo , sentiti libero):

#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;
    }
}

Non ho avuto la possibilità di testarlo con NIS / LDAP ma ho notato che se ci sono più voci per lo stesso utente /etc/passwd, ignora tutto tranne il primo.

Esempio di utilizzo:

$ ./get_user ""
The login name for 0 is: root

$ ./get_user 99
Invalid user ID: 99

3

In generale, consiglierei di non farlo. Il mapping da nomi utente a uid non è uno a uno e la codifica dei presupposti che è possibile riconvertire da un uid per ottenere un nome utente sta andando a rompere le cose. Ad esempio, eseguo spesso contenitori dello spazio dei nomi utente completamente senza root facendo in modo che i file passwde groupnel contenitore mappino tutti i nomi utente e gruppo su id 0; questo consente l'installazione dei pacchetti per funzionare senza chownerrori. Ma se qualcosa cerca di riconvertire 0 in un uid e non ottiene ciò che si aspetta, si romperà gratuitamente. Quindi, in questo esempio, invece di riconvertire e confrontare i nomi utente, è necessario convertire in uid e confrontare in quello spazio.

Se hai davvero bisogno di fare questa operazione, potrebbe essere possibile farlo in modo semi-portabile se sei root, creando un file temporaneo, inserendolo nell'UID chown, quindi usando lsper rileggere e analizzare il nome del proprietario. Ma userei solo un approccio ben noto che non è standardizzato ma "portatile in pratica", come uno di quelli che hai già trovato.

Ma ancora una volta, non farlo. A volte qualcosa di difficile da fare è inviarti un messaggio.


1
Commenti eliminati. Vorrei ricordare ad entrambi i partecipanti che i commenti devono essere civili.
Terdon
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.