Cách tương thích POSIX để có được tên người dùng được liên kết với ID người dùng


23

Tôi thường muốn có được tên đăng nhập được liên kết với ID người dùng và vì nó được chứng minh là trường hợp sử dụng phổ biến, tôi đã quyết định viết một hàm shell để làm điều này. Mặc dù tôi chủ yếu sử dụng các bản phân phối GNU / Linux, tôi cố gắng viết các tập lệnh của mình sao cho dễ mang theo nhất có thể và để kiểm tra xem những gì tôi đang làm có tương thích với POSIX không.

Phân tích /etc/passwd

Cách tiếp cận đầu tiên tôi đã thử là phân tích /etc/passwd(sử dụng awk).

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

Tuy nhiên, vấn đề với cách tiếp cận này là thông tin đăng nhập có thể không cục bộ, ví dụ: xác thực người dùng có thể thông qua NIS hoặc LDAP.

Sử dụng getentlệnh

Sử dụng getent passwddễ mang theo hơn phân tích cú pháp /etc/passwdvì điều này cũng truy vấn cơ sở dữ liệu NIS hoặc LDAP không cục bộ.

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

Thật không may, getenttiện ích dường như không được chỉ định bởi POSIX.

Sử dụng idlệnh

id là tiện ích được chuẩn hóa POSIX để nhận dữ liệu về danh tính của người dùng.

Việc triển khai BSD và GNU chấp nhận ID người dùng dưới dạng toán hạng:

Điều này có nghĩa là nó có thể được sử dụng để in tên đăng nhập được liên kết với ID người dùng:

id -nu "$uid"

Tuy nhiên, việc cung cấp ID người dùng làm toán hạng không được chỉ định trong POSIX; nó chỉ mô tả việc sử dụng tên đăng nhập làm toán hạng.

Kết hợp tất cả những điều trên

Tôi đã cân nhắc việc kết hợp ba cách tiếp cận trên vào một cái gì đó như sau:

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
}

Tuy nhiên, điều này là vụng về, không phù hợp và - quan trọng hơn - không mạnh mẽ; nó thoát với trạng thái khác không nếu ID người dùng không hợp lệ hoặc không tồn tại. Trước khi tôi viết một tập lệnh shell dài hơn và phân tích và phân tích trạng thái thoát của mỗi lệnh gọi, tôi nghĩ tôi sẽ hỏi ở đây:

Có cách nào thanh lịch và di động hơn (tương thích POSIX) để lấy tên đăng nhập được liên kết với ID người dùng không?


10
Để thêm phần thú vị, hãy xem xét rằng nhiều tên người dùng có thể ánh xạ tới cùng một id ...
Stephen Kitt

Vấn đề với nhiều tên người dùng được ánh xạ tới cùng một id trong bối cảnh của câu hỏi này là không getenthoặc idsẽ không trả lại bất cứ điều gì qua trận đấu đầu tiên; cách duy nhất để tìm thấy tất cả là liệt kê tất cả người dùng, nếu cơ sở dữ liệu người dùng cho phép điều đó. ( /etc/passwdRõ ràng là tìm kiếm các tác phẩm cho người dùng được xác định ở đó.)
Stephen Kitt

1
Cảm ơn @StephenKitt Tôi đã tạo một mục như vậy trong tôi /etc/passwd/etc/shadowđể kiểm tra kịch bản này và xác minh rằng cả hai idgetent passwdhành xử như bạn mô tả. Nếu, ở một giai đoạn nào đó, tôi kết thúc việc sử dụng một hệ thống mà người dùng có nhiều tên, tôi sẽ làm giống như các tiện ích hệ thống này và chỉ đơn giản coi sự xuất hiện đầu tiên là tên chính tắc cho người dùng đó.
Anthony G - công lý cho Monica

1
Liệu POSIX đòi hỏi một user id được liên kết với một tên người dùng ở tất cả ? Bất kỳ chương trình nào chạy bằng root đều có thể gọi setuid(some_id)và không có yêu cầu nào some_idcó thể là một phần của bất kỳ cơ sở dữ liệu người dùng nào. Với những thứ như không gian tên người dùng trên Linux, điều này có thể trở thành một giả định làm tê liệt cho các tập lệnh của bạn.
mosvy

1
@Philippos có vẻ như là một cách gọi getpwuid()chức năng đắt tiền lsdùng để dịch UID sang tên đăng nhập. Câu trả lời của Gilles là một cách trực tiếp và hiệu quả hơn để thực hiện điều này.
Anthony G - công lý cho Monica

Câu trả lời:


14

Một cách phổ biến để làm điều này là kiểm tra xem chương trình bạn muốn có tồn tại và có sẵn từ bạn không PATH. Ví dụ:

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
}

Vì POSIX idkhông hỗ trợ các đối số UID, nên elifmệnh đề idphải kiểm tra không chỉ idlà có trong PATH hay không, mà còn có chạy hay không mà không có lỗi. Điều này có nghĩa là nó có thể chạy idhai lần, điều may mắn là sẽ không có tác động đáng chú ý đến hiệu suất. Cũng có thể là cả hai idawksẽ được chạy, với cùng một hiệu suất không đáng kể.

BTW, với phương pháp này, không cần lưu trữ đầu ra. Chỉ một trong số chúng sẽ được chạy, vì vậy chỉ có một bản in đầu ra cho hàm trả về.


để đối phó với khả năng xảy ra nhiều tên người dùng có cùng một uid, quấn tất cả mọi thứ từ ifđể fi{ ... } | head -n 1. tức là thả tất cả trừ trận đấu uid đầu tiên. nhưng điều đó có nghĩa là bạn sẽ phải nắm bắt mã thoát của bất kỳ chương trình nào đã chạy.
cas

Cảm ơn câu trả lời. Tôi đã hy vọng rằng có thể có một số tiện ích khác mà tôi đã không gặp nhưng điều này là hữu ích. Vì tôi không có quyền truy cập vào việc triển khai idmà không chấp nhận ID là toán hạng, tôi cho rằng việc kiểm tra trạng thái thoát của nó có thể có vấn đề - làm thế nào để biết sự khác biệt giữa tên đăng nhập không tồn tại hoặc UID không tồn tại Tên đăng nhập chỉ có thể bao gồm các ký tự số: gnu.org/software/coreutils/manual/html_node/ Kẻ
Anthony G - công lý cho Monica

1
Với mỗi lần chỉnh sửa, chức năng sẽ trở nên mạnh mẽ hơn. :) Về lưu ý đó, có lẽ tôi sẽ sử dụng if command -v getent >/dev/null;thay vì if [ -x /usr/bin/getent ] ;tình cờ các tiện ích này có một đường dẫn khác.
Anthony G - công lý cho Monica

3
Vâng. Tôi thường xuyên sử dụng command -vcho mục đích này: pubs.opengroup.org/onlinepub/9699919799/utilities/command.html (mặc dù tôi chỉ từng thử nghiệm nó với dashshell dựng sẵn).
Anthony G - công lý cho Monica

1
@AnthonyGeoghegan Nếu bạn phải làm việc trên các hệ thống cổ xưa, hãy type foo >/dev/null 2>/dev/nulllàm việc trên mọi sh mà tôi từng thấy. commandlà tương đối hiện đại.
Gilles 'SO- ngừng trở thành ác quỷ'

6

Không có gì trong POSIX sẽ giúp được gì ngoài id. Cố gắng idvà quay trở lại để phân tích cú pháp /etc/passwdcó thể là di động như trong thực tế.

BusyBox idkhông chấp nhận ID người dùng, nhưng các hệ thống với BusyBox thường là các hệ thống nhúng tự trị trong đó phân tích cú pháp /etc/passwdlà đủ.

Trong trường hợp bạn gặp phải một hệ thống không phải GNU mà idkhông chấp nhận ID người dùng, bạn cũng có thể thử gọi getpwuidqua Perl, với khả năng nó khả dụng:

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

Hoặc Python:

if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi

2
Phân tích cú pháp hoàn toàn /etc/passwdkhông khả dụng và sẽ không hoạt động đối với các phụ trợ không phải là tệp passwd như LDAP.
R ..

Tôi như thế, tôi sẽ ăn cắp nó
cas

1
@R .. Người hỏi nhận thức được điều đó, câu trả lời này không yêu cầu khác, vậy ý ​​kiến ​​của bạn là gì?
Gilles 'SO- ngừng trở nên xấu xa'

Cảm ơn câu trả lời này. Tôi yên tâm rằng không có tiện ích nào khác mà tôi không biết. Dường như POSIX chỉ định một hàm C tiêu chuẩn để dịch UID sang tên đăng nhập nhưng không nhất thiết phải là một lệnh tương ứng (trừ id).
Anthony G - công lý cho Monica

2
Như một dự phòng cuối cùng, kiểm tra xem có trình biên dịch ac trên hệ thống không, sau đó biên dịch trình bao bọc getpwuid () được cung cấp ...
rackandboneman

5

POSIX chỉ định getpwuidlà hàm C tiêu chuẩn để tìm kiếm cơ sở dữ liệu người dùng cho ID người dùng cho phép ID được dịch sang tên đăng nhập. Tôi đã tải xuống mã nguồn cho coreutils GNU và có thể thấy chức năng này đang được sử dụng để triển khai các tiện ích như idls.

Như một bài tập học tập, tôi đã viết chương trình C nhanh và bẩn này để chỉ đơn giản là hoạt động như một trình bao bọc cho chức năng này. Hãy nhớ rằng tôi đã không lập trình ở C từ thời đại học (nhiều năm trước) và tôi không có ý định sử dụng nó trong sản xuất nhưng tôi nghĩ tôi sẽ đăng nó ở đây như một bằng chứng về khái niệm (nếu có ai muốn chỉnh sửa nó , đừng ngại):

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

Tôi đã không có cơ hội thử nghiệm nó với NIS / LDAP nhưng tôi nhận thấy rằng nếu có nhiều mục nhập cho cùng một người dùng /etc/passwd, nó sẽ bỏ qua tất cả trừ mục đầu tiên.

Ví dụ sử dụng:

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

$ ./get_user 99
Invalid user ID: 99

3

Nói chung, tôi sẽ khuyên bạn không nên làm điều này. Ánh xạ từ tên người dùng sang uids không phải là một và một giả định mã hóa mà bạn có thể chuyển đổi lại từ một uid để có được tên người dùng sẽ phá vỡ mọi thứ. Ví dụ, tôi thường chạy các vùng chứa không gian tên người dùng hoàn toàn không cần root bằng cách tạo passwdgroupcác tệp trong vùng chứa ánh xạ tất cả tên người dùng và nhóm thành id 0; Điều này cho phép cài đặt các gói để làm việc mà chownkhông thất bại. Nhưng nếu một cái gì đó cố gắng chuyển đổi 0 trở lại thành một uid và không đạt được những gì nó mong đợi, nó sẽ phá vỡ một cách vô cớ. Vì vậy, trong ví dụ này, thay vì chuyển đổi lại và so sánh tên người dùng, bạn nên chuyển đổi sang uids và so sánh trong không gian đó.

Nếu bạn thực sự cần phải thực hiện thao tác này, có thể thực hiện bán một cách hợp lý nếu bạn đã root, bằng cách tạo tệp tạm thời, đưa chownnó vào uid, sau đó sử dụng lsđể đọc lại và phân tích tên của chủ sở hữu. Nhưng tôi sẽ chỉ sử dụng một phương pháp nổi tiếng không được chuẩn hóa mà là "di động trong thực tế", giống như một trong những phương pháp bạn đã tìm thấy.

Nhưng một lần nữa, đừng làm điều này. Đôi khi một cái gì đó khó làm là gửi tin nhắn cho bạn.


1
Bình luận thanh trừng. Tôi muốn nhắc nhở cả hai người tham gia rằng các bình luận cần phải là dân sự.
terdon
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.