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, это может оказаться утомительным допущением для ваших сценариев.
Мосви
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предложение for idдолжно проверять не только то, idнаходится ли в PATH, но также и то, будет ли оно работать без ошибок. Это означает, что он может работать idдважды, что, к счастью, не окажет заметного влияния на производительность. Также возможно, что оба idи awkбудут запущены, с одинаковым незначительным ударом по производительности.

Кстати, с помощью этого метода нет необходимости сохранять результаты. Будет запущен только один из них, поэтому только один напечатает вывод для возврата функции.

саз
источник
чтобы справиться с возможностью нескольких имен пользователей , имеющих один и тот же идентификатор пользователя , завернуть все от ifдо fiв { ... } | head -n 1. то есть отбросить все, кроме первого совпадения UID. но это будет означать, что вам придется записывать код выхода любой программы, которая была запущена.
Cas
Спасибо за ответ. Я надеялся, что может быть какая-то другая утилита, с которой я не сталкивался, но это полезно. Поскольку у меня нет доступа к реализации id, которая не принимает идентификатор в качестве операнда, я подумал, что проверить его состояние выхода может быть проблематично - как определить разницу между именем входа, которое не существует, или идентификатором UID, который не существует Имя для входа может состоять только из цифровых символов: gnu.org/software/coreutils/manual/html_node/…
Энтони Дж - правосудие для Моники
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работает над каждым sh, который я когда-либо видел. 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.
R ..
Мне нравится это, я украду это
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файлы в контейнере карты всех имена пользователей и групп к идентификатору 0; это позволяет устанавливать пакеты без chownсбоев. Но если что-то попытается преобразовать 0 обратно в uid и не получит то, что ожидает, оно будет бесполезно сломано. Таким образом, в этом примере вместо обратного преобразования и сравнения имен пользователей следует преобразовать в uids и сравнить в этом пространстве.

Если вам действительно нужно выполнить эту операцию, возможно, вы сможете сделать полупортативно, если вы root, сделав временный файл, chownвставив его в uid, а затем используя lsдля чтения и анализа имени владельца. Но я бы просто использовал известный подход, который не стандартизирован, но «переносим на практике», как один из тех, что вы уже нашли.

Но опять же, не делай этого. Иногда что-то сложное сделать, это отправить вам сообщение.

Р..
источник
1
Комментарии удалены. Я хотел бы напомнить обоим участникам, что комментарии должны быть гражданскими.
Тердон