[PATCH 0/4] Make tzselect more portable
These patches allow tzselect to run on almost any Bourne-compatible shell (tested: bash, BusyBox ash, dash, ksh93, mksh, pdksh, posh, yash, and zsh) and fix an issue with older versions of BusyBox awk (and makes the setting of FS more consistent throughout tzselect). The prompts are identical to those printed by ksh or bash, except that the final prompt now gives a more generic error message than the old "Please enter 1 for Yes, or 2 for No." message. I hereby disclaim copyright interest in these changes. -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
* tzselect.ksh (_select): New function. --- tzselect.ksh | 130 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/tzselect.ksh b/tzselect.ksh index 1934dd0..89cdfe3 100644 --- a/tzselect.ksh +++ b/tzselect.ksh @@ -8,19 +8,10 @@ REPORT_BUGS_TO=tz@iana.org # Interact with the user via stderr and stdin. # Contributed by Paul Eggert. +# Korn/Bash-like _select function contributed by P. J. McDermott. # Porting notes: # -# This script requires a Posix-like shell with 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 -# source from one of these locations: -# -# Bash <http://www.gnu.org/software/bash/bash.html> -# Korn Shell <http://www.kornshell.com/> -# Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/> -# # 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: @@ -28,6 +19,48 @@ REPORT_BUGS_TO=tz@iana.org # Gawk (GNU awk) <http://www.gnu.org/software/gawk/> # mawk <http://invisible-island.net/mawk/> +_select() +{( + # Field width of the prompt numbers. + width=$(printf 'scale = 0; l(%d) / l(10) + 1\n' $# | bc -l) + + reply= + while :; do + case "$reply" in + '') + i=0 + for word in "$@"; do + i=$(($i + 1)) + printf >&2 "%${width}d) %s\n" $i "$word" + done + ;; + *[!0-9]*) + printf >&2 'Please enter a number in range.\n' + ;; + *) + if [ $reply -gt 0 ] && [ $reply -le $# ]; then + i=0 + for word in "$@"; do + i=$(($i + 1)) + if [ $i -eq $reply ]; then + printf '%s\n' "$word" + return 0 + fi + done + fi + printf >&2 'Please enter a number in range.\n' + ;; + esac + + # Prompt and read input. + printf >&2 '%s' "${PS3-#? }" + if ! read -r reply; then + # EOF or error. + printf >&2 '\n' + return 1 + fi + done +)} # Specify default values for environment variables if they are unset. : ${AWK=awk} @@ -106,12 +139,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=' @@ -208,21 +235,16 @@ while ) eval ' - select continent in '"$quoted_continents"' \ + if ! continent=$(_select '"$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 + "TZ - I want to specify the time zone using the Posix TZ format.") + then + exit 1 + fi + case $continent in + Americas) continent=America;; + *" "*) continent=$(expr "$continent" : '\''\([^ ]*\)'\'') + esac ' esac @@ -280,13 +302,9 @@ while '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 + if ! region=$(_select $regions); then + exit 1 + fi TZ=$(echo "$distance_table" | $AWK -v region="$region" ' BEGIN { FS="\t" } $NF == region { print $4 } @@ -322,17 +340,10 @@ while *"$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;; + if ! country=$(_select $countries); then + exit 1 + fi + ;; *) country=$countries esac @@ -361,16 +372,10 @@ while *"$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;; + if ! region=$(_select $regions); then + exit 1 + fi + ;; *) region=$regions esac @@ -440,14 +445,7 @@ 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 + ok=$(_select Yes No) case $ok in '') exit 1;; Yes) break -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
Thanks. That patch had some problems with Solaris 9 /bin/sh, and also its output wasn't as nice as that of the builtin 'select' command, so I pushed the following patch instead, which I hope addresses the issues raised by your first 3 patches.
From be06aa48dbe1d0dad499006eab66e34ed07dcb5f Mon Sep 17 00:00:00 2001 From: Paul Eggert <eggert@cs.ucla.edu> Date: Sun, 6 Oct 2013 02:11:51 -0700 Subject: [PATCH] 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. --- Makefile | 8 ++- NEWS | 6 ++ tzselect.ksh | 185 ++++++++++++++++++++++++++++++++++------------------------- 3 files changed, 119 insertions(+), 80 deletions(-) diff --git a/Makefile b/Makefile index 74923e7..9b04c2d 100644 --- a/Makefile +++ b/Makefile @@ -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. diff --git a/NEWS b/NEWS index 58f8126..89fbd86 100644 --- a/NEWS +++ b/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 diff --git a/tzselect.ksh b/tzselect.ksh index 1934dd0..7f789bd 100644 --- a/tzselect.ksh +++ b/tzselect.ksh @@ -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,7 +367,7 @@ 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. @@ -322,24 +375,15 @@ while *"$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,7 +397,7 @@ while } } $1 == cc { print $4 } - ' <$TZ_ZONE_TABLE) + ' <$TZ_ZONE_TABLE` # If there's more than one region, ask the user which one. @@ -361,22 +405,14 @@ while *"$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= -- 1.8.1.2
On 2013-10-06 06:01, Paul Eggert wrote:
Thanks. That patch had some problems with Solaris 9 /bin/sh,
OK. I don't have access to Solaris 9 /bin/sh, so I couldn't test that.
and also its output wasn't as nice as that of the builtin 'select' command,
As far as I could tell, the output of my select replacement function was exactly the same as that of the select construct of bash and ksh93. I guess I'm not sure what you mean here.
so I pushed the following patch instead, which I hope addresses the issues raised by your first 3 patches.
Thanks. It looks good (better than what I posted) and works well. [...]
+else + doselect() { + # Field width of the prompt numbers. + select_width=`expr $# : '.*'`
Since posting the patch, I realized that the printf and bc pipeline here was unnecessary. A "${##}" expansion would have been better than what I proposed. This expr is also better.
+ 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 ;;
Shouldn't this print to stderr? $ bash -c 'select x in x; do break; done' >/dev/null 1) x #? 1 $ ksh93 -c 'select x in x; do break; done' >/dev/null 1) x #? 1 $ dash tzselect.ksh >/dev/null Please identify a location so that time zone rules can be set correctly. Please select a continent, ocean, "coord", or "TZ". #? Before this patch, under normal operation the only output on stdout was the `echo "$TZ"` at the end.
+ *[!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
Good catch; this shift is certainly better than my loop. :)
+ echo >&2 'Please enter a number in range.' + esac + + # Prompt and read input. + printf %s >&2 "${PS3-#? }" + read select_i || exit
Yes, exit is better here.
+ done + } +fi
Thanks, -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
Patrick 'P. J.' McDermott wrote:
As far as I could tell, the output of my select replacement function was exactly the same as that of the select construct of bash and ksh93.
'select' generates multicolumn output whereas the replacement is always single-column. Multicolumn is nicer on smaller displays.
A "${##}" expansion would have been better than what I proposed.
Unfortunately Solaris /bin/sh doesn't support that.
+ printf "%${select_width}d) %s\\n" $select_i "$select_word" + done ;;
Shouldn't this print to stderr?
Yes, thanks for catching that. I pushed this further patch:
From 0f9614ba9e3a5afbf667231d8dbb6ab3b5df6354 Mon Sep 17 00:00:00 2001 From: Paul Eggert <eggert@cs.ucla.edu> Date: Sun, 6 Oct 2013 12:28:30 -0700 Subject: [PATCH] * tzselect.ksh (doselect): Prompt to stderr, not stdout.
(Thanks to Patrick 'P. J.' McDermott.) --- tzselect.ksh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tzselect.ksh b/tzselect.ksh index 47c67bd..9d70691 100644 --- a/tzselect.ksh +++ b/tzselect.ksh @@ -119,7 +119,7 @@ else for select_word do select_i=`expr $select_i + 1` - printf "%${select_width}d) %s\\n" $select_i "$select_word" + printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word" done ;; *[!0-9]*) echo >&2 'Please enter a number in range.' ;; @@ -133,7 +133,7 @@ else esac # Prompt and read input. - printf %s >&2 "${PS3-#? }" + printf >&2 %s "${PS3-#? }" read select_i || exit done } -- 1.8.1.2
On Sun, 06 Oct 2013, Paul Eggert wrote:
A "${##}" expansion would have been better than what I proposed.
Unfortunately Solaris /bin/sh doesn't support that.
Why do we care? Solaris has /usr/xpg4/bin/sh, and /bin/ksh. --apb (Alan Barrett)
Alan Barrett wrote:
Why do we care? Solaris has /usr/xpg4/bin/sh, and /bin/ksh.
... not to mention /usr/dt/bin/dtksh. But in the past these implementations have had annoying bugs when running shell scripts, and I'd rather not rely on them. /bin/sh, with all its shortcomings, is better tested. (In my experience /bin/bash is more reliable than /bin/ksh on Solaris but it is not always installed.) If it were a major porting hassle I'd insist on ksh/bash/whatever, but here the hassle is minor and /bin/sh suffices.
On Oct 6, 10:00pm, apb@cequrux.com (Alan Barrett) wrote: -- Subject: Re: [tz] [PATCH 1/4] tzselect: Replace Korn/Bash-only select cons | On Sun, 06 Oct 2013, Paul Eggert wrote: | >> A "${##}" expansion would have been better than what I proposed. | > | >Unfortunately Solaris /bin/sh doesn't support that. | | Why do we care? Solaris has /usr/xpg4/bin/sh, and /bin/ksh. Solaris, 1980 is calling and wants its /bin/sh back! christos
* tzselect.ksh: Move to ... * tzselect.sh: ... here. --- Makefile | 4 +- tzselect.ksh | 469 ----------------------------------------------------------- tzselect.sh | 469 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 471 insertions(+), 471 deletions(-) delete mode 100644 tzselect.ksh create mode 100644 tzselect.sh diff --git a/Makefile b/Makefile index 74923e7..96d9acb 100644 --- a/Makefile +++ b/Makefile @@ -310,7 +310,7 @@ LIBOBJS= localtime.o asctime.o difftime.o HEADERS= tzfile.h private.h NONLIBSRCS= zic.c zdump.c scheck.c ialloc.c NEWUCBSRCS= date.c strftime.c -SOURCES= $(HEADERS) $(LIBSRCS) $(NONLIBSRCS) $(NEWUCBSRCS) tzselect.ksh +SOURCES= $(HEADERS) $(LIBSRCS) $(NONLIBSRCS) $(NEWUCBSRCS) tzselect.sh MANS= newctime.3 newstrftime.3 newtzset.3 time2posix.3 \ tzfile.5 tzselect.8 zic.8 zdump.8 MANTXTS= newctime.3.txt newstrftime.3.txt newtzset.3.txt \ @@ -425,7 +425,7 @@ libtz.a: $(LIBOBJS) date: $(DATEOBJS) $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(DATEOBJS) $(LDLIBS) -tzselect: tzselect.ksh +tzselect: tzselect.sh sed \ -e 's|#!/bin/bash|#!$(KSHELL)|g' \ -e 's|AWK=[^}]*|AWK=$(AWK)|g' \ diff --git a/tzselect.ksh b/tzselect.ksh deleted file mode 100644 index 89cdfe3..0000000 --- a/tzselect.ksh +++ /dev/null @@ -1,469 +0,0 @@ -#!/bin/bash - -PKGVERSION='(tzcode) ' -TZVERSION=see_Makefile -REPORT_BUGS_TO=tz@iana.org - -# Ask the user about the time zone, and output the resulting TZ value to stdout. -# Interact with the user via stderr and stdin. - -# Contributed by Paul Eggert. -# Korn/Bash-like _select function contributed by P. J. McDermott. - -# Porting notes: -# -# 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: -# -# Gawk (GNU awk) <http://www.gnu.org/software/gawk/> -# mawk <http://invisible-island.net/mawk/> - -_select() -{( - # Field width of the prompt numbers. - width=$(printf 'scale = 0; l(%d) / l(10) + 1\n' $# | bc -l) - - reply= - while :; do - case "$reply" in - '') - i=0 - for word in "$@"; do - i=$(($i + 1)) - printf >&2 "%${width}d) %s\n" $i "$word" - done - ;; - *[!0-9]*) - printf >&2 'Please enter a number in range.\n' - ;; - *) - if [ $reply -gt 0 ] && [ $reply -le $# ]; then - i=0 - for word in "$@"; do - i=$(($i + 1)) - if [ $i -eq $reply ]; then - printf '%s\n' "$word" - return 0 - fi - done - fi - printf >&2 'Please enter a number in range.\n' - ;; - esac - - # Prompt and read input. - printf >&2 '%s' "${PS3-#? }" - if ! read -r reply; then - # EOF or error. - printf >&2 '\n' - return 1 - fi - done -)} - -# Specify default values for environment variables if they are unset. -: ${AWK=awk} -: ${TZDIR=$(pwd)} - -# Check for awk Posix compliance. -($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 -[ $? = 123 ] || { - echo >&2 "$0: Sorry, your \`$AWK' program is not Posix compatible." - exit 1 -} - -coord= -location_limit=10 - -usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT] -Select a time zone interactively. - -Options: - - -c COORD - Instead of asking for continent and then country and then city, - ask for selection from time zones whose largest cities - are closest to the location with geographical coordinates COORD. - COORD should use ISO 6709 notation, for example, '-c +4852+00220' - for Paris (in degrees and minutes, North and East), or - '-c -35-058' for Buenos Aires (in degrees, South and West). - - -n LIMIT - Display at most LIMIT locations when -c is used (default $location_limit). - - --version - Output version information. - - --help - Output this help. - -Report bugs to $REPORT_BUGS_TO." - -while getopts c:n:-: opt -do - case $opt$OPTARG in - c*) - coord=$OPTARG ;; - n*) - location_limit=$OPTARG ;; - -help) - exec echo "$usage" ;; - -version) - exec echo "tzselect $PKGVERSION$TZVERSION" ;; - -*) - echo >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;; - *) - echo >&2 "$0: try '$0 --help'"; exit 1 ;; - esac -done - -shift $((OPTIND-1)) -case $# in -0) ;; -*) echo >&2 "$0: $1: unknown argument"; exit 1 ;; -esac - -# Make sure the tables are readable. -TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab -TZ_ZONE_TABLE=$TZDIR/zone.tab -for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE -do - <$f || { - echo >&2 "$0: time zone files are not set up correctly" - exit 1 - } -done - -newline=' -' -IFS=$newline - -# Awk script to read a time zone table and output the same table, -# with each column preceded by its distance from 'here'. -output_distances=' - BEGIN { - FS = "\t" - while (getline <TZ_COUNTRY_TABLE) - if ($0 ~ /^[^#]/) - country[$1] = $2 - country["US"] = "US" # Otherwise the strings get too long. - } - function convert_coord(coord, deg, min, ilen, sign, sec) { - if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) { - degminsec = coord - intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000) - minsec = degminsec - intdeg * 10000 - intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100) - sec = minsec - intmin * 100 - deg = (intdeg * 3600 + intmin * 60 + sec) / 3600 - } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) { - degmin = coord - intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100) - min = degmin - intdeg * 100 - deg = (intdeg * 60 + min) / 60 - } else - deg = coord - return deg * 0.017453292519943296 - } - function convert_latitude(coord) { - match(coord, /..*[-+]/) - return convert_coord(substr(coord, 1, RLENGTH - 1)) - } - function convert_longitude(coord) { - match(coord, /..*[-+]/) - return convert_coord(substr(coord, RLENGTH)) - } - # Great-circle distance between points with given latitude and longitude. - # Inputs and output are in radians. This uses the great-circle special - # case of the Vicenty formula for distances on ellipsoids. - function dist(lat1, long1, lat2, long2, dlong, x, y, num, denom) { - dlong = long2 - long1 - x = cos (lat2) * sin (dlong) - y = cos (lat1) * sin (lat2) - sin (lat1) * cos (lat2) * cos (dlong) - num = sqrt (x * x + y * y) - denom = sin (lat1) * sin (lat2) + cos (lat1) * cos (lat2) * cos (dlong) - return atan2(num, denom) - } - BEGIN { - coord_lat = convert_latitude(coord) - coord_long = convert_longitude(coord) - } - /^[^#]/ { - here_lat = convert_latitude($2) - here_long = convert_longitude($2) - line = $1 "\t" $2 "\t" $3 "\t" country[$1] - if (NF == 4) - line = line " - " $4 - printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line - } -' - -# Begin the main loop. We come back here if the user wants to retry. -while - - echo >&2 'Please identify a location' \ - 'so that time zone rules can be set correctly.' - - continent= - country= - region= - - case $coord in - ?*) - continent=coord;; - '') - - # Ask the user for continent or ocean. - - echo >&2 'Please select a continent, ocean, "coord", or "TZ".' - - quoted_continents=$( - $AWK -F'\t' ' - /^[^#]/ { - entry = substr($3, 1, index($3, "/") - 1) - if (entry == "America") - entry = entry "s" - if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) - entry = entry " Ocean" - printf "'\''%s'\''\n", entry - } - ' $TZ_ZONE_TABLE | - sort -u | - tr '\n' ' ' - echo '' - ) - - eval ' - if ! continent=$(_select '"$quoted_continents"' \ - "coord - I want to use geographical coordinates." \ - "TZ - I want to specify the time zone using the Posix TZ format.") - then - exit 1 - fi - 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 - echo >&2 'Please enter the desired value' \ - 'of the TZ environment variable.' - echo >&2 'For example, GST-10 is a zone named GST' \ - 'that is 10 hours ahead (east) of UTC.' - read TZ - $AWK -v TZ="$TZ" 'BEGIN { - tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+" - time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?" - offset = "[-+]?" time - date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)" - datetime = "," date "(/" time ")?" - tzpattern = "^(:.*|" tzname offset "(" tzname \ - "(" offset ")?(" datetime datetime ")?)?)$" - if (TZ ~ tzpattern) exit 1 - exit 0 - }' - do - echo >&2 "\`$TZ' is not a conforming" \ - 'Posix time zone string.' - done - TZ_for_date=$TZ;; - *) - case $continent in - coord) - case $coord in - '') - echo >&2 'Please enter coordinates' \ - 'in ISO 6709 notation.' - echo >&2 'For example, +4042-07403 stands for' - echo >&2 '40 degrees 42 minutes north,' \ - '74 degrees 3 minutes west.' - read coord;; - esac - 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 ' - 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". - if ! region=$(_select $regions); then - exit 1 - fi - 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' \ - -v continent="$continent" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - /^#/ { next } - $3 ~ ("^" continent "/") { - if (!cc_seen[$1]++) cc_list[++ccs] = $1 - } - END { - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/) cc_name[$1] = $2 - } - for (i = 1; i <= ccs; i++) { - country = cc_list[i] - if (cc_name[country]) { - country = cc_name[country] - } - print country - } - } - ' <$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.' - if ! country=$(_select $countries); then - exit 1 - fi - ;; - *) - country=$countries - esac - - - # Get list of names of time zone rule regions in the country. - regions=$($AWK -F'\t' \ - -v country="$country" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - BEGIN { - cc = country - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/ && country == $2) { - cc = $1 - break - } - } - } - $1 == cc { print $4 } - ' <$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.' - if ! region=$(_select $regions); then - exit 1 - fi - ;; - *) - region=$regions - esac - - # Determine TZ from country and region. - TZ=$($AWK -F'\t' \ - -v country="$country" \ - -v region="$region" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - BEGIN { - cc = country - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/ && country == $2) { - cc = $1 - break - } - } - } - $1 == cc && $4 == region { print $3 } - ' <$TZ_ZONE_TABLE) - esac - - # Make sure the corresponding zoneinfo file exists. - TZ_for_date=$TZDIR/$TZ - <$TZ_for_date || { - echo >&2 "$0: time zone files are not set up correctly" - exit 1 - } - esac - - - # Use the proposed TZ to output the current date relative to UTC. - # Loop until they agree in seconds. - # Give up after 8 unsuccessful tries. - - 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]\)') - case $TZsec in - $UTsec) - extra_info=" -Local time is now: $TZdate. -Universal Time is now: $UTdate." - break - esac - done - - - # Output TZ info and ask the user to confirm. - - echo >&2 "" - echo >&2 "The following information has been given:" - echo >&2 "" - case $country%$region%$coord in - ?*%?*%) echo >&2 " $country$newline $region";; - ?*%%) echo >&2 " $country";; - %?*%?*) echo >&2 " coord $coord$newline $region";; - %%?*) echo >&2 " coord $coord";; - +) echo >&2 " TZ='$TZ'" - esac - echo >&2 "" - echo >&2 "Therefore TZ='$TZ' will be used.$extra_info" - echo >&2 "Is the above information OK?" - - ok=$(_select Yes No) - case $ok in - '') exit 1;; - Yes) break - esac -do coord= -done - -case $SHELL in -*csh) file=.login line="setenv TZ '$TZ'";; -*) file=.profile line="TZ='$TZ'; export TZ" -esac - -echo >&2 " -You can make this change permanent for yourself by appending the line - $line -to the file '$file' in your home directory; then log out and log in again. - -Here is that TZ value again, this time on standard output so that you -can use the $0 command in shell scripts:" - -echo "$TZ" diff --git a/tzselect.sh b/tzselect.sh new file mode 100644 index 0000000..89cdfe3 --- /dev/null +++ b/tzselect.sh @@ -0,0 +1,469 @@ +#!/bin/bash + +PKGVERSION='(tzcode) ' +TZVERSION=see_Makefile +REPORT_BUGS_TO=tz@iana.org + +# Ask the user about the time zone, and output the resulting TZ value to stdout. +# Interact with the user via stderr and stdin. + +# Contributed by Paul Eggert. +# Korn/Bash-like _select function contributed by P. J. McDermott. + +# Porting notes: +# +# 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: +# +# Gawk (GNU awk) <http://www.gnu.org/software/gawk/> +# mawk <http://invisible-island.net/mawk/> + +_select() +{( + # Field width of the prompt numbers. + width=$(printf 'scale = 0; l(%d) / l(10) + 1\n' $# | bc -l) + + reply= + while :; do + case "$reply" in + '') + i=0 + for word in "$@"; do + i=$(($i + 1)) + printf >&2 "%${width}d) %s\n" $i "$word" + done + ;; + *[!0-9]*) + printf >&2 'Please enter a number in range.\n' + ;; + *) + if [ $reply -gt 0 ] && [ $reply -le $# ]; then + i=0 + for word in "$@"; do + i=$(($i + 1)) + if [ $i -eq $reply ]; then + printf '%s\n' "$word" + return 0 + fi + done + fi + printf >&2 'Please enter a number in range.\n' + ;; + esac + + # Prompt and read input. + printf >&2 '%s' "${PS3-#? }" + if ! read -r reply; then + # EOF or error. + printf >&2 '\n' + return 1 + fi + done +)} + +# Specify default values for environment variables if they are unset. +: ${AWK=awk} +: ${TZDIR=$(pwd)} + +# Check for awk Posix compliance. +($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 +[ $? = 123 ] || { + echo >&2 "$0: Sorry, your \`$AWK' program is not Posix compatible." + exit 1 +} + +coord= +location_limit=10 + +usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT] +Select a time zone interactively. + +Options: + + -c COORD + Instead of asking for continent and then country and then city, + ask for selection from time zones whose largest cities + are closest to the location with geographical coordinates COORD. + COORD should use ISO 6709 notation, for example, '-c +4852+00220' + for Paris (in degrees and minutes, North and East), or + '-c -35-058' for Buenos Aires (in degrees, South and West). + + -n LIMIT + Display at most LIMIT locations when -c is used (default $location_limit). + + --version + Output version information. + + --help + Output this help. + +Report bugs to $REPORT_BUGS_TO." + +while getopts c:n:-: opt +do + case $opt$OPTARG in + c*) + coord=$OPTARG ;; + n*) + location_limit=$OPTARG ;; + -help) + exec echo "$usage" ;; + -version) + exec echo "tzselect $PKGVERSION$TZVERSION" ;; + -*) + echo >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;; + *) + echo >&2 "$0: try '$0 --help'"; exit 1 ;; + esac +done + +shift $((OPTIND-1)) +case $# in +0) ;; +*) echo >&2 "$0: $1: unknown argument"; exit 1 ;; +esac + +# Make sure the tables are readable. +TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab +TZ_ZONE_TABLE=$TZDIR/zone.tab +for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE +do + <$f || { + echo >&2 "$0: time zone files are not set up correctly" + exit 1 + } +done + +newline=' +' +IFS=$newline + +# Awk script to read a time zone table and output the same table, +# with each column preceded by its distance from 'here'. +output_distances=' + BEGIN { + FS = "\t" + while (getline <TZ_COUNTRY_TABLE) + if ($0 ~ /^[^#]/) + country[$1] = $2 + country["US"] = "US" # Otherwise the strings get too long. + } + function convert_coord(coord, deg, min, ilen, sign, sec) { + if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) { + degminsec = coord + intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000) + minsec = degminsec - intdeg * 10000 + intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100) + sec = minsec - intmin * 100 + deg = (intdeg * 3600 + intmin * 60 + sec) / 3600 + } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) { + degmin = coord + intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100) + min = degmin - intdeg * 100 + deg = (intdeg * 60 + min) / 60 + } else + deg = coord + return deg * 0.017453292519943296 + } + function convert_latitude(coord) { + match(coord, /..*[-+]/) + return convert_coord(substr(coord, 1, RLENGTH - 1)) + } + function convert_longitude(coord) { + match(coord, /..*[-+]/) + return convert_coord(substr(coord, RLENGTH)) + } + # Great-circle distance between points with given latitude and longitude. + # Inputs and output are in radians. This uses the great-circle special + # case of the Vicenty formula for distances on ellipsoids. + function dist(lat1, long1, lat2, long2, dlong, x, y, num, denom) { + dlong = long2 - long1 + x = cos (lat2) * sin (dlong) + y = cos (lat1) * sin (lat2) - sin (lat1) * cos (lat2) * cos (dlong) + num = sqrt (x * x + y * y) + denom = sin (lat1) * sin (lat2) + cos (lat1) * cos (lat2) * cos (dlong) + return atan2(num, denom) + } + BEGIN { + coord_lat = convert_latitude(coord) + coord_long = convert_longitude(coord) + } + /^[^#]/ { + here_lat = convert_latitude($2) + here_long = convert_longitude($2) + line = $1 "\t" $2 "\t" $3 "\t" country[$1] + if (NF == 4) + line = line " - " $4 + printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line + } +' + +# Begin the main loop. We come back here if the user wants to retry. +while + + echo >&2 'Please identify a location' \ + 'so that time zone rules can be set correctly.' + + continent= + country= + region= + + case $coord in + ?*) + continent=coord;; + '') + + # Ask the user for continent or ocean. + + echo >&2 'Please select a continent, ocean, "coord", or "TZ".' + + quoted_continents=$( + $AWK -F'\t' ' + /^[^#]/ { + entry = substr($3, 1, index($3, "/") - 1) + if (entry == "America") + entry = entry "s" + if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) + entry = entry " Ocean" + printf "'\''%s'\''\n", entry + } + ' $TZ_ZONE_TABLE | + sort -u | + tr '\n' ' ' + echo '' + ) + + eval ' + if ! continent=$(_select '"$quoted_continents"' \ + "coord - I want to use geographical coordinates." \ + "TZ - I want to specify the time zone using the Posix TZ format.") + then + exit 1 + fi + 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 + echo >&2 'Please enter the desired value' \ + 'of the TZ environment variable.' + echo >&2 'For example, GST-10 is a zone named GST' \ + 'that is 10 hours ahead (east) of UTC.' + read TZ + $AWK -v TZ="$TZ" 'BEGIN { + tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+" + time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?" + offset = "[-+]?" time + date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)" + datetime = "," date "(/" time ")?" + tzpattern = "^(:.*|" tzname offset "(" tzname \ + "(" offset ")?(" datetime datetime ")?)?)$" + if (TZ ~ tzpattern) exit 1 + exit 0 + }' + do + echo >&2 "\`$TZ' is not a conforming" \ + 'Posix time zone string.' + done + TZ_for_date=$TZ;; + *) + case $continent in + coord) + case $coord in + '') + echo >&2 'Please enter coordinates' \ + 'in ISO 6709 notation.' + echo >&2 'For example, +4042-07403 stands for' + echo >&2 '40 degrees 42 minutes north,' \ + '74 degrees 3 minutes west.' + read coord;; + esac + 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 ' + 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". + if ! region=$(_select $regions); then + exit 1 + fi + 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' \ + -v continent="$continent" \ + -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ + ' + /^#/ { next } + $3 ~ ("^" continent "/") { + if (!cc_seen[$1]++) cc_list[++ccs] = $1 + } + END { + while (getline <TZ_COUNTRY_TABLE) { + if ($0 !~ /^#/) cc_name[$1] = $2 + } + for (i = 1; i <= ccs; i++) { + country = cc_list[i] + if (cc_name[country]) { + country = cc_name[country] + } + print country + } + } + ' <$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.' + if ! country=$(_select $countries); then + exit 1 + fi + ;; + *) + country=$countries + esac + + + # Get list of names of time zone rule regions in the country. + regions=$($AWK -F'\t' \ + -v country="$country" \ + -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ + ' + BEGIN { + cc = country + while (getline <TZ_COUNTRY_TABLE) { + if ($0 !~ /^#/ && country == $2) { + cc = $1 + break + } + } + } + $1 == cc { print $4 } + ' <$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.' + if ! region=$(_select $regions); then + exit 1 + fi + ;; + *) + region=$regions + esac + + # Determine TZ from country and region. + TZ=$($AWK -F'\t' \ + -v country="$country" \ + -v region="$region" \ + -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ + ' + BEGIN { + cc = country + while (getline <TZ_COUNTRY_TABLE) { + if ($0 !~ /^#/ && country == $2) { + cc = $1 + break + } + } + } + $1 == cc && $4 == region { print $3 } + ' <$TZ_ZONE_TABLE) + esac + + # Make sure the corresponding zoneinfo file exists. + TZ_for_date=$TZDIR/$TZ + <$TZ_for_date || { + echo >&2 "$0: time zone files are not set up correctly" + exit 1 + } + esac + + + # Use the proposed TZ to output the current date relative to UTC. + # Loop until they agree in seconds. + # Give up after 8 unsuccessful tries. + + 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]\)') + case $TZsec in + $UTsec) + extra_info=" +Local time is now: $TZdate. +Universal Time is now: $UTdate." + break + esac + done + + + # Output TZ info and ask the user to confirm. + + echo >&2 "" + echo >&2 "The following information has been given:" + echo >&2 "" + case $country%$region%$coord in + ?*%?*%) echo >&2 " $country$newline $region";; + ?*%%) echo >&2 " $country";; + %?*%?*) echo >&2 " coord $coord$newline $region";; + %%?*) echo >&2 " coord $coord";; + +) echo >&2 " TZ='$TZ'" + esac + echo >&2 "" + echo >&2 "Therefore TZ='$TZ' will be used.$extra_info" + echo >&2 "Is the above information OK?" + + ok=$(_select Yes No) + case $ok in + '') exit 1;; + Yes) break + esac +do coord= +done + +case $SHELL in +*csh) file=.login line="setenv TZ '$TZ'";; +*) file=.profile line="TZ='$TZ'; export TZ" +esac + +echo >&2 " +You can make this change permanent for yourself by appending the line + $line +to the file '$file' in your home directory; then log out and log in again. + +Here is that TZ value again, this time on standard output so that you +can use the $0 command in shell scripts:" + +echo "$TZ" -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
* tzselect.sh: Set interpreter to /bin/sh. * Makefile (KSHELL): Remove. All uses removed. --- Makefile | 6 +----- tzselect.sh | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 96d9acb..2ec915d 100644 --- a/Makefile +++ b/Makefile @@ -246,10 +246,6 @@ 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. -KSHELL= /bin/bash - # The path where SGML DTDs are kept. # The default is appropriate for Ubuntu 12.10. SGML_TOPDIR= /usr @@ -427,7 +423,7 @@ date: $(DATEOBJS) tzselect: tzselect.sh sed \ - -e 's|#!/bin/bash|#!$(KSHELL)|g' \ + -e 's|#!/bin/sh|#!$(SHELL)|g' \ -e 's|AWK=[^}]*|AWK=$(AWK)|g' \ -e 's|\(PKGVERSION\)=.*|\1='\''($(PACKAGE)) '\''|' \ -e 's|\(REPORT_BUGS_TO\)=.*|\1=$(BUGEMAIL)|' \ diff --git a/tzselect.sh b/tzselect.sh index 89cdfe3..72e7504 100644 --- a/tzselect.sh +++ b/tzselect.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh PKGVERSION='(tzcode) ' TZVERSION=see_Makefile -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
Before version 1.21.0, BusyBox awk didn't unescape the argument to the -F option. As a result, tzselect couldn't parse tables with such versions of BusyBox awk. See <https://bugs.busybox.net/show_bug.cgi?id=5126> and <http://git.busybox.net/busybox/commit?id=ea664dd>. * tzselect.sh: Replace "\t" in awk -F arguments with the tab character. --- tzselect.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tzselect.sh b/tzselect.sh index 72e7504..829a808 100644 --- a/tzselect.sh +++ b/tzselect.sh @@ -219,7 +219,8 @@ while echo >&2 'Please select a continent, ocean, "coord", or "TZ".' quoted_continents=$( - $AWK -F'\t' ' + $AWK ' + BEGIN { FS = "\t" } /^[^#]/ { entry = substr($3, 1, index($3, "/") - 1) if (entry == "America") @@ -312,10 +313,11 @@ while ;; *) # Get list of names of countries in the continent or ocean. - countries=$($AWK -F'\t' \ + countries=$($AWK \ -v continent="$continent" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' + BEGIN { FS = "\t" } /^#/ { next } $3 ~ ("^" continent "/") { if (!cc_seen[$1]++) cc_list[++ccs] = $1 @@ -350,11 +352,12 @@ while # Get list of names of time zone rule regions in the country. - regions=$($AWK -F'\t' \ + regions=$($AWK \ -v country="$country" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { + FS = "\t" cc = country while (getline <TZ_COUNTRY_TABLE) { if ($0 !~ /^#/ && country == $2) { @@ -381,12 +384,13 @@ while esac # Determine TZ from country and region. - TZ=$($AWK -F'\t' \ + TZ=$($AWK \ -v country="$country" \ -v region="$region" \ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ ' BEGIN { + FS = "\t" cc = country while (getline <TZ_COUNTRY_TABLE) { if ($0 !~ /^#/ && country == $2) { -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
On 2013-10-05 12:36, Patrick 'P. J.' McDermott wrote:
* tzselect.sh: Replace "\t" in awk -F arguments with the tab character.
Sorry, this is old and inaccurate. This line should say: * tzselect.sh: Replace awk -F options with FS assignments. -- Patrick "P. J." McDermott http://www.pehjota.net/ Lead Developer, ProteanOS http://www.proteanos.com/
Thanks, that patch looks good, and I merged it with the other changes I made and pushed it into the experimental version on github.
participants (4)
-
Alan Barrett -
christos@zoulas.com -
Patrick 'P. J.' McDermott -
Paul Eggert