diff --git a/NEWS b/NEWS index 871d4acb3..07ae72fe6 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,9 @@ Any uppercase BUG_* names are modernish shell bug IDs. return value that fits in a signed integer, typically a 32-bit value. Note that $? is truncated to 8 bits when the current (sub)shell exits. +- The head and tail builtins now correctly handle files that do not have an + ending newline. (Note that the tail builtin is not compiled in by default.) + 2021-12-05: - Fixed an issue on illumos that caused some parameters in the getconf diff --git a/src/cmd/ksh93/tests/builtins.sh b/src/cmd/ksh93/tests/builtins.sh index 4e20cea82..7cb83d622 100755 --- a/src/cmd/ksh93/tests/builtins.sh +++ b/src/cmd/ksh93/tests/builtins.sh @@ -1396,5 +1396,22 @@ got=$?; exp=1 got=$?; exp=2 (( got == exp )) || err_exit "cd -eP to empty string has wrong exit status (expected $exp, got $got)" +# ====== +# The head and tail builtins should work on files without newlines +if builtin head 2> /dev/null; then + print -n nonewline > "$tmp/nonewline" + exp=nonewline + got=$(head -1 "$tmp/nonewline") + [[ $got == $exp ]] || err_exit "head builtin fails to correctly handle files without an ending newline" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +fi +if builtin tail 2> /dev/null; then + print -n 'newline\nnonewline' > "$tmp/nonewline" + exp=nonewline + got=$(tail -1 "$tmp/nonewline") + [[ $got == $exp ]] || err_exit "tail builtin fails to correctly handle files without an ending newline" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +fi + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/lib/libcmd/head.c b/src/lib/libcmd/head.c index 415b1b3d9..ebba2051a 100644 --- a/src/lib/libcmd/head.c +++ b/src/lib/libcmd/head.c @@ -1,7 +1,7 @@ /*********************************************************************** * * * This software is part of the ast package * -* Copyright (c) 1992-2012 AT&T Intellectual Property * +* Copyright (c) 1992-2013 AT&T Intellectual Property * * Copyright (c) 2020-2021 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 1.0 * @@ -15,8 +15,8 @@ * AT&T Research * * Florham Park NJ * * * -* Glenn Fowler * -* David Korn * +* Glenn Fowler * +* David Korn * * * ***********************************************************************/ #pragma prototyped @@ -28,7 +28,7 @@ */ static const char usage[] = -"[-n?\n@(#)$Id: head (AT&T Research) 2012-05-31 $\n]" +"[-n?\n@(#)$Id: head (AT&T Research) 2013-09-19 $\n]" "[--catalog?" ERROR_CATALOG "]" "[+NAME?head - output beginning portion of one or more files ]" "[+DESCRIPTION?\bhead\b copies one or more input files to standard " @@ -81,6 +81,7 @@ b_head(int argc, register char** argv, Shbltin_t* context) register off_t keep = 10; register off_t skip = 0; register int delim = '\n'; + off_t moved; int header = 1; char* format = (char*)header_fmt+1; @@ -145,9 +146,16 @@ b_head(int argc, register char** argv, Shbltin_t* context) sfprintf(sfstdout, format, cp); format = (char*)header_fmt; if (skip > 0) - sfmove(fp, NiL, skip, delim); - if (sfmove(fp, sfstdout, keep, delim) < 0 && !ERROR_PIPE(errno) && errno != EINTR) + { + if ((moved = sfmove(fp, NiL, skip, delim)) < 0 && !ERROR_PIPE(errno) && errno != EINTR) + error(ERROR_system(0), "%s: skip error", cp); + if (delim >= 0 && moved < skip) + goto next; + } + if ((moved = sfmove(fp, sfstdout, keep, delim)) < 0 && !ERROR_PIPE(errno) && errno != EINTR || + delim >= 0 && moved < keep && sfmove(fp, sfstdout, SF_UNBOUND, -1) < 0 && !ERROR_PIPE(errno) && errno != EINTR) error(ERROR_system(0), "%s: read error", cp); + next: if (fp != sfstdin) sfclose(fp); } while (cp = *argv++); diff --git a/src/lib/libcmd/tail.c b/src/lib/libcmd/tail.c index 12909453e..6f51b7822 100644 --- a/src/lib/libcmd/tail.c +++ b/src/lib/libcmd/tail.c @@ -1,7 +1,7 @@ /*********************************************************************** * * * This software is part of the ast package * -* Copyright (c) 1992-2012 AT&T Intellectual Property * +* Copyright (c) 1992-2013 AT&T Intellectual Property * * Copyright (c) 2020-2021 Contributors to ksh 93u+m * * and is licensed under the * * Eclipse Public License, Version 1.0 * @@ -15,8 +15,8 @@ * AT&T Research * * Florham Park NJ * * * -* Glenn Fowler * -* David Korn * +* Glenn Fowler * +* David Korn * * * ***********************************************************************/ #pragma prototyped @@ -29,7 +29,7 @@ */ static const char usage[] = -"+[-?\n@(#)$Id: tail (AT&T Research) 2012-10-10 $\n]" +"+[-?\n@(#)$Id: tail (AT&T Research) 2013-09-19 $\n]" "[--catalog?" ERROR_CATALOG "]" "[+NAME?tail - output trailing portion of one or more files ]" "[+DESCRIPTION?\btail\b copies one or more input files to standard output " @@ -164,6 +164,7 @@ tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) register Sfoff_t last; register char* s; register char* t; + unsigned char incomplete; struct stat st; last = sfsize(fp); @@ -175,6 +176,7 @@ tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) return first; return offset; } + incomplete = 1; for (;;) { if ((offset = last - SF_BUFSIZE) < first) @@ -184,6 +186,15 @@ tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) if (!(s = sfreserve(fp, n, SF_LOCKR))) return -1; t = s + n; + if (incomplete) + { + if (t > s && *(t - 1) != delim && number-- <= 0) + { + sfread(fp, s, 0); + return offset + (t - s); + } + incomplete = 0; + } while (t > s) if (*--t == delim && number-- <= 0) { @@ -415,6 +426,7 @@ b_tail(int argc, char** argv, Shbltin_t* context) char* t; char* r; char* file; + Sfoff_t moved; Sfoff_t offset; Sfoff_t number = DEFAULT; unsigned long timeout = 0; @@ -765,8 +777,8 @@ b_tail(int argc, char** argv, Shbltin_t* context) if (number < 0 || !number && (flags & POSITIVE)) { sfset(ip, SF_SHARE, 1); - if (number < -1) - sfmove(ip, NiL, -number - 1, delim); + if (number < -1 && (moved = sfmove(ip, NiL, -(number + 1), delim)) >= 0 && delim >= 0 && moved < -(number + 1)) + (void)sfgetr(ip, delim, SF_LASTR); if (flags & REVERSE) rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); else