[tz] [PROPOSED 1/3] Avoid expr in tzselect

Paul Eggert eggert at cs.ucla.edu
Tue Jan 2 22:08:22 UTC 2024


* tzselect.ksh: Rewrite to not use the expr command.
Various implementations have problems or dump core given unusual
inputs, so to avoid these issues use awk or sh (which we need to
use anyway) instead of using expr.
---
 tzselect.ksh | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/tzselect.ksh b/tzselect.ksh
index 2073bccd..cc58db03 100644
--- a/tzselect.ksh
+++ b/tzselect.ksh
@@ -40,6 +40,9 @@ REPORT_BUGS_TO=tz at iana.org
 # The substr avoids problems when VALUE is of the form X=Y and would be
 # misinterpreted as an assignment.
 
+# This script does not want path expansion.
+set -f
+
 # Specify default values for environment variables if they are unset.
 : ${AWK=awk}
 : ${TZDIR=`pwd`}
@@ -113,7 +116,8 @@ then
 else
   doselect() {
     # Field width of the prompt numbers.
-    select_width=`expr $# : '.*'`
+    print_nargs_length="BEGIN {print length(\"$#\");}"
+    select_width=`$AWK "$print_nargs_length"`
 
     select_i=
 
@@ -124,14 +128,14 @@ else
 	select_i=0
 	for select_word
 	do
-	  select_i=`expr $select_i + 1`
+	  select_i=`$AWK "BEGIN { print $select_i + 1 }"`
 	  printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
 	done;;
       *[!0-9]*)
 	echo >&2 'Please enter a number in range.';;
       *)
 	if test 1 -le $select_i && test $select_i -le $#; then
-	  shift `expr $select_i - 1`
+	  shift `$AWK "BEGIN { print $select_i - 1 }"`
 	  select_result=$1
 	  break
 	fi
@@ -165,7 +169,7 @@ do
   esac
 done
 
-shift `expr $OPTIND - 1`
+shift `$AWK "BEGIN { print $OPTIND - 1 }"`
 case $# in
 0) ;;
 *) say >&2 "$0: $1: unknown argument"; exit 1
@@ -439,7 +443,12 @@ while
       continent=$select_result
       case $continent in
       Americas) continent=America;;
-      *" "*)    continent=`expr "$continent" : '\''\([^ ]*\)'\''`
+      *)
+	# Get the first word of $continent.  Path expansion is disabled
+	# so this works even with "*", which should not happen.
+	IFS=" "
+	for continent in $continent ""; do break; done
+	IFS=$newline;;
       esac
       case $zonetabtype,$continent in
       zonenow,*) ;;
@@ -746,15 +755,18 @@ while
   do
     TZdate=`LANG=C TZ="$TZ_for_date" date`
     UTdate=`LANG=C TZ=UTC0 date`
-    TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
-    UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
-    case $TZsec in
-    $UTsec)
+    if $AWK '
+	  function getsecs(d) {
+	    return match(d, /.*:[0-5][0-9]/) ? substr(d, RLENGTH - 1, 2) : ""
+	  }
+	  BEGIN { exit getsecs(ARGV[1]) != getsecs(ARGV[2]) }
+       ' ="$TZdate" ="$UTdate"
+    then
       extra_info="
 Selected time is now:	$TZdate.
 Universal Time is now:	$UTdate."
       break
-    esac
+    fi
   done
 
 
-- 
2.40.1



More information about the tz mailing list