aboutsummaryrefslogtreecommitdiffstats
path: root/doc
diff options
context:
space:
mode:
Diffstat (limited to 'doc')
-rw-r--r--doc/ChangeLog5
-rw-r--r--doc/gawk.info797
-rw-r--r--doc/gawk.texi345
-rw-r--r--doc/gawktexi.in345
4 files changed, 1085 insertions, 407 deletions
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