ユーザーIDに関連付けられたユーザー名を取得するPOSIX互換の方法


23

ユーザーIDに関連付けられたログイン名を取得したいことがよくあります。これは一般的なユースケースであることが証明されているため、これを行うシェル関数を作成することにしました。私は主に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 ユーザーのIDに関するデータを取得するためのPOSIX標準化ユーティリティです。

BSDおよびGNU実装は、オペランドとしてユーザーIDを受け入れます。

これは、ユーザーIDに関連付けられたログイン名の印刷に使用できることを意味します。

id -nu "$uid"

ただし、オペランドとしてユーザーIDを指定することはPOSIXでは指定されていません。オペランドとしてのログイン名の使用についてのみ説明します。

上記のすべてを組み合わせる

上記の3つのアプローチを次のように組み合わせることを検討しました。

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
}

ただし、これは不格好で洗練されておらず、さらに重要なことには堅牢ではありません。ユーザーIDが無効または存在しない場合、ゼロ以外のステータスで終了します。各コマンド呼び出しの終了ステータスを分析して保存する、より長くて不格好なシェルスクリプトを作成する前に、ここで質問したいと思いました。

ユーザーIDに関連付けられたログイン名を取得する、よりエレガントでポータブルな(POSIX互換の)方法はありますか?


10
さらに楽しくするために、複数のユーザー名が同じIDにマップできることを考慮してください...
Stephen Kitt

この質問のコンテキストで同じIDにマップされた複数のユーザー名の問題は、最初の一致を超えて何getentid返さないことです。それらすべてを見つける唯一の方法は、ユーザーデータベースで許可されている場合、すべてのユーザーを列挙することです。(/etc/passwd明らかにそこに定義されているユーザーのための作品を探しています。)
スティーブン・キット

1
おかげ@StephenKitt私は私の中で、このようなエントリを作成/etc/passwdし、/etc/shadowこのシナリオをテストするために、両方のことを検証idし、getent passwdあなたが説明するように動作します。ある段階で、ユーザーが複数の名前を持つシステムを使用することになった場合、これらのシステムユーティリティと同じことを行い、最初の出現をそのユーザーの正規名として単純に扱います。
アンソニーG-モニカの正義

1
ユーザー名に関連付けられてないPOSIXは、ユーザーIDが必要なすべての?rootとして実行されているプログラムはすべてを呼び出すことができsetuid(some_id)some_idユーザーデータベースの一部である必要性はありません。Linuxのユーザー名前空間のようなものでは、これはあなたのスクリプトにとって重大な前提になるかもしれません。
mosvy

1
@Philipposは、UIDをログイン名に変換するためgetpwuid()ls使用する関数を呼び出す高価な方法のようです。ジルの答えは、これを達成するためのより直接的で効率的な方法です。
アンソニーG-モニカの正義

回答:


14

これを行う一般的な方法の1つは、必要なプログラムが存在しているかどうかをテストすることです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句はPATHにあるidかどうかだけでidなく、エラーなしで実行されるかどうかもテストする必要があります。これは、id2回実行される可能性があることを意味しますが、幸いなことにパフォーマンスに顕著な影響はありません。また、両方idawk実行する可能性もありますが、パフォーマンスヒットは無視できます。

ところで、この方法では、出力を保存する必要はありません。実行されるのはそのうちの1つだけなので、関数が返す出力を出力するのは1つだけです。


同じUIDを持つ複数のユーザ名の可能性に対処するために、からすべてをラップifするfiの中で{ ... } | head -n 1。すなわち、一致する最初のUIDを除くすべてをドロップします。ただし、実行されたプログラムの終了コードをキャプチャする必要があります。
cas

答えてくれてありがとう。私は出会わなかった他のユーティリティがあるかもしれないと思っていましたが、これは役に立ちます。私はidオペランドとしてIDを受け入れない実装にアクセスできないため、終了ステータスのテストには問題があると考えました。存在しないログイン名とUIDの違いをどのように判断するか存在しません。:ログイン名だけの数値文字で構成することが可能ですgnu.org/software/coreutils/manual/html_node/...
正義モニカのための-アンソニー・G

1
すべての編集で、機能はより堅牢になっています。:)そのメモでは、これらのユーティリティが異なるパスを持っているという偶然のif command -v getent >/dev/null;代わりにおそらく使用するでしょうif [ -x /usr/bin/getent ] ;
アンソニーG-モニカの正義

3
はい。私command -vはこの目的のために定期的に使用しています:pubs.opengroup.org/onlinepubs/9699919799/utilities/command.htmldashシェル組み込みでのみテストしたことがありますが)。
アンソニーG-モニカの正義

1
@AnthonyGeoghegan古代のシステムで作業する必要がある場合type foo >/dev/null 2>/dev/null、私が今まで見たすべてのsh で動作します。command比較的モダンです。
ジル「SO-悪であるのをやめる」

6

POSIXには以外に役立つものはありませんidid解析してみて、フォールバックすること/etc/passwdは、おそらく実際の移植と同じくらい移植性があります。

BusyBox idはユーザーIDを受け入れませんが、BusyBoxを備えたシステムは通常、解析/etc/passwdで十分な自律型組み込みシステムです。

idユーザーIDを受け入れない非GNUシステムに遭遇した場合は、getpwuidPerl を介して呼び出してみることもできます(利用可能な場合)。

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はまったく移植性がなく、LDAPなどの非passwdファイルバックエンドでは機能しません。
R ..

私はそれが好きです、私はそれを盗みます
cas

1
@R ..質問者はそれを認識しており、この回答はそうではないと主張していないので、あなたのコメントのポイントは何ですか?
ジル「SO-悪であるのをやめる」

この答えをありがとう。私は気づいていない他のユーティリティがないことを安心しました。POSIXはUIDをログイン名に変換する標準C関数を指定しているように見えますが、必ずしも対応するコマンド(id)で。
アンソニーG-モニカの正義

2
最後のフォールバックとして、システムにacコンパイラーがあるかどうかを確認し、提供されたgetpwuid()ラッパーをコンパイルします...
rackandboneman

5

POSIX getpwuidは、ユーザーデータベースを検索してユーザーIDを検索する標準C関数として、IDをログイン名に変換できるように指定します。私は、GNUのソースコードダウンロードのcoreutilsをして、この関数のようなユーティリティの彼らの実装で使用されて見ることができるidls

学習課題として、私はこの機能のラッパーとして機能するだけの簡単で汚い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

通常、これを行うことはお勧めしません。ユーザー名からuidへのマッピングは1対1ではありません。また、ユーザー名を取得するためにuid から変換できるというエンコードの前提は問題を解決します。たとえば、コンテナ内のpasswdおよびgroupファイルをすべてのユーザー名とグループ名をid 0にマッピングすることにより、完全にルートフリーのユーザー名前空間コンテナを実行することがよくあります。これにより、パッケージのインストールがchown失敗することなく機能します。しかし、何かが0をuidに変換して戻そうとしても、期待どおりの結果が得られない場合、それは無意味に壊れます。したがって、この例では、ユーザー名を変換して比較する代わりに、uidに変換してそのスペースで比較する必要があります。

本当にこの操作を行う必要がある場合、一時ファイルを作成chownしてuidに接続し、それを使用lsして所有者の名前を読み戻して解析することにより、rootであればセミポータブルに実行できる可能性があります。しかし、私は、あなたがすでに見つけたものの1つのように、標準化されていないが「実際には移植可能」な、よく知られたアプローチを使用します。

しかし、再び、これをしないでください。難しいことは、メッセージを送信することです。


1
コメントを削除しました。コメントは市民的である必要があることを両方の参加者に思い出させたいと思います。
テルドン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.