Cara POSIX kompatibel untuk mendapatkan nama pengguna yang terkait dengan ID pengguna

23

Saya sering ingin mendapatkan nama login yang dikaitkan dengan ID pengguna dan karena itu terbukti menjadi kasus penggunaan umum, saya memutuskan untuk menulis fungsi shell untuk melakukan ini. Walaupun saya terutama menggunakan distribusi GNU / Linux, saya mencoba untuk menulis skrip saya agar se portable mungkin dan untuk memeriksa bahwa apa yang saya lakukan kompatibel dengan POSIX.

Parse /etc/passwd

Pendekatan pertama yang saya coba adalah mengurai /etc/passwd(menggunakan awk).

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

Namun, masalah dengan pendekatan ini adalah bahwa login mungkin tidak bersifat lokal, mis. Otentikasi pengguna bisa melalui NIS atau LDAP.

Gunakan getentperintah

Menggunakan getent passwdlebih portabel daripada penguraian /etc/passwdkarena ini juga menanyakan database NIS atau LDAP non-lokal.

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

Sayangnya, getentutilitas tersebut sepertinya tidak ditentukan oleh POSIX.

Gunakan idperintah

id adalah utilitas standar POSIX untuk mendapatkan data tentang identitas pengguna.

Implementasi BSD dan GNU menerima ID Pengguna sebagai operan:

Ini berarti dapat digunakan untuk mencetak nama login yang terkait dengan ID Pengguna:

id -nu "$uid"

Namun, memberikan ID Pengguna karena operan tidak ditentukan dalam POSIX; itu hanya menggambarkan penggunaan nama login sebagai operan.

Menggabungkan semua hal di atas

Saya mempertimbangkan untuk menggabungkan ketiga pendekatan di atas menjadi sesuatu seperti berikut:

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
}

Namun, ini kikuk, tidak elegan dan - lebih penting - tidak kuat; keluar dengan status tidak nol jika ID Pengguna tidak valid atau tidak ada. Sebelum saya menulis skrip shell yang lebih panjang dan clunkier yang menganalisis dan menyimpan status keluar dari setiap permintaan perintah, saya pikir saya akan bertanya di sini:

Apakah ada cara yang lebih elegan dan portabel (kompatibel dengan POSIX) untuk mendapatkan nama login yang dikaitkan dengan ID pengguna?

Anthony G - keadilan untuk Monica
sumber
10
Untuk kesenangan tambahan, pertimbangkan bahwa beberapa nama pengguna dapat memetakan ke id yang sama ...
Stephen Kitt
Masalah dengan beberapa nama pengguna yang dipetakan ke id yang sama dalam konteks pertanyaan ini adalah bahwa tidak ada getentatau tidak idakan mengembalikan apa pun melewati pertandingan pertama; satu-satunya cara untuk menemukan mereka semua adalah dengan menghitung semua pengguna, jika database pengguna mengizinkannya. (Mencari /etc/passwdkarya untuk pengguna yang ditentukan di sana, jelas.)
Stephen Kitt
1
Terima kasih @StephenKitt saya membuat entri seperti itu di saya /etc/passwddan /etc/shadowuntuk menguji skenario ini dan memverifikasi bahwa keduanya iddan getent passwdberperilaku seperti yang Anda jelaskan. Jika, pada tahap tertentu, saya akhirnya menggunakan sistem di mana pengguna memiliki beberapa nama, saya akan melakukan hal yang sama seperti utilitas sistem ini dan hanya memperlakukan kejadian pertama sebagai nama kanonik untuk pengguna itu.
Anthony G - keadilan untuk Monica
1
Apakah POSIX memerlukan id pengguna untuk dikaitkan dengan nama pengguna sama sekali ? Setiap program yang berjalan sebagai root dapat memanggil setuid(some_id), dan tidak ada persyaratan yang some_iddapat menjadi bagian dari basis data pengguna apa pun. Dengan hal-hal seperti ruang nama pengguna di Linux, ini bisa berubah menjadi asumsi yang melumpuhkan untuk skrip Anda.
Mosvy
1
@ Pilipos yang sepertinya merupakan cara yang mahal untuk memanggil getpwuid()fungsi yang lsdigunakan untuk menerjemahkan UID ke nama masuk. Jawaban Gilles adalah cara yang lebih langsung dan efisien untuk mencapai ini.
Anthony G - keadilan untuk Monica

Jawaban:

14

Salah satu cara umum untuk melakukan ini adalah untuk menguji apakah program yang Anda inginkan ada dan tersedia dari Anda PATH. Sebagai contoh:

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
}

Karena POSIX idtidak mendukung argumen UID, elifklausa untuk idharus menguji tidak hanya apakah idada di PATH, tetapi juga apakah itu akan berjalan tanpa kesalahan. Ini berarti dapat berjalan iddua kali, yang untungnya tidak akan berdampak nyata pada kinerja. Hal ini juga mungkin bahwa kedua iddan awkakan dijalankan, dengan diabaikan memukul kinerja yang sama.

BTW, dengan metode ini, tidak perlu menyimpan output. Hanya satu dari mereka yang akan dijalankan, jadi hanya satu yang akan mencetak output agar fungsi kembali.

cas
sumber
untuk mengatasi kemungkinan beberapa nama pengguna yang memiliki uid yang sama, membungkus segala sesuatu dari ifke fidalam { ... } | head -n 1. yaitu drop semua kecuali pertandingan uid pertama. tetapi itu berarti Anda harus mengambil kode keluar dari program apa pun yang dijalankan.
cas
Terima kasih atas jawabannya. Saya berharap mungkin ada beberapa utilitas lain yang belum saya temui tetapi ini membantu. Karena saya tidak memiliki akses ke implementasi idyang tidak menerima ID sebagai operan, saya pikir menguji status keluarnya bisa bermasalah - bagaimana cara mengetahui perbedaan antara nama login yang tidak ada atau UID yang tidak ada. Dimungkinkan untuk nama login hanya terdiri dari karakter numerik: gnu.org/software/coreutils/manual/html_node/…
Anthony G - justice for Monica
1
Dengan setiap pengeditan, fungsi menjadi lebih kuat. :) Pada catatan itu, saya mungkin akan menggunakan if command -v getent >/dev/null;bukannya if [ -x /usr/bin/getent ] ;pada kesempatan bahwa utilitas ini memiliki jalur yang berbeda.
Anthony G - keadilan untuk Monica
3
Iya nih. Saya secara teratur menggunakan command -vuntuk tujuan ini: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (meskipun saya hanya pernah mengujinya dengan dashshell builtin).
Anthony G - keadilan untuk Monica
1
@AnthonyGeoghegan Jika Anda harus bekerja pada sistem kuno, type foo >/dev/null 2>/dev/nullbekerja pada setiap sh yang pernah saya lihat. commandrelatif modern.
Gilles 'SANGAT berhenti menjadi jahat'
6

Tidak ada dalam POSIX yang akan membantu selain id. Mencoba iddan jatuh kembali ke penguraian /etc/passwdmungkin sama portabelnya seperti ketika dalam praktek.

BusyBox's idtidak menerima ID pengguna, tetapi sistem dengan BusyBox biasanya merupakan sistem embedded otonom di mana penguraian /etc/passwdsudah cukup.

Jika Anda menemukan sistem non-GNU di mana idtidak menerima ID pengguna, Anda juga dapat mencoba menelepon getpwuidmelalui Perl, jika tersedia:

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

Atau Python:

if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi
Gilles 'SANGAT berhenti menjadi jahat'
sumber
2
Parsing /etc/passwdsama sekali tidak portabel, dan tidak akan berfungsi untuk backend non-passwd-file seperti LDAP.
R ..
Saya suka itu, saya akan mencurinya
cas
1
@R .. Penanya mengetahui hal itu, jawaban ini tidak mengklaim sebaliknya, jadi apa gunanya komentar Anda?
Gilles 'SANGAT berhenti menjadi jahat'
Terima kasih atas jawaban ini. Saya diyakinkan bahwa tidak ada utilitas lain yang tidak saya sadari. Sepertinya POSIX menentukan fungsi C standar untuk menerjemahkan UID ke nama login tetapi tidak harus berupa perintah yang sesuai (selain id).
Anthony G - keadilan untuk Monica
2
Sebagai fallback terakhir, periksa apakah ada compiler ac pada sistem, lalu kompilasi wrapper getpwuid () yang disediakan ...
rackandboneman
5

POSIX menentukan getpwuidsebagai fungsi C standar untuk mencari basis data pengguna untuk ID pengguna yang memungkinkan ID diterjemahkan ke nama login. Saya mengunduh kode sumber untuk GNU coreutils dan dapat melihat fungsi ini digunakan dalam implementasi utilitas seperti iddan ls.

Sebagai latihan pembelajaran, saya menulis program C cepat-dan-kotor ini hanya untuk bertindak sebagai pembungkus untuk fungsi ini. Ingatlah bahwa saya belum memprogram di C sejak kuliah (bertahun-tahun yang lalu) dan saya tidak bermaksud menggunakan ini dalam produksi tetapi saya pikir saya akan mempostingnya di sini sebagai bukti konsep (jika ada yang ingin mengeditnya) , merasa bebas):

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

Saya tidak memiliki kesempatan untuk mengujinya dengan NIS / LDAP tetapi saya perhatikan bahwa jika ada beberapa entri untuk pengguna yang sama /etc/passwd, ia mengabaikan semua kecuali yang pertama.

Contoh penggunaan:

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

$ ./get_user 99
Invalid user ID: 99
Anthony G - keadilan untuk Monica
sumber
3

Secara umum, saya akan merekomendasikan untuk tidak melakukan ini. Pemetaan dari nama pengguna ke uids bukan satu-ke-satu, dan menyandikan asumsi yang dapat Anda konversi kembali dari uid untuk mendapatkan nama pengguna akan merusak banyak hal. Sebagai contoh, saya sering menjalankan wadah user-namespace bebas-root sepenuhnya dengan membuat passwddan groupfile dalam wadah memetakan semua nama pengguna dan grup ke id 0; ini memungkinkan instalasi paket untuk bekerja tanpa chowngagal. Tetapi jika sesuatu mencoba mengubah 0 kembali menjadi uid dan tidak mendapatkan apa yang diharapkannya, itu akan rusak. Jadi, dalam contoh ini, alih-alih mengonversi kembali dan membandingkan nama pengguna, Anda harus mengonversi menjadi uids dan membandingkan di ruang itu.

Jika Anda benar-benar perlu melakukan operasi ini, dimungkinkan untuk melakukan semi-portable jika Anda melakukan root, dengan membuat file temp, chownmemasukkannya ke uid, kemudian menggunakan lsuntuk membaca kembali dan mem-parsing nama pemilik. Tetapi saya hanya akan menggunakan pendekatan terkenal yang tidak terstandarisasi tetapi "portabel dalam praktik", seperti yang sudah Anda temukan.

Tetapi sekali lagi, jangan lakukan ini. Terkadang sesuatu yang sulit dilakukan adalah mengirimi Anda pesan.

R ..
sumber
1
Komentar dibersihkan. Saya ingin mengingatkan kedua peserta bahwa komentar harus sopan.
terdon