Subject: Re: bug#29266: gzip-1.8.41 test results: help-version on HP-UX

From: Paul Eggert <[email-masked]>
Date: Sun, 12 Nov 2017 23:53:35 -0800

Bruno Haible wrote:
> ! # Produce output and exit with code 1 if there is a write error.
> ! # Use 'exec echo', not plain 'echo', because the 'echo' built-in in
> ! # HP-UX /bin/sh does not check for write errors.
> ! # Use '|| exit 1', because the 'echo' program on HP-UX exits with
> ! # code 2 in case of a write error, but we want code 1.
> ! --help)    (exec echo "$usage") || exit 1; exit 0;;
> ! --version) (exec echo "$version") || exit 1; exit 0;;

Thanks for the patch. I don't think we need worry about the first problem, since 
gzip assumes a working POSIX shell and the first problem is a failure to conform 
to POSIX. We can ask builders on HP-UX to work around the problem by configuring 
with SHELL=/bin/bash, or with some other POSIX-compatible shell.

The second problem is indeed a bug in gzip, since it shouldn't assume that echo 
exits with status 1 on failure (it could be some other positive status).

I notice that some gzip scripts already fix that bug, and some other scripts do 
not fix it. Also, while we're at it, scripts should use printf instead of echo 
if the strings might contain backslash (at least in theory; admittedly a 
backslash in a version number would be pretty weird). I looked for these 
problems in all the scripts and installed the attached to fix what I found.
From f55f2be13c2f1afa8d76daadabb3b24c7fe34743 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sun, 12 Nov 2017 23:02:02 -0800
Subject: [PATCH] maint: script diagnostics status cleanup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem reported by Bruno Haible (Bug#29266#20).
* NEWS: Mention this.
* gunzip.in, gzexe.in, zcat.in, zcmp.in, zdiff.in, zforce.in:
* zgrep.in, zless.in, zmore.in, znew.in:
Use printf instead of echo if the argument might contain ‘\’, at
least in theory.  Don’t assume printf exits with status 1 on
failure; it might be some other positive status.
* gzexe.in: Use printf consistently instead of echo, and
proscribe it instead of echo.
---
 NEWS      |  5 +++++
 gunzip.in |  4 ++--
 gzexe.in  | 40 ++++++++++++++++++++--------------------
 zcat.in   |  4 ++--
 zcmp.in   |  5 ++---
 zdiff.in  |  9 +++++----
 zforce.in | 10 +++++-----
 zgrep.in  |  4 ++--
 zless.in  |  4 ++--
 zmore.in  |  4 ++--
 znew.in   | 30 +++++++++++++++---------------
 11 files changed, 62 insertions(+), 57 deletions(-)

diff --git a/NEWS b/NEWS
index 34eae0d..68af1dc 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,11 @@ GNU gzip NEWS                                    -*- outline -*-
   1970 and after 2106, and timestamps after 2038 on platforms with
   32-bit signed time_t.  [bug present since the beginning]
 
+  Commands implemented via shell scripts are now more consistent about
+  failure status.  For example, 'gunzip --help >/dev/full' now
+  consistently exits with status 1 (error), instead of with status 2
+  (warning) on some platforms.  [bug present since the beginning]
+
   Support for VMS and Amiga has been removed.  It was not working anyway,
   and it reportedly caused file name glitches on MS-Windowsish platforms.
 
diff --git a/gunzip.in b/gunzip.in
index a5dfb89..0602724 100644
--- a/gunzip.in
+++ b/gunzip.in
@@ -50,8 +50,8 @@ With no FILE, or when FILE is -, read standard input.
 Report bugs to <[email-masked]>."
 
 case $1 in
---help)    exec echo "$usage";;
---version) exec echo "$version";;
+--help)    printf '%s\n' "$usage"   || exit 1; exit;;
+--version) printf '%s\n' "$version" || exit 1; exit;;
 esac
 
 exec gzip -d "$@"
diff --git a/gzexe.in b/gzexe.in
index 87413a3..290fa1c 100644
--- a/gzexe.in
+++ b/gzexe.in
@@ -59,15 +59,15 @@ res=0
 while :; do
   case $1 in
   -d) decomp=1; shift;;
-  --h*) exec echo "$usage";;
-  --v*) exec echo "$version";;
+  --h*) printf '%s\n' "$usage"   || exit 1; exit;;
+  --v*) printf '%s\n' "$version" || exit 1; exit;;
   --) shift; break;;
   *) break;;
   esac
 done
 
 if test $# -eq 0; then
-  echo >&2 "$0: missing operand
+  printf >&2 '%s\n' "$0: missing operand
 Try \`$0 --help' for more information."
   exit 1
 fi
@@ -87,29 +87,29 @@ for i do
   esac
   if test ! -f "$file" || test ! -r "$file"; then
     res=$?
-    echo >&2 "$0: $i is not a readable regular file"
+    printf >&2 '%s\n' "$0: $i is not a readable regular file"
     continue
   fi
   if test $decomp -eq 0; then
     if sed -e 1d -e 2q "$file" | grep "^skip=[0-9][0-9]*$" >/dev/null; then
-      echo >&2 "$0: $i is already gzexe'd"
+      printf >&2 '%s\n' "$0: $i is already gzexe'd"
       continue
     fi
   fi
   if test -u "$file"; then
-    echo >&2 "$0: $i has setuid permission, unchanged"
+    printf >&2 '%s\n' "$0: $i has setuid permission, unchanged"
     continue
   fi
   if test -g "$file"; then
-    echo >&2 "$0: $i has setgid permission, unchanged"
+    printf >&2 '%s\n' "$0: $i has setgid permission, unchanged"
     continue
   fi
   case /$file in
   */basename | */bash | */cat | */chmod | */cp | \
-  */dirname | */echo | */expr | */gzip | \
-  */ln | */mkdir | */mktemp | */mv | */rm | \
+  */dirname | */expr | */gzip | \
+  */ln | */mkdir | */mktemp | */mv | */printf | */rm | \
   */sed | */sh | */sleep | */test | */tail)
-    echo >&2 "$0: $i might depend on itself"; continue;;
+    printf >&2 '%s\n' "$0: $i might depend on itself"; continue;;
   esac
 
   dir=`dirname "$file"` || dir=$TMPDIR
@@ -125,7 +125,7 @@ for i do
     tmp=$dir/gzexe$$
   fi && { cp -p "$file" "$tmp" 2>/dev/null || cp "$file" "$tmp"; } || {
     res=$?
-    echo >&2 "$0: cannot copy $file"
+    printf >&2 '%s\n' "$0: cannot copy $file"
     continue
   }
   if test -w "$tmp"; then
@@ -134,7 +134,7 @@ for i do
     writable=0
     chmod u+w "$tmp" || {
       res=$?
-      echo >&2 "$0: cannot chmod $tmp"
+      printf >&2 '%s\n' "$0: cannot chmod $tmp"
       continue
     }
   fi
@@ -170,7 +170,7 @@ case $0 in
 */*) gztmp=$gztmpdir/`basename "$0"`;;
 esac || { (exit 127); exit 127; }
 
-case `echo X | tail -n +1 2>/dev/null` in
+case `printf 'X\n' | tail -n +1 2>/dev/null` in
 X) tail_n=-n;;
 *) tail_n=;;
 esac
@@ -180,13 +180,13 @@ if tail $tail_n +$skip <"$0" | gzip -cd > "$gztmp"; then
   (sleep 5; rm -fr "$gztmpdir") 2>/dev/null &
   "$gztmp" ${1+"$@"}; res=$?
 else
-  echo >&2 "Cannot decompress $0"
+  printf >&2 '%s\n' "Cannot decompress $0"
   (exit 127); res=127
 fi; exit $res
 EOF
     gzip -cv9 "$file") > "$tmp" || {
       res=$?
-      echo >&2 "$0: compression not possible for $i, file unchanged."
+      printf >&2 '%s\n' "$0: compression not possible for $i, file unchanged."
       continue
     }
 
@@ -198,29 +198,29 @@ EOF
     skip=[0-9] | skip=[0-9][0-9] | skip=[0-9][0-9][0-9])
       eval "$skip_line";;
     esac
-    case `echo X | tail -n +1 2>/dev/null` in
+    case `printf 'X\n' | tail -n +1 2>/dev/null` in
     X) tail_n=-n;;
     *) tail_n=;;
     esac
     tail $tail_n +$skip "$file" | gzip -cd > "$tmp" || {
       res=$?
-      echo >&2 "$0: $i probably not in gzexe format, file unchanged."
+      printf >&2 '%s\n' "$0: $i probably not in gzexe format, file unchanged."
       continue
     }
   fi
   test $writable -eq 1 || chmod u-w "$tmp" || {
     res=$?
-    echo >&2 "$0: $tmp: cannot chmod"
+    printf >&2 '%s\n' "$0: $tmp: cannot chmod"
     continue
   }
   ln -f "$file" "$file~" || {
     res=$?
-    echo >&2 "$0: cannot backup $i as $i~"
+    printf >&2 '%s\n' "$0: cannot backup $i as $i~"
     continue
   }
   mv -f "$tmp" "$file" || {
     res=$?
-    echo >&2 "$0: cannot rename $tmp to $i"
+    printf >&2 '%s\n' "$0: cannot rename $tmp to $i"
     continue
   }
   tmp=
diff --git a/zcat.in b/zcat.in
index 524ac51..9cf4c2d 100644
--- a/zcat.in
+++ b/zcat.in
@@ -44,8 +44,8 @@ With no FILE, or when FILE is -, read standard input.
 Report bugs to <[email-masked]>."
 
 case $1 in
---help)    exec echo "$usage";;
---version) exec echo "$version";;
+--help)    printf '%s\n' "$usage"   || exit 1; exit;;
+--version) printf '%s\n' "$version" || exit 1; exit;;
 esac
 
 exec gzip -cd "$@"
diff --git a/zcmp.in b/zcmp.in
index 7847350..b0a4ca2 100644
--- a/zcmp.in
+++ b/zcmp.in
@@ -36,10 +36,9 @@ If a FILE is '-' or missing, read standard input.
 
 Report bugs to <[email-masked]>."
 
-st=0
 case $1 in
---help)    echo "$usage" || st=2; exit $st;;
---version) echo "$version" || st=2; exit $st;;
+--help)    printf '%s\n' "$usage"   || exit 2; exit;;
+--version) printf '%s\n' "$version" || exit 2; exit;;
 esac
 
 exec zdiff --__cmp "$@"
diff --git a/zdiff.in b/zdiff.in
index e1efc1e..0d1e749 100644
--- a/zdiff.in
+++ b/zdiff.in
@@ -50,8 +50,8 @@ escape='
 
 while :; do
   case $1 in
-  --h*) printf '%s\n' "$usage" || exit 2; exit;;
-  --v*) echo "$version" || exit 2; exit;;
+  --h*) printf '%s\n' "$usage"   || exit 2; exit;;
+  --v*) printf '%s\n' "$version" || exit 2; exit;;
   --) shift; break;;
   -*\'*) cmp="$cmp '"`printf '%sX\n' "$1" | sed "$escape"`;;
   -?*) cmp="$cmp '$1'";;
@@ -78,7 +78,7 @@ if test $# -eq 1; then
       (gzip -cd -- "$1" 4>&-; echo $? >&4) 3>&- | eval "$cmp" - '"$FILE"' >&3
     );;
   *)
-    echo >&2 "$0: $1: unknown compressed file extension"
+    printf >&2 '%s\n' "$0: $1: unknown compressed file extension"
     exit 2;;
   esac
 elif test $# -eq 2; then
@@ -152,7 +152,8 @@ elif test $# -eq 2; then
                 esac;;
         esac
 else
-        echo >&2 "$0: invalid number of operands; try \`$0 --help' for help"
+        printf >&2 '%s\n' \
+          "$0: invalid number of operands; try \`$0 --help' for help"
         exit 2
 fi
 
diff --git a/zforce.in b/zforce.in
index 1770af3..f0ff71c 100644
--- a/zforce.in
+++ b/zforce.in
@@ -38,20 +38,20 @@ not compress them twice.
 Report bugs to <[email-masked]>."
 
 if test $# = 0; then
-  echo >&2 "$0: invalid number of operands; try \`$0 --help' for help"
+  printf >&2 '%s\n' "$0: invalid number of operands; try \`$0 --help' for help"
   exit 1
 fi
 
 res=0
 for i do
   case "$i" in
-  --h*) exec echo "$usage";;
-  --v*) exec echo "$version";;
+  --h*) printf '%s\n' "$usage"  ; exit;;
+  --v*) printf '%s\n' "$version"; exit;;
   *[-.]z | *[-.]gz | *.t[ag]z) continue;;
   esac
 
   if test ! -f "$i" ; then
-    echo zforce: $i not a file
+    printf '%s\n' "zforce: $i not a file"
     res=1
     continue
   fi
@@ -60,7 +60,7 @@ for i do
 
     new="$i.gz"
     if mv "$i" "$new"; then
-      echo $i -- replaced with $new
+      printf '%s\n' "$i -- replaced with $new"
     else
       res=$?
     fi
diff --git a/zgrep.in b/zgrep.in
index 2ed007b..0660634 100644
--- a/zgrep.in
+++ b/zgrep.in
@@ -144,7 +144,7 @@ while test $# -ne 0; do
     fi
     have_pat=1;;
   (--h | --he | --hel | --help)
-    echo "$usage" || exit 2
+    printf '%s\n' "$usage" || exit 2
     exit;;
   (-H | --wi | --wit | --with | --with- | --with-f | --with-fi \
   | --with-fil | --with-file | --with-filen | --with-filena | --with-filenam \
@@ -158,7 +158,7 @@ while test $# -ne 0; do
   (-h | --no-f*)
     no_filename=1;;
   (-V | --v | --ve | --ver | --vers | --versi | --versio | --version)
-    echo "$version" || exit 2
+    printf '%s\n' "$version" || exit 2
     exit;;
   esac
 
diff --git a/zless.in b/zless.in
index 9a3f399..5c20d82 100644
--- a/zless.in
+++ b/zless.in
@@ -32,8 +32,8 @@ Options are the same as for 'less'.
 Report bugs to <[email-masked]>."
 
 case $1 in
---help)    exec echo "$usage";;
---version) exec echo "$version";;
+--help)    printf '%s\n' "$usage"  ; exit;;
+--version) printf '%s\n' "$version"; exit;;
 esac
 
 if test "${LESSMETACHARS+set}" != set; then
diff --git a/zmore.in b/zmore.in
index 1c9cebb..1bd3bdb 100644
--- a/zmore.in
+++ b/zmore.in
@@ -31,8 +31,8 @@ Like 'more', but operate on the uncompressed contents of any compressed FILEs.
 Report bugs to <[email-masked]>."
 
 case $1 in
-  --h*) exec printf '%s\n' "$usage";;
-  --v*) exec printf '%s\n' "$version";;
+  --h*) printf '%s\n' "$usage"  ; exit;;
+  --v*) printf '%s\n' "$version"; exit;;
   --) shift;;
   -?*) printf >&2 '%s\n' "$0: $1: unknown option; try '$0 --help' for help"
        exit 1;;
diff --git a/znew.in b/znew.in
index 29038df..5c98256 100644
--- a/znew.in
+++ b/znew.in
@@ -60,8 +60,8 @@ ext=.gz
 for arg
 do
   case "$arg" in
-  --help)      exec echo "$usage";;
-  --version)   exec echo "$version";;
+  --help)      printf '%s\n' "$usage"  ; exit;;
+  --version)   printf '%s\n' "$version"; exit;;
   -*)     opt="$opt $arg"; shift;;
    *)     break;;
   esac
@@ -72,24 +72,24 @@ if test $# -eq 0; then
   exit 1
 fi
 
-opt=`echo "$opt" | sed -e 's/ //g' -e 's/-//g'`
+opt=`printf '%s\n' "$opt" | sed -e 's/ //g' -e 's/-//g'`
 case "$opt" in
-  *t*) check=1; opt=`echo "$opt" | sed 's/t//g'`
+  *t*) check=1; opt=`printf '%s\n' "$opt" | sed 's/t//g'`
 esac
 case "$opt" in
-  *K*) keep=1; check=1; opt=`echo "$opt" | sed 's/K//g'`
+  *K*) keep=1; check=1; opt=`printf '%s\n' "$opt" | sed 's/K//g'`
 esac
 case "$opt" in
-  *P*) pipe=1; opt=`echo "$opt" | sed 's/P//g'`
+  *P*) pipe=1; opt=`printf '%s\n' "$opt" | sed 's/P//g'`
 esac
 if test -n "$opt"; then
   opt="-$opt"
 fi
 
 for i do
-  n=`echo "$i" | sed 's/.Z$//'`
+  n=`printf '%s\n' "$i" | sed 's/.Z$//'`
   if test ! -f "$n.Z" ; then
-    echo "$n.Z not found"
+    printf '%s\n' "$n.Z not found"
     res=1; continue
   fi
   test $keep -eq 1 && old=`wc -c < "$n.Z"`
@@ -99,7 +99,7 @@ for i do
       touch -r"$n.Z" -- "$n$ext" 2>/dev/null
       chmod --reference="$n.Z" -- "$n$ext" 2>/dev/null
     else
-      echo "error while recompressing $n.Z"
+      printf '%s\n' "error while recompressing $n.Z"
       res=1; continue
     fi
   else
@@ -107,7 +107,7 @@ for i do
       if cp -p "$n.Z" "$n.$$"; then
         :
       else
-        echo "cannot backup $n.Z"
+        printf '%s\n' "cannot backup $n.Z"
         res=1; continue
       fi
     fi
@@ -115,7 +115,7 @@ for i do
       :
     else
       test $check -eq 1 && mv "$n.$$" "$n.Z"
-      echo "error while uncompressing $n.Z"
+      printf '%s\n' "error while uncompressing $n.Z"
       res=1; continue
     fi
     if gzip $opt "$n"; then
@@ -123,10 +123,10 @@ for i do
     else
       if test $check -eq 1; then
         mv "$n.$$" "$n.Z" && rm -f "$n"
-        echo "error while recompressing $n"
+        printf '%s\n' "error while recompressing $n"
       else
         # compress $n  (might be dangerous if disk full)
-        echo "error while recompressing $n, left uncompressed"
+        printf '%s\n' "error while recompressing $n, left uncompressed"
       fi
       res=1; continue
     fi
@@ -139,7 +139,7 @@ for i do
     else
       mv "$n.$$" "$n.Z" && rm -f "$n$ext"
     fi
-    echo "$n.Z smaller than $n$ext -- unchanged"
+    printf '%s\n' "$n.Z smaller than $n$ext -- unchanged"
 
   elif test $check -eq 1; then
     if gzip -t "$n$ext" ; then
@@ -147,7 +147,7 @@ for i do
     else
       test $pipe -eq 0 && mv "$n.$$" "$n.Z"
       rm -f "$n$ext"
-      echo "error while testing $n$ext, $n.Z unchanged"
+      printf '%s\n' "error while testing $n$ext, $n.Z unchanged"
       res=1; continue
     fi
   elif test $pipe -eq 1; then
-- 
2.7.4