Skip to content

Commit be06aa4

Browse files
committed
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.
1 parent b9b27d5 commit be06aa4

File tree

3 files changed

+119
-80
lines changed

3 files changed

+119
-80
lines changed

Makefile

+6-2
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,12 @@ ZFLAGS=
246246
# The name of a Posix-compliant `awk' on your system.
247247
AWK= awk
248248

249-
# The full path name of a Posix-compliant shell that supports the Korn shell's
250-
# 'select' statement, as an extension. These days, Bash is the most popular.
249+
# The full path name of a Posix-compliant shell, preferably one that supports
250+
# the Korn shell's 'select' statement as an extension.
251+
# These days, Bash is the most popular.
252+
# It should be OK to set this to /bin/sh, on platforms where /bin/sh
253+
# lacks 'select' or doesn't completely conform to Posix, but /bin/bash
254+
# is typically nicer if it works.
251255
KSHELL= /bin/bash
252256

253257
# The path where SGML DTDs are kept.

NEWS

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ Unreleased, experimental changes
88
This avoids some year-2038 glitches introduced in 2013g.
99
(Thanks to Yoshito Umaoka for reporting the problem.)
1010

11+
Changes affecting API
12+
13+
The 'tzselect' command no longer requires the 'select' command,
14+
and should now work with /bin/sh on more platforms. (Thanks to
15+
Patrick 'P. J.' McDermott for reporting the problem.)
16+
1117
Changes affecting the build procedure
1218

1319
The builder can specify which programs to use, if any, instead of

tzselect.ksh

+107-78
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ REPORT_BUGS_TO=tz@iana.org
1111

1212
# Porting notes:
1313
#
14-
# This script requires a Posix-like shell with the extension of a
14+
# This script requires a Posix-like shell and prefers the extension of a
1515
# 'select' statement. The 'select' statement was introduced in the
1616
# Korn shell and is available in Bash and other shell implementations.
1717
# If your host lacks both Bash and the Korn shell, you can get their
@@ -21,6 +21,10 @@ REPORT_BUGS_TO=tz@iana.org
2121
# Korn Shell <http://www.kornshell.com/>
2222
# Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/>
2323
#
24+
# For portability to Solaris 9 /bin/sh this script avoids some POSIX
25+
# features and common extensions, such as $(...) (which works sometimes
26+
# but not others), $((...)), and $10.
27+
#
2428
# This script also uses several features of modern awk programs.
2529
# If your host lacks awk, or has an old awk that does not conform to Posix,
2630
# you can use either of the following free programs instead:
@@ -31,7 +35,7 @@ REPORT_BUGS_TO=tz@iana.org
3135

3236
# Specify default values for environment variables if they are unset.
3337
: ${AWK=awk}
34-
: ${TZDIR=$(pwd)}
38+
: ${TZDIR=`pwd`}
3539

3640
# Check for awk Posix compliance.
3741
($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
@@ -67,6 +71,74 @@ Options:
6771
6872
Report bugs to $REPORT_BUGS_TO."
6973

74+
# Ask the user to select from the function's arguments,
75+
# and assign the selected argument to the variable 'select_result'.
76+
# Exit on EOF or I/O error. Use the shell's 'select' builtin if available,
77+
# falling back on a less-nice but portable substitute otherwise.
78+
if
79+
case $BASH_VERSION in
80+
?*) : ;;
81+
'')
82+
# '; exit' should be redundant, but Dash doesn't properly fail without it.
83+
(eval 'set --; select x; do break; done; exit') 2>/dev/null
84+
esac
85+
then
86+
# Do this inside 'eval', as otherwise the shell might exit when parsing it
87+
# even though it is never executed.
88+
eval '
89+
doselect() {
90+
select select_result
91+
do
92+
case $select_result in
93+
"") echo >&2 "Please enter a number in range." ;;
94+
?*) break
95+
esac
96+
done || exit
97+
}
98+
99+
# Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
100+
case $BASH_VERSION in
101+
[01].*)
102+
case `echo 1 | (select x in x; do break; done) 2>/dev/null` in
103+
?*) PS3=
104+
esac
105+
esac
106+
'
107+
else
108+
doselect() {
109+
# Field width of the prompt numbers.
110+
select_width=`expr $# : '.*'`
111+
112+
select_i=
113+
114+
while :
115+
do
116+
case $select_i in
117+
'')
118+
select_i=0
119+
for select_word
120+
do
121+
select_i=`expr $select_i + 1`
122+
printf "%${select_width}d) %s\\n" $select_i "$select_word"
123+
done ;;
124+
*[!0-9]*)
125+
echo >&2 'Please enter a number in range.' ;;
126+
*)
127+
if test 1 -le $select_i && test $select_i -le $#; then
128+
shift `expr $select_i - 1`
129+
select_result=$1
130+
break
131+
fi
132+
echo >&2 'Please enter a number in range.'
133+
esac
134+
135+
# Prompt and read input.
136+
printf %s >&2 "${PS3-#? }"
137+
read select_i || exit
138+
done
139+
}
140+
fi
141+
70142
while getopts c:n:-: opt
71143
do
72144
case $opt$OPTARG in
@@ -85,7 +157,7 @@ do
85157
esac
86158
done
87159

88-
shift $((OPTIND-1))
160+
shift `expr $OPTIND - 1`
89161
case $# in
90162
0) ;;
91163
*) echo >&2 "$0: $1: unknown argument"; exit 1 ;;
@@ -107,11 +179,6 @@ newline='
107179
IFS=$newline
108180

109181

110-
# Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
111-
case $(echo 1 | (select x in x; do break; done) 2>/dev/null) in
112-
?*) PS3=
113-
esac
114-
115182
# Awk script to read a time zone table and output the same table,
116183
# with each column preceded by its distance from 'here'.
117184
output_distances='
@@ -191,7 +258,7 @@ while
191258

192259
echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
193260

194-
quoted_continents=$(
261+
quoted_continents=`
195262
$AWK -F'\t' '
196263
/^[^#]/ {
197264
entry = substr($3, 1, index($3, "/") - 1)
@@ -205,30 +272,21 @@ while
205272
sort -u |
206273
tr '\n' ' '
207274
echo ''
208-
)
275+
`
209276

210277
eval '
211-
select continent in '"$quoted_continents"' \
278+
doselect '"$quoted_continents"' \
212279
"coord - I want to use geographical coordinates." \
213280
"TZ - I want to specify the time zone using the Posix TZ format."
214-
do
215-
case $continent in
216-
"")
217-
echo >&2 "Please enter a number in range.";;
218-
?*)
219-
case $continent in
220-
Americas) continent=America;;
221-
*" "*) continent=$(expr "$continent" : '\''\([^ ]*\)'\'')
222-
esac
223-
break
224-
esac
225-
done
281+
continent=$select_result
282+
case $continent in
283+
Americas) continent=America;;
284+
*" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
285+
esac
226286
'
227287
esac
228288

229289
case $continent in
230-
'')
231-
exit 1;;
232290
TZ)
233291
# Ask the user for a Posix TZ string. Check that it conforms.
234292
while
@@ -265,36 +323,31 @@ while
265323
'74 degrees 3 minutes west.'
266324
read coord;;
267325
esac
268-
distance_table=$($AWK \
326+
distance_table=`$AWK \
269327
-v coord="$coord" \
270328
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
271329
"$output_distances" <$TZ_ZONE_TABLE |
272330
sort -n |
273331
sed "${location_limit}q"
274-
)
275-
regions=$(echo "$distance_table" | $AWK '
332+
`
333+
regions=`echo "$distance_table" | $AWK '
276334
BEGIN { FS = "\t" }
277335
{ print $NF }
278-
')
336+
'`
279337
echo >&2 'Please select one of the following' \
280338
'time zone regions,'
281339
echo >&2 'listed roughly in increasing order' \
282340
"of distance from $coord".
283-
select region in $regions
284-
do
285-
case $region in
286-
'') echo >&2 'Please enter a number in range.';;
287-
?*) break;;
288-
esac
289-
done
290-
TZ=$(echo "$distance_table" | $AWK -v region="$region" '
341+
doselect $regions
342+
region=$select_result
343+
TZ=`echo "$distance_table" | $AWK -v region="$region" '
291344
BEGIN { FS="\t" }
292345
$NF == region { print $4 }
293-
')
346+
'`
294347
;;
295348
*)
296349
# Get list of names of countries in the continent or ocean.
297-
countries=$($AWK -F'\t' \
350+
countries=`$AWK -F'\t' \
298351
-v continent="$continent" \
299352
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
300353
'
@@ -314,32 +367,23 @@ while
314367
print country
315368
}
316369
}
317-
' <$TZ_ZONE_TABLE | sort -f)
370+
' <$TZ_ZONE_TABLE | sort -f`
318371

319372

320373
# If there's more than one country, ask the user which one.
321374
case $countries in
322375
*"$newline"*)
323376
echo >&2 'Please select a country' \
324377
'whose clocks agree with yours.'
325-
select country in $countries
326-
do
327-
case $country in
328-
'') echo >&2 'Please enter a number in range.';;
329-
?*) break
330-
esac
331-
done
332-
333-
case $country in
334-
'') exit 1
335-
esac;;
378+
doselect $countries
379+
country=$select_result;;
336380
*)
337381
country=$countries
338382
esac
339383

340384

341385
# Get list of names of time zone rule regions in the country.
342-
regions=$($AWK -F'\t' \
386+
regions=`$AWK -F'\t' \
343387
-v country="$country" \
344388
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
345389
'
@@ -353,30 +397,22 @@ while
353397
}
354398
}
355399
$1 == cc { print $4 }
356-
' <$TZ_ZONE_TABLE)
400+
' <$TZ_ZONE_TABLE`
357401

358402

359403
# If there's more than one region, ask the user which one.
360404
case $regions in
361405
*"$newline"*)
362406
echo >&2 'Please select one of the following' \
363407
'time zone regions.'
364-
select region in $regions
365-
do
366-
case $region in
367-
'') echo >&2 'Please enter a number in range.';;
368-
?*) break
369-
esac
370-
done
371-
case $region in
372-
'') exit 1
373-
esac;;
408+
doselect $regions
409+
region=$select_result;;
374410
*)
375411
region=$regions
376412
esac
377413

378414
# Determine TZ from country and region.
379-
TZ=$($AWK -F'\t' \
415+
TZ=`$AWK -F'\t' \
380416
-v country="$country" \
381417
-v region="$region" \
382418
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
@@ -391,7 +427,7 @@ while
391427
}
392428
}
393429
$1 == cc && $4 == region { print $3 }
394-
' <$TZ_ZONE_TABLE)
430+
' <$TZ_ZONE_TABLE`
395431
esac
396432

397433
# Make sure the corresponding zoneinfo file exists.
@@ -410,10 +446,10 @@ while
410446
extra_info=
411447
for i in 1 2 3 4 5 6 7 8
412448
do
413-
TZdate=$(LANG=C TZ="$TZ_for_date" date)
414-
UTdate=$(LANG=C TZ=UTC0 date)
415-
TZsec=$(expr "$TZdate" : '.*:\([0-5][0-9]\)')
416-
UTsec=$(expr "$UTdate" : '.*:\([0-5][0-9]\)')
449+
TZdate=`LANG=C TZ="$TZ_for_date" date`
450+
UTdate=`LANG=C TZ=UTC0 date`
451+
TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
452+
UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
417453
case $TZsec in
418454
$UTsec)
419455
extra_info="
@@ -440,16 +476,9 @@ Universal Time is now: $UTdate."
440476
echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
441477
echo >&2 "Is the above information OK?"
442478

443-
ok=
444-
select ok in Yes No
445-
do
446-
case $ok in
447-
'') echo >&2 'Please enter 1 for Yes, or 2 for No.';;
448-
?*) break
449-
esac
450-
done
479+
doselect Yes No
480+
ok=$select_result
451481
case $ok in
452-
'') exit 1;;
453482
Yes) break
454483
esac
455484
do coord=

0 commit comments

Comments
 (0)