Subject: Re: bug#47243: pr lacks -p

From: Collin Funk <[email-masked]>
Date: Mon, 28 Jul 2025 21:39:16 -0700

Paul Eggert <[email-masked]> writes:

> On 2025-07-28 10:36, Collin Funk wrote:
>> I don't really like the idea of changing '-f' depending on whether
>> POSIXLY_CORRECT is defined. So I would prefer this as well.
>
> On second thought (sorry...) I now think I understand why GNU pr
> behaves the way it does. The GNU coding standards[1] say "...please
> don’t make the behavior of a command-line program depend on the type
> of output device it gets as standard output or standard input. Device
> independence is an important principle of the system’s design; do not
> compromise it merely to save someone from typing an option now and
> then."
>
> The standards go on to list some exceptions, such as 'ls'. 'pr' is not
> one of the exceptions. Currently there are a few other exceptions in
> Coreutils ('df', 'mv', 'nohup', 'rm', 'tail') but I guess 'pr' didn't
> make the cut when it was written.
>
> That is, my theory is that when GNU 'pr' was written, its original
> author Pete TerMaat followed the GNU coding standards, and
> deliberately made '-f' and '-F' aliases, because that way the behavior
> of pr would not depend on isatty (STDOUT_FILENO). And Pete did not
> implement -p because it didn't sound useful if we follow the coding
> standards.
>
> If this theory is correct, then a fix that would be more in the spirit
> of GNU would be something like this:

Interesting, I did not know about this part of the GNU Coding Standards.

> (a) Implement -p as if isatty (STDOUT_FILENO) is true. This form of -p
> is useful only when stdout is a terminal, but that's good enough since
> nobody uses (or will use :-) -p.
>
> (b) If POSIXLY_CORRECT is set, change -f and -p so that they both
> worry about isatty (STDOUT_FILENO), as POSIX requires.
>
> This would conform to POSIX, would address Pádraig's concern about
> compatibility, and would follow the GNU coding standards.

That sounds good to me. I attached a v4 patch that should behave this
way and address the other issues that you mentioned.

> It'd be less useful in practice, but that doesn't matter since the
> practical need for this behavior vanished decades ago.

Thanks for again for the thorough review and explanations. I find it
funny that I assumed this change was simple and learned it's purpose was
for logging in on a teletype. The Model 37 predates me ~30 years, so it
never occured to me that the purpose was to not print logins.

Collin

>From bb063435c2019af6a6b9e26c67c4b378b9243919 Mon Sep 17 00:00:00 2001
Message-ID: <bb06[email-masked]>
From: Collin Funk <[email-masked]>
Date: Sun, 27 Jul 2025 15:00:15 -0700
Subject: [PATCH v4] pr: implement '-p' and modify '-f' conforming to POSIX

* src/pr.c (pause_option, pause_on_first_page, tty_fd): New variables.
(short_options): Add '-p'.
(long_options): Add '--pause'.
(main): Add the option. Open and close a file descriptor to
/dev/tty. Disable pausing if POSIXLY_CORRECT is set.
(print_files): If the pausing is enabled, emit '\a' to standard error
and wait until a newline is read to print the next page.
(usage): Mention the new option.
* doc/coreutils.texi (pr invocation): Document the new option. Document
the behaviors of POSIXLY_CORRECT on -p and -f.
* NEWS: Mention the new option. Mention the new behavior of 'pr -f' with
the POSIXLY_CORRECT environment variable set.
---
 NEWS               |  7 +++++
 doc/coreutils.texi | 17 ++++++++++++
 src/pr.c           | 66 +++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 86 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 0b2be7116..6e78c81d3 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   'factor' is now much faster at identifying large prime numbers,
   and significantly faster on composite numbers greater than 2^128.
 
+  'pr -f' with the POSIXLY_CORRECT environment variable set will pause
+  until a newline is read from /dev/tty before printing the first page.
+
 ** Bug fixes
 
   cksum was not compilable by Apple LLVM 10.0.0 x86-64, which
@@ -64,6 +67,10 @@ GNU coreutils NEWS                                    -*- outline -*-
   Iranian locale (fa_IR) and for the Ethiopian locale (am_ET), and also
   does so more consistently for the Thailand locale (th_TH.UTF-8).
 
+  pr now supports the -p option, to pause upon printing each page until
+  a newline character is read from /dev/tty, as required by POSIX.  The
+  corresponding long option is --pause.
+
 
 * Noteworthy changes in release 9.7 (2025-04-09) [stable]
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 7ca5b222c..25e4e4e51 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -2726,6 +2726,11 @@ @node pr invocation
 Use a form feed instead of newlines to separate output pages.  This does
 not alter the default page length of 66 lines.
 
+@vindex POSIXLY_CORRECT
+If the @env{POSIXLY_CORRECT} environment variable is set and standard
+output is a tty, then using @option{-f} will also pause until a newline
+is read from @file{/dev/tty} before printing the first page.
+
 @item -h @var{header}
 @itemx --header=@var{header}
 @opindex -h
@@ -2825,6 +2830,18 @@ @node pr invocation
 set with the @option{-W/-w} option.  A limited overflow may occur with
 numbered single column output (compare @option{-n} option).
 
+@item -p
+@itemx --pause
+@opindex -p
+@opindex --pause
+After printing each page, print an alert (bell) to standard error and
+wait for a newline to be read from @file{/dev/tty} before printing the
+next page.
+
+@vindex POSIXLY_CORRECT
+If the @env{POSIXLY_CORRECT} environment variable is set and standard
+output is not a tty, this option will be ignored.
+
 @item -r
 @itemx --no-file-warnings
 @opindex -r
diff --git a/src/pr.c b/src/pr.c
index e7081a059..298be4ebd 100644
--- a/src/pr.c
+++ b/src/pr.c
@@ -711,6 +711,15 @@ static char *custom_header;
 /* (-D) Date format for the header.  */
 static char const *date_format;
 
+/* If true, pause after each page until a newline is read from /dev/tty.  */
+static bool pause_option;
+
+/* If true, pause on only the first page.  */
+static bool pause_on_first_page;
+
+/* Used to read from /dev/tty if --p/--pause is in use.  */
+static int tty_fd;
+
 /* The local time zone rules, as per the TZ environment variable.  */
 static timezone_t localtz;
 
@@ -738,7 +747,7 @@ enum
 };
 
 static char const short_options[] =
-  "-0123456789D:FJN:S::TW:abcde::fh:i::l:mn::o:rs::tvw:";
+  "-0123456789D:FJN:S::TW:abcde::fh:i::l:mn::po:rs::tvw:";
 
 static struct option const long_options[] =
 {
@@ -758,6 +767,7 @@ static struct option const long_options[] =
   {"number-lines", optional_argument, nullptr, 'n'},
   {"first-line-number", required_argument, nullptr, 'N'},
   {"indent", required_argument, nullptr, 'o'},
+  {"pause", no_argument, nullptr, 'p'},
   {"no-file-warnings", no_argument, nullptr, 'r'},
   {"separator", optional_argument, nullptr, 's'},
   {"sep-string", optional_argument, nullptr, 'S'},
@@ -868,6 +878,8 @@ main (int argc, char **argv)
   idx_t n_digits = 0;
   idx_t n_alloc = 0;
 
+  bool posixly_correct = (getenv ("POSIXLY_CORRECT") != nullptr);
+
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
   setlocale (LC_ALL, "");
@@ -958,6 +970,8 @@ main (int argc, char **argv)
           untabify_input = true;
           break;
         case 'f':
+          pause_on_first_page = posixly_correct;
+          FALLTHROUGH;
         case 'F':
           use_form_feed = true;
           break;
@@ -999,6 +1013,9 @@ main (int argc, char **argv)
           chars_per_margin = getoptnum (optarg, 0,
                                         _("'-o MARGIN' invalid line offset"));
           break;
+        case 'p':
+          pause_option = true;
+          break;
         case 'r':
           ignore_failed_opens = true;
           break;
@@ -1061,7 +1078,7 @@ main (int argc, char **argv)
     }
 
   if (! date_format)
-    date_format = (getenv ("POSIXLY_CORRECT") && !hard_locale (LC_TIME)
+    date_format = (posixly_correct && !hard_locale (LC_TIME)
                    ? "%b %e %H:%M %Y"
                    : "%Y-%m-%d %H:%M");
 
@@ -1079,6 +1096,22 @@ main (int argc, char **argv)
     error (EXIT_FAILURE, 0,
            _("cannot specify both printing across and printing in parallel"));
 
+  /* POSIX states that the pausing behavior of -f and -p should only occur if
+     standard output is a tty.  Only behave this way is POSIXLY_CORRECT is set
+     since the GNU Coding Standards discourages changing program behavior based
+     on output device type.  */
+  if ((pause_option || pause_on_first_page) && posixly_correct
+      && ! isatty (STDOUT_FILENO))
+    pause_option = pause_on_first_page = false;
+
+  if (pause_option || pause_on_first_page)
+    {
+      tty_fd = open ("/dev/tty", O_RDONLY);
+      if (tty_fd < 0)
+        error (EXIT_FAILURE, errno, _("cannot open %s for reading"),
+               quoteaf ("/dev/tty"));
+    }
+
 /* Translate some old short options to new/long options.
    To meet downward compatibility with other UNIX pr utilities
    and some POSIX specifications. */
@@ -1149,6 +1182,8 @@ main (int argc, char **argv)
 
   if (have_read_stdin && fclose (stdin) == EOF)
     error (EXIT_FAILURE, errno, _("standard input"));
+  if (pause_option && close (tty_fd) < 0)
+    error (EXIT_FAILURE, errno, "%s", quotef ("/dev/tty"));
   main_exit (failed_opens ? EXIT_FAILURE : EXIT_SUCCESS);
 }
 
@@ -1636,8 +1671,29 @@ print_files (int number_of_files, char **av)
   init_funcs ();
 
   line_number = line_count;
-  while (print_page ())
-    ;
+  while (true)
+    {
+      if (pause_option || (pause_on_first_page && page_number == 1))
+        {
+          if (putc ('\a', stderr) == EOF || fflush (stderr) != 0)
+            write_error ();
+          while (true)
+            {
+              unsigned char ch;
+              ssize_t bytes_read = read (tty_fd, &ch, sizeof ch);
+              if (bytes_read < 0)
+                error (EXIT_FAILURE, errno, _("error reading %s"),
+                       quoteaf ("/dev/tty"));
+              /* Just exit if the user presses Ctrl-D.  */
+              if (bytes_read == 0)
+                return;
+              if (ch == '\n')
+                break;
+            }
+        }
+      if (! print_page ())
+        break;
+    }
 }
 
 /* Initialize header information.
@@ -2824,6 +2880,8 @@ Paginate or columnate FILE(s) for printing.\n\
   -o, --indent=MARGIN\n\
                     offset each line with MARGIN (zero) spaces, do not\n\
                     affect -w or -W, MARGIN will be added to PAGE_WIDTH\n\
+  -p, --pause       pause at the beginning of each page until a newline\n\
+                    is read from /dev/tty.\n\
   -r, --no-file-warnings\n\
                     omit warning when a file cannot be opened\n\
 "), stdout);
-- 
2.50.1