diff options
-rw-r--r-- | awklib/eg/prog/id.awk | 154 | ||||
-rw-r--r-- | doc/ChangeLog | 5 | ||||
-rw-r--r-- | doc/gawk.info | 797 | ||||
-rw-r--r-- | doc/gawk.texi | 345 | ||||
-rw-r--r-- | doc/gawktexi.in | 345 |
5 files changed, 1218 insertions, 428 deletions
diff --git a/awklib/eg/prog/id.awk b/awklib/eg/prog/id.awk index b6061f9b..50c40c05 100644 --- a/awklib/eg/prog/id.awk +++ b/awklib/eg/prog/id.awk @@ -1,61 +1,173 @@ # id.awk --- implement id in awk # -# Requires user and group library functions +# Requires user and group library functions and getopt # # Arnold Robbins, arnold@skeeve.com, Public Domain # May 1993 # Revised February 1996 # Revised May 2014 # Revised September 2014 +# Revised September 2020 # 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 Ouput 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 +} BEGIN { - uid = PROCINFO["uid"] - euid = PROCINFO["euid"] - gid = PROCINFO["gid"] - egid = PROCINFO["egid"] + # 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() + } + if (groupset_only && real_ids_only) + usage() + else if (ARGC - Optind > 1) + usage() + 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++ + } + 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 + } + 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 + } + 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 + } printf("uid=%d", uid) pw = getpwuid(uid) - pr_first_field(pw) - - if (euid != uid) { + print_first_field(pw) + if (euid != uid && ! real_ids_only) { printf(" euid=%d", euid) pw = getpwuid(euid) - pr_first_field(pw) + print_first_field(pw) } - printf(" gid=%d", gid) pw = getgrgid(gid) - pr_first_field(pw) + print_first_field(pw) - if (egid != gid) { + if (egid != gid && ! real_ids_only) { printf(" egid=%d", egid) pw = getgrgid(egid) - pr_first_field(pw) + print_first_field(pw) } - - for (i = 1; ("group" i) in PROCINFO; i++) { + for (i = 1; i in groupset; i++) { if (i == 1) printf(" groups=") - group = PROCINFO["group" i] + group = groupset[i] printf("%d", group) pw = getgrgid(group) - pr_first_field(pw) - if (("group" (i+1)) in PROCINFO) + print_first_field(pw) + if ((i + 1) in groupset) printf(",") } print "" } - -function pr_first_field(str, a) +function get_first_field(str, a) { if (str != "") { split(str, a, ":") - printf("(%s)", a[1]) + return a[1] + } +} +function print_first_field(str) +{ + first = get_first_field(str) + printf("(%s)", first) +} +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 +ignore + +end ignore + 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 } } diff --git a/doc/ChangeLog b/doc/ChangeLog index a032b4da..765ce401 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,8 @@ +2020-09-11 Arnold D. Robbins <arnold@skeeve.com> + + * gawktexi.in (Id Program): Rewrite to be POSIX compliant. + Update explanatory text as well. + 2020-09-04 Arnold D. Robbins <arnold@skeeve.com> * gawktexi.in: Index BWK quotes separately. Finish using "BWK awk" diff --git a/doc/gawk.info b/doc/gawk.info index 865286f6..4e90d5b7 100644 --- a/doc/gawk.info +++ b/doc/gawk.info @@ -18112,91 +18112,274 @@ user and group names. The output might look like this: array (*note Built-in Variables::). However, the 'id' utility provides a more palatable output than just individual numbers. - Here is a simple version of 'id' written in 'awk'. It uses the user -database library functions (*note Passwd Functions::) and the group -database library functions (*note Group Functions::) from *note Library + 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 fairly straightforward. All the work is done in the -'BEGIN' rule. The user and group ID numbers are obtained from -'PROCINFO'. The code is repetitive. The entry in the user database for -the real user ID number is split into parts at the ':'. The name is the -first field. Similar code is used for the effective user ID number and -the group numbers: + 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 + # 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 Ouput 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 { - uid = PROCINFO["uid"] - euid = PROCINFO["euid"] - gid = PROCINFO["gid"] - egid = PROCINFO["egid"] + # 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) - pr_first_field(pw) + print_first_field(pw) - if (euid != uid) { + 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) - pr_first_field(pw) + print_first_field(pw) } + Similar logic applies to the real and effective group IDs: + printf(" gid=%d", gid) pw = getgrgid(gid) - pr_first_field(pw) + print_first_field(pw) - if (egid != gid) { + if (egid != gid && ! real_ids_only) { printf(" egid=%d", egid) pw = getgrgid(egid) - pr_first_field(pw) + print_first_field(pw) } - for (i = 1; ("group" i) in PROCINFO; i++) { + Finally, we print the group set and the terminating newline: + + for (i = 1; i in groupset; i++) { if (i == 1) printf(" groups=") - group = PROCINFO["group" i] + group = groupset[i] printf("%d", group) pw = getgrgid(group) - pr_first_field(pw) - if (("group" (i+1)) in PROCINFO) + print_first_field(pw) + if ((i + 1) in groupset) printf(",") } print "" } - function pr_first_field(str, a) + 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, ":") - printf("(%s)", a[1]) + return a[1] } } - 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 function is then used by 'print_first_field()' to output the +given name surrounded by parentheses: - 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' is incremented past -the last group in the array and the loop exits. + function print_first_field(str) + { + first = get_first_field(str) + printf("(%s)", first) + } - 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. + 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: - The 'pr_first_field()' function simply isolates out some code that is -used repeatedly, making the whole program shorter and cleaner. In -particular, moving the check for the empty string into this function -saves several lines of code. + 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 + } + } File: gawk.info, Node: Split Program, Next: Tee Program, Prev: Id Program, Up: Clones @@ -35916,7 +36099,7 @@ Index * hyphen (-), -= operator <1>: Precedence. (line 94) * i debugger command (alias for info): Debugger Info. (line 13) * id utility: Id Program. (line 6) -* id.awk program: Id Program. (line 31) +* id.awk program: Id Program. (line 34) * if statement, use of regexps in: Regexp Usage. (line 19) * if statement, actions, changing: Ranges. (line 25) * if statement: If Statement. (line 6) @@ -37744,271 +37927,271 @@ Node: Cut Program728246 Node: Egrep Program738175 Ref: Egrep Program-Footnote-1745687 Node: Id Program745797 -Node: Split Program749477 -Ref: Split Program-Footnote-1752935 -Node: Tee Program753064 -Node: Uniq Program755854 -Node: Wc Program763418 -Ref: Wc Program-Footnote-1767673 -Node: Miscellaneous Programs767767 -Node: Dupword Program768980 -Node: Alarm Program771010 -Node: Translate Program775865 -Ref: Translate Program-Footnote-1780430 -Node: Labels Program780700 -Ref: Labels Program-Footnote-1784051 -Node: Word Sorting784135 -Node: History Sorting788207 -Node: Extract Program790432 -Node: Simple Sed798486 -Node: Igawk Program801560 -Ref: Igawk Program-Footnote-1815891 -Ref: Igawk Program-Footnote-2816093 -Ref: Igawk Program-Footnote-3816215 -Node: Anagram Program816330 -Node: Signature Program819392 -Node: Programs Summary820639 -Node: Programs Exercises821853 -Ref: Programs Exercises-Footnote-1825983 -Node: Advanced Features826069 -Node: Nondecimal Data828059 -Node: Array Sorting829650 -Node: Controlling Array Traversal830350 -Ref: Controlling Array Traversal-Footnote-1838718 -Node: Array Sorting Functions838836 -Ref: Array Sorting Functions-Footnote-1843927 -Node: Two-way I/O844123 -Ref: Two-way I/O-Footnote-1851844 -Ref: Two-way I/O-Footnote-2852031 -Node: TCP/IP Networking852113 -Node: Profiling855231 -Node: Advanced Features Summary864545 -Node: Internationalization866389 -Node: I18N and L10N867869 -Node: Explaining gettext868556 -Ref: Explaining gettext-Footnote-1874448 -Ref: Explaining gettext-Footnote-2874633 -Node: Programmer i18n874798 -Ref: Programmer i18n-Footnote-1879747 -Node: Translator i18n879796 -Node: String Extraction880590 -Ref: String Extraction-Footnote-1881722 -Node: Printf Ordering881808 -Ref: Printf Ordering-Footnote-1884594 -Node: I18N Portability884658 -Ref: I18N Portability-Footnote-1887114 -Node: I18N Example887177 -Ref: I18N Example-Footnote-1890452 -Ref: I18N Example-Footnote-2890525 -Node: Gawk I18N890634 -Node: I18N Summary891283 -Node: Debugger892624 -Node: Debugging893624 -Node: Debugging Concepts894065 -Node: Debugging Terms895874 -Node: Awk Debugging898449 -Ref: Awk Debugging-Footnote-1899394 -Node: Sample Debugging Session899526 -Node: Debugger Invocation900060 -Node: Finding The Bug901446 -Node: List of Debugger Commands907920 -Node: Breakpoint Control909253 -Node: Debugger Execution Control912947 -Node: Viewing And Changing Data916309 -Node: Execution Stack919850 -Node: Debugger Info921487 -Node: Miscellaneous Debugger Commands925558 -Node: Readline Support930620 -Node: Limitations931516 -Node: Debugging Summary934070 -Node: Namespaces935349 -Node: Global Namespace936460 -Node: Qualified Names937858 -Node: Default Namespace938857 -Node: Changing The Namespace939598 -Node: Naming Rules941212 -Node: Internal Name Management943060 -Node: Namespace Example944102 -Node: Namespace And Features946664 -Node: Namespace Summary948099 -Node: Arbitrary Precision Arithmetic949576 -Node: Computer Arithmetic951063 -Ref: table-numeric-ranges954829 -Ref: table-floating-point-ranges955322 -Ref: Computer Arithmetic-Footnote-1955980 -Node: Math Definitions956037 -Ref: table-ieee-formats959353 -Ref: Math Definitions-Footnote-1959956 -Node: MPFR features960061 -Node: FP Math Caution961779 -Ref: FP Math Caution-Footnote-1962851 -Node: Inexactness of computations963220 -Node: Inexact representation964180 -Node: Comparing FP Values965540 -Node: Errors accumulate966781 -Node: Getting Accuracy968214 -Node: Try To Round970924 -Node: Setting precision971823 -Ref: table-predefined-precision-strings972520 -Node: Setting the rounding mode974350 -Ref: table-gawk-rounding-modes974724 -Ref: Setting the rounding mode-Footnote-1978655 -Node: Arbitrary Precision Integers978834 -Ref: Arbitrary Precision Integers-Footnote-1982009 -Node: Checking for MPFR982158 -Node: POSIX Floating Point Problems983632 -Ref: POSIX Floating Point Problems-Footnote-1987917 -Node: Floating point summary987955 -Node: Dynamic Extensions990145 -Node: Extension Intro991698 -Node: Plugin License992964 -Node: Extension Mechanism Outline993761 -Ref: figure-load-extension994200 -Ref: figure-register-new-function995765 -Ref: figure-call-new-function996857 -Node: Extension API Description998919 -Node: Extension API Functions Introduction1000632 -Ref: table-api-std-headers1002468 -Node: General Data Types1006717 -Ref: General Data Types-Footnote-11015347 -Node: Memory Allocation Functions1015646 -Ref: Memory Allocation Functions-Footnote-11020147 -Node: Constructor Functions1020246 -Node: API Ownership of MPFR and GMP Values1023712 -Node: Registration Functions1025025 -Node: Extension Functions1025725 -Node: Exit Callback Functions1031047 -Node: Extension Version String1032297 -Node: Input Parsers1032960 -Node: Output Wrappers1045681 -Node: Two-way processors1050193 -Node: Printing Messages1052458 -Ref: Printing Messages-Footnote-11053629 -Node: Updating ERRNO1053782 -Node: Requesting Values1054521 -Ref: table-value-types-returned1055258 -Node: Accessing Parameters1056194 -Node: Symbol Table Access1057431 -Node: Symbol table by name1057943 -Ref: Symbol table by name-Footnote-11060967 -Node: Symbol table by cookie1061095 -Ref: Symbol table by cookie-Footnote-11065280 -Node: Cached values1065344 -Ref: Cached values-Footnote-11068880 -Node: Array Manipulation1069033 -Ref: Array Manipulation-Footnote-11070124 -Node: Array Data Types1070161 -Ref: Array Data Types-Footnote-11072819 -Node: Array Functions1072911 -Node: Flattening Arrays1077409 -Node: Creating Arrays1084385 -Node: Redirection API1089152 -Node: Extension API Variables1091985 -Node: Extension Versioning1092696 -Ref: gawk-api-version1093125 -Node: Extension GMP/MPFR Versioning1094856 -Node: Extension API Informational Variables1096484 -Node: Extension API Boilerplate1097557 -Node: Changes from API V11101531 -Node: Finding Extensions1103103 -Node: Extension Example1103662 -Node: Internal File Description1104460 -Node: Internal File Ops1108540 -Ref: Internal File Ops-Footnote-11119890 -Node: Using Internal File Ops1120030 -Ref: Using Internal File Ops-Footnote-11122413 -Node: Extension Samples1122687 -Node: Extension Sample File Functions1124216 -Node: Extension Sample Fnmatch1131865 -Node: Extension Sample Fork1133352 -Node: Extension Sample Inplace1134570 -Node: Extension Sample Ord1138195 -Node: Extension Sample Readdir1139031 -Ref: table-readdir-file-types1139920 -Node: Extension Sample Revout1140987 -Node: Extension Sample Rev2way1141576 -Node: Extension Sample Read write array1142316 -Node: Extension Sample Readfile1144258 -Node: Extension Sample Time1145353 -Node: Extension Sample API Tests1147105 -Node: gawkextlib1147597 -Node: Extension summary1150515 -Node: Extension Exercises1154217 -Node: Language History1155459 -Node: V7/SVR3.11157115 -Node: SVR41159267 -Node: POSIX1160701 -Node: BTL1162082 -Node: POSIX/GNU1162811 -Node: Feature History1168589 -Node: Common Extensions1184908 -Node: Ranges and Locales1186191 -Ref: Ranges and Locales-Footnote-11190807 -Ref: Ranges and Locales-Footnote-21190834 -Ref: Ranges and Locales-Footnote-31191069 -Node: Contributors1191292 -Node: History summary1197289 -Node: Installation1198669 -Node: Gawk Distribution1199613 -Node: Getting1200097 -Node: Extracting1201060 -Node: Distribution contents1202698 -Node: Unix Installation1209178 -Node: Quick Installation1209860 -Node: Shell Startup Files1212274 -Node: Additional Configuration Options1213363 -Node: Configuration Philosophy1215678 -Node: Non-Unix Installation1218047 -Node: PC Installation1218507 -Node: PC Binary Installation1219345 -Node: PC Compiling1219780 -Node: PC Using1220897 -Node: Cygwin1224450 -Node: MSYS1225674 -Node: VMS Installation1226276 -Node: VMS Compilation1227067 -Ref: VMS Compilation-Footnote-11228296 -Node: VMS Dynamic Extensions1228354 -Node: VMS Installation Details1230039 -Node: VMS Running1232292 -Node: VMS GNV1236571 -Node: VMS Old Gawk1237306 -Node: Bugs1237777 -Node: Bug address1238440 -Node: Usenet1241422 -Node: Maintainers1242426 -Node: Other Versions1243611 -Node: Installation summary1250699 -Node: Notes1251908 -Node: Compatibility Mode1252702 -Node: Additions1253484 -Node: Accessing The Source1254409 -Node: Adding Code1255846 -Node: New Ports1262065 -Node: Derived Files1266440 -Ref: Derived Files-Footnote-11272100 -Ref: Derived Files-Footnote-21272135 -Ref: Derived Files-Footnote-31272733 -Node: Future Extensions1272847 -Node: Implementation Limitations1273505 -Node: Extension Design1274715 -Node: Old Extension Problems1275859 -Ref: Old Extension Problems-Footnote-11277377 -Node: Extension New Mechanism Goals1277434 -Ref: Extension New Mechanism Goals-Footnote-11280798 -Node: Extension Other Design Decisions1280987 -Node: Extension Future Growth1283100 -Node: Notes summary1283706 -Node: Basic Concepts1284864 -Node: Basic High Level1285545 -Ref: figure-general-flow1285827 -Ref: figure-process-flow1286512 -Ref: Basic High Level-Footnote-11289813 -Node: Basic Data Typing1289998 -Node: Glossary1293326 -Node: Copying1325211 -Node: GNU Free Documentation License1362754 -Node: Index1387874 +Node: Split Program755743 +Ref: Split Program-Footnote-1759201 +Node: Tee Program759330 +Node: Uniq Program762120 +Node: Wc Program769684 +Ref: Wc Program-Footnote-1773939 +Node: Miscellaneous Programs774033 +Node: Dupword Program775246 +Node: Alarm Program777276 +Node: Translate Program782131 +Ref: Translate Program-Footnote-1786696 +Node: Labels Program786966 +Ref: Labels Program-Footnote-1790317 +Node: Word Sorting790401 +Node: History Sorting794473 +Node: Extract Program796698 +Node: Simple Sed804752 +Node: Igawk Program807826 +Ref: Igawk Program-Footnote-1822157 +Ref: Igawk Program-Footnote-2822359 +Ref: Igawk Program-Footnote-3822481 +Node: Anagram Program822596 +Node: Signature Program825658 +Node: Programs Summary826905 +Node: Programs Exercises828119 +Ref: Programs Exercises-Footnote-1832249 +Node: Advanced Features832335 +Node: Nondecimal Data834325 +Node: Array Sorting835916 +Node: Controlling Array Traversal836616 +Ref: Controlling Array Traversal-Footnote-1844984 +Node: Array Sorting Functions845102 +Ref: Array Sorting Functions-Footnote-1850193 +Node: Two-way I/O850389 +Ref: Two-way I/O-Footnote-1858110 +Ref: Two-way I/O-Footnote-2858297 +Node: TCP/IP Networking858379 +Node: Profiling861497 +Node: Advanced Features Summary870811 +Node: Internationalization872655 +Node: I18N and L10N874135 +Node: Explaining gettext874822 +Ref: Explaining gettext-Footnote-1880714 +Ref: Explaining gettext-Footnote-2880899 +Node: Programmer i18n881064 +Ref: Programmer i18n-Footnote-1886013 +Node: Translator i18n886062 +Node: String Extraction886856 +Ref: String Extraction-Footnote-1887988 +Node: Printf Ordering888074 +Ref: Printf Ordering-Footnote-1890860 +Node: I18N Portability890924 +Ref: I18N Portability-Footnote-1893380 +Node: I18N Example893443 +Ref: I18N Example-Footnote-1896718 +Ref: I18N Example-Footnote-2896791 +Node: Gawk I18N896900 +Node: I18N Summary897549 +Node: Debugger898890 +Node: Debugging899890 +Node: Debugging Concepts900331 +Node: Debugging Terms902140 +Node: Awk Debugging904715 +Ref: Awk Debugging-Footnote-1905660 +Node: Sample Debugging Session905792 +Node: Debugger Invocation906326 +Node: Finding The Bug907712 +Node: List of Debugger Commands914186 +Node: Breakpoint Control915519 +Node: Debugger Execution Control919213 +Node: Viewing And Changing Data922575 +Node: Execution Stack926116 +Node: Debugger Info927753 +Node: Miscellaneous Debugger Commands931824 +Node: Readline Support936886 +Node: Limitations937782 +Node: Debugging Summary940336 +Node: Namespaces941615 +Node: Global Namespace942726 +Node: Qualified Names944124 +Node: Default Namespace945123 +Node: Changing The Namespace945864 +Node: Naming Rules947478 +Node: Internal Name Management949326 +Node: Namespace Example950368 +Node: Namespace And Features952930 +Node: Namespace Summary954365 +Node: Arbitrary Precision Arithmetic955842 +Node: Computer Arithmetic957329 +Ref: table-numeric-ranges961095 +Ref: table-floating-point-ranges961588 +Ref: Computer Arithmetic-Footnote-1962246 +Node: Math Definitions962303 +Ref: table-ieee-formats965619 +Ref: Math Definitions-Footnote-1966222 +Node: MPFR features966327 +Node: FP Math Caution968045 +Ref: FP Math Caution-Footnote-1969117 +Node: Inexactness of computations969486 +Node: Inexact representation970446 +Node: Comparing FP Values971806 +Node: Errors accumulate973047 +Node: Getting Accuracy974480 +Node: Try To Round977190 +Node: Setting precision978089 +Ref: table-predefined-precision-strings978786 +Node: Setting the rounding mode980616 +Ref: table-gawk-rounding-modes980990 +Ref: Setting the rounding mode-Footnote-1984921 +Node: Arbitrary Precision Integers985100 +Ref: Arbitrary Precision Integers-Footnote-1988275 +Node: Checking for MPFR988424 +Node: POSIX Floating Point Problems989898 +Ref: POSIX Floating Point Problems-Footnote-1994183 +Node: Floating point summary994221 +Node: Dynamic Extensions996411 +Node: Extension Intro997964 +Node: Plugin License999230 +Node: Extension Mechanism Outline1000027 +Ref: figure-load-extension1000466 +Ref: figure-register-new-function1002031 +Ref: figure-call-new-function1003123 +Node: Extension API Description1005185 +Node: Extension API Functions Introduction1006898 +Ref: table-api-std-headers1008734 +Node: General Data Types1012983 +Ref: General Data Types-Footnote-11021613 +Node: Memory Allocation Functions1021912 +Ref: Memory Allocation Functions-Footnote-11026413 +Node: Constructor Functions1026512 +Node: API Ownership of MPFR and GMP Values1029978 +Node: Registration Functions1031291 +Node: Extension Functions1031991 +Node: Exit Callback Functions1037313 +Node: Extension Version String1038563 +Node: Input Parsers1039226 +Node: Output Wrappers1051947 +Node: Two-way processors1056459 +Node: Printing Messages1058724 +Ref: Printing Messages-Footnote-11059895 +Node: Updating ERRNO1060048 +Node: Requesting Values1060787 +Ref: table-value-types-returned1061524 +Node: Accessing Parameters1062460 +Node: Symbol Table Access1063697 +Node: Symbol table by name1064209 +Ref: Symbol table by name-Footnote-11067233 +Node: Symbol table by cookie1067361 +Ref: Symbol table by cookie-Footnote-11071546 +Node: Cached values1071610 +Ref: Cached values-Footnote-11075146 +Node: Array Manipulation1075299 +Ref: Array Manipulation-Footnote-11076390 +Node: Array Data Types1076427 +Ref: Array Data Types-Footnote-11079085 +Node: Array Functions1079177 +Node: Flattening Arrays1083675 +Node: Creating Arrays1090651 +Node: Redirection API1095418 +Node: Extension API Variables1098251 +Node: Extension Versioning1098962 +Ref: gawk-api-version1099391 +Node: Extension GMP/MPFR Versioning1101122 +Node: Extension API Informational Variables1102750 +Node: Extension API Boilerplate1103823 +Node: Changes from API V11107797 +Node: Finding Extensions1109369 +Node: Extension Example1109928 +Node: Internal File Description1110726 +Node: Internal File Ops1114806 +Ref: Internal File Ops-Footnote-11126156 +Node: Using Internal File Ops1126296 +Ref: Using Internal File Ops-Footnote-11128679 +Node: Extension Samples1128953 +Node: Extension Sample File Functions1130482 +Node: Extension Sample Fnmatch1138131 +Node: Extension Sample Fork1139618 +Node: Extension Sample Inplace1140836 +Node: Extension Sample Ord1144461 +Node: Extension Sample Readdir1145297 +Ref: table-readdir-file-types1146186 +Node: Extension Sample Revout1147253 +Node: Extension Sample Rev2way1147842 +Node: Extension Sample Read write array1148582 +Node: Extension Sample Readfile1150524 +Node: Extension Sample Time1151619 +Node: Extension Sample API Tests1153371 +Node: gawkextlib1153863 +Node: Extension summary1156781 +Node: Extension Exercises1160483 +Node: Language History1161725 +Node: V7/SVR3.11163381 +Node: SVR41165533 +Node: POSIX1166967 +Node: BTL1168348 +Node: POSIX/GNU1169077 +Node: Feature History1174855 +Node: Common Extensions1191174 +Node: Ranges and Locales1192457 +Ref: Ranges and Locales-Footnote-11197073 +Ref: Ranges and Locales-Footnote-21197100 +Ref: Ranges and Locales-Footnote-31197335 +Node: Contributors1197558 +Node: History summary1203555 +Node: Installation1204935 +Node: Gawk Distribution1205879 +Node: Getting1206363 +Node: Extracting1207326 +Node: Distribution contents1208964 +Node: Unix Installation1215444 +Node: Quick Installation1216126 +Node: Shell Startup Files1218540 +Node: Additional Configuration Options1219629 +Node: Configuration Philosophy1221944 +Node: Non-Unix Installation1224313 +Node: PC Installation1224773 +Node: PC Binary Installation1225611 +Node: PC Compiling1226046 +Node: PC Using1227163 +Node: Cygwin1230716 +Node: MSYS1231940 +Node: VMS Installation1232542 +Node: VMS Compilation1233333 +Ref: VMS Compilation-Footnote-11234562 +Node: VMS Dynamic Extensions1234620 +Node: VMS Installation Details1236305 +Node: VMS Running1238558 +Node: VMS GNV1242837 +Node: VMS Old Gawk1243572 +Node: Bugs1244043 +Node: Bug address1244706 +Node: Usenet1247688 +Node: Maintainers1248692 +Node: Other Versions1249877 +Node: Installation summary1256965 +Node: Notes1258174 +Node: Compatibility Mode1258968 +Node: Additions1259750 +Node: Accessing The Source1260675 +Node: Adding Code1262112 +Node: New Ports1268331 +Node: Derived Files1272706 +Ref: Derived Files-Footnote-11278366 +Ref: Derived Files-Footnote-21278401 +Ref: Derived Files-Footnote-31278999 +Node: Future Extensions1279113 +Node: Implementation Limitations1279771 +Node: Extension Design1280981 +Node: Old Extension Problems1282125 +Ref: Old Extension Problems-Footnote-11283643 +Node: Extension New Mechanism Goals1283700 +Ref: Extension New Mechanism Goals-Footnote-11287064 +Node: Extension Other Design Decisions1287253 +Node: Extension Future Growth1289366 +Node: Notes summary1289972 +Node: Basic Concepts1291130 +Node: Basic High Level1291811 +Ref: figure-general-flow1292093 +Ref: figure-process-flow1292778 +Ref: Basic High Level-Footnote-11296079 +Node: Basic Data Typing1296264 +Node: Glossary1299592 +Node: Copying1331477 +Node: GNU Free Documentation License1369020 +Node: Index1394140 End Tag Table diff --git a/doc/gawk.texi b/doc/gawk.texi index 686a0b71..82427bda 100644 --- a/doc/gawk.texi +++ b/doc/gawk.texi @@ -59,7 +59,7 @@ @c applies to and all the info about who's publishing this edition @c These apply across the board. -@set UPDATE-MONTH August, 2020 +@set UPDATE-MONTH September, 2020 @set VERSION 5.1 @set PATCHLEVEL 0 @@ -25613,27 +25613,31 @@ This information is part of what is provided by @command{gawk}'s However, the @command{id} utility provides a more palatable output than just individual numbers. -Here is a simple version of @command{id} written in @command{awk}. -It uses the user database library functions -(@pxref{Passwd Functions}) +The POSIX version of @command{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 @command{id} written in @command{awk}. +It uses the @code{getopt()} library function +(@pxref{Getopt Function}), +the user database library functions +(@pxref{Passwd Functions}), and the group database library functions (@pxref{Group Functions}) from @ref{Library Functions}. -The program is fairly straightforward. All the work is done in the -@code{BEGIN} rule. The user and group ID numbers are obtained from -@code{PROCINFO}. -The code is repetitive. The entry in the user database for the real user ID -number is split into parts at the @samp{:}. The name is the first field. -Similar code is used for the effective user ID number and the group -numbers: +The program is moderately straightforward. All the work is done in the +@code{BEGIN} rule. +It starts with explanatory comments, a list of options, +and then a @code{usage()} function: @cindex @code{id.awk} program @example @c file eg/prog/id.awk # id.awk --- implement id in awk # -# Requires user and group library functions +# Requires user and group library functions and getopt @c endfile @ignore @c file eg/prog/id.awk @@ -25643,6 +25647,7 @@ numbers: # Revised February 1996 # Revised May 2014 # Revised September 2014 +# Revised September 2020 @c endfile @end ignore @@ -25651,83 +25656,323 @@ numbers: # 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 Ouput 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 + @group -BEGIN @{ - uid = PROCINFO["uid"] - euid = PROCINFO["euid"] - gid = PROCINFO["gid"] - egid = PROCINFO["egid"] +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 +@} @end group +@c endfile +@end example + +The first step is to parse the options using @code{getopt()}, +and to set various flag variables according to the options given: + +@example +@c file eg/prog/id.awk +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() + @} +@c endfile +@end example + +The next step is to check that no conflicting options were +provided. @option{-G} and @option{-r} are mutually exclusive. +It is also not allowed to provide more than one user name +on the command line: + +@example +@c file eg/prog/id.awk + if (groupset_only && real_ids_only) + usage() + else if (ARGC - Optind > 1) + usage() +@c endfile +@end example + +The user and group ID numbers are obtained from +@code{PROCINFO} for the current user, or from the +user and password databases for a user supplied on +the command line. In the latter case, @code{real_ids_only} +is set, since it's not possible to print information about +the effective user and group IDs: + +@example +@c file eg/prog/id.awk + 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++ + @} +@c endfile +@end example + +The test in the @code{for} loop is worth noting. +Any supplementary groups in the @code{PROCINFO} array have the +indices @code{"group1"} through @code{"group@var{N}"} for some +@var{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 +@code{"group"}, and then using @code{in} to see if that value is +in the array (@pxref{Reference to Elements}). Eventually, @code{i} increments past +the last group in the array and the loop exits. + +The loop is also correct if there are @emph{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 @option{-G} (print just the group set), we then select +whether to print names or numbers. In either case, when done +we exit: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example + +Otherwise, for @option{-g} (effective group ID only), we +check if @option{-r} was also provided, in which case we +use the real group ID. Then based on @option{-n}, we decide +whether to print names or numbers. Here too, when done, +we exit: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example +The @code{get_first_field()} function extracts the group name from +the group database entry for the given group ID. + +Similar processing logic applies to @option{-u} (effective user ID only), +combined with @option{-r} and @option{-n}: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example + +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: + +@example +@c file eg/prog/id.awk printf("uid=%d", uid) pw = getpwuid(uid) - pr_first_field(pw) + print_first_field(pw) +@c endfile +@end example -@group - if (euid != uid) @{ +The @code{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: + +@example +@c file eg/prog/id.awk + if (euid != uid && ! real_ids_only) @{ printf(" euid=%d", euid) pw = getpwuid(euid) -@end group -@group - pr_first_field(pw) + print_first_field(pw) @} -@end group +@c endfile +@end example + +Similar logic applies to the real and effective group IDs: +@example +@c file eg/prog/id.awk printf(" gid=%d", gid) pw = getgrgid(gid) - pr_first_field(pw) + print_first_field(pw) - if (egid != gid) @{ + if (egid != gid && ! real_ids_only) @{ printf(" egid=%d", egid) pw = getgrgid(egid) - pr_first_field(pw) + print_first_field(pw) @} +@c endfile +@end example + +Finally, we print the group set and the terminating newline: - for (i = 1; ("group" i) in PROCINFO; i++) @{ +@example +@c file eg/prog/id.awk + for (i = 1; i in groupset; i++) @{ if (i == 1) printf(" groups=") - group = PROCINFO["group" i] + group = groupset[i] printf("%d", group) pw = getgrgid(group) - pr_first_field(pw) - if (("group" (i+1)) in PROCINFO) + print_first_field(pw) + if ((i + 1) in groupset) printf(",") @} print "" @} +@c endfile +@end example + +The @code{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 @samp{:} characters: -function pr_first_field(str, a) +@example +@c file eg/prog/id.awk +function get_first_field(str, a) @{ if (str != "") @{ split(str, a, ":") - printf("(%s)", a[1]) + return a[1] @} @} @c endfile @end example -The test in the @code{for} loop is worth noting. -Any supplementary groups in the @code{PROCINFO} array have the -indices @code{"group1"} through @code{"group@var{N}"} for some -@var{N} (i.e., the total number of supplementary groups). -However, we don't know in advance how many of these groups -there are. +This function is then used by @code{print_first_field()} to +output the given name surrounded by parentheses: -This loop works by starting at one, concatenating the value with -@code{"group"}, and then using @code{in} to see if that value is -in the array (@pxref{Reference to Elements}). Eventually, @code{i} is incremented past -the last group in the array and the loop exits. +@example +@c file eg/prog/id.awk +function print_first_field(str) +@{ + first = get_first_field(str) + printf("(%s)", first) +@} +@c endfile +@end example -The loop is also correct if there are @emph{no} supplementary -groups; then the condition is false the first time it's -tested, and the loop body never executes. +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 @code{get_first_field()} saves several +lines of code. -The @code{pr_first_field()} function simply isolates out some -code that is used repeatedly, making the whole program -shorter and cleaner. In particular, moving the check for -the empty string into this function saves several lines of code. +Finally, @code{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: +@example +@c file eg/prog/id.awk +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 +@c endfile +@end example + +Getting the group set is a little awkward. The library routine +@code{getgruser()} returns a list of group @emph{names}. These +have to be gone through and turned back into group numbers, +so that the rest of the code will work as expected: + +@example +@c file eg/prog/id.awk +@ignore + +@end ignore + 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 + @} +@} +@c endfile +@end example @node Split Program @subsection Splitting a Large File into Pieces diff --git a/doc/gawktexi.in b/doc/gawktexi.in index ceaa64b0..16914084 100644 --- a/doc/gawktexi.in +++ b/doc/gawktexi.in @@ -54,7 +54,7 @@ @c applies to and all the info about who's publishing this edition @c These apply across the board. -@set UPDATE-MONTH August, 2020 +@set UPDATE-MONTH September, 2020 @set VERSION 5.1 @set PATCHLEVEL 0 @@ -24623,27 +24623,31 @@ This information is part of what is provided by @command{gawk}'s However, the @command{id} utility provides a more palatable output than just individual numbers. -Here is a simple version of @command{id} written in @command{awk}. -It uses the user database library functions -(@pxref{Passwd Functions}) +The POSIX version of @command{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 @command{id} written in @command{awk}. +It uses the @code{getopt()} library function +(@pxref{Getopt Function}), +the user database library functions +(@pxref{Passwd Functions}), and the group database library functions (@pxref{Group Functions}) from @ref{Library Functions}. -The program is fairly straightforward. All the work is done in the -@code{BEGIN} rule. The user and group ID numbers are obtained from -@code{PROCINFO}. -The code is repetitive. The entry in the user database for the real user ID -number is split into parts at the @samp{:}. The name is the first field. -Similar code is used for the effective user ID number and the group -numbers: +The program is moderately straightforward. All the work is done in the +@code{BEGIN} rule. +It starts with explanatory comments, a list of options, +and then a @code{usage()} function: @cindex @code{id.awk} program @example @c file eg/prog/id.awk # id.awk --- implement id in awk # -# Requires user and group library functions +# Requires user and group library functions and getopt @c endfile @ignore @c file eg/prog/id.awk @@ -24653,6 +24657,7 @@ numbers: # Revised February 1996 # Revised May 2014 # Revised September 2014 +# Revised September 2020 @c endfile @end ignore @@ -24661,83 +24666,323 @@ numbers: # 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 Ouput 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 + @group -BEGIN @{ - uid = PROCINFO["uid"] - euid = PROCINFO["euid"] - gid = PROCINFO["gid"] - egid = PROCINFO["egid"] +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 +@} @end group +@c endfile +@end example + +The first step is to parse the options using @code{getopt()}, +and to set various flag variables according to the options given: + +@example +@c file eg/prog/id.awk +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() + @} +@c endfile +@end example + +The next step is to check that no conflicting options were +provided. @option{-G} and @option{-r} are mutually exclusive. +It is also not allowed to provide more than one user name +on the command line: + +@example +@c file eg/prog/id.awk + if (groupset_only && real_ids_only) + usage() + else if (ARGC - Optind > 1) + usage() +@c endfile +@end example + +The user and group ID numbers are obtained from +@code{PROCINFO} for the current user, or from the +user and password databases for a user supplied on +the command line. In the latter case, @code{real_ids_only} +is set, since it's not possible to print information about +the effective user and group IDs: + +@example +@c file eg/prog/id.awk + 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++ + @} +@c endfile +@end example + +The test in the @code{for} loop is worth noting. +Any supplementary groups in the @code{PROCINFO} array have the +indices @code{"group1"} through @code{"group@var{N}"} for some +@var{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 +@code{"group"}, and then using @code{in} to see if that value is +in the array (@pxref{Reference to Elements}). Eventually, @code{i} increments past +the last group in the array and the loop exits. + +The loop is also correct if there are @emph{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 @option{-G} (print just the group set), we then select +whether to print names or numbers. In either case, when done +we exit: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example + +Otherwise, for @option{-g} (effective group ID only), we +check if @option{-r} was also provided, in which case we +use the real group ID. Then based on @option{-n}, we decide +whether to print names or numbers. Here too, when done, +we exit: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example +The @code{get_first_field()} function extracts the group name from +the group database entry for the given group ID. + +Similar processing logic applies to @option{-u} (effective user ID only), +combined with @option{-r} and @option{-n}: + +@example +@c file eg/prog/id.awk + 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 + @} +@c endfile +@end example + +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: + +@example +@c file eg/prog/id.awk printf("uid=%d", uid) pw = getpwuid(uid) - pr_first_field(pw) + print_first_field(pw) +@c endfile +@end example -@group - if (euid != uid) @{ +The @code{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: + +@example +@c file eg/prog/id.awk + if (euid != uid && ! real_ids_only) @{ printf(" euid=%d", euid) pw = getpwuid(euid) -@end group -@group - pr_first_field(pw) + print_first_field(pw) @} -@end group +@c endfile +@end example + +Similar logic applies to the real and effective group IDs: +@example +@c file eg/prog/id.awk printf(" gid=%d", gid) pw = getgrgid(gid) - pr_first_field(pw) + print_first_field(pw) - if (egid != gid) @{ + if (egid != gid && ! real_ids_only) @{ printf(" egid=%d", egid) pw = getgrgid(egid) - pr_first_field(pw) + print_first_field(pw) @} +@c endfile +@end example + +Finally, we print the group set and the terminating newline: - for (i = 1; ("group" i) in PROCINFO; i++) @{ +@example +@c file eg/prog/id.awk + for (i = 1; i in groupset; i++) @{ if (i == 1) printf(" groups=") - group = PROCINFO["group" i] + group = groupset[i] printf("%d", group) pw = getgrgid(group) - pr_first_field(pw) - if (("group" (i+1)) in PROCINFO) + print_first_field(pw) + if ((i + 1) in groupset) printf(",") @} print "" @} +@c endfile +@end example + +The @code{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 @samp{:} characters: -function pr_first_field(str, a) +@example +@c file eg/prog/id.awk +function get_first_field(str, a) @{ if (str != "") @{ split(str, a, ":") - printf("(%s)", a[1]) + return a[1] @} @} @c endfile @end example -The test in the @code{for} loop is worth noting. -Any supplementary groups in the @code{PROCINFO} array have the -indices @code{"group1"} through @code{"group@var{N}"} for some -@var{N} (i.e., the total number of supplementary groups). -However, we don't know in advance how many of these groups -there are. +This function is then used by @code{print_first_field()} to +output the given name surrounded by parentheses: -This loop works by starting at one, concatenating the value with -@code{"group"}, and then using @code{in} to see if that value is -in the array (@pxref{Reference to Elements}). Eventually, @code{i} is incremented past -the last group in the array and the loop exits. +@example +@c file eg/prog/id.awk +function print_first_field(str) +@{ + first = get_first_field(str) + printf("(%s)", first) +@} +@c endfile +@end example -The loop is also correct if there are @emph{no} supplementary -groups; then the condition is false the first time it's -tested, and the loop body never executes. +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 @code{get_first_field()} saves several +lines of code. -The @code{pr_first_field()} function simply isolates out some -code that is used repeatedly, making the whole program -shorter and cleaner. In particular, moving the check for -the empty string into this function saves several lines of code. +Finally, @code{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: +@example +@c file eg/prog/id.awk +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 +@c endfile +@end example + +Getting the group set is a little awkward. The library routine +@code{getgruser()} returns a list of group @emph{names}. These +have to be gone through and turned back into group numbers, +so that the rest of the code will work as expected: + +@example +@c file eg/prog/id.awk +@ignore + +@end ignore + 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 + @} +@} +@c endfile +@end example @node Split Program @subsection Splitting a Large File into Pieces |