Permalink
Browse files

tzselect: port to /bin/sh

Problem reported by Patrick 'P. J.' McDermott in
<http://mm.icann.org/pipermail/tz/2013-October/020441.html>.
This code is quite a bit different from what he proposed.
* tzselect.ksh: Rewrite so that it should work with /bin/sh on
common platforms.  For portability to Solaris 9 /bin/sh, use
`...`, not $(...), and avoid $((...)).
(doselect): New function.  Use this instead of plain 'select'.
Callers no longer need to worry whether it sets the var to empty.
* Makefile, NEWS: Document this.
  • Loading branch information...
eggert committed Oct 6, 2013
1 parent b9b27d5 commit be06aa48dbe1d0dad499006eab66e34ed07dcb5f
Showing with 119 additions and 80 deletions.
  1. +6 −2 Makefile
  2. +6 −0 NEWS
  3. +107 −78 tzselect.ksh
View
@@ -246,8 +246,12 @@ ZFLAGS=
# The name of a Posix-compliant `awk' on your system.
AWK= awk
# The full path name of a Posix-compliant shell that supports the Korn shell's
# 'select' statement, as an extension. These days, Bash is the most popular.
# The full path name of a Posix-compliant shell, preferably one that supports
# the Korn shell's 'select' statement as an extension.
# These days, Bash is the most popular.
# It should be OK to set this to /bin/sh, on platforms where /bin/sh
# lacks 'select' or doesn't completely conform to Posix, but /bin/bash
# is typically nicer if it works.
KSHELL= /bin/bash
# The path where SGML DTDs are kept.
View
6 NEWS
@@ -8,6 +8,12 @@ Unreleased, experimental changes
This avoids some year-2038 glitches introduced in 2013g.
(Thanks to Yoshito Umaoka for reporting the problem.)
Changes affecting API
The 'tzselect' command no longer requires the 'select' command,
and should now work with /bin/sh on more platforms. (Thanks to
Patrick 'P. J.' McDermott for reporting the problem.)
Changes affecting the build procedure
The builder can specify which programs to use, if any, instead of
View
@@ -11,7 +11,7 @@ REPORT_BUGS_TO=tz@iana.org
# Porting notes:
#
# This script requires a Posix-like shell with the extension of a
# This script requires a Posix-like shell and prefers the extension of a
# 'select' statement. The 'select' statement was introduced in the
# Korn shell and is available in Bash and other shell implementations.
# If your host lacks both Bash and the Korn shell, you can get their
@@ -21,6 +21,10 @@ REPORT_BUGS_TO=tz@iana.org
# Korn Shell <http://www.kornshell.com/>
# Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/>
#
# For portability to Solaris 9 /bin/sh this script avoids some POSIX
# features and common extensions, such as $(...) (which works sometimes
# but not others), $((...)), and $10.
#
# This script also uses several features of modern awk programs.
# If your host lacks awk, or has an old awk that does not conform to Posix,
# you can use either of the following free programs instead:
@@ -31,7 +35,7 @@ REPORT_BUGS_TO=tz@iana.org
# Specify default values for environment variables if they are unset.
: ${AWK=awk}
: ${TZDIR=$(pwd)}
: ${TZDIR=`pwd`}
# Check for awk Posix compliance.
($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
@@ -67,6 +71,74 @@ Options:
Report bugs to $REPORT_BUGS_TO."
# Ask the user to select from the function's arguments,
# and assign the selected argument to the variable 'select_result'.
# Exit on EOF or I/O error. Use the shell's 'select' builtin if available,
# falling back on a less-nice but portable substitute otherwise.
if
case $BASH_VERSION in
?*) : ;;
'')
# '; exit' should be redundant, but Dash doesn't properly fail without it.
(eval 'set --; select x; do break; done; exit') 2>/dev/null
esac
then
# Do this inside 'eval', as otherwise the shell might exit when parsing it
# even though it is never executed.
eval '
doselect() {
select select_result
do
case $select_result in
"") echo >&2 "Please enter a number in range." ;;
?*) break
esac
done || exit
}
# Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
case $BASH_VERSION in
[01].*)
case `echo 1 | (select x in x; do break; done) 2>/dev/null` in
?*) PS3=
esac
esac
'
else
doselect() {
# Field width of the prompt numbers.
select_width=`expr $# : '.*'`
select_i=
while :
do
case $select_i in
'')
select_i=0
for select_word
do
select_i=`expr $select_i + 1`
printf "%${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`
select_result=$1
break
fi
echo >&2 'Please enter a number in range.'
esac
# Prompt and read input.
printf %s >&2 "${PS3-#? }"
read select_i || exit
done
}
fi
while getopts c:n:-: opt
do
case $opt$OPTARG in
@@ -85,7 +157,7 @@ do
esac
done
shift $((OPTIND-1))
shift `expr $OPTIND - 1`
case $# in
0) ;;
*) echo >&2 "$0: $1: unknown argument"; exit 1 ;;
@@ -107,11 +179,6 @@ newline='
IFS=$newline
# Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
case $(echo 1 | (select x in x; do break; done) 2>/dev/null) in
?*) PS3=
esac
# Awk script to read a time zone table and output the same table,
# with each column preceded by its distance from 'here'.
output_distances='
@@ -191,7 +258,7 @@ while
echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
quoted_continents=$(
quoted_continents=`
$AWK -F'\t' '
/^[^#]/ {
entry = substr($3, 1, index($3, "/") - 1)
@@ -205,30 +272,21 @@ while
sort -u |
tr '\n' ' '
echo ''
)
`
eval '
select continent in '"$quoted_continents"' \
doselect '"$quoted_continents"' \
"coord - I want to use geographical coordinates." \
"TZ - I want to specify the time zone using the Posix TZ format."
do
case $continent in
"")
echo >&2 "Please enter a number in range.";;
?*)
case $continent in
Americas) continent=America;;
*" "*) continent=$(expr "$continent" : '\''\([^ ]*\)'\'')
esac
break
esac
done
continent=$select_result
case $continent in
Americas) continent=America;;
*" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
esac
'
esac
case $continent in
'')
exit 1;;
TZ)
# Ask the user for a Posix TZ string. Check that it conforms.
while
@@ -265,36 +323,31 @@ while
'74 degrees 3 minutes west.'
read coord;;
esac
distance_table=$($AWK \
distance_table=`$AWK \
-v coord="$coord" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
"$output_distances" <$TZ_ZONE_TABLE |
sort -n |
sed "${location_limit}q"
)
regions=$(echo "$distance_table" | $AWK '
`
regions=`echo "$distance_table" | $AWK '
BEGIN { FS = "\t" }
{ print $NF }
')
'`
echo >&2 'Please select one of the following' \
'time zone regions,'
echo >&2 'listed roughly in increasing order' \
"of distance from $coord".
select region in $regions
do
case $region in
'') echo >&2 'Please enter a number in range.';;
?*) break;;
esac
done
TZ=$(echo "$distance_table" | $AWK -v region="$region" '
doselect $regions
region=$select_result
TZ=`echo "$distance_table" | $AWK -v region="$region" '
BEGIN { FS="\t" }
$NF == region { print $4 }
')
'`
;;
*)
# Get list of names of countries in the continent or ocean.
countries=$($AWK -F'\t' \
countries=`$AWK -F'\t' \
-v continent="$continent" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
'
@@ -314,32 +367,23 @@ while
print country
}
}
' <$TZ_ZONE_TABLE | sort -f)
' <$TZ_ZONE_TABLE | sort -f`
# If there's more than one country, ask the user which one.
case $countries in
*"$newline"*)
echo >&2 'Please select a country' \
'whose clocks agree with yours.'
select country in $countries
do
case $country in
'') echo >&2 'Please enter a number in range.';;
?*) break
esac
done
case $country in
'') exit 1
esac;;
doselect $countries
country=$select_result;;
*)
country=$countries
esac
# Get list of names of time zone rule regions in the country.
regions=$($AWK -F'\t' \
regions=`$AWK -F'\t' \
-v country="$country" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
'
@@ -353,30 +397,22 @@ while
}
}
$1 == cc { print $4 }
' <$TZ_ZONE_TABLE)
' <$TZ_ZONE_TABLE`
# If there's more than one region, ask the user which one.
case $regions in
*"$newline"*)
echo >&2 'Please select one of the following' \
'time zone regions.'
select region in $regions
do
case $region in
'') echo >&2 'Please enter a number in range.';;
?*) break
esac
done
case $region in
'') exit 1
esac;;
doselect $regions
region=$select_result;;
*)
region=$regions
esac
# Determine TZ from country and region.
TZ=$($AWK -F'\t' \
TZ=`$AWK -F'\t' \
-v country="$country" \
-v region="$region" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
@@ -391,7 +427,7 @@ while
}
}
$1 == cc && $4 == region { print $3 }
' <$TZ_ZONE_TABLE)
' <$TZ_ZONE_TABLE`
esac
# Make sure the corresponding zoneinfo file exists.
@@ -410,10 +446,10 @@ while
extra_info=
for i in 1 2 3 4 5 6 7 8
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]\)')
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)
extra_info="
@@ -440,16 +476,9 @@ Universal Time is now: $UTdate."
echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
echo >&2 "Is the above information OK?"
ok=
select ok in Yes No
do
case $ok in
'') echo >&2 'Please enter 1 for Yes, or 2 for No.';;
?*) break
esac
done
doselect Yes No
ok=$select_result
case $ok in
'') exit 1;;
Yes) break
esac
do coord=

0 comments on commit be06aa4

Please sign in to comment.