bug#47243: pr lacks -p

Collin Funk <[email protected]>
Newsgroups gmane.comp.gnu.core-utils.bugs
Message-ID <[email protected]>
Paul Eggert <[email protected]> writes:

>> +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.
>
> This sentence should start "Before" not "After", with the rest of the
> sentence changed accordingly. There is no bell printed after the last
> page.
>
> Looking at the code, it seems that this mistake has leaked into the
> code as well, in that sometimes 'pr' will print an alert and wait for
> '\n' at end of file, when there is no page to print. If I'm right,
> that needs to be fixed; pr should wait for '\n' only if it will
> actually print something afterward.

Fixing this required a bit more understanding of the code on my part,
but I think the attached patch should address it.

The existing code uses a 'pad_vertically' variable to keep track of
whether the current page has text or a header that needs to be
printed. Therefore, any time we set it to true, we must also pause. But
only once, at the start of the page.

Collin
v5-0001-pr-implement-p-and-modify-f-conforming-to-POSIX.patch (text/x-patch, 9.6 KB)
From 8afb79bbf294687fd4bdd3c92d1599f5021f703a Mon Sep 17 00:00:00 2001
Message-ID: <8afb79bbf294687fd4bdd3c92d1599f5021f703a.1753850358.git.collin.funk1@gmail.com>
From: Collin Funk <[email protected]>
Date: Sun, 27 Jul 2025 15:00:15 -0700
Subject: [PATCH v5] pr: implement '-p' and modify '-f' conforming to POSIX

* src/pr.c (last_paused_page_number, pause_option, pause_on_first_page)
(tty_fd): New variables.
(short_options): Add '-p'.
(long_options): Add '--pause'.
(main): Add the option. Open a file descriptor to /dev/tty. Disable
pausing if POSIXLY_CORRECT is set.
(pause_maybe): New function.
(read_line, print_stored): Call it whenever pad_vertically is set to
true.
(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           | 80 ++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 101 insertions(+), 3 deletions(-)

diff --git a/NEWS b/NEWS
index 0b2be7116..3034ce718 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 before 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 40ecf3126..5df11f910 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
+This option will also pause until a newline is read from @file{/dev/tty}
+before printing the first page if the @env{POSIXLY_CORRECT} environment
+variable is set and standard output is associated with a terminal.
+
 @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
+Before 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
+This option is ignored if the @env{POSIXLY_CORRECT} environment variable
+is set and standard output is not associated with a terminal.
+
 @item -r
 @itemx --no-file-warnings
 @opindex -r
diff --git a/src/pr.c b/src/pr.c
index e7081a059..5d945910c 100644
--- a/src/pr.c
+++ b/src/pr.c
@@ -419,6 +419,7 @@ typedef struct COLUMN COLUMN;
 
 static int char_to_clump (char c);
 static bool read_line (COLUMN *p);
+static void pause_maybe (void);
 static bool print_page (void);
 static bool print_stored (COLUMN *p);
 static bool open_file (char *name, COLUMN *p);
@@ -618,6 +619,9 @@ static int files_ready_to_read = 0;
 /* Current page number.  Displayed in header. */
 static uintmax_t page_number;
 
+/* The last page number that we paused on.  */
+static uintmax_t last_paused_page_number;
+
 /* Current line number.  Displayed when -n flag is specified.
 
    When printing files in parallel (-m flag), line numbering is as follows:
@@ -711,6 +715,15 @@ static char *custom_header;
 /* (-D) Date format for the header.  */
 static char const *date_format;
 
+/* If true, pause before each page until a newline is read from /dev/tty.  */
+static bool pause_option;
+
+/* If true, pause before 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 +751,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 +771,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 +882,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 +974,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 +1017,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 +1082,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 +1100,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 occur only if
+     standard output is associated with a terminal.  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. */
@@ -1640,6 +1677,36 @@ print_files (int number_of_files, char **av)
     ;
 }
 
+/* Pause until reading a newline from /dev/tty if needed.  This function should
+   not be called until we know that we have text or a header to print for a
+   page.  This means it must be called when pad_vertically is set to true.  */
+
+static void
+pause_maybe (void)
+{
+  if ((pause_option || (pause_on_first_page && page_number == 1))
+      && last_paused_page_number < page_number)
+    {
+      last_paused_page_number = page_number;
+      if (pause_option
+          && (fputc ('\a', stderr) == EOF || fflush (stderr) != 0))
+        write_error ();
+      while (true)
+        {
+          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;
+        }
+    }
+}
+
 /* Initialize header information.
    If DESC is non-negative, it is a file descriptor open to
    FILENAME for reading.  */
@@ -2452,6 +2519,7 @@ read_line (COLUMN *p)
       if (print_a_header && !storing_columns)
         {
           pad_vertically = true;
+          pause_maybe ();
           print_header ();
         }
       else if (keep_FF)
@@ -2476,6 +2544,7 @@ read_line (COLUMN *p)
   if (p->char_func != store_char)
     {
       pad_vertically = true;
+      pause_maybe ();
 
       if (print_a_header && !storing_columns)
         print_header ();
@@ -2586,7 +2655,10 @@ print_stored (COLUMN *p)
   pad_vertically = true;
 
   if (print_a_header)
-    print_header ();
+    {
+      pause_maybe ();
+      print_header ();
+    }
 
   if (p->status == FF_FOUND)
     {
@@ -2824,6 +2896,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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.