POSIX-kompatible Methode zum Abrufen des mit einer Benutzer-ID verknüpften Benutzernamens

23

Ich möchte oft den Anmeldenamen einer Benutzer-ID zuordnen. Da dies ein häufiger Anwendungsfall ist, habe ich mich dazu entschlossen, eine Shell-Funktion zu schreiben. Während ich hauptsächlich GNU / Linux-Distributionen verwende, versuche ich, meine Skripte so portabel wie möglich zu gestalten und zu überprüfen, ob meine Arbeit mit POSIX kompatibel ist.

Parse /etc/passwd

Der erste Ansatz, den ich versuchte, war das Parsen /etc/passwd(Verwenden awk).

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

Das Problem bei diesem Ansatz ist jedoch, dass die Anmeldungen möglicherweise nicht lokal sind, z. B. kann die Benutzerauthentifizierung über NIS oder LDAP erfolgen.

Verwenden Sie den getentBefehl

Die Verwendung getent passwdist portabler als das Parsen, /etc/passwdda hierdurch auch nicht-lokale NIS- oder LDAP-Datenbanken abgefragt werden.

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

Leider getentscheint das Dienstprogramm nicht von POSIX angegeben zu werden.

Verwenden Sie den idBefehl

id ist das POSIX-standardisierte Dienstprogramm zum Abrufen von Daten zur Identität eines Benutzers.

BSD- und GNU-Implementierungen akzeptieren eine Benutzer-ID als Operanden:

Das heißt, es kann verwendet werden, um den Anmeldenamen zu drucken, der einer Benutzer-ID zugeordnet ist:

id -nu "$uid"

Die Angabe von Benutzer-IDs als Operand ist in POSIX jedoch nicht angegeben. Es wird nur die Verwendung eines Anmeldenamens als Operand beschrieben.

Kombinieren Sie alle oben genannten

Ich überlegte, ob ich die drei oben genannten Ansätze in etwa wie folgt kombinieren sollte:

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
}

Dies ist jedoch klobig, unelegant und - was noch wichtiger ist - nicht robust; Es wird mit einem Status ungleich Null beendet, wenn die Benutzer-ID ungültig oder nicht vorhanden ist. Bevor ich ein längeres und komplexeres Shell-Skript schreibe, das den Beendigungsstatus jedes Befehlsaufrufs analysiert und speichert, dachte ich, ich würde hier fragen:

Gibt es eine elegantere und portablere (POSIX-kompatible) Möglichkeit, den Anmeldenamen einer Benutzer-ID zuzuordnen?

Anthony G - Gerechtigkeit für Monica
quelle
10
Für zusätzlichen Spaß sollten Sie berücksichtigen, dass mehrere Benutzernamen derselben ID zugeordnet werden können ...
Stephen Kitt
Das Problem bei mehreren Benutzernamen, die im Kontext dieser Frage derselben ID zugeordnet sind, ist, dass nach der ersten Übereinstimmung weder etwas zurückgegeben getentnoch zurückgegeben idwird. Die einzige Möglichkeit, sie alle zu finden, besteht darin, alle Benutzer aufzulisten, sofern die Benutzerdatenbank dies zulässt. (In /etc/passwdWerken nach dort definierten Benutzern suchen , offensichtlich.)
Stephen Kitt
1
Danke @StephenKitt Ich habe einen solchen Eintrag in meinem erstellt /etc/passwdund /etc/shadowdieses Szenario getestet und überprüft, ob beide idund getent passwddas von Ihnen beschriebene Verhalten zutreffen . Wenn ich irgendwann ein System verwende, auf dem ein Benutzer mehrere Namen hat, mache ich dasselbe wie diese Systemdienstprogramme und behandle einfach das erste Vorkommen als den kanonischen Namen für diesen Benutzer.
Anthony G - Gerechtigkeit für Monica
1
Does POSIX benötigen eine Benutzer - ID mit einem Benutzernamen zugeordnet werden überhaupt ? Jedes Programm, das als root ausgeführt wird, kann aufrufen setuid(some_id), und es gibt keine Anforderung, some_iddie Teil einer Benutzerdatenbank sein kann. Bei Benutzernamensräumen unter Linux kann dies zu einer lähmenden Annahme für Ihre Skripte werden.
Mosvy
1
@Philippos, das scheint eine teure Methode zum Aufrufen der getpwuid()Funktion lszu sein, mit der UIDs in Anmeldenamen übersetzt werden. Gilles 'Antwort ist eine direktere und effizientere Möglichkeit, dies zu erreichen.
Anthony G - Gerechtigkeit für Monica

Antworten:

14

Ein gängiger Weg, dies zu tun, besteht darin, zu testen, ob das gewünschte Programm existiert und von Ihrem verfügbar ist PATH. Beispielsweise:

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
}

Da POSIX idkeine UID-Argumente unterstützt, muss in der elifKlausel für idnicht nur geprüft werden, ob idsich der Pfad befindet, sondern auch, ob er fehlerfrei ausgeführt wird. Dies bedeutet, dass es möglicherweise idzweimal ausgeführt wird, was sich glücklicherweise nicht merklich auf die Leistung auswirkt. Es ist auch möglich, dass beide idund ausgeführt awkwerden, mit der gleichen vernachlässigbaren Leistungseinbuße.

Übrigens muss bei dieser Methode die Ausgabe nicht gespeichert werden. Nur einer von ihnen wird ausgeführt, sodass nur einer die Ausgabe druckt, damit die Funktion zurückkehrt.

cas
quelle
Um mit der Möglichkeit umzugehen, dass mehrere Benutzernamen dieselbe Benutzer-ID haben, schließen Sie alles von ifbis fiin ein { ... } | head -n 1. dh alle bis auf das erste UID-Match löschen. Dies bedeutet jedoch, dass Sie den Exit-Code des ausgeführten Programms erfassen müssen.
cas
Danke für die Antwort. Ich hatte gehofft, dass es ein anderes Hilfsprogramm geben könnte, auf das ich nicht gestoßen bin, aber das ist hilfreich. Da ich keinen Zugriff auf eine Implementierung habe id, die keine ID als Operanden akzeptiert, stellte ich fest, dass das Testen des Exit-Status problematisch sein kann - wie man den Unterschied zwischen einem nicht vorhandenen Anmeldenamen und einer UID erkennt existiert nicht. Es ist möglich, dass ein Anmeldename nur aus numerischen Zeichen besteht: gnu.org/software/coreutils/manual/html_node/…
Anthony G - Gerechtigkeit für Monica
1
Mit jedem Edit wird die Funktion robuster. :) In diesem Sinne würde ich wahrscheinlich if command -v getent >/dev/null;anstelle des if [ -x /usr/bin/getent ] ;Zufalls verwenden, dass diese Dienstprogramme einen anderen Pfad haben.
Anthony G - Gerechtigkeit für Monica
3
Ja. Ich verwende regelmäßig command -vfür diesen Zweck: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (obwohl ich es nur mit der dasheingebauten Shell getestet habe).
Anthony G - Gerechtigkeit für Monica
1
@AnthonyGeoghegan Wenn Sie jemals an alten Systemen arbeiten müssen, type foo >/dev/null 2>/dev/nullarbeiten Sie an jedem Sh, den ich jemals gesehen habe. commandist vergleichsweise modern.
Gilles 'SO - hör auf böse zu sein'
6

Es gibt nichts in POSIX, das anders als helfen würde id. Der Versuch id, das Parsen wieder aufzunehmen, /etc/passwdist wahrscheinlich so portabel wie es in der Praxis üblich ist.

BusyBox idakzeptiert keine Benutzer-IDs, aber Systeme mit BusyBox sind normalerweise autonome eingebettete Systeme, bei denen das Parsen /etc/passwdausreicht.

Falls Sie auf ein Nicht-GNU-System stoßen , auf dem idBenutzer-IDs nicht akzeptiert werden, können Sie auch versuchen, getpwuidüber Perl anzurufen, sofern dies möglich ist:

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

Oder Python:

if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi
Gilles 'SO - hör auf böse zu sein'
quelle
2
Das Parsen /etc/passwdist überhaupt nicht portierbar und funktioniert nicht für Backends, die keine Passwd-Dateien enthalten, wie z. B. LDAP.
R ..
Ich mag das, ich werde es stehlen
cas
1
@R .. Der Fragesteller ist sich dessen bewusst, diese Antwort erhebt keinen Anspruch auf etwas anderes. Wozu ist Ihr Kommentar also gut?
Gilles 'SO - hör auf böse zu sein'
Danke für diese Antwort. Ich bin mir sicher, dass es keinen anderen Nutzen gibt, den ich nicht kenne. Es sieht so aus, als ob POSIX eine Standard-C-Funktion zum Übersetzen der UID in einen Anmeldenamen spezifiziert, aber nicht unbedingt einen entsprechenden Befehl (außer id).
Anthony G - Gerechtigkeit für Monica
2
Überprüfen Sie als letzten Fallback, ob sich ein AC-Compiler auf dem System befindet, und kompilieren Sie dann einen bereitgestellten getpwuid () - Wrapper ...
rackandboneman
5

POSIX gibt getpwuidals Standard-C-Funktion an, dass die Benutzerdatenbank nach einer Benutzer-ID durchsucht werden soll, damit die ID in einen Anmeldenamen übersetzt werden kann. Ich habe den Quellcode für GNU coreutils heruntergeladen und kann sehen, dass diese Funktion bei der Implementierung von Dienstprogrammen wie idund verwendet wird ls.

Als Lernübung habe ich dieses einfache C-Programm geschrieben, um einfach als Wrapper für diese Funktion zu fungieren. Denken Sie daran, dass ich seit dem College (vor vielen Jahren) nicht mehr in C programmiert habe und dies nicht in der Produktion verwenden möchte, aber ich dachte, ich würde es hier als Proof-of-Concept posten (wenn jemand es bearbeiten möchte) , fühlen Sie sich frei):

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

Ich hatte keine Möglichkeit, es mit NIS / LDAP zu testen, aber ich bemerkte, dass, wenn es mehrere Einträge für denselben Benutzer gibt /etc/passwd, alle außer dem ersten ignoriert werden.

Anwendungsbeispiel:

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

$ ./get_user 99
Invalid user ID: 99
Anthony G - Gerechtigkeit für Monica
quelle
3

Generell würde ich davon abraten. Die Zuordnung von Benutzernamen zu Benutzer-IDs ist keine Eins-zu-Eins-Zuordnung, und die Kodierung von Annahmen, die Sie von einer Benutzer-ID zurückkonvertieren können, um einen Benutzernamen zu erhalten , kann zu Problemen führen. Zum Beispiel führe ich häufig vollständig root-freie Benutzer-Namespace-Container aus, indem ich die passwdund group-Dateien im Container dazu bringe, alle Benutzer- und Gruppennamen der ID 0 zuzuordnen. Auf diese Weise können Pakete problemlos installiert werden chown. Aber wenn etwas versucht, 0 zurück in eine UID umzuwandeln und nicht das bekommt, was es erwartet, wird es unbeabsichtigt kaputt gehen. In diesem Beispiel sollten Sie also, anstatt zurück zu konvertieren und Benutzernamen zu vergleichen, in UIDs konvertieren und in diesem Bereich vergleichen.

Wenn Sie diesen Vorgang wirklich ausführen müssen, ist es unter Umständen möglich, einen semi- chownportablen Vorgang als Root auszuführen, indem Sie eine temporäre Datei erstellen, diese an die UID senden und dann lsden Namen des Besitzers mit lesen und analysieren. Aber ich würde nur einen bekannten Ansatz verwenden, der nicht standardisiert, sondern "in der Praxis portabel" ist, wie einer, den Sie bereits gefunden haben.

Aber tu das nicht. Manchmal ist es schwierig, Ihnen eine Nachricht zu senden.

R ..
quelle
1
Kommentare gelöscht Ich möchte beide Teilnehmer daran erinnern, dass Kommentare höflich sein müssen.
terdon