"Todd C. Miller" <[email protected]> writes:
> On Sun, 13 Feb 2022 12:07:31 -0800, Greg Steuck wrote:
>
>> I noticed that despite the OKs the code didn't submitted. Should we
>> revive this diff now and continue in the tree?
>>
>> I have vested interest: I don't want to fix up lang/ghc test suite which
>> is full of seq(1).
>
> Here's an updated diff that removes the -t option which is not
> present in either the GNU or Plan9 versions. I've added -h and -v
> flag support to be more compatible with the GNU version.
>
> Like the GNU version, this seq always uses a newline terminator and
> only displays the separator between numbers (not at the end). This
> is different from the NetBSD version.
OK gnezdo@
Thank you very much!
>
> - todd
>
> Index: usr.bin/seq/Makefile
> ===================================================================
> RCS file: usr.bin/seq/Makefile
> diff -N usr.bin/seq/Makefile
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/seq/Makefile 16 Nov 2020 03:52:57 -0000
> @@ -0,0 +1,8 @@
> +# $OpenBSD$
> +
> +PROG= seq
> +CFLAGS+= -Wall
> +LDADD+= -lm
> +DPADD+= ${LIBM}
> +
> +.include <bsd.prog.mk>
> Index: usr.bin/seq/seq.1
> ===================================================================
> RCS file: usr.bin/seq/seq.1
> diff -N usr.bin/seq/seq.1
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/seq/seq.1 21 Feb 2022 18:05:31 -0000
> @@ -0,0 +1,197 @@
> +.\" $OpenBSD$
> +.\"
> +.\" Copyright (c) 2005 The NetBSD Foundation, Inc.
> +.\" All rights reserved.
> +.\"
> +.\" This code is derived from software contributed to The NetBSD Foundation
> +.\" by Brian Ginsbach.
> +.\"
> +.\" Redistribution and use in source and binary forms, with or without
> +.\" modification, are permitted provided that the following conditions
> +.\" are met:
> +.\" 1. Redistributions of source code must retain the above copyright
> +.\" notice, this list of conditions and the following disclaimer.
> +.\" 2. Redistributions in binary form must reproduce the above copyright
> +.\" notice, this list of conditions and the following disclaimer in the
> +.\" documentation and/or other materials provided with the distribution.
> +.\"
> +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
> +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> LIMITED
> +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
> PARTICULAR
> +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
> +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
> THE
> +.\" POSSIBILITY OF SUCH DAMAGE.
> +.\"
> +.Dd $Mdocdate$
> +.Dt SEQ 1
> +.Os
> +.Sh NAME
> +.Nm seq
> +.Nd print sequences of numbers
> +.Sh SYNOPSIS
> +.Nm
> +.Op Fl hvw
> +.Op Fl f Ar format
> +.Op Fl s Ar string
> +.Op Ar first Op Ar incr
> +.Ar last
> +.Sh DESCRIPTION
> +The
> +.Nm
> +utility prints a sequence of numbers, one per line
> +.Pq default ,
> +from
> +.Ar first
> +.Pq default 1 ,
> +to near
> +.Ar last
> +as possible, in increments of
> +.Ar incr
> +.Pq default 1 .
> +When
> +.Ar first
> +is larger than
> +.Ar last ,
> +the default
> +.Ar incr
> +is -1.
> +.Pp
> +All numbers are interpreted as floating point.
> +.Pp
> +Normally integer values are printed as decimal integers.
> +.Pp
> +The
> +.Nm
> +utility accepts the following options:
> +.Bl -tag -width Ar
> +.It Fl f Ar format , Fl -format Ar format
> +Use a
> +.Xr printf 3
> +style
> +.Ar format
> +to print each number.
> +Only the
> +.Cm A ,
> +.Cm a ,
> +.Cm E ,
> +.Cm e ,
> +.Cm F ,
> +.Cm f ,
> +.Cm G ,
> +.Cm g ,
> +and
> +.Cm %
> +conversion characters are valid, along with any optional
> +flags and an optional numeric minimum field width or precision.
> +The
> +.Ar format
> +can contain character escape sequences in backslash notation as
> +defined in
> +.St -ansiC .
> +The default is
> +.Cm %g .
> +.It Fl h , Fl -help
> +Display the program usage and exit.
> +.It Fl s Ar string , Fl -separator Ar string
> +Use
> +.Ar string
> +to separate numbers.
> +The
> +.Ar string
> +can contain character escape sequences in backslash notation as
> +defined in
> +.St -ansiC .
> +The default is
> +.Cm \en .
> +.It Fl v , Fl -version
> +Display the verion number and exit.
> +.It Fl w , Fl -fixed-width
> +Equalize the widths of all numbers by padding with zeros as necessary.
> +This option has no effect with the
> +.Fl f
> +option.
> +If any sequence numbers will be printed in exponential notation,
> +the default conversion is changed to
> +.Cm %e .
> +.El
> +.Sh EXIT STATUS
> +.Ex -std
> +.Sh EXAMPLES
> +Generate a sequence from 1 to 3 (inclusive) with a default increment of 1:
> +.Bd -literal -offset indent
> +# seq 1 3
> +1
> +2
> +3
> +.Ed
> +.Pp
> +Generate a sequence from 3 to 1 (inclusive) with a default increment of -1:
> +.Bd -literal -offset indent
> +# seq 3 1
> +3
> +2
> +1
> +.Ed
> +.Pp
> +Generate a sequence from 0 to 0.1 (inclusive) with an increment of 0.05 and
> padding
> +with leading zeroes.
> +.Bd -literal -offset indent
> +# seq -w 0 .05 .1
> +0.00
> +0.05
> +0.10
> +.Ed
> +.Pp
> +Generate a sequence from 1 to 3 (inclusive) with a default increment of 1,
> +and a custom separator string:
> +.Bd -literal -offset indent
> +# seq -s " " 1 3
> +1 2 3
> +.Ed
> +.Pp
> +Generate a sequence from 1 to 2 (inclusive) with an increment of 0.2 and
> +print the results with two digits after the decimal point (using a
> +.Xr printf 3
> +style format):
> +.Bd -literal -offset indent
> +# seq -f %.2f 1 0.2 2
> +1.00
> +1.20
> +1.40
> +1.60
> +1.80
> +2.00
> +.Ed
> +.Sh SEE ALSO
> +.Xr jot 1 ,
> +.Xr printf 1 ,
> +.Xr printf 3
> +.Sh HISTORY
> +A
> +.Nm
> +command appeared in
> +.At v8 .
> +This version of
> +.Nm
> +appeared in
> +.Nx 3.0
> +and was ported to
> +.Ox 7.1 .
> +It was based on the command of the same name in
> +Plan 9 from Bell Labs and the GNU core utilities.
> +The GNU
> +.Nm
> +command first appeared in the 1.13 shell utilities release.
> +.Sh BUGS
> +The
> +.Fl w
> +option does not handle the transition from pure floating point
> +to exponent representation very well.
> +The
> +.Nm
> +utility is not bug for bug compatible with other implementations.
> Index: usr.bin/seq/seq.c
> ===================================================================
> RCS file: usr.bin/seq/seq.c
> diff -N usr.bin/seq/seq.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/seq/seq.c 21 Feb 2022 17:54:08 -0000
> @@ -0,0 +1,417 @@
> +/* $OpenBSD$ */
> +
> +/*-
> + * Copyright (c) 2005 The NetBSD Foundation, Inc.
> + * All rights reserved.
> + *
> + * This code is derived from software contributed to The NetBSD Foundation
> + * by Brian Ginsbach.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
> + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
> LIMITED
> + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
> + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
> + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <ctype.h>
> +#include <err.h>
> +#include <errno.h>
> +#include <getopt.h>
> +#include <math.h>
> +#include <locale.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +#define VERSION "1.0"
> +#define ZERO '0'
> +#define SPACE ' '
> +
> +#define MAXIMUM(a, b) (((a) < (b))? (b) : (a))
> +#define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+')
> +#define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E')
> +#define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7')
> +
> +/* Globals */
> +
> +static const char *decimal_point = "."; /* default */
> +static char default_format[] = { "%g" }; /* default */
> +
> +static const struct option long_opts[] =
> +{
> + {"format", required_argument, NULL, 'f'},
> + {"help", no_argument, NULL, 'h'},
> + {"separator", required_argument, NULL, 's'},
> + {"version", no_argument, NULL, 'v'},
> + {"equal-width", no_argument, NULL, 'w'},
> + {NULL, no_argument, NULL, 0}
> +};
> +
> +/* Prototypes */
> +
> +static double e_atof(const char *);
> +
> +static int decimal_places(const char *);
> +static int numeric(const char *);
> +static int valid_format(const char *);
> +
> +static char *generate_format(double, double, double, int, char);
> +
> +static __dead void usage(int error);
> +
> +/*
> + * The seq command will print out a numeric sequence from 1, the default,
> + * to a user specified upper limit by 1. The lower bound and increment
> + * maybe indicated by the user on the command line. The sequence can
> + * be either whole, the default, or decimal numbers.
> + */
> +int
> +main(int argc, char *argv[])
> +{
> + int c = 0;
> + int equalize = 0;
> + double first = 1.0;
> + double last = 0.0;
> + double incr = 0.0;
> + double last_shown_value = 0.0;
> + double cur, step;
> + struct lconv *locale;
> + char *fmt = NULL;
> + const char *sep = "\n";
> + const char *term = "\n";
> + char *cur_print, *last_print;
> + char pad = ZERO;
> +
> + /* Determine the locale's decimal point. */
> + locale = localeconv();
> + if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
> + decimal_point = locale->decimal_point;
> +
> + /*
> + * Process options, but handle negative numbers separately
> + * least they trip up getopt(3).
> + */
> + while ((optind < argc) && !numeric(argv[optind]) &&
> + (c = getopt_long(argc, argv, "+f:hs:vw", long_opts, NULL)) != -1) {
> +
> + switch (c) {
> + case 'f': /* format (plan9/GNU) */
> + fmt = optarg;
> + equalize = 0;
> + break;
> + case 's': /* separator (GNU) */
> + sep = optarg;
> + break;
> + case 'v': /* version (GNU) */
> + printf("seq version %s\n", VERSION);
> + return 0;
> + case 'w': /* equal width (plan9/GNU) */
> + if (fmt == NULL) {
> + if (equalize++)
> + pad = SPACE;
> + }
> + break;
> + case 'h': /* help (GNU) */
> + usage(0);
> + break;
> + default:
> + usage(1);
> + break;
> + }
> + }
> +
> + argc -= optind;
> + argv += optind;
> + if (argc < 1 || argc > 3)
> + usage(1);
> +
> + last = e_atof(argv[argc - 1]);
> +
> + if (argc > 1)
> + first = e_atof(argv[0]);
> +
> + if (argc > 2) {
> + incr = e_atof(argv[1]);
> + /* Plan 9/GNU don't do zero */
> + if (incr == 0.0)
> + errx(1, "zero %screment", (first < last)? "in" : "de");
> + }
> +
> + /* default is one for Plan 9/GNU work alike */
> + if (incr == 0.0)
> + incr = (first < last) ? 1.0 : -1.0;
> +
> + if (incr <= 0.0 && first < last)
> + errx(1, "needs positive increment");
> +
> + if (incr >= 0.0 && first > last)
> + errx(1, "needs negative decrement");
> +
> + if (fmt != NULL) {
> + if (!valid_format(fmt))
> + errx(1, "invalid format string: `%s'", fmt);
> + /*
> + * XXX to be bug for bug compatible with Plan 9 add a
> + * newline if none found at the end of the format string.
> + */
> + } else
> + fmt = generate_format(first, incr, last, equalize, pad);
> +
> + for (step = 1, cur = first; incr > 0 ? cur <= last : cur >= last;
> + cur = first + incr * step++) {
> + if (cur != first)
> + fputs(sep, stdout);
> + printf(fmt, cur);
> + last_shown_value = cur;
> + }
> +
> + /*
> + * Did we miss the last value of the range in the loop above?
> + *
> + * We might have, so check if the printable version of the last
> + * computed value ('cur') and desired 'last' value are equal. If they
> + * are equal after formatting truncation, but 'cur' and
> + * 'last_shown_value' are not equal, it means the exit condition of the
> + * loop held true due to a rounding error and we still need to print
> + * 'last'.
> + */
> + asprintf(&cur_print, fmt, cur);
> + asprintf(&last_print, fmt, last);
> + if (strcmp(cur_print, last_print) == 0 && cur != last_shown_value) {
> + if (cur != first)
> + fputs(sep, stdout);
> + fputs(last_print, stdout);
> + }
> + free(cur_print);
> + free(last_print);
> +
> + fputs(term, stdout);
> +
> + return 0;
> +}
> +
> +/*
> + * numeric - verify that string is numeric
> + */
> +static int
> +numeric(const char *s)
> +{
> + int seen_decimal_pt, decimal_pt_len;
> +
> + /* skip any sign */
> + if (ISSIGN((unsigned char)*s))
> + s++;
> +
> + seen_decimal_pt = 0;
> + decimal_pt_len = strlen(decimal_point);
> + while (*s) {
> + if (!isdigit((unsigned char)*s)) {
> + if (!seen_decimal_pt &&
> + strncmp(s, decimal_point, decimal_pt_len) == 0) {
> + s += decimal_pt_len;
> + seen_decimal_pt = 1;
> + continue;
> + }
> + if (ISEXP((unsigned char)*s)) {
> + s++;
> + if (ISSIGN((unsigned char)*s) ||
> + isdigit((unsigned char)*s)) {
> + s++;
> + continue;
> + }
> + }
> + break;
> + }
> + s++;
> + }
> + return *s == '\0';
> +}
> +
> +/*
> + * valid_format - validate user specified format string
> + */
> +static int
> +valid_format(const char *fmt)
> +{
> + unsigned conversions = 0;
> +
> + while (*fmt != '\0') {
> + /* scan for conversions */
> + if (*fmt != '%') {
> + fmt++;
> + continue;
> + }
> + fmt++;
> +
> + /* allow %% but not things like %10% */
> + if (*fmt == '%') {
> + fmt++;
> + continue;
> + }
> +
> + /* flags */
> + while (*fmt != '\0' && strchr("#0- +'", *fmt)) {
> + fmt++;
> + }
> +
> + /* field width */
> + while (*fmt != '\0' && strchr("0123456789", *fmt)) {
> + fmt++;
> + }
> +
> + /* precision */
> + if (*fmt == '.') {
> + fmt++;
> + while (*fmt != '\0' && strchr("0123456789", *fmt)) {
> + fmt++;
> + }
> + }
> +
> + /* conversion */
> + switch (*fmt) {
> + case 'A':
> + case 'a':
> + case 'E':
> + case 'e':
> + case 'F':
> + case 'f':
> + case 'G':
> + case 'g':
> + /* floating point formats are accepted */
> + conversions++;
> + break;
> + default:
> + /* anything else is not */
> + return 0;
> + }
> + }
> +
> + /* PR 236347 -- user format strings must have a conversion */
> + return conversions == 1;
> +}
> +
> +/*
> + * e_atof - convert an ASCII string to a double
> + * exit if string is not a valid double, or if converted value would
> + * cause overflow or underflow
> + */
> +static double
> +e_atof(const char *num)
> +{
> + char *endp;
> + double dbl;
> +
> + errno = 0;
> + dbl = strtod(num, &endp);
> +
> + if (errno == ERANGE)
> + /* under or overflow */
> + err(2, "%s", num);
> + else if (*endp != '\0')
> + /* "junk" left in number */
> + errx(2, "invalid floating point argument: %s", num);
> +
> + /* zero shall have no sign */
> + if (dbl == -0.0)
> + dbl = 0;
> + return dbl;
> +}
> +
> +/*
> + * decimal_places - count decimal places in a number (string)
> + */
> +static int
> +decimal_places(const char *number)
> +{
> + int places = 0;
> + char *dp;
> +
> + /* look for a decimal point */
> + if ((dp = strstr(number, decimal_point))) {
> + dp += strlen(decimal_point);
> +
> + while (isdigit((unsigned char)*dp++))
> + places++;
> + }
> + return places;
> +}
> +
> +/*
> + * generate_format - create a format string
> + *
> + * XXX to be bug for bug compatible with Plan9 and GNU return "%g"
> + * when "%g" prints as "%e" (this way no width adjustments are made)
> + */
> +static char *
> +generate_format(double first, double incr, double last, int equalize, char
> pad)
> +{
> + static char buf[256];
> + char cc = '\0';
> + int precision, width1, width2, places;
> +
> + if (equalize == 0)
> + return default_format;
> +
> + /* figure out "last" value printed */
> + if (first > last)
> + last = first - incr * floor((first - last) / incr);
> + else
> + last = first + incr * floor((last - first) / incr);
> +
> + snprintf(buf, sizeof(buf), "%g", incr);
> + if (strchr(buf, 'e'))
> + cc = 'e';
> + precision = decimal_places(buf);
> +
> + width1 = snprintf(buf, sizeof(buf), "%g", first);
> + if (strchr(buf, 'e'))
> + cc = 'e';
> + if ((places = decimal_places(buf)))
> + width1 -= (places + strlen(decimal_point));
> +
> + precision = MAXIMUM(places, precision);
> +
> + width2 = snprintf(buf, sizeof(buf), "%g", last);
> + if (strchr(buf, 'e'))
> + cc = 'e';
> + if ((places = decimal_places(buf)))
> + width2 -= (places + strlen(decimal_point));
> +
> + /* XXX if incr is floating point fix the precision */
> + if (precision) {
> + snprintf(buf, sizeof(buf), "%%%c%d.%d%c", pad,
> + MAXIMUM(width1, width2) + (int) strlen(decimal_point) +
> + precision, precision, (cc) ? cc : 'f');
> + } else {
> + snprintf(buf, sizeof(buf), "%%%c%d%c", pad,
> + MAXIMUM(width1, width2), (cc) ? cc : 'g');
> + }
> +
> + return buf;
> +}
> +
> +static __dead void
> +usage(int error)
> +{
> + fprintf(stderr,
> + "usage: %s [-vw] [-f format] [-s string] [first [incr]] last\n",
> + getprogname());
> + exit(error);
> +}