Często chcę uzyskać nazwę logowania powiązaną z identyfikatorem użytkownika, a ponieważ okazało się, że jest to częsty przypadek użycia, postanowiłem napisać funkcję powłoki, aby to zrobić. Chociaż głównie używam dystrybucji GNU / Linux, staram się pisać skrypty, aby były jak najbardziej przenośne i sprawdzać, czy to, co robię, jest zgodne z POSIX.
Analizować /etc/passwd
Pierwsze podejście, które próbowałem, to parsowanie /etc/passwd
(używanie awk
).
awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
Jednak problem z tym podejściem polega na tym, że logowanie może nie być lokalne, np. Uwierzytelnianie użytkownika może odbywać się przez NIS lub LDAP.
Użyj getent
polecenia
Użycie getent passwd
jest bardziej przenośne niż parsowanie, /etc/passwd
ponieważ wysyła również zapytania do nielokalnych baz danych NIS lub LDAP.
getent passwd "$uid" | cut -d: -f1
Niestety getent
narzędzie nie wydaje się być określane przez POSIX.
Użyj id
polecenia
id
jest standardowym narzędziem POSIX do uzyskiwania danych o tożsamości użytkownika.
Implementacje BSD i GNU akceptują identyfikator użytkownika jako operand:
- id (1) - strony podręcznika OpenBSD
- id (1) - strona podręcznika FreeBSD
- GNU Coreutils: wywołanie identyfikatora
Oznacza to, że można go użyć do wydrukowania nazwy logowania powiązanej z identyfikatorem użytkownika:
id -nu "$uid"
Jednak podanie ID użytkownika jako argumentu operacji nie jest określone w POSIX; opisuje tylko użycie nazwy logowania jako operandu.
Łącząc wszystkie powyższe
Rozważałem połączenie powyższych trzech podejść w coś takiego:
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
}
Jest to jednak niezgrabne, nieeleganckie i - co ważniejsze - nietrwałe; kończy działanie ze statusem niezerowym, jeśli identyfikator użytkownika jest nieprawidłowy lub nie istnieje. Zanim napisałem dłuższy i bardziej niezgrabny skrypt powłoki, który analizuje i przechowuje status wyjścia każdego wywołania polecenia, pomyślałem, że zapytam tutaj:
Czy istnieje bardziej elegancki i przenośny (zgodny z POSIX) sposób uzyskiwania nazwy logowania powiązanej z identyfikatorem użytkownika?
getent
nieid
zwróci niczego po pierwszym dopasowaniu; jedynym sposobem na znalezienie ich wszystkich jest wyliczenie wszystkich użytkowników, jeśli baza danych użytkowników na to pozwala. (/etc/passwd
Oczywiście poszukuje prac dla zdefiniowanych tam użytkowników)./etc/passwd
i/etc/shadow
do przetestowania tego scenariusza i zweryfikowałem, że obaid
igetent passwd
zachowują się jak opisano. Jeśli w pewnym momencie skończę z systemem, w którym użytkownik ma wiele nazw, zrobię to samo co te narzędzia systemowe i po prostu traktuję pierwsze wystąpienie jako kanoniczną nazwę tego użytkownika.setuid(some_id)
i nie ma wymagań, któresome_id
mogą być częścią bazy danych użytkowników. Przy takich rzeczach, jak przestrzenie nazw użytkowników w systemie Linux, może to okazać się paraliżującym założeniem dla twoich skryptów.getpwuid()
funkcji, którals
używa do tłumaczenia identyfikatorów UID na nazwy logowania. Odpowiedź Gillesa jest bardziej bezpośrednim i skutecznym sposobem na osiągnięcie tego.Odpowiedzi:
Jednym z powszechnych sposobów jest sprawdzenie, czy żądany program istnieje i jest dostępny z twojego
PATH
. Na przykład:Ponieważ POSIX
id
nie obsługuje argumentów UID,elif
klauzula dlaid
musi sprawdzać nie tylko, czyid
jest w PATH, ale także czy będzie działać bezbłędnie. Oznacza to, że może działaćid
dwa razy, co na szczęście nie będzie miało zauważalnego wpływu na wydajność. Jest również możliwe, że obaid
iawk
będzie działać z taką samą znikomym przeboju wydajności.BTW, dzięki tej metodzie nie trzeba przechowywać danych wyjściowych. Uruchomiony zostanie tylko jeden z nich, więc tylko jeden wydrukuje wyjście funkcji.
źródło
if
sięfi
w{ ... } | head -n 1
. tzn. upuść wszystko oprócz pierwszego dopasowania UID. ale to będzie oznaczało, że będziesz musiał przechwycić kod wyjścia dowolnego uruchomionego programu.id
, która nie akceptuje identyfikatora jako argumentu, pomyślałem, że testowanie jego statusu wyjścia może być problematyczne - jak odróżnić nieistniejącą nazwę logowania od identyfikatora UID, który nie istnieje Nazwa logowania może składać się wyłącznie ze znaków numerycznych: gnu.org/software/coreutils/manual/html_node/…if command -v getent >/dev/null;
zamiastif [ -x /usr/bin/getent ] ;
przypadkowego, że te narzędzia mają inną ścieżkę.command -v
do tego celu: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (chociaż testowałem go tylko zdash
wbudowaną powłoką).type foo >/dev/null 2>/dev/null
działa na każdym sh, który kiedykolwiek widziałem.command
jest stosunkowo nowoczesny.W POSIX nie ma nic, co by pomogło poza
id
. Próbowanieid
parsowania i powrót do niego/etc/passwd
jest prawdopodobnie tak przenośne, jak w praktyce.BusyBox
id
nie akceptuje identyfikatorów użytkowników, ale systemy z BusyBox są zwykle autonomicznymi systemami osadzonymi, w których parsowanie/etc/passwd
jest wystarczające.W przypadku napotkania systemu innego niż GNU, w którym
id
nie akceptuje się identyfikatorów użytkowników, możesz także spróbować zadzwonićgetpwuid
przez Perla, jeśli istnieje taka możliwość:Lub Python:
źródło
/etc/passwd
nie jest w ogóle przenośna i nie będzie działać w przypadku backendów plików innych niż passwd, takich jak LDAP.id
).POSIX określa
getpwuid
jako standardową funkcję C wyszukiwanie bazy danych użytkownika w celu znalezienia identyfikatora użytkownika, umożliwiając jego przetłumaczenie na nazwę logowania. Pobrałem kod źródłowy dla GNU coreutils i widzę, że ta funkcja jest używana w ich implementacji narzędzi takich jakid
ils
.Jako ćwiczenie do nauki napisałem ten szybki i brudny program C, który po prostu działa jako opakowanie dla tej funkcji. Pamiętaj, że nie programowałem w C od college'u (wiele lat temu) i nie zamierzam używać tego w produkcji, ale pomyślałem, że opublikuję go tutaj jako dowód koncepcji (jeśli ktoś chce go edytować , nie krępuj się):
Nie miałem okazji przetestować go za pomocą NIS / LDAP, ale zauważyłem, że jeśli istnieje wiele wpisów dla tego samego użytkownika
/etc/passwd
, ignorowane są wszystkie oprócz pierwszego.Przykładowe użycie:
źródło
Ogólnie odradzam to robić. Odwzorowanie nazw użytkowników na identyfikatory użytkownika nie jest jeden do jednego, a założenia kodowania, które można przekonwertować z identyfikatora użytkownika, aby uzyskać nazwę użytkownika, doprowadzą do załamania. Na przykład często uruchamiam całkowicie wolne od rootów kontenery przestrzeni nazw użytkowników, dzięki czemu pliki
passwd
igroup
w kontenerze mapują wszystkie nazwy użytkowników i grup na id 0; pozwala to na instalowanie pakietów do pracy bezchown
awarii. Ale jeśli coś spróbuje przekonwertować 0 z powrotem na UID i nie dostanie tego, czego się spodziewa, nieuzasadnione. Dlatego w tym przykładzie zamiast konwertować i porównywać nazwy użytkowników, powinieneś przekonwertować na UID i porównać w tym miejscu.Jeśli naprawdę potrzebujesz wykonać tę operację, może być możliwe wykonanie częściowo przenośne, jeśli jesteś rootem, tworząc plik tymczasowy,
chown
zapisując go w UID, a następnie używającls
do odczytu i analizy nazwy właściciela. Ale po prostu zastosowałbym dobrze znane podejście, które nie jest ustandaryzowane, ale „przenośne w praktyce”, jak jedno z tych, które już znalazłeś.Ale znowu, nie rób tego. Czasami trudno jest wysłać wiadomość.
źródło