POSIX-сумісний спосіб отримати ім’я користувача, пов'язане з ідентифікатором користувача


23

Я часто хочу отримати ім’я для входу, пов’язане з ідентифікатором користувача, і тому що це, як виявилося, є звичайним випадком використання, я вирішив написати функцію оболонки для цього. Хоча я в основному використовую дистрибутиви GNU / Linux, я намагаюся писати свої сценарії, щоб вони були максимально портативними і перевіряли, що те, що я роблю, сумісне з POSIX.

Розбираємо /etc/passwd

Перший підхід, який я спробував, був розбір /etc/passwd(використання awk).

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

Однак проблема такого підходу полягає в тому, що входи можуть бути не локальними, наприклад, автентифікація користувача може здійснюватися через NIS або LDAP.

Використовуйте getentкоманду

Використання getent passwdє більш портативним, ніж аналіз, /etc/passwdоскільки це також запитує не локальні бази даних NIS або LDAP.

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

На жаль, getentутиліта, схоже, не визначена POSIX.

Використовуйте idкоманду

id - це стандартизована POSIX утиліта для отримання даних про особу користувача.

Реалізації BSD і GNU приймають ідентифікатор користувача як операнд:

Це означає, що його можна використовувати для друку імені для входу, пов’язаного з ідентифікатором користувача:

id -nu "$uid"

Однак надання ідентифікаторів користувача як операнду не визначено в POSIX; він лише описує використання імені входу як операнда.

Поєднуючи все вищезазначене

Я розглядав можливість поєднання вищезазначених трьох підходів у щось подібне:

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
}

Однак це незграбно, неелегантно і - що ще важливіше - не надійно; він закінчується з ненульовим статусом, якщо ідентифікатор користувача недійсний або не існує. Перш ніж написати більш довгий і незграбний сценарій оболонки, який аналізує та зберігає статус виходу кожного виклику команди, я подумав, що запитаю тут:

Чи існує більш елегантний і портативний (сумісний з POSIX) спосіб отримання імені для входу, пов’язаного з ідентифікатором користувача?


10
Для додаткової забави врахуйте, що кілька імен користувачів можуть відображатись на один і той же ідентифікатор ...
Стівен Кітт,

Проблема з декількома іменами користувачів, зіставленими на один і той же ідентифікатор у контексті цього питання, полягає в тому, що ані getentне idповерне нічого, ні минуле першого матчу; Єдиний спосіб їх знайти - це перерахувати всіх користувачів, якщо це дозволяє база даних користувачів. ( /etc/passwdОчевидно, шукаючи роботи для визначених там користувачів.)
Стівен Кітт,

1
Дякую @StephenKitt, я створив такий запис у своєму /etc/passwdі /etc/shadowперевірив цей сценарій і перевірив, що обидва idі так getent passwdповодяться, як ви описуєте. Якщо на якомусь етапі я в кінцевому підсумку використовую систему, де користувач має декілька імен, я зроблю те саме, що ці системні утиліти і просто трактую перше виникнення як канонічне ім'я для цього користувача.
Ентоні Г - справедливість для Моніки

1
Чи потрібен POSIX взагалі асоціювати ідентифікатор користувача з ім'ям користувача ? Будь-яка програма, що працює як root, може дзвонити setuid(some_id), і немає жодної вимоги, яка some_idможе бути частиною будь-якої бази даних користувачів. З такими речами, як простори імен користувачів в Linux, це може стати несподіваним припущенням для ваших сценаріїв.
mosvy

1
@Philippos, що здається дорогим способом виклику getpwuid()функції, яка lsвикористовує для перекладу UID для входу до імен. Відповідь Жиля - це більш прямий і ефективний спосіб досягти цього.
Ентоні Г - справедливість для Моніки

Відповіді:


14

Один із поширених способів зробити це - перевірити, чи потрібна програма існує та є у вас PATH. Наприклад:

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
}

Оскільки POSIX idне підтримує аргументи UID, elifпункт для idповинен перевірити не лише те, чи idє в PATH, але й чи буде він запускатися без помилок. Це означає, що він може працювати idдвічі, що, на щастя, не матиме помітного впливу на продуктивність. Можливо також, що обидва idі awkбудуть працювати, з однаковим мізерним хітом продуктивності.

До речі, за допомогою цього методу немає необхідності зберігати вихід. Буде запущений лише один із них, тому лише один надрукує вихід для повернення функції.


щоб впоратися з можливістю кількох імен користувачів , що мають один і той же ідентифікатор користувача , загорнути все від ifдо fiв { ... } | head -n 1. тобто відкиньте всі, крім першого матчу uid. але це означає, що вам доведеться зафіксувати вихідний код будь-якої програми, яку запустили.
cas

Дякую за відповідь. Я сподівався, що може бути якась інша утиліта, на яку я не натрапив, але це корисно. Оскільки у мене немає доступу до реалізації id, яка не приймає ідентифікатор як операнд, я вважав, що перевірка його статусу виходу може бути проблематичною - як визначити різницю між іменем входу, яке не існує, або UID, що не існує. Ім'я для входу може містити
Ентоні Г - справедливість для Моніки,

1
З кожним редагуванням функція стає більш надійною. :) З if command -v getent >/dev/null;цього if [ -x /usr/bin/getent ] ;приводу я, мабуть, використовував би не випадковість, що ці утиліти мають інший шлях.
Ентоні Г - справедливість для Моніки

3
Так. Я регулярно використовую command -vдля цієї мети: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (хоча я лише коли-небудь перевіряв це на dashвбудованій оболонці).
Ентоні Г - справедливість для Моніки

1
@AnthonyGeoghegan Якщо вам коли-небудь доводиться працювати над старовинними системами, type foo >/dev/null 2>/dev/nullпрацює над кожним ш, що я коли-небудь бачив. commandє порівняно сучасним.
Жиль "ТАК - перестань бути злим"

6

У POSIX немає нічого, що могло б допомогти, крім іншого id. Спроба idта повернення до розбору /etc/passwd, ймовірно, настільки ж портативні, як і на практиці.

BusyBox idне приймає ідентифікатори користувачів, але системи з BusyBox, як правило, є автономними вбудованими системами, де /etc/passwdдостатньо розбору .

Якщо ви зіткнулися із системою, що не належить до GNU, де idне приймає ідентифікатори користувачів, ви також можете спробувати зателефонувати getpwuidчерез Perl з можливістю:

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

Або Python:

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

2
Парсинг /etc/passwdвзагалі не є портативним і не працюватиме для файлів, що не передаються passwd-файлами, як LDAP.
Р ..

Мені це подобається, я його вкраду
cas

1
@R .. Запитувач знає про це, ця відповідь не стверджує інакше, тож який сенс у вашому коментарі?
Жил "ТАК - перестань бути злим"

Дякую за цю відповідь. Я запевняю, що не існує іншої утиліти, про яку я не знав. Схоже, POSIX задає стандартну функцію C для перекладу UID на ім’я для входу, але не обов'язково відповідну команду (крім id).
Ентоні Г - справедливість для Моніки

2
Як остання резервна перевірка, перевірте, чи є в системі компілятор змінного струму, а потім компілюйте додану обгортку getpwuid () ...
rackandboneman

5

POSIX визначає getpwuidяк стандартну функцію C для пошуку в базі даних користувачів для ідентифікатора користувача, що дозволяє перевести ідентифікатор на ім’я для входу. Я завантажив вихідний код для GNU coreutils і бачу, як ця функція використовується при їх реалізації таких утиліт, як idі ls.

Як навчальну вправу я написав цю швидку та брудну програму C, щоб просто діяти як обгортка для цієї функції. Майте на увазі, що я не програмував на C з коледжу (багато років тому), і я не збираюся використовувати це у виробництві, але я подумав, що опублікую це як доказ концепції (якщо хтось хоче його редагувати , Почувайся вільно):

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

У мене не було можливості протестувати це за допомогою NIS / LDAP, але я помітив, що якщо у одного користувача є кілька записів /etc/passwd, він ігнорує всі, крім першої.

Приклад використання:

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

$ ./get_user 99
Invalid user ID: 99

3

Як правило, я б не рекомендував цього робити. Зображення від імен користувачів до uids - це не однозначно, і кодування припущень, які ви можете перетворити назад з uid, щоб отримати ім'я користувача, зірве щось. Наприклад, я часто запускаю повністю безкореневі контейнери простору імен користувачів, роблячи файли passwdта groupфайли в контейнері всі імена користувачів і груп на id 0; це дозволяє встановити пакети для роботи без chownвідмов. Але якщо щось спробує перетворити 0 назад на uid і не отримає того, що очікує, воно безперешкодно зламається. Отже, у цьому прикладі замість перетворення назад та порівняння імен користувачів слід перетворити на uids та порівняти у цьому просторі.

Якщо вам дійсно потрібно виконати цю операцію, можливо, це можливо зробити напівпереносно, якщо ви користуєтеся корінгом, зробивши тимчасовий файл, chownпередавши його на uid, потім використовуючи lsдля читання назад та розбору імені власника. Але я б просто застосував відомий підхід, який не є стандартизованим, але "портативним на практиці", як один із тих, які ви вже знайшли.

Але знову ж таки, не робіть цього. Іноді щось важко зробити - це надіслати вам повідомлення.


1
Коментарі очищені. Я хотів би нагадати обом учасникам, що коментарі повинні бути цивільними.
terdon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.