วิธีที่เข้ากันได้กับ POSIX เพื่อรับชื่อผู้ใช้ที่เชื่อมโยงกับ ID ผู้ใช้


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 เป็นยูทิลิตี้มาตรฐาน POSIX สำหรับการรับข้อมูลเกี่ยวกับตัวตนของผู้ใช้

การปรับใช้ BSD และ GNU ยอมรับ ID ผู้ใช้ในฐานะตัวถูกดำเนินการ:

หมายความว่าสามารถใช้พิมพ์ชื่อล็อกอินที่เชื่อมโยงกับ ID ผู้ใช้:

id -nu "$uid"

อย่างไรก็ตามการระบุ ID ผู้ใช้เป็นตัวถูกดำเนินการไม่ได้ระบุใน 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
}

อย่างไรก็ตามนี่คือ clunky, inelegant และ - ที่สำคัญกว่า - ไม่แข็งแกร่ง; มันออกจากสถานะไม่เป็นศูนย์หาก ID ผู้ใช้ไม่ถูกต้องหรือไม่มีอยู่ ก่อนที่ฉันจะเขียนสคริปต์เชลล์ที่ยาวกว่าและ clunkier ที่วิเคราะห์และเก็บสถานะการออกของการเรียกใช้คำสั่งแต่ละครั้งฉันคิดว่าฉันจะถามที่นี่:

มีวิธีที่สะดวกและพกพามากขึ้น (เข้ากันได้กับ POSIX) ในการรับชื่อล็อกอินที่เชื่อมโยงกับ ID ผู้ใช้หรือไม่?


10
เพื่อความสนุกสนานเพิ่มเติมโปรดพิจารณาว่าชื่อผู้ใช้หลายชื่อสามารถจับคู่กับ ID เดียวกัน ...
Stephen Kitt

ปัญหาที่เกิดขึ้นกับชื่อผู้ใช้หลายชื่อที่แมปกับรหัสเดียวกันในบริบทของคำถามนี้คือจะไม่มีgetentและidจะไม่คืนสิ่งใด ๆ ในการแข่งขันครั้งแรก วิธีเดียวที่จะค้นหาพวกเขาทั้งหมดคือการระบุผู้ใช้ทั้งหมดหากฐานข้อมูลผู้ใช้อนุญาต (มองหา/etc/passwdงานสำหรับผู้ใช้ที่กำหนดไว้อย่างชัดเจน)
Stephen Kitt

1
ขอบคุณ @StephenKitt ฉันสร้างรายการดังกล่าวในของฉัน/etc/passwdและ/etc/shadowเพื่อทดสอบสถานการณ์นี้และตรวจสอบว่าทั้งสองidและgetent passwdทำงานตามที่คุณอธิบาย หากในบางขั้นตอนฉันจบลงด้วยการใช้ระบบที่ผู้ใช้มีชื่อหลายชื่อฉันจะทำเช่นเดียวกันกับระบบสาธารณูปโภคเหล่านี้
Anthony G - ความยุติธรรมสำหรับโมนิก้า

1
ไม่ POSIX ต้องใช้รหัสผู้ใช้จะเชื่อมโยงกับชื่อผู้ใช้ที่ทุกคน ? โปรแกรมใด ๆ ที่ทำงานในฐานะรูทสามารถโทรsetuid(some_id)ได้และไม่มีข้อกำหนดใด ๆ ที่some_idอาจเป็นส่วนหนึ่งของฐานข้อมูลผู้ใช้ ด้วยสิ่งต่าง ๆ เช่นเนมสเปซของผู้ใช้บน Linux สิ่งนี้อาจกลายเป็นข้อสันนิษฐานที่ไม่แน่นอนสำหรับสคริปต์ของคุณ
mosvy

1
@Phippippos ที่ดูเหมือนว่าเป็นวิธีการโทรgetpwuid()ฟังก์ชั่นที่มีราคาแพงซึ่งlsใช้ในการแปล UID ให้เป็นชื่อล็อกอิน คำตอบของ Gillesเป็นวิธีที่ทำให้การทำสิ่งนี้สำเร็จและมีประสิทธิภาพมากขึ้น
Anthony G - ความยุติธรรมสำหรับโมนิกา

คำตอบ:


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อยู่ในเส้นทาง แต่ยังไม่ว่ามันจะทำงานโดยไม่มีข้อผิดพลาด ซึ่งหมายความว่าอาจทำงานidสองครั้งซึ่งโชคดีที่จะไม่ส่งผลกระทบต่อประสิทธิภาพการทำงาน นอกจากนี้ยังเป็นไปได้ว่าทั้งคู่idและawkจะถูกเรียกใช้โดยมีประสิทธิภาพการทำงานเล็กน้อยเช่นเดียวกัน

BTW ด้วยวิธีนี้ไม่จำเป็นต้องเก็บผลลัพธ์ จะมีเพียงหนึ่งรายการเท่านั้นที่ทำงานดังนั้นจะมีเพียงหนึ่งรายการเท่านั้นที่พิมพ์ออกมาเพื่อให้ฟังก์ชันส่งคืน


ที่จะรับมือกับความเป็นไปได้ของหลายชื่อผู้มี uid เดียวกันห่อทุกอย่างจากifไปในfi { ... } | head -n 1ie ดร็อปทั้งหมดยกเว้น uid แรกที่ตรงกัน แต่นั่นจะหมายความว่าคุณจะต้องจับรหัสทางออกของโปรแกรมใดก็ตามที่ทำงาน
cas

ขอบคุณสำหรับคำตอบ. ฉันหวังว่าอาจมียูทิลิตี้อื่น ๆ ที่ฉันไม่ได้เจอ แต่มันมีประโยชน์ เนื่องจากฉันไม่สามารถเข้าถึงการใช้งานidที่ไม่ยอมรับ ID ในฐานะตัวถูกดำเนินการฉันคิดว่าการทดสอบสถานะการออกอาจเป็นปัญหาได้ - วิธีบอกความแตกต่างระหว่างชื่อเข้าสู่ระบบที่ไม่มีอยู่หรือ UID ที่ ไม่มีตัวตน เป็นไปได้ที่ชื่อล็อกอินประกอบด้วยอักขระตัวเลขเท่านั้น: gnu.org/software/coreutils/manual/html_node/…
Anthony G - ความยุติธรรมสำหรับ Monica

1
ทุกการแก้ไขฟังก์ชั่นจะแข็งแกร่งขึ้น :) ในบันทึกนั้นฉันอาจใช้if command -v getent >/dev/null;แทนif [ -x /usr/bin/getent ] ;โอกาสที่ยูทิลิตี้เหล่านี้มีเส้นทางที่แตกต่างกัน
Anthony G - ความยุติธรรมสำหรับโมนิก้า

3
ใช่. ฉันใช้command -vเพื่อจุดประสงค์นี้เป็นประจำ: pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html (แม้ว่าฉันเคยทดสอบกับdashเชลล์บิวด์)
Anthony G - ความยุติธรรมสำหรับ Monica

1
@AnthonyGeoghegan หากคุณต้องทำงานกับระบบโบราณtype foo >/dev/null 2>/dev/nullทำงานกับทุก ๆ sh ที่ฉันเคยเห็น commandค่อนข้างทันสมัย
Gilles 'หยุดความชั่วร้าย'

6

POSIX ไม่มีอะไรที่จะช่วยidได้ การลองidและถอยกลับไปที่การแจง/etc/passwdอาจเป็นแบบพกพาได้ในทางปฏิบัติ

BusyBox idไม่ยอมรับ ID ผู้ใช้ แต่ระบบที่มี BusyBox มักเป็นระบบฝังตัวแบบอิสระที่แยกวิเคราะห์/etc/passwdได้เพียงพอ

ในกรณีที่คุณพบระบบที่ไม่ใช่ GNU ที่idไม่ยอมรับ 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 .. ผู้ถามทราบว่าคำตอบนี้ไม่ได้อ้างสิทธิ์เป็นอย่างอื่นดังนั้นความคิดเห็นของคุณคืออะไร
Gilles 'หยุดความชั่วร้าย'

ขอบคุณสำหรับคำตอบนี้ ฉันมั่นใจว่าไม่มีอรรถประโยชน์อื่น ๆ ที่ฉันไม่ทราบ ดูเหมือนว่า POSIX จะระบุฟังก์ชัน C มาตรฐานเพื่อแปล UID เป็นชื่อล็อกอิน แต่ไม่จำเป็นต้องเป็นคำสั่งที่เกี่ยวข้อง (นอกเหนือจากid)
Anthony G - ความยุติธรรมสำหรับโมนิก้า

2
ในฐานะที่เป็นทางเลือกสุดท้ายตรวจสอบว่ามีคอมไพเลอร์ ac ในระบบจากนั้นรวบรวมเสื้อคลุม getpwuid () ที่ให้มา ...
rackandboneman

5

POSIX ระบุgetpwuidว่าเป็นฟังก์ชัน C มาตรฐานเพื่อค้นหาฐานข้อมูลผู้ใช้สำหรับ ID ผู้ใช้ที่อนุญาตให้แปล ID เป็นชื่อล็อกอิน ฉันดาวน์โหลดซอร์สโค้ดสำหรับ GNU coreutilsและสามารถดูฟังก์ชั่นนี้ถูกนำมาใช้ในการดำเนินงานของพวกเขาของสาธารณูปโภคเช่นและidls

ในแบบฝึกหัดการเรียนรู้ฉันเขียนโปรแกรม C ที่รวดเร็วและสกปรกเพื่อทำตัวเป็น wrapper สำหรับฟังก์ชั่นนี้ โปรดจำไว้ว่าฉันไม่ได้ตั้งโปรแกรมใน 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 และเปรียบเทียบในพื้นที่นั้น

หากคุณจำเป็นต้องทำการดำเนินการนี้จริงๆมันอาจเป็นไปได้ที่จะทำครึ่งแบบพกพาถ้าคุณรูทโดยการสร้างไฟล์ temp นำchownไปสู่ ​​uid จากนั้นใช้lsเพื่ออ่านและแยกชื่อเจ้าของ แต่ฉันจะใช้วิธีที่รู้จักกันดีซึ่งไม่ได้มาตรฐาน แต่ "พกพาในทางปฏิบัติ" เช่นเดียวกับที่คุณพบแล้ว

แต่อย่าทำเช่นนี้อีก บางครั้งสิ่งที่ทำยากก็คือการส่งข้อความถึงคุณ


1
ลบความคิดเห็นแล้ว ฉันต้องการเตือนผู้เข้าร่วมทั้งสองว่าการแสดงความคิดเห็นต้องเป็นเรื่องของพลเรือน
terdon
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.