File: gawk.info, Node: Id Program, Next: Split Program, Prev: Egrep Program, Up: Clones 11.2.3 Printing Out User Information ------------------------------------ The 'id' utility lists a user's real and effective user ID numbers, real and effective group ID numbers, and the user's group set, if any. 'id' only prints the effective user ID and group ID if they are different from the real ones. If possible, 'id' also supplies the corresponding user and group names. The output might look like this: $ id -| uid=1000(arnold) gid=1000(arnold) groups=1000(arnold),4(adm),7(lp),27(sudo) This information is part of what is provided by 'gawk''s 'PROCINFO' array (*note Built-in Variables::). However, the 'id' utility provides a more palatable output than just individual numbers. The POSIX version of 'id' takes several options that give you control over the output's format, such as printing only real ids, or printing only numbers or only names. Additionally, you can print the information for a specific user, instead of that of the current user. Here is a version of POSIX 'id' written in 'awk'. It uses the 'getopt()' library function (*note Getopt Function::), the user database library functions (*note Passwd Functions::), and the group database library functions (*note Group Functions::) from *note Library Functions::. The program is moderately straightforward. All the work is done in the 'BEGIN' rule. It starts with explanatory comments, a list of options, and then a 'usage()' function: # id.awk --- implement id in awk # # Requires user and group library functions and getopt # output is: # uid=12(foo) euid=34(bar) gid=3(baz) \ # egid=5(blat) groups=9(nine),2(two),1(one) # Options: # -G Output all group ids as space separated numbers (ruid, euid, groups) # -g Output only the euid as a number # -n Output name instead of the numeric value (with -g/-G/-u) # -r Output ruid/rguid instead of effective id # -u Output only effective user id, as a number function usage() { printf("Usage:\n" \ "\tid [user]\n" \ "\tid -G [-n] [user]\n" \ "\tid -g [-nr] [user]\n" \ "\tid -u [-nr] [user]\n") > "/dev/stderr" exit 1 } The first step is to parse the options using 'getopt()', and to set various flag variables according to the options given: BEGIN { # parse args while ((c = getopt(ARGC, ARGV, "Ggnru")) != -1) { if (c == "G") groupset_only++ else if (c == "g") egid_only++ else if (c == "n") names_not_groups++ else if (c == "r") real_ids_only++ else if (c == "u") euid_only++ else usage() } The next step is to check that no conflicting options were provided. '-G' and '-r' are mutually exclusive. It is also not allowed to provide more than one user name on the command line: if (groupset_only && real_ids_only) usage() else if (ARGC - Optind > 1) usage() The user and group ID numbers are obtained from 'PROCINFO' for the current user, or from the user and password databases for a user supplied on the command line. In the latter case, 'real_ids_only' is set, since it's not possible to print information about the effective user and group IDs: if (ARGC - Optind == 0) { # gather info for current user uid = PROCINFO["uid"] euid = PROCINFO["euid"] gid = PROCINFO["gid"] egid = PROCINFO["egid"] for (i = 1; ("group" i) in PROCINFO; i++) groupset[i] = PROCINFO["group" i] } else { fill_info_for_user(ARGV[ARGC-1]) real_ids_only++ } The test in the 'for' loop is worth noting. Any supplementary groups in the 'PROCINFO' array have the indices '"group1"' through '"groupN"' for some N (i.e., the total number of supplementary groups). However, we don't know in advance how many of these groups there are. This loop works by starting at one, concatenating the value with '"group"', and then using 'in' to see if that value is in the array (*note Reference to Elements::). Eventually, 'i' increments past the last group in the array and the loop exits. The loop is also correct if there are _no_ supplementary groups; then the condition is false the first time it's tested, and the loop body never executes. Now, based on the options, we decide what information to print. For '-G' (print just the group set), we then select whether to print names or numbers. In either case, when done we exit: if (groupset_only) { if (names_not_groups) { for (i = 1; i in groupset; i++) { entry = getgrgid(groupset[i]) name = get_first_field(entry) printf("%s", name) if ((i + 1) in groupset) printf(" ") } } else { for (i = 1; i in groupset; i++) { printf("%u", groupset[i]) if ((i + 1) in groupset) printf(" ") } } print "" # final newline exit 0 } Otherwise, for '-g' (effective group ID only), we check if '-r' was also provided, in which case we use the real group ID. Then based on '-n', we decide whether to print names or numbers. Here too, when done, we exit: else if (egid_only) { id = real_ids_only ? gid : egid if (names_not_groups) { entry = getgrgid(id) name = get_first_field(entry) printf("%s\n", name) } else { printf("%u\n", id) } exit 0 } The 'get_first_field()' function extracts the group name from the group database entry for the given group ID. Similar processing logic applies to '-u' (effective user ID only), combined with '-r' and '-n': else if (euid_only) { id = real_ids_only ? uid : euid if (names_not_groups) { entry = getpwuid(id) name = get_first_field(entry) printf("%s\n", name) } else { printf("%u\n", id) } exit 0 } At this point, we haven't exited yet, so we print the regular, default output, based either on the current user's information, or that of the user whose name was provided on the command line. We start with the real user ID: printf("uid=%d", uid) pw = getpwuid(uid) print_first_field(pw) The 'print_first_field()' function prints the user's login name from the password file entry, surrounded by parentheses. It is shown soon. Printing the effective user ID is next: if (euid != uid && ! real_ids_only) { printf(" euid=%d", euid) pw = getpwuid(euid) print_first_field(pw) } Similar logic applies to the real and effective group IDs: printf(" gid=%d", gid) pw = getgrgid(gid) print_first_field(pw) if (egid != gid && ! real_ids_only) { printf(" egid=%d", egid) pw = getgrgid(egid) print_first_field(pw) } Finally, we print the group set and the terminating newline: for (i = 1; i in groupset; i++) { if (i == 1) printf(" groups=") group = groupset[i] printf("%d", group) pw = getgrgid(group) print_first_field(pw) if ((i + 1) in groupset) printf(",") } print "" } The 'get_first_field()' function extracts the first field from a password or group file entry for use as a user or group name. Fields are separated by ':' characters: function get_first_field(str, a) { if (str != "") { split(str, a, ":") return a[1] } } This function is then used by 'print_first_field()' to output the given name surrounded by parentheses: function print_first_field(str) { first = get_first_field(str) printf("(%s)", first) } These two functions simply isolate out some code that is used repeatedly, making the whole program shorter and cleaner. In particular, moving the check for the empty string into 'get_first_field()' saves several lines of code. Finally, 'fill_info_for_user()' fetches user, group, and group set information for the user named on the command. The code is fairly straightforward, merely requiring that we exit if the given user doesn't exist: function fill_info_for_user(user, pwent, fields, groupnames, grent, groups, i) { pwent = getpwnam(user) if (pwent == "") { printf("id: '%s': no such user\n", user) > "/dev/stderr" exit 1 } split(pwent, fields, ":") uid = fields[3] + 0 gid = fields[4] + 0 Getting the group set is a little awkward. The library routine 'getgruser()' returns a list of group _names_. These have to be gone through and turned back into group numbers, so that the rest of the code will work as expected: groupnames = getgruser(user) split(groupnames, groups, " ") for (i = 1; i in groups; i++) { grent = getgrnam(groups[i]) split(grent, fields, ":") groupset[i] = fields[3] + 0 } }