diff options
| author | Joseph Myers <joseph@codesourcery.com> | 2013-12-20 13:10:07 +0000 |
|---|---|---|
| committer | Joseph Myers <joseph@codesourcery.com> | 2013-12-20 13:10:07 +0000 |
| commit | 85bff96ad652b463f83d4cf19239eff1535e186a (patch) | |
| tree | d651b5e72fe828f47613e6813ac5d54b0b7aaf66 | |
| parent | b7867a3bfb9d7e00204c088feb227abddfc7bb43 (diff) | |
| download | glibc-85bff96ad652b463f83d4cf19239eff1535e186a.tar.xz glibc-85bff96ad652b463f83d4cf19239eff1535e186a.zip | |
Update timezone code from tzcode 2013i.
Now we have Paul's support for version-3 tz files checked in, this
patch updates all the code we take (unmodified) from tzcode to version
2013i (which includes the support for generating version-3 tz files
where necessary).
Tested x86_64.
* timezone/checktab.awk: Update from tzcode 2013i.
* timezone/private.h: Likewise.
* timezone/scheck.c: Likewise.
* timezone/tzfile.h: Likewise.
* timezone/tzselect.ksh: Likewise.
* timezone/zdump.c: Likewise.
* timezone/zic.c: Likewise.
| -rw-r--r-- | ChangeLog | 8 | ||||
| -rw-r--r-- | timezone/checktab.awk | 29 | ||||
| -rw-r--r-- | timezone/private.h | 148 | ||||
| -rw-r--r-- | timezone/scheck.c | 29 | ||||
| -rw-r--r-- | timezone/tzfile.h | 25 | ||||
| -rw-r--r-- | timezone/tzselect.ksh | 366 | ||||
| -rw-r--r-- | timezone/zdump.c | 327 | ||||
| -rw-r--r-- | timezone/zic.c | 620 |
8 files changed, 1072 insertions, 480 deletions
@@ -1,5 +1,13 @@ 2013-12-20 Joseph Myers <joseph@codesourcery.com> + * timezone/checktab.awk: Update from tzcode 2013i. + * timezone/private.h: Likewise. + * timezone/scheck.c: Likewise. + * timezone/tzfile.h: Likewise. + * timezone/tzselect.ksh: Likewise. + * timezone/zdump.c: Likewise. + * timezone/zic.c: Likewise. + * math/auto-libm-test-in: Add tests of cpow. * math/auto-libm-test-out: Regenerated. * math/libm-test.inc (cpow_test_data): Use AUTO_TESTS_cc_c. diff --git a/timezone/checktab.awk b/timezone/checktab.awk index c88b12f1ba..fec4f628e5 100644 --- a/timezone/checktab.awk +++ b/timezone/checktab.awk @@ -9,6 +9,9 @@ BEGIN { if (!zone_table) zone_table = "zone.tab" if (!want_warnings) want_warnings = -1 + # A special (and we hope temporary) case. + tztab["America/Montreal"] = 1 + while (getline <iso_table) { iso_NR++ if ($0 ~ /^#/) continue @@ -69,13 +72,10 @@ BEGIN { status = 1 } cc0 = cc - if (tz2cc[tz]) { - printf "%s:%d: %s: duplicate TZ column\n", \ - zone_table, zone_NR, tz >>"/dev/stderr" - status = 1 - } - tz2cc[tz] = cc - tz2comments[tz] = comments + cctz = cc tz + cctztab[cctz] = 1 + tztab[tz] = 1 + tz2comments[cctz] = comments tz2NR[tz] = zone_NR if (cc2name[cc]) { cc_used[cc]++ @@ -92,16 +92,19 @@ BEGIN { } } - for (tz in tz2cc) { - if (cc_used[tz2cc[tz]] == 1) { - if (tz2comments[tz]) { + for (cctz in cctztab) { + cc = substr (cctz, 1, 2) + tz = substr (cctz, 3) + if (cc_used[cc] == 1) { + if (tz2comments[cctz]) { printf "%s:%d: unnecessary comment `%s'\n", \ - zone_table, tz2NR[tz], tz2comments[tz] \ + zone_table, tz2NR[tz], \ + tz2comments[cctz] \ >>"/dev/stderr" status = 1 } } else { - if (!tz2comments[tz]) { + if (!tz2comments[cctz]) { printf "%s:%d: missing comment\n", \ zone_table, tz2NR[tz] >>"/dev/stderr" status = 1 @@ -125,7 +128,7 @@ BEGIN { if (src != dst) tz = $3 } if (tz && tz ~ /\//) { - if (!tz2cc[tz]) { + if (!tztab[tz]) { printf "%s: no data for `%s'\n", zone_table, tz \ >>"/dev/stderr" status = 1 diff --git a/timezone/private.h b/timezone/private.h index 1d1d391f56..4eb0ab6221 100644 --- a/timezone/private.h +++ b/timezone/private.h @@ -34,6 +34,10 @@ #define HAVE_INCOMPATIBLE_CTIME_R 0 #endif /* !defined INCOMPATIBLE_CTIME_R */ +#ifndef HAVE_LINK +#define HAVE_LINK 1 +#endif /* !defined HAVE_LINK */ + #ifndef HAVE_SETTIMEOFDAY #define HAVE_SETTIMEOFDAY 3 #endif /* !defined HAVE_SETTIMEOFDAY */ @@ -124,19 +128,76 @@ #include "stdint.h" #endif /* !HAVE_STDINT_H */ +#ifndef HAVE_INTTYPES_H +# define HAVE_INTTYPES_H HAVE_STDINT_H +#endif +#if HAVE_INTTYPES_H +# include <inttypes.h> +#endif + #ifndef INT_FAST64_MAX /* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ #if defined LLONG_MAX || defined __LONG_LONG_MAX__ typedef long long int_fast64_t; +# ifdef LLONG_MAX +# define INT_FAST64_MIN LLONG_MIN +# define INT_FAST64_MAX LLONG_MAX +# else +# define INT_FAST64_MIN __LONG_LONG_MIN__ +# define INT_FAST64_MAX __LONG_LONG_MAX__ +# endif +# define SCNdFAST64 "lld" #else /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ #if (LONG_MAX >> 31) < 0xffffffff Please use a compiler that supports a 64-bit integer type (or wider); you may need to compile with "-DHAVE_STDINT_H". #endif /* (LONG_MAX >> 31) < 0xffffffff */ typedef long int_fast64_t; +# define INT_FAST64_MIN LONG_MIN +# define INT_FAST64_MAX LONG_MAX +# define SCNdFAST64 "ld" #endif /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ #endif /* !defined INT_FAST64_MAX */ +#ifndef INT_FAST32_MAX +# if INT_MAX >> 31 == 0 +typedef long int_fast32_t; +# else +typedef int int_fast32_t; +# endif +#endif + +#ifndef INTMAX_MAX +# if defined LLONG_MAX || defined __LONG_LONG_MAX__ +typedef long long intmax_t; +# define strtoimax strtoll +# define PRIdMAX "lld" +# ifdef LLONG_MAX +# define INTMAX_MAX LLONG_MAX +# define INTMAX_MIN LLONG_MIN +# else +# define INTMAX_MAX __LONG_LONG_MAX__ +# define INTMAX_MIN __LONG_LONG_MIN__ +# endif +# else +typedef long intmax_t; +# define strtoimax strtol +# define PRIdMAX "ld" +# define INTMAX_MAX LONG_MAX +# define INTMAX_MIN LONG_MIN +# endif +#endif + +#ifndef UINTMAX_MAX +# if defined ULLONG_MAX || defined __LONG_LONG_MAX__ +typedef unsigned long long uintmax_t; +# define PRIuMAX "llu" +# else +typedef unsigned long uintmax_t; +# define PRIuMAX "lu" +# endif +#endif + #ifndef INT32_MAX #define INT32_MAX 0x7fffffff #endif /* !defined INT32_MAX */ @@ -144,10 +205,26 @@ typedef long int_fast64_t; #define INT32_MIN (-1 - INT32_MAX) #endif /* !defined INT32_MIN */ -#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__) +#if 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define ATTRIBUTE_CONST __attribute__ ((const)) # define ATTRIBUTE_PURE __attribute__ ((__pure__)) +# define ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec)) #else +# define ATTRIBUTE_CONST /* empty */ # define ATTRIBUTE_PURE /* empty */ +# define ATTRIBUTE_FORMAT(spec) /* empty */ +#endif + +#if !defined _Noreturn && __STDC_VERSION__ < 201112 +# if 2 < __GNUC__ + (8 <= __GNUC_MINOR__) +# define _Noreturn __attribute__ ((__noreturn__)) +# else +# define _Noreturn +# endif +#endif + +#if __STDC_VERSION__ < 199901 && !defined restrict +# define restrict /* empty */ #endif /* @@ -165,6 +242,58 @@ extern char * asctime_r(struct tm const *, char *); #endif /* +** Compile with -Dtime_tz=T to build the tz package with a private +** time_t type equivalent to T rather than the system-supplied time_t. +** This debugging feature can test unusual design decisions +** (e.g., time_t wider than 'long', or unsigned time_t) even on +** typical platforms. +*/ +#ifdef time_tz +static time_t sys_time(time_t *x) { return time(x); } + +# undef ctime +# define ctime tz_ctime +# undef ctime_r +# define ctime_r tz_ctime_r +# undef difftime +# define difftime tz_difftime +# undef gmtime +# define gmtime tz_gmtime +# undef gmtime_r +# define gmtime_r tz_gmtime_r +# undef localtime +# define localtime tz_localtime +# undef localtime_r +# define localtime_r tz_localtime_r +# undef mktime +# define mktime tz_mktime +# undef time +# define time tz_time +# undef time_t +# define time_t tz_time_t + +typedef time_tz time_t; + +char *ctime(time_t const *); +char *ctime_r(time_t const *, char *); +double difftime(time_t, time_t); +struct tm *gmtime(time_t const *); +struct tm *gmtime_r(time_t const *restrict, struct tm *restrict); +struct tm *localtime(time_t const *); +struct tm *localtime_r(time_t const *restrict, struct tm *restrict); +time_t mktime(struct tm *); + +static time_t +time(time_t *p) +{ + time_t r = sys_time(0); + if (p) + *p = r; + return r; +} +#endif + +/* ** Private function declarations. */ @@ -192,14 +321,15 @@ const char * scheck(const char * string, const char * format); #define TYPE_SIGNED(type) (((type) -1) < 0) #endif /* !defined TYPE_SIGNED */ -/* -** Since the definition of TYPE_INTEGRAL contains floating point numbers, -** it cannot be used in preprocessor directives. -*/ - -#ifndef TYPE_INTEGRAL -#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) -#endif /* !defined TYPE_INTEGRAL */ +/* The minimum and maximum finite time values. */ +static time_t const time_t_min = + (TYPE_SIGNED(time_t) + ? (time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1) + : 0); +static time_t const time_t_max = + (TYPE_SIGNED(time_t) + ? - (~ 0 < 0) - ((time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1)) + : -1); #ifndef INT_STRLEN_MAXIMUM /* diff --git a/timezone/scheck.c b/timezone/scheck.c index ed60980d83..8bd01a858f 100644 --- a/timezone/scheck.c +++ b/timezone/scheck.c @@ -25,26 +25,35 @@ scheck(const char *const string, const char *const format) return result; fp = format; tp = fbuf; + + /* + ** Copy directives, suppressing each conversion that is not + ** already suppressed. Scansets containing '%' are not + ** supported; e.g., the conversion specification "%[%]" is not + ** supported. Also, multibyte characters containing a + ** non-leading '%' byte are not supported. + */ while ((*tp++ = c = *fp++) != '\0') { if (c != '%') continue; - if (*fp == '%') { - *tp++ = *fp++; - continue; + if (is_digit(*fp)) { + char const *f = fp; + char *t = tp; + do { + *t++ = c = *f++; + } while (is_digit(c)); + if (c == '$') { + fp = f; + tp = t; + } } *tp++ = '*'; if (*fp == '*') ++fp; - while (is_digit(*fp)) - *tp++ = *fp++; - if (*fp == 'l' || *fp == 'h') - *tp++ = *fp++; - else if (*fp == '[') - do *tp++ = *fp++; - while (*fp != '\0' && *fp != ']'); if ((*tp++ = *fp++) == '\0') break; } + *(tp - 1) = '%'; *tp++ = 'c'; *tp = '\0'; diff --git a/timezone/tzfile.h b/timezone/tzfile.h index 0f6c687f16..529650dd8a 100644 --- a/timezone/tzfile.h +++ b/timezone/tzfile.h @@ -39,7 +39,7 @@ struct tzhead { char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2' as of 2005 */ + char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ char tzh_reserved[15]; /* reserved--must be zero */ char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ @@ -55,7 +55,7 @@ struct tzhead { ** tzh_timecnt (char [4])s coded transition times a la time(2) ** tzh_timecnt (unsigned char)s types of local time starting at above ** tzh_typecnt repetitions of -** one (char [4]) coded UTC offset in seconds +** one (char [4]) coded UT offset in seconds ** one (unsigned char) used to set tm_isdst ** one (unsigned char) that's an abbreviation list index ** tzh_charcnt (char)s '\0'-terminated zone abbreviations @@ -68,7 +68,7 @@ struct tzhead { ** if absent, transition times are ** assumed to be wall clock time ** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition -** time is UTC, if FALSE, +** time is UT, if FALSE, ** transition time is local time ** if absent, transition times are ** assumed to be local time @@ -82,6 +82,13 @@ struct tzhead { ** instants after the last transition time stored in the file ** (with nothing between the newlines if there is no POSIX representation for ** such instants). +** +** If tz_version is '3' or greater, the above is extended as follows. +** First, the POSIX TZ string's hour offset may range from -167 +** through 167 as compared to the POSIX-required 0 through 24. +** Second, its DST start time may be January 1 at 00:00 and its stop +** time December 31 at 24:00 plus the difference between DST and +** standard time, indicating DST all year. */ /* @@ -94,16 +101,8 @@ struct tzhead { #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES -#ifndef NOSOLAR +/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ #define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ -#endif /* !defined NOSOLAR */ -#ifdef NOSOLAR -/* -** Must be at least 14 for Europe/Riga as of Jan 12 1995, -** as noted by Earl Chew. -*/ -#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ -#endif /* !defined NOSOLAR */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS @@ -122,7 +121,7 @@ struct tzhead { #define DAYSPERNYEAR 365 #define DAYSPERLYEAR 366 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) #define MONSPERYEAR 12 #define TM_SUNDAY 0 diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh index 8e66b44273..9d7069116a 100644 --- a/timezone/tzselect.ksh +++ b/timezone/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 @@ -40,21 +44,125 @@ REPORT_BUGS_TO=tz@iana.org exit 1 } -if [ "$1" = "--help" ]; then - cat <<EOF -Usage: tzselect +coord= +location_limit=10 + +usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT] Select a time zone interactively. -Report bugs to $REPORT_BUGS_TO. -EOF - exit -elif [ "$1" = "--version" ]; then - cat <<EOF -tzselect $PKGVERSION$TZVERSION -EOF - exit +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." + +# 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 >&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` + select_result=$1 + break + fi + echo >&2 'Please enter a number in range.' + esac + + # Prompt and read input. + printf >&2 %s "${PS3-#? }" + read select_i || exit + done + } fi +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 `expr $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 @@ -71,11 +179,65 @@ 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=' + 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 @@ -87,39 +249,46 @@ while country= region= + case $coord in + ?*) + continent=coord;; + '') # Ask the user for continent or ocean. - echo >&2 'Please select a continent or ocean.' - - select continent in \ - Africa \ - Americas \ - Antarctica \ - 'Arctic Ocean' \ - Asia \ - 'Atlantic Ocean' \ - Australia \ - Europe \ - 'Indian Ocean' \ - 'Pacific Ocean' \ - 'none - I want to specify the time zone using the Posix TZ format.' - do + echo >&2 'Please select a continent, ocean, "coord", or "TZ".' + + quoted_continents=` + $AWK ' + BEGIN { FS = "\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 '' + ` + |
