web-dev-qa-db-ja.com

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

ユーザー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互換)方法はありますか?

26

これを行う一般的な方法の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引数をサポートしていないため、Elifid句は、idがPATHにあるかどうかだけでなく、エラーなしで実行されます。これは、idが2回実行される可能性があることを意味します。 idawkの両方が実行され、パフォーマンスへの影響はほとんど無視できる可能性もあります。

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

14
cas

POSIXにはid以外に役立つものはありません。 idを試し、解析にフォールバック/etc/passwdは、実際のところ、おそらく移植可能です。

BusyBoxのidはユーザーIDを受け入れませんが、BusyBoxを備えたシステムは通常、/etc/passwdで十分です。

GNU以外のシステムでidがユーザーIDを受け付けない場合は、Perlで getpwuid を呼び出してみてください。 :

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

POSIXは getpwuid を標準C関数として指定して、ユーザーデータベースでユーザーIDを検索し、IDをログイン名に変換できるようにしています。 GNUcoreutilsのソースコードをダウンロードして、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
6

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

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

ただし、これを行わないでください。時には、あなたにメッセージを送るのが難しいことがある。