logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 8f02fa4fe8dfb0f31557b89c656058b246e74f19
parent 4036111e8cb961d05bc5aefa4e72b25739339874
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sun, 29 Jun 2025 19:49:25 +0200

cmd/head: add negative values support to -n <num>

Diffstat:

Mcmd/head.114+++++++++++---
Mcmd/head.c121++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mtest-cmd/head.sh7++++++-
3 files changed, 116 insertions(+), 26 deletions(-)

diff --git a/cmd/head.1 b/cmd/head.1 @@ -1,7 +1,7 @@ .\" utils-std: Collection of commonly available Unix tools .\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> .\" SPDX-License-Identifier: MPL-2.0 -.Dd September 09, 2024 +.Dd June 29, 2025 .Dt HEAD 1 .Os .Sh NAME @@ -10,7 +10,7 @@ .Sh SYNOPSIS .Nm .Op Fl qvz -.Op Fl c Ar size | Fl n Ar num | Fl Ar num +.Op Fl c Ar size | Fl n Oo Ar - Oc Ns Ar num | Fl Ar num .Op Ar file... .Sh DESCRIPTION .Nm @@ -35,12 +35,20 @@ in each can be multiplied by one of KMGTP (Kilo, Mega, Giga, Tera, ...) using power of 1024 by default, which can be either explicit via adding iB (like MiB), or turned into powers of 1000 by adding B (like MB). -.It Fl n Ar num , Fl Ar num +.It Fl n Oo Ar - Oc Ns Ar num , Fl Ar num Read the first .Ar num lines in each .Ar file . .Pp +If +.Ar num +is negative, then it is relative to end-of-file, for example +.Cm head +.Fl n +.Ar -2 +will omit the last 2 lines. +.Pp The .Fl Ar num form is historical, new scripts should use the standard diff --git a/cmd/head.c b/cmd/head.c @@ -20,7 +20,7 @@ static const char *header_fmt = "==> %s <==\n"; const char *argv0 = "head"; -size_t lines = 10; +ssize_t lines = 10; size_t bytes = 0; char *buf = NULL; @@ -92,31 +92,108 @@ copy_lines(const char *filename) } } - for(size_t i = 0; i < lines; i++) + // Relative to EOF, GNU extension + if(lines < 0) { - ssize_t nread = getdelim(&buf, &buflen, delim, in); - if(nread < 0) + struct strbuf { - if(errno == 0) break; + size_t len; + size_t cap; + char *buf; + }; + size_t nlines = (size_t)-lines; + size_t wini = 0; + struct strbuf *win = calloc(nlines, sizeof(struct strbuf)); + if(!win) + { + fprintf(stderr, "%s: error: Failed allocating lines array: %s\n", argv0, strerror(errno)); + return 1; + } - fprintf(stderr, - "%s: error: Failed reading line from '%s': %s\n", - argv0, - filename, - strerror(errno)); - err = 1; - break; + while(true) + { + ssize_t ret = getdelim(&buf, &buflen, delim, in); + if(ret < 0) + { + if(errno == 0) break; + + fprintf(stderr, + "%s: error: Failed reading line from '%s': %s\n", + argv0, + filename, + strerror(errno)); + err = 1; + break; + } + size_t nread = ret; + + if(win[wini].len) + { + if(write(STDOUT_FILENO, win[wini].buf, win[wini].len) < 0) + { + fprintf(stderr, + "%s: error: Failed writing line from '%s' to stdout: %s\n", + argv0, + filename, + strerror(errno)); + err = 1; + break; + } + } + + if(nread == 0) continue; + + if(win[wini].cap < nread) + { + win[wini].buf = realloc(win[wini].buf, nread); + if(!win[wini].buf) + { + fprintf( + stderr, "%s: error: Failed (re)allocating line buffer: %s\n", argv0, strerror(errno)); + return 1; + } + win[wini].cap = nread; + } + + win[wini].len = nread; + memcpy(win[wini].buf, buf, nread); + wini = (wini + 1) % nlines; } - if(write(STDOUT_FILENO, buf, nread) < 0) + for(size_t i = 0; i < nlines; i++) + free(win[i].buf); + + free(win); + } + else + { + size_t nlines = lines; + for(size_t i = 0; i < nlines; i++) { - fprintf(stderr, - "%s: error: Failed writing line from '%s' to stdout: %s\n", - argv0, - filename, - strerror(errno)); - err = 1; - break; + ssize_t nread = getdelim(&buf, &buflen, delim, in); + if(nread < 0) + { + if(errno == 0) break; + + fprintf(stderr, + "%s: error: Failed reading line from '%s': %s\n", + argv0, + filename, + strerror(errno)); + err = 1; + break; + } + + if(write(STDOUT_FILENO, buf, nread) < 0) + { + fprintf(stderr, + "%s: error: Failed writing line from '%s' to stdout: %s\n", + argv0, + filename, + strerror(errno)); + err = 1; + break; + } } } @@ -156,7 +233,7 @@ main(int argc, char *argv[]) char *arg = argv[optind] + 1; char *endptr = NULL; - lines = strtoul(arg, &endptr, 0); + lines = strtol(arg, &endptr, 0); if(!(errno == 0 && endptr != NULL && *endptr == '\0')) { fprintf(stderr, @@ -206,7 +283,7 @@ main(int argc, char *argv[]) case 'n': { char *endptr = NULL; - lines = strtoul(optarg, &endptr, 0); + lines = strtol(optarg, &endptr, 0); if(!(errno == 0 && endptr != NULL && *endptr == '\0')) { fprintf(stderr, diff --git a/test-cmd/head.sh b/test-cmd/head.sh @@ -4,7 +4,7 @@ WD="$(dirname "$0")/../" target="${WD}/cmd/head" -plans=15 +plans=17 . "${WD}/test-cmd/tap.sh" t --input="$(seq 1 20)" 20l '' "$(seq 1 10) @@ -49,3 +49,8 @@ t_cmd 40c '40960' head_40kc head_40d() { "$target" -c 40d /dev/zero | wc -c | tr -d '[:space:]'; } t_cmd --exit=1 40d "head: error: Unrecognised unit 'd' 0" head_40d + +t --input="$(seq 1 20)" 20l-2l '-n -2' "$(seq 1 18) +" + +t --input="$(seq 1 5)" 5l-10l '-n -10' ""