From 2bb551efd8b3e50d715482a8f3b89f88bae74397 Mon Sep 17 00:00:00 2001 From: Leah Neukirchen Date: Wed, 3 Jul 2024 17:55:45 +0200 Subject: Rename fq -> nqtail, tq -> nqterm There's only so many two letter binaries that can exist at the same time. A newer project, called fq, arrived and is clashing with nq in many packaging systems, and nqtail is a fine name too. --- .gitignore | 2 +- Makefile | 4 +- NEWS.md | 6 ++ README.md | 26 +++--- _nq | 6 +- fq.1 | 71 ---------------- fq.c | 270 ------------------------------------------------------------- fq.sh | 17 ---- nq.1 | 16 ++-- nqtail.1 | 71 ++++++++++++++++ nqtail.c | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nqtail.sh | 17 ++++ nqterm | 25 ++++++ nqterm.1 | 56 +++++++++++++ tests | 10 +-- tq | 25 ------ tq.1 | 56 ------------- 17 files changed, 478 insertions(+), 470 deletions(-) delete mode 100644 fq.1 delete mode 100644 fq.c delete mode 100755 fq.sh create mode 100644 nqtail.1 create mode 100644 nqtail.c create mode 100755 nqtail.sh create mode 100755 nqterm create mode 100644 nqterm.1 delete mode 100755 tq delete mode 100644 tq.1 diff --git a/.gitignore b/.gitignore index 9801fb2..4084f8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ *~ -fq nq +nqtail ,*.* diff --git a/Makefile b/Makefile index c24de67..db51654 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -ALL=nq fq tq +ALL=nq nqtail nqterm CFLAGS=-g -Wall -O2 @@ -12,7 +12,7 @@ INSTALL=install all: $(ALL) clean: FRC - rm -f nq fq + rm -f nq nqtail check: FRC all prove -v ./tests diff --git a/NEWS.md b/NEWS.md index 9d68484..af1fa51 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +## 1.0 (2024-07-03) + +* **Incompatible change:** The fq utility has been renamed to nqtail. +* **Incompatible change:** The tq utility has been renamed to nqterm. +* nq: add support for a $NQFAILDIR + ## 0.5 (2022-03-26) * **Notable change:** nq now creates files with permissions 0666 and diff --git a/README.md b/README.md index a420dc7..9aa5d03 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Build targets `clean`, `depends`, `all`, without occupying the terminal: % nq make clean % nq make depends % nq make all - % fq + % nqtail ... look at output, can interrupt with C-c any time without stopping the build ... @@ -70,7 +70,7 @@ Simple download queue, accessible from multiple terminals: % mkdir -p /tmp/downloads % alias qget='NQDIR=/tmp/downloads nq wget' - % alias qwait='NQDIR=/tmp/downloads fq -q' + % alias qwait='NQDIR=/tmp/downloads nqtail -q' window1% qget http://mymirror/big1.iso window2% qget http://mymirror/big2.iso window3% qget http://mymirror/big3.iso @@ -86,8 +86,8 @@ too!): ,14f6f3034f8.17035 remote% ^D % ssh remote - remote% fq - ... see output, fq exits when job finished ... + remote% nqtail + ... see output, nqtail exits when job finished ... ## Assumptions @@ -103,14 +103,16 @@ too!): Two helper programs are provided: -**`fq`** outputs the log of the currently running jobs, exiting when the -jobs are done. If no job is running, the output of the last job is -shown. `fq -a` shows the output of all jobs, `fq -q` only shows one -line per job. `fq` uses `inotify` on Linux and falls back to polling -for size change else. (`fq.sh` is a similar tool, not quite as robust, -implemented as shell-script calling `tail`.) +**`nqtail`** outputs the log of the currently running jobs, exiting +when the jobs are done. If no job is running, the output of the last +job is shown. `nqtail -a` shows the output of all jobs, `nqtail -q` +only shows one line per job. `nqtail` uses `inotify` on Linux and +falls back to polling for size change else. (`nqtail.sh` is a similar +tool, not quite as robust, implemented as shell-script calling +`tail`.) -**`tq`** wraps `nq` and displays the `fq` output in a new `tmux` or screen window. +**`nqterm`** wraps `nq` and displays the `nqtail` output in a new +`tmux` or screen window. (A pure shell implementation of `nq` is provided as `nq.sh`. It needs `flock` from util-linux, and only has a timer resolution of 1s. @@ -136,7 +138,7 @@ Perl's `prove` installed. Any directory can be a queue for `nq`. `task-spooler` can have different queues for different terminals. -* You can follow the output of an `nq` queue tail-style with `fq`. +* You can follow the output of an `nq` queue tail-style with `nqtail`. * The syntax is different: `at` and `batch` take whole scripts from the standard input or a file; `nq` takes a single command as its diff --git a/_nq b/_nq index 27269b9..a5b86b6 100644 --- a/_nq +++ b/_nq @@ -1,4 +1,4 @@ -#compdef nq tq fq +#compdef nq nqtail nqterm _nq_job() { compadd "$@" -- ${NQDIR:-.}/,*.*(:t) @@ -6,7 +6,7 @@ _nq_job() { _nq() { case "$service" in - fq) _arguments -s -A : \ + nqtail) _arguments -s -A : \ '-q[show only one line per job]' \ '-a[output for all jobs]' \ '*::job:_nq_job' @@ -17,7 +17,7 @@ _nq() { '(-):command name: _command_names -e' \ '*::arguments:_normal' ;; - tq) _arguments : \ + nqterm) _arguments : \ '(-):command name: _command_names -e' \ '*::arguments:_normal' ;; diff --git a/fq.1 b/fq.1 deleted file mode 100644 index dec179b..0000000 --- a/fq.1 +++ /dev/null @@ -1,71 +0,0 @@ -.Dd January 31, 2021 -.Dt FQ 1 -.Os -.Sh NAME -.Nm fq -.Nd job queue log viewer -.Sh SYNOPSIS -.Nm -.Op Fl a -.Op Fl n -.Op Fl q -.Op Ar job\ id ... -.Sh DESCRIPTION -.Nm -is a simple utility for -.Dq following -the output of -.Xr nq 1 -jobs. -.Pp -Without arguments, the output of the currently running and queued -as-of-now jobs is emitted; else the presented job ids are used. -.Pp -.Nm -automatically terminates after the corresponding jobs are done. -.Pp -The options are as follows: -.Bl -tag -width Ds -.It Fl a -Output all log files, even of already finished jobs. -.It Fl n -Don't wait for new output. -Can be used to look at enqueued commands. -.It Fl q -Only print the first line of each job output -(i.e. the -.Li exec -line). -.El -.Sh ENVIRONMENT -.Bl -hang -width Ds -.It Ev NQDIR -Directory where lock files/job output resides, see -.Xr nq 1 . -.El -.Sh EXIT STATUS -.Ex -std -.Sh INTERNALS -On Linux, -.Xr inotify 7 -is used to monitor job output. -On FreeBSD and macOS, -.Xr kqueue 2 -is used. -On other operating systems, polling is used. -.Sh SEE ALSO -.Xr nq 1 , -.Xr tq 1 -.Sh AUTHORS -.An Leah Neukirchen Aq Mt leah@vuxu.org -.Sh LICENSE -.Nm -is in the public domain. -.Pp -To the extent possible under law, -the creator of this work -has waived all copyright and related or -neighboring rights to this work. -.Pp -.Lk http://creativecommons.org/publicdomain/zero/1.0/ -.\" .Sh BUGS diff --git a/fq.c b/fq.c deleted file mode 100644 index a61240f..0000000 --- a/fq.c +++ /dev/null @@ -1,270 +0,0 @@ -/* - * fq [FILES...] - follow output of nq jobs, quitting when they are done - * - * To the extent possible under law, Leah Neukirchen - * has waived all copyright and related or neighboring rights to this work. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#define DELAY 250000 - -#ifdef __linux__ -#define USE_INOTIFY -#endif - -#if defined(__FreeBSD__) || defined(__APPLE__) -#define USE_KEVENT -#endif - -#ifdef USE_INOTIFY -#include -char ibuf[8192]; -#endif - -#ifdef USE_KEVENT -#include -#endif - -char buf[8192]; - -static int -islocked(int fd) -{ - if (flock(fd, LOCK_SH | LOCK_NB) == -1) { - return (errno == EWOULDBLOCK); - } else { - flock(fd, LOCK_UN); - return 0; - } -} - -static int -alphabetic(const void *a, const void *b) -{ - return strcmp(*(char **)a, *(char **)b); -} - -int -main(int argc, char *argv[]) -{ - int i, fd, dirfd; - off_t off, loff; - ssize_t rd; - int didsth = 0, seen_nl = 0; - int opt = 0, aflag = 0, nflag = 0, qflag = 0; - char *path; - -#ifdef USE_INOTIFY - int ifd, wd; -#endif -#ifdef USE_KEVENT - int kq, note; - struct kevent kev; -#endif - - close(0); - - while ((opt = getopt(argc, argv, "+anq")) != -1) { - switch (opt) { - case 'a': - aflag = 1; - break; - case 'n': - nflag = 1; - break; - case 'q': - qflag = 1; - break; - default: - fputs("usage: fq [-anq] [JOBID...]\n", stderr); - exit(1); - } - } - - path = getenv("NQDIR"); - if (!path) - path = "."; - -#ifdef O_DIRECTORY - dirfd = open(path, O_RDONLY | O_DIRECTORY); -#else - dirfd = open(path, O_RDONLY); -#endif - if (dirfd < 0) { - perror("open dir"); - exit(111); - } - - if (optind == argc) { /* behave as if $NQDIR/,* was passed. */ - DIR *dir; - struct dirent *d; - int len = 0; - - argc = 0; - argv = 0; - optind = 0; - - dir = fdopendir(dirfd); - if (!dir) { - perror("fdopendir"); - exit(111); - } - - while ((d = readdir(dir))) { - if (d->d_name[0] != ',') - continue; - if (argc >= len) { - len = 2*len + 1; - argv = realloc(argv, len * sizeof (char *)); - if (!argv) - exit(222); - } - argv[argc] = strdup(d->d_name); - if (!argv[argc]) - exit(222); - argc++; - } - - qsort(argv, argc, sizeof (char *), alphabetic); - } - -#ifdef USE_INOTIFY - ifd = inotify_init(); - if (ifd < 0) - exit(111); -#endif -#ifdef USE_KEVENT - kq = kqueue(); - if (kq < 0) - exit(111); -#endif - - for (i = optind; i < argc; i++) { - loff = 0; - seen_nl = 0; - - fd = openat(dirfd, argv[i], O_RDONLY); - if (fd < 0) - continue; - - /* skip not running jobs, unless -a was passed, or we did not - * output anything yet and are at the last argument. */ - if (!aflag && !islocked(fd) && (didsth || i != argc - 1)) { - close(fd); - continue; - } - - write(1, "==> ", 4); - write(1, argv[i], strlen(argv[i])); - write(1, qflag ? " " : "\n", 1); - - didsth = 1; - -#ifdef USE_INOTIFY - char fullpath[PATH_MAX]; - snprintf(fullpath, sizeof fullpath, "%s/%s", path, argv[i]); - wd = inotify_add_watch(ifd, fullpath, IN_MODIFY | IN_CLOSE_WRITE); - if (wd == -1) { - perror("inotify_add_watch"); - exit(111); - } -#endif -#ifdef USE_KEVENT - note = NOTE_WRITE; -#ifdef __APPLE__ - note |= NOTE_FUNLOCK; -#endif -#ifdef __FreeBSD__ - note |= NOTE_CLOSE_WRITE; -#endif - EV_SET(&kev, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note, 0, NULL); - if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { - perror("kevent"); - exit(111); - } -#endif - - while (1) { - off = lseek(fd, 0, SEEK_END); - - if (off < loff) - loff = off; /* file truncated */ - - if (off == loff) { - if (nflag && islocked(fd)) - break; - - if (flock(fd, LOCK_SH | LOCK_NB) == -1 && - errno == EWOULDBLOCK) { -#if defined(USE_INOTIFY) - /* any inotify event is good */ - read(ifd, ibuf, sizeof ibuf); -#elif defined(USE_KEVENT) - kevent(kq, NULL, 0, &kev, 1, NULL); -#else - /* poll for size change */ - while (off == lseek(fd, 0, SEEK_END)) - usleep(DELAY); -#endif - continue; - } else { - flock(fd, LOCK_UN); - break; - } - } - - if (off - loff > sizeof buf) - off = loff + sizeof buf; - - rd = pread(fd, &buf, off - loff, loff); - if (qflag) { - if (!seen_nl) { - char *s; - if ((s = memchr(buf, '\n', rd))) { - write(1, buf, s+1-buf); - seen_nl = 1; - } else { - write(1, buf, rd); - } - } - } else { - write(1, buf, rd); - } - - loff += rd; - } - - if (qflag && !seen_nl) - write(1, "\n", 1); - -#ifdef USE_INOTIFY - inotify_rm_watch(ifd, wd); -#endif -#ifdef USE_KEVENT - EV_SET(&kev, fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); - kevent(kq, &kev, 1, NULL, 0, NULL); -#endif - close(fd); - } - -#ifdef USE_INOTIFY - close(ifd); -#endif -#ifdef USE_KEVENT - close(kq); -#endif - return 0; -} diff --git a/fq.sh b/fq.sh deleted file mode 100755 index 99774fe..0000000 --- a/fq.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# fq - tail -F the queue outputs, quitting when the job finishes - -tailed=false -for f in ${NQDIR:-.}/,*; do - if ! nq -t $f; then - tailed=true - printf '==> %s\n' "$f" - tail -F $f & p=$! - nq -w $f - kill $p - fi -done - -if ! $tailed; then - cat $f -fi diff --git a/nq.1 b/nq.1 index 921a4b4..8bef666 100644 --- a/nq.1 +++ b/nq.1 @@ -1,4 +1,4 @@ -.Dd March 13, 2021 +.Dd July 3, 2024 .Dt NQ 1 .Os .Sh NAME @@ -47,7 +47,7 @@ and detaches from the terminal immediately, running the job in the background. Standard output and standard error are redirected into the job id file. -.Xr fq 1 +.Xr nqtail 1 can be used to conveniently watch the log files. .Pp The options are as follows: @@ -139,7 +139,7 @@ without occupying the terminal: % nq make clean % nq make depends % nq make all -% fq +% nqtail \&... look at output, can interrupt with C-c any time without stopping the build ... .Ed @@ -147,7 +147,7 @@ without stopping the build ... Simple download queue, accessible from multiple terminals: .Bd -literal -offset indent % alias qget='NQDIR=/tmp/downloads nq wget' -% alias qwait='NQDIR=/tmp/downloads fq -q' +% alias qwait='NQDIR=/tmp/downloads nqtail -q' window1% qget http://mymirror/big1.iso window2% qget http://mymirror/big2.iso window3% qget http://mymirror/big3.iso @@ -167,8 +167,8 @@ remote% nq ./run-benchmark ,14f6f3034f8.17035 remote% ^D % ssh remote -remote% fq -\&... see output, fq exits when job finished ... +remote% nqtail +\&... see output, nqtail exits when job finished ... .Ed .Sh TRICKS The "file extension" of the log file is actually the PID of the job. @@ -238,8 +238,8 @@ into .Pa \&. ) . .El .Sh SEE ALSO -.Xr fq 1 , -.Xr tq 1 . +.Xr nqtail 1 , +.Xr nqterm 1 . .Pp Alternatives to the .Nm diff --git a/nqtail.1 b/nqtail.1 new file mode 100644 index 0000000..87273ee --- /dev/null +++ b/nqtail.1 @@ -0,0 +1,71 @@ +.Dd July 3, 2024 +.Dt NQTAIL 1 +.Os +.Sh NAME +.Nm nqtail +.Nd job queue log viewer +.Sh SYNOPSIS +.Nm +.Op Fl a +.Op Fl n +.Op Fl q +.Op Ar job\ id ... +.Sh DESCRIPTION +.Nm +is a simple utility for +.Dq following +the output of +.Xr nq 1 +jobs. +.Pp +Without arguments, the output of the currently running and queued +as-of-now jobs is emitted; else the presented job ids are used. +.Pp +.Nm +automatically terminates after the corresponding jobs are done. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Output all log files, even of already finished jobs. +.It Fl n +Don't wait for new output. +Can be used to look at enqueued commands. +.It Fl q +Only print the first line of each job output +(i.e. the +.Li exec +line). +.El +.Sh ENVIRONMENT +.Bl -hang -width Ds +.It Ev NQDIR +Directory where lock files/job output resides, see +.Xr nq 1 . +.El +.Sh EXIT STATUS +.Ex -std +.Sh INTERNALS +On Linux, +.Xr inotify 7 +is used to monitor job output. +On FreeBSD and macOS, +.Xr kqueue 2 +is used. +On other operating systems, polling is used. +.Sh SEE ALSO +.Xr nq 1 , +.Xr nqterm 1 +.Sh AUTHORS +.An Leah Neukirchen Aq Mt leah@vuxu.org +.Sh LICENSE +.Nm +is in the public domain. +.Pp +To the extent possible under law, +the creator of this work +has waived all copyright and related or +neighboring rights to this work. +.Pp +.Lk http://creativecommons.org/publicdomain/zero/1.0/ +.\" .Sh BUGS diff --git a/nqtail.c b/nqtail.c new file mode 100644 index 0000000..5dba4b3 --- /dev/null +++ b/nqtail.c @@ -0,0 +1,270 @@ +/* + * nqtail [FILES...] - follow output of nq jobs, quitting when they are done + * + * To the extent possible under law, Leah Neukirchen + * has waived all copyright and related or neighboring rights to this work. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DELAY 250000 + +#ifdef __linux__ +#define USE_INOTIFY +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +#define USE_KEVENT +#endif + +#ifdef USE_INOTIFY +#include +char ibuf[8192]; +#endif + +#ifdef USE_KEVENT +#include +#endif + +char buf[8192]; + +static int +islocked(int fd) +{ + if (flock(fd, LOCK_SH | LOCK_NB) == -1) { + return (errno == EWOULDBLOCK); + } else { + flock(fd, LOCK_UN); + return 0; + } +} + +static int +alphabetic(const void *a, const void *b) +{ + return strcmp(*(char **)a, *(char **)b); +} + +int +main(int argc, char *argv[]) +{ + int i, fd, dirfd; + off_t off, loff; + ssize_t rd; + int didsth = 0, seen_nl = 0; + int opt = 0, aflag = 0, nflag = 0, qflag = 0; + char *path; + +#ifdef USE_INOTIFY + int ifd, wd; +#endif +#ifdef USE_KEVENT + int kq, note; + struct kevent kev; +#endif + + close(0); + + while ((opt = getopt(argc, argv, "+anq")) != -1) { + switch (opt) { + case 'a': + aflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'q': + qflag = 1; + break; + default: + fputs("usage: nqtail [-anq] [JOBID...]\n", stderr); + exit(1); + } + } + + path = getenv("NQDIR"); + if (!path) + path = "."; + +#ifdef O_DIRECTORY + dirfd = open(path, O_RDONLY | O_DIRECTORY); +#else + dirfd = open(path, O_RDONLY); +#endif + if (dirfd < 0) { + perror("open dir"); + exit(111); + } + + if (optind == argc) { /* behave as if $NQDIR/,* was passed. */ + DIR *dir; + struct dirent *d; + int len = 0; + + argc = 0; + argv = 0; + optind = 0; + + dir = fdopendir(dirfd); + if (!dir) { + perror("fdopendir"); + exit(111); + } + + while ((d = readdir(dir))) { + if (d->d_name[0] != ',') + continue; + if (argc >= len) { + len = 2*len + 1; + argv = realloc(argv, len * sizeof (char *)); + if (!argv) + exit(222); + } + argv[argc] = strdup(d->d_name); + if (!argv[argc]) + exit(222); + argc++; + } + + qsort(argv, argc, sizeof (char *), alphabetic); + } + +#ifdef USE_INOTIFY + ifd = inotify_init(); + if (ifd < 0) + exit(111); +#endif +#ifdef USE_KEVENT + kq = kqueue(); + if (kq < 0) + exit(111); +#endif + + for (i = optind; i < argc; i++) { + loff = 0; + seen_nl = 0; + + fd = openat(dirfd, argv[i], O_RDONLY); + if (fd < 0) + continue; + + /* skip not running jobs, unless -a was passed, or we did not + * output anything yet and are at the last argument. */ + if (!aflag && !islocked(fd) && (didsth || i != argc - 1)) { + close(fd); + continue; + } + + write(1, "==> ", 4); + write(1, argv[i], strlen(argv[i])); + write(1, qflag ? " " : "\n", 1); + + didsth = 1; + +#ifdef USE_INOTIFY + char fullpath[PATH_MAX]; + snprintf(fullpath, sizeof fullpath, "%s/%s", path, argv[i]); + wd = inotify_add_watch(ifd, fullpath, IN_MODIFY | IN_CLOSE_WRITE); + if (wd == -1) { + perror("inotify_add_watch"); + exit(111); + } +#endif +#ifdef USE_KEVENT + note = NOTE_WRITE; +#ifdef __APPLE__ + note |= NOTE_FUNLOCK; +#endif +#ifdef __FreeBSD__ + note |= NOTE_CLOSE_WRITE; +#endif + EV_SET(&kev, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note, 0, NULL); + if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { + perror("kevent"); + exit(111); + } +#endif + + while (1) { + off = lseek(fd, 0, SEEK_END); + + if (off < loff) + loff = off; /* file truncated */ + + if (off == loff) { + if (nflag && islocked(fd)) + break; + + if (flock(fd, LOCK_SH | LOCK_NB) == -1 && + errno == EWOULDBLOCK) { +#if defined(USE_INOTIFY) + /* any inotify event is good */ + read(ifd, ibuf, sizeof ibuf); +#elif defined(USE_KEVENT) + kevent(kq, NULL, 0, &kev, 1, NULL); +#else + /* poll for size change */ + while (off == lseek(fd, 0, SEEK_END)) + usleep(DELAY); +#endif + continue; + } else { + flock(fd, LOCK_UN); + break; + } + } + + if (off - loff > sizeof buf) + off = loff + sizeof buf; + + rd = pread(fd, &buf, off - loff, loff); + if (qflag) { + if (!seen_nl) { + char *s; + if ((s = memchr(buf, '\n', rd))) { + write(1, buf, s+1-buf); + seen_nl = 1; + } else { + write(1, buf, rd); + } + } + } else { + write(1, buf, rd); + } + + loff += rd; + } + + if (qflag && !seen_nl) + write(1, "\n", 1); + +#ifdef USE_INOTIFY + inotify_rm_watch(ifd, wd); +#endif +#ifdef USE_KEVENT + EV_SET(&kev, fd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); + kevent(kq, &kev, 1, NULL, 0, NULL); +#endif + close(fd); + } + +#ifdef USE_INOTIFY + close(ifd); +#endif +#ifdef USE_KEVENT + close(kq); +#endif + return 0; +} diff --git a/nqtail.sh b/nqtail.sh new file mode 100755 index 0000000..d2d3816 --- /dev/null +++ b/nqtail.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# nqtail - tail -F the queue outputs, quitting when the job finishes + +tailed=false +for f in ${NQDIR:-.}/,*; do + if ! nq -t $f; then + tailed=true + printf '==> %s\n' "$f" + tail -F $f & p=$! + nq -w $f + kill $p + fi +done + +if ! $tailed; then + cat $f +fi diff --git a/nqterm b/nqterm new file mode 100755 index 0000000..06b5741 --- /dev/null +++ b/nqterm @@ -0,0 +1,25 @@ +#!/bin/sh +# nqterm CMD... - tmux/screen wrapper for nq to display output in new window + +set -e + +s=$(nq "$@") +p=${s##*.} + +printf '%s\n' "$s" + +if [ -n "$p" ]; then + if [ -n "$TMUX" ]; then + tmux new-window -a -d -n '<' -c '#{pane_current_path}' \ + "trap true INT QUIT TERM EXIT; + nqtail $s || kill $p; + printf '[%d exited, ^D to exit.]\n' $p; + cat >/dev/null" + elif [ -n "$STY" ]; then + screen -t '<' sh -c "trap true INT QUIT TERM EXIT; + nqtail $s || kill $p + printf '[%d exited, ^D to exit.]\n' $p; + cat >/dev/null" + screen -X other + fi +fi diff --git a/nqterm.1 b/nqterm.1 new file mode 100644 index 0000000..a217f8f --- /dev/null +++ b/nqterm.1 @@ -0,0 +1,56 @@ +.Dd July 3, 2024 +.Dt NQTERM 1 +.Os +.Sh NAME +.Nm nqterm +.Nd job queue wrapper for tmux/screen +.Sh SYNOPSIS +.Nm +.Ar command\ line ... +.Sh DESCRIPTION +.Nm +is a tiny wrapper around the +.Xr nq 1 +job queue which automatically spawns a corresponding +.Xr nqtail 1 +watching process in a new +.Xr tmux 1 +or +.Xr screen 1 +window. +.Pp +You can cancel the +.Xr nq 1 +job by pressing +.Ic C-c +in the job output window. +.Pp +After the job has finished, the window will +close on +.Ic C-d . +.Sh ENVIRONMENT +.Bl -hang -width Ds +.It Ev NQDIR +Directory where lock files/job output resides, see +.Xr nq 1 . +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr nq 1 , +.Xr nqtail 1 , +.Xr screen 1 , +.Xr tmux 1 +.Sh AUTHORS +.An Leah Neukirchen Aq Mt leah@vuxu.org +.Sh LICENSE +.Nm +is in the public domain. +.Pp +To the extent possible under law, +the creator of this work +has waived all copyright and related or +neighboring rights to this work. +.Pp +.Lk http://creativecommons.org/publicdomain/zero/1.0/ +.\" .Sh BUGS diff --git a/tests b/tests index 0e664ad..0f4b933 100755 --- a/tests +++ b/tests @@ -1,7 +1,7 @@ #!/bin/sh : ${NQ:=../nq} -: ${FQ:=../fq} +: ${NQTAIL:=../nqtail} set -e @@ -86,19 +86,19 @@ mkdir test.dir ( cd test.dir -printf '# fq tests\n' +printf '# nqtail tests\n' check 'spawning four jobs' 'f1=$($NQ sleep 100)' check 'spawning four jobs' 'f2=$($NQ echo two)' check 'spawning four jobs' 'f3=$($NQ sleep 300)' check 'spawning four jobs' 'f4=$($NQ sleep 400)' -check 'fq tracks first job' '($FQ ,* & p=$!; sleep 1; kill $p) | sed 3q | grep -q sleep.*100' +check 'nqtail tracks first job' '($NQTAIL ,* & p=$!; sleep 1; kill $p) | sed 3q | grep -q sleep.*100' check 'killing first job' kill ${f1##*.} check 'killing fourth job' kill ${f4##*.} sleep 1 -check 'fq tracks third job' '($FQ ,* & p=$!; sleep 1; kill $p) | sed 3q | grep -q sleep.*300' +check 'nqtail tracks third job' '($NQTAIL ,* & p=$!; sleep 1; kill $p) | sed 3q | grep -q sleep.*300' check 'killing third job' kill ${f3##*.} sleep 1 -check 'fq outputs last job when no job running' '$FQ ,* | sed 3q | grep -q sleep.*400' +check 'nqtail outputs last job when no job running' '$NQTAIL ,* | sed 3q | grep -q sleep.*400' ) rm -rf test.dir diff --git a/tq b/tq deleted file mode 100755 index b3449c4..0000000 --- a/tq +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -# tq CMD... - tmux/screen wrapper for nq to display output in new window - -set -e - -s=$(nq "$@") -p=${s##*.} - -printf '%s\n' "$s" - -if [ -n "$p" ]; then - if [ -n "$TMUX" ]; then - tmux new-window -a -d -n '<' -c '#{pane_current_path}' \ - "trap true INT QUIT TERM EXIT; - fq $s || kill $p; - printf '[%d exited, ^D to exit.]\n' $p; - cat >/dev/null" - elif [ -n "$STY" ]; then - screen -t '<' sh -c "trap true INT QUIT TERM EXIT; - fq $s || kill $p - printf '[%d exited, ^D to exit.]\n' $p; - cat >/dev/null" - screen -X other - fi -fi diff --git a/tq.1 b/tq.1 deleted file mode 100644 index b0eaaa1..0000000 --- a/tq.1 +++ /dev/null @@ -1,56 +0,0 @@ -.Dd August 25, 2015 -.Dt TQ 1 -.Os -.Sh NAME -.Nm tq -.Nd job queue wrapper for tmux/screen -.Sh SYNOPSIS -.Nm -.Ar command\ line ... -.Sh DESCRIPTION -.Nm -is a tiny wrapper around the -.Xr nq 1 -job queue which automatically spawns a corresponding -.Xr fq 1 -watching process in a new -.Xr tmux 1 -or -.Xr screen 1 -window. -.Pp -You can cancel the -.Xr nq 1 -job by pressing -.Ic C-c -in the job output window. -.Pp -After the job has finished, the window will -close on -.Ic C-d . -.Sh ENVIRONMENT -.Bl -hang -width Ds -.It Ev NQDIR -Directory where lock files/job output resides, see -.Xr nq 1 . -.El -.Sh EXIT STATUS -.Ex -std -.Sh SEE ALSO -.Xr fq 1 , -.Xr nq 1 , -.Xr screen 1 , -.Xr tmux 1 -.Sh AUTHORS -.An Leah Neukirchen Aq Mt leah@vuxu.org -.Sh LICENSE -.Nm -is in the public domain. -.Pp -To the extent possible under law, -the creator of this work -has waived all copyright and related or -neighboring rights to this work. -.Pp -.Lk http://creativecommons.org/publicdomain/zero/1.0/ -.\" .Sh BUGS -- cgit 1.4.1