gawk-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] gawk branch, gawk-5.1-stable, updated. gawk-4.1.0-4114-g7d08b2c


From: Arnold Robbins
Subject: [SCM] gawk branch, gawk-5.1-stable, updated. gawk-4.1.0-4114-g7d08b2c
Date: Fri, 11 Sep 2020 05:00:06 -0400 (EDT)

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "gawk".

The branch, gawk-5.1-stable has been updated
       via  7d08b2cd4dd4af16bac395bf1b93d7ed03cdca09 (commit)
       via  fbb3e167f9f7e3e6b1372bf3088393b6e894b2cf (commit)
       via  2f2803957202a42b7dbfc00c62affe23b5c70d76 (commit)
      from  a9440d51fdf9286dc657b10368503aecb74eb19d (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://git.sv.gnu.org/cgit/gawk.git/commit/?id=7d08b2cd4dd4af16bac395bf1b93d7ed03cdca09

commit 7d08b2cd4dd4af16bac395bf1b93d7ed03cdca09
Author: Arnold D. Robbins <arnold@skeeve.com>
Date:   Fri Sep 11 11:59:40 2020 +0300

    Fix small doc issue.

diff --git a/awklib/eg/prog/id.awk b/awklib/eg/prog/id.awk
index 50c40c0..ff89203 100644
--- a/awklib/eg/prog/id.awk
+++ b/awklib/eg/prog/id.awk
@@ -160,9 +160,7 @@ function fill_info_for_user(user,
     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++) {
diff --git a/doc/gawk.texi b/doc/gawk.texi
index 82427bd..8057350 100644
--- a/doc/gawk.texi
+++ b/doc/gawk.texi
@@ -25959,10 +25959,12 @@ 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
+@c file eg/prog/id.awk
 
+@c endfile
 @end ignore
+@c file eg/prog/id.awk
     groupnames = getgruser(user)
     split(groupnames, groups, " ")
     for (i = 1; i in groups; i++) @{
diff --git a/doc/gawktexi.in b/doc/gawktexi.in
index 1691408..5a11434 100644
--- a/doc/gawktexi.in
+++ b/doc/gawktexi.in
@@ -24969,10 +24969,12 @@ 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
+@c file eg/prog/id.awk
 
+@c endfile
 @end ignore
+@c file eg/prog/id.awk
     groupnames = getgruser(user)
     split(groupnames, groups, " ")
     for (i = 1; i in groups; i++) @{

http://git.sv.gnu.org/cgit/gawk.git/commit/?id=fbb3e167f9f7e3e6b1372bf3088393b6e894b2cf

commit fbb3e167f9f7e3e6b1372bf3088393b6e894b2cf
Author: Arnold D. Robbins <arnold@skeeve.com>
Date:   Fri Sep 11 11:46:54 2020 +0300

    Update aux files.

diff --git a/build-aux/ChangeLog b/build-aux/ChangeLog
index 025a3ef..8f07b4c 100644
--- a/build-aux/ChangeLog
+++ b/build-aux/ChangeLog
@@ -1,3 +1,7 @@
+2020-09-11         Arnold D. Robbins     <arnold@skeeve.com>
+
+       * config.sub: Updated from GNULIB.
+
 2020-08-18         Arnold D. Robbins     <arnold@skeeve.com>
 
        * config.guess, config.sub: Updated from GNULIB.
diff --git a/build-aux/config.sub b/build-aux/config.sub
index 0753e30..780c2bb 100755
--- a/build-aux/config.sub
+++ b/build-aux/config.sub
@@ -2,7 +2,7 @@
 # Configuration validation subroutine script.
 #   Copyright 1992-2020 Free Software Foundation, Inc.
 
-timestamp='2020-08-17'
+timestamp='2020-09-08'
 
 # This file is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by
@@ -1367,13 +1367,7 @@ case $os in
                os=psos
                ;;
        qnx*)
-               case $cpu in
-                   x86 | i*86)
-                       ;;
-                   *)
-                       os=nto-$os
-                       ;;
-               esac
+               os=qnx
                ;;
        hiux*)
                os=hiuxwe2
@@ -1722,7 +1716,7 @@ case $os in
             | skyos* | haiku* | rdos* | toppers* | drops* | es* \
             | onefs* | tirtos* | phoenix* | fuchsia* | redox* | bme* \
             | midnightbsd* | amdhsa* | unleashed* | emscripten* | wasi* \
-            | nsk* | powerunix* | genode* | zvmoe* )
+            | nsk* | powerunix* | genode* | zvmoe* | qnx* )
                ;;
        # This one is extra strict with allowed versions
        sco3.2v2 | sco3.2v[4-9]* | sco5v6*)

http://git.sv.gnu.org/cgit/gawk.git/commit/?id=2f2803957202a42b7dbfc00c62affe23b5c70d76

commit 2f2803957202a42b7dbfc00c62affe23b5c70d76
Author: Arnold D. Robbins <arnold@skeeve.com>
Date:   Fri Sep 11 11:45:49 2020 +0300

    Update id program to POSIX.

diff --git a/awklib/eg/prog/id.awk b/awklib/eg/prog/id.awk
index b6061f9..50c40c0 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 a032b4d..765ce40 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 865286f..4e90d5b 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 686a0b7..82427bd 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 ceaa64b..1691408 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

-----------------------------------------------------------------------

Summary of changes:
 awklib/eg/prog/id.awk | 152 ++++++++--
 build-aux/ChangeLog   |   4 +
 build-aux/config.sub  |  12 +-
 doc/ChangeLog         |   5 +
 doc/gawk.info         | 797 +++++++++++++++++++++++++++++++-------------------
 doc/gawk.texi         | 347 ++++++++++++++++++----
 doc/gawktexi.in       | 347 ++++++++++++++++++----
 7 files changed, 1227 insertions(+), 437 deletions(-)


hooks/post-receive
-- 
gawk



reply via email to

[Prev in Thread] Current Thread [Next in Thread]