2 * watch -- execute a program repeatedly, displaying output fullscreen
4 * Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com>
5 * (with mods and corrections by Francois Pinard).
7 * Substantially reworked, new features (differences option, SIGWINCH
8 * handling, unlimited command length, long line handling) added Apr
9 * 1999 by Mike Coleman <mkc@acm.org>.
11 * Changes by Albert Cahalan, 2002-2003.
12 * stderr handling, exec, and beep option added by Morty Abzug, 2008
13 * Unicode Support added by Jarrod Lowe <procps@rrod.net> in 2009.
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 2.1 of the License, or (at your option) any later version.
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
25 * You should have received a copy of the GNU Lesser General Public
26 * License along with this library; if not, write to the Free Software
27 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32 #include "fileutils.h"
45 #include <sys/ioctl.h>
52 # define _XOPEN_SOURCE_EXTENDED 1
55 #endif /* WITH_WATCH8BIT */
60 # define isprint(x) ( (x>=' '&&x<='~') || (x>=0xa0) )
64 /* Boolean command line options */
66 #define WATCH_DIFF (1 << 1)
67 #define WATCH_CUMUL (1 << 2)
68 #define WATCH_EXEC (1 << 3)
69 #define WATCH_BEEP (1 << 4)
70 #define WATCH_COLOR (1 << 5)
71 #define WATCH_ERREXIT (1 << 6)
72 #define WATCH_CHGEXIT (1 << 7)
74 static int curses_started = 0;
75 static long height = 24, width = 80;
76 static int screen_size_changed = 0;
77 static int first_screen = 1;
78 static int show_title = 2; /* number of lines used, 2 or 0 */
79 static int precise_timekeeping = 0;
80 static int line_wrap = 1;
82 #define min(x,y) ((x) > (y) ? (y) : (x))
83 #define MAX_ANSIBUF 100
85 static void __attribute__ ((__noreturn__))
88 fputs(USAGE_HEADER, out);
90 _(" %s [options] command\n"), program_invocation_short_name);
91 fputs(USAGE_OPTIONS, out);
92 fputs(_(" -b, --beep beep if command has a non-zero exit\n"), out);
93 fputs(_(" -c, --color interpret ANSI color and style sequences\n"), out);
94 fputs(_(" -d, --differences[=<permanent>]\n"
95 " highlight changes between updates\n"), out);
96 fputs(_(" -e, --errexit exit if command has a non-zero exit\n"), out);
97 fputs(_(" -g, --chgexit exit when output from command changes\n"), out);
98 fputs(_(" -n, --interval <secs> seconds to wait between updates\n"), out);
99 fputs(_(" -p, --precise attempt run command in precise intervals\n"), out);
100 fputs(_(" -t, --no-title turn off header\n"), out);
101 fputs(_(" -w, --no-wrap turn off line wrapping\n"), out);
102 fputs(_(" -x, --exec pass command to exec instead of \"sh -c\"\n"), out);
103 fputs(USAGE_SEPARATOR, out);
104 fputs(USAGE_HELP, out);
105 fputs(_(" -v, --version output version information and exit\n"), out);
106 fprintf(out, USAGE_MAN_TAIL("watch(1)"));
108 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
111 static int nr_of_colors;
112 static int attributes;
115 static int more_colors;
118 static void reset_ansi(void)
120 attributes = A_NORMAL;
125 static void init_ansi_colors(void)
129 short ncurses_colors[] = {
130 -1, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW,
131 COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
133 nr_of_colors = sizeof(ncurses_colors) / sizeof(short);
135 more_colors = (COLORS >= 16) && (COLOR_PAIRS >= 16 * 16);
137 // Initialize ncurses colors. -1 is terminal default
138 // 0-7 are auto created standard colors initialized by ncurses
140 // Initialize using ANSI SGR 8-bit specified colors
141 // 8-15 are bright colors
142 init_color(8, 333, 333, 333); // Bright black
143 init_color(9, 1000, 333, 333); // Bright red
144 init_color(10, 333, 1000, 333); // Bright green
145 init_color(11, 1000, 1000, 333); // Bright yellow
146 init_color(12, 333, 333, 1000); // Bright blue
147 init_color(13, 1000, 333, 1000); // Bright magenta
148 init_color(14, 333, 1000, 1000); // Bright cyan
149 // Often ncurses is built with only 256 colors, so lets
150 // stop here - so we can support the -1 terminal default
151 //init_color(15, 1000, 1000, 1000); // Bright white
155 // Initialize all color pairs with ncurses
156 for (bg_col = 0; bg_col < nr_of_colors; bg_col++)
157 for (fg_col = 0; fg_col < nr_of_colors; fg_col++)
158 init_pair(bg_col * nr_of_colors + fg_col + 1, fg_col - 1, bg_col - 1);
164 static int process_ansi_color_escape_sequence(char** escape_sequence) {
165 // process SGR ANSI color escape sequence
167 // 38;5;⟨n⟩ (set fg color to n)
168 // 48;5;⟨n⟩ (set bg color to n)
170 // Eg 24-bit (not yet implemented)
171 // ESC[ 38;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB foreground color
172 // ESC[ 48;2;⟨r⟩;⟨g⟩;⟨b⟩ m Select RGB background color
175 if ((*escape_sequence)[0] != ';')
176 return 0; /* not understood */
178 if ((*escape_sequence)[1] == '5') {
179 // 8 bit! ANSI specifies a predefined set of 256 colors here.
180 if ((*escape_sequence)[2] != ';')
181 return 0; /* not understood */
182 num = strtol((*escape_sequence) + 3, escape_sequence, 10);
183 if (num >= 0 && num <= 7) {
184 // 0-7 are standard colors same as SGR 30-37
187 if (num >= 8 && num <= 15) {
188 // 8-15 are standard colors same as SGR 90-97
189 return more_colors ? num + 1 : num - 8 + 1;
192 // Remainder aren't yet implemented
193 // 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
194 // 232-255: grayscale from black to white in 24 steps
197 return 0; /* not understood */
201 static int set_ansi_attribute(const int attrib, char** escape_sequence)
204 case -1: /* restore last settings */
206 case 0: /* restore default settings */
209 case 1: /* set bold / increased intensity */
210 attributes |= A_BOLD;
212 case 2: /* set decreased intensity (if supported) */
216 case 3: /* set italic (if supported) */
217 attributes |= A_ITALIC;
220 case 4: /* set underline */
221 attributes |= A_UNDERLINE;
223 case 5: /* set blinking */
224 attributes |= A_BLINK;
226 case 7: /* set inversed */
227 attributes |= A_REVERSE;
229 case 21: /* unset bold / increased intensity */
230 attributes &= ~A_BOLD;
232 case 22: /* unset bold / any intensity modifier */
233 attributes &= ~(A_BOLD | A_DIM);
236 case 23: /* unset italic */
237 attributes &= ~A_ITALIC;
240 case 24: /* unset underline */
241 attributes &= ~A_UNDERLINE;
243 case 25: /* unset blinking */
244 attributes &= ~A_BLINK;
246 case 27: /* unset inversed */
247 attributes &= ~A_REVERSE;
250 fg_col = process_ansi_color_escape_sequence(escape_sequence);
252 return 0; /* not understood */
259 bg_col = process_ansi_color_escape_sequence(escape_sequence);
261 return 0; /* not understood */
268 if (attrib >= 30 && attrib <= 37) { /* set foreground color */
269 fg_col = attrib - 30 + 1;
270 } else if (attrib >= 40 && attrib <= 47) { /* set background color */
271 bg_col = attrib - 40 + 1;
272 } else if (attrib >= 90 && attrib <= 97) { /* set bright fg color */
273 fg_col = more_colors ? attrib - 90 + 9 : attrib - 90 + 1;
274 } else if (attrib >= 100 && attrib <= 107) { /* set bright bg color */
275 bg_col = more_colors ? attrib - 100 + 9 : attrib - 100 + 1;
277 return 0; /* Not understood */
280 attr_set(attributes, bg_col * nr_of_colors + fg_col + 1, NULL);
284 static void process_ansi(FILE * fp)
288 char buf[MAX_ANSIBUF];
289 char *numstart, *endptr;
301 for (i = 0; i < MAX_ANSIBUF; i++) {
303 /* COLOUR SEQUENCE ENDS in 'm' */
308 if ((c < '0' || c > '9') && c != ';') {
314 * buf now contains a semicolon-separated list of decimal integers,
315 * each indicating an attribute to apply.
316 * For example, buf might contain "0;1;31", derived from the color
317 * escape sequence "<ESC>[0;1;31m". There can be 1 or more
318 * attributes to apply, but typically there are between 1 and 3.
321 /* Special case of <ESC>[m */
323 set_ansi_attribute(0, NULL);
325 for (endptr = numstart = buf; *endptr != '\0'; numstart = endptr + 1) {
326 ansi_attribute = strtol(numstart, &endptr, 10);
327 if (!set_ansi_attribute(ansi_attribute, &endptr))
332 static void __attribute__ ((__noreturn__)) do_exit(int status)
340 static void die(int notused __attribute__ ((__unused__)))
342 do_exit(EXIT_SUCCESS);
345 static void winch_handler(int notused __attribute__ ((__unused__)))
347 screen_size_changed = 1;
350 static char env_col_buf[24];
351 static char env_row_buf[24];
352 static int incoming_cols;
353 static int incoming_rows;
355 static void get_terminal_size(void)
358 if (!incoming_cols) {
359 /* have we checked COLUMNS? */
360 const char *s = getenv("COLUMNS");
365 t = strtol(s, &endptr, 0);
366 if (!*endptr && 0 < t)
368 width = incoming_cols;
369 snprintf(env_col_buf, sizeof env_col_buf, "COLUMNS=%ld",
374 if (!incoming_rows) {
375 /* have we checked LINES? */
376 const char *s = getenv("LINES");
381 t = strtol(s, &endptr, 0);
382 if (!*endptr && 0 < t)
384 height = incoming_rows;
385 snprintf(env_row_buf, sizeof env_row_buf, "LINES=%ld",
390 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &w) == 0) {
391 if (incoming_cols < 0 || incoming_rows < 0) {
392 if (incoming_rows < 0 && w.ws_row > 0) {
394 snprintf(env_row_buf, sizeof env_row_buf,
395 "LINES=%ld", height);
398 if (incoming_cols < 0 && w.ws_col > 0) {
400 snprintf(env_col_buf, sizeof env_col_buf,
401 "COLUMNS=%ld", width);
408 /* get current time in usec */
409 typedef unsigned long long watch_usec_t;
410 #define USECS_PER_SEC (1000000ull)
411 static watch_usec_t get_time_usec()
414 gettimeofday(&now, NULL);
415 return USECS_PER_SEC * now.tv_sec + now.tv_usec;
418 #ifdef WITH_WATCH8BIT
419 /* read a wide character from a popen'd stream */
420 #define MAX_ENC_BYTES 16
421 wint_t my_getwc(FILE * s);
422 wint_t my_getwc(FILE * s)
424 /* assuming no encoding ever consumes more than 16 bytes */
425 char i[MAX_ENC_BYTES];
432 if (i[byte] == EOF) {
437 mbtowc(NULL, NULL, 0);
438 convert = mbtowc(&rval, i, byte);
441 /* legal conversion */
444 if (byte == MAX_ENC_BYTES) {
446 /* at least *try* to fix up */
447 ungetc(i[--byte], s);
454 #endif /* WITH_WATCH8BIT */
456 #ifdef WITH_WATCH8BIT
457 static void output_header(wchar_t *restrict wcommand, int wcommand_characters, double interval)
459 static void output_header(char *restrict command, double interval)
460 #endif /* WITH_WATCH8BIT */
462 time_t t = time(NULL);
463 char *ts = ctime(&t);
466 int max_host_name_len = (int) sysconf(_SC_HOST_NAME_MAX);
467 char hostname[max_host_name_len + 1];
468 int command_columns = 0; /* not including final \0 */
470 gethostname(hostname, sizeof(hostname));
473 * left justify interval and command, right justify hostname and time,
474 * clipping all to fit window width
476 int hlen = asprintf(&header, _("Every %.1fs: "), interval);
477 int rhlen = asprintf(&right_header, _("%s: %s"), hostname, ts);
481 * width < rhlen : print nothing
482 * width < rhlen + hlen + 1: print hostname, ts
483 * width = rhlen + hlen + 1: print header, hostname, ts
484 * width < rhlen + hlen + 4: print header, ..., hostname, ts
485 * width < rhlen + hlen + wcommand_columns: print header,
486 * truncated wcommand, ..., hostname, ts
487 * width > "": print header, wcomand, hostname, ts
488 * this is slightly different from how it used to be
495 if (rhlen + hlen + 1 <= width) {
496 mvaddstr(0, 0, header);
497 if (rhlen + hlen + 2 <= width) {
498 if (width < rhlen + hlen + 4) {
499 mvaddstr(0, width - rhlen - 4, "... ");
501 #ifdef WITH_WATCH8BIT
502 command_columns = wcswidth(wcommand, -1);
503 if (width < rhlen + hlen + command_columns) {
504 /* print truncated */
505 int available = width - rhlen - hlen;
506 int in_use = command_columns;
507 int wcomm_len = wcommand_characters;
508 while (available - 4 < in_use) {
510 in_use = wcswidth(wcommand, wcomm_len);
512 mvaddnwstr(0, hlen, wcommand, wcomm_len);
513 mvaddstr(0, width - rhlen - 4, "... ");
515 mvaddwstr(0, hlen, wcommand);
518 command_columns = strlen(command);
519 if (width < rhlen + hlen + command_columns) {
520 /* print truncated */
521 mvaddnstr(0, hlen, command, width - rhlen - hlen - 4);
522 mvaddstr(0, width - rhlen - 4, "... ");
524 mvaddnstr(0, hlen, command, width - rhlen - hlen);
526 #endif /* WITH_WATCH8BIT */
530 mvaddstr(0, width - rhlen + 1, right_header);
536 static void find_eol(FILE *p)
539 #ifdef WITH_WATCH8BIT
549 #endif /* WITH_WATCH8BIT */
552 static int run_command(char *restrict command, char **restrict command_argv)
563 if (pipe(pipefd) < 0)
564 xerr(7, _("unable to create IPC pipes"));
566 /* flush stdout and stderr, since we're about to do fd stuff */
570 /* fork to prepare to run command */
573 if (child < 0) { /* fork error */
574 xerr(2, _("unable to fork process"));
575 } else if (child == 0) { /* in child */
576 close(pipefd[0]); /* child doesn't need read side of pipe */
577 close(1); /* prepare to replace stdout with pipe */
578 if (dup2(pipefd[1], 1) < 0) { /* replace stdout with write side of pipe */
579 xerr(3, _("dup2 failed"));
581 close(pipefd[1]); /* once duped, the write fd isn't needed */
582 dup2(1, 2); /* stderr should default to stdout */
584 if (flags & WATCH_EXEC) { /* pass command to exec instead of system */
585 if (execvp(command_argv[0], command_argv) == -1) {
586 xerr(4, _("unable to execute '%s'"),
590 status = system(command); /* watch manpage promises sh quoting */
591 /* propagate command exit status as child exit status */
592 if (!WIFEXITED(status)) { /* child exits nonzero if command does */
595 exit(WEXITSTATUS(status));
600 /* otherwise, we're in parent */
601 close(pipefd[1]); /* close write side of pipe */
602 if ((p = fdopen(pipefd[0], "r")) == NULL)
603 xerr(5, _("fdopen"));
606 for (y = show_title; y < height; y++) {
607 int eolseen = 0, tabpending = 0, tabwaspending = 0;
608 if (flags & WATCH_COLOR)
609 set_ansi_attribute(-1, NULL);
610 #ifdef WITH_WATCH8BIT
613 for (x = 0; x < width; x++) {
614 #ifdef WITH_WATCH8BIT
621 if (tabwaspending && (flags & WATCH_COLOR))
622 set_ansi_attribute(-1, NULL);
626 /* if there is a tab pending, just
627 * spit spaces until the next stop
628 * instead of reading characters */
630 #ifdef WITH_WATCH8BIT
638 } while (c != WEOF && !iswprint(c)
644 || !(flags & WATCH_COLOR)));
648 while (c != EOF && !isprint(c)
652 || !(flags & WATCH_COLOR)));
654 if (c == L'\033' && (flags & WATCH_COLOR)) {
660 if (!oldeolseen && x == 0) {
667 #ifdef WITH_WATCH8BIT
668 if (x == width - 1 && wcwidth(c) == 2) {
670 x = -1; /* process this double-width */
671 carry = c; /* character on the next line */
672 continue; /* because it won't fit here */
674 if (c == WEOF || c == L'\n' || c == L'\t') {
676 if (flags & WATCH_COLOR)
680 if (c == EOF || c == '\n' || c == '\t') {
682 if (flags & WATCH_COLOR)
686 if (tabpending && (((x + 1) % 8) == 0)) {
693 if (!first_screen && !exit_early && (flags & WATCH_CHGEXIT)) {
694 #ifdef WITH_WATCH8BIT
697 exit_early = (wchar_t) c != oldc.chars[0];
699 chtype oldch = inch();
700 unsigned char oldc = oldch & A_CHARTEXT;
701 exit_early = (unsigned char)c != oldc;
704 if (flags & WATCH_DIFF) {
705 #ifdef WITH_WATCH8BIT
709 && ((wchar_t) c != oldc.chars[0]
711 ((flags & WATCH_CUMUL)
712 && (oldc.attr & A_ATTRIBUTES)));
714 chtype oldch = inch();
715 unsigned char oldc = oldch & A_CHARTEXT;
717 && ((unsigned char)c != oldc
719 ((flags & WATCH_CUMUL)
720 && (oldch & A_ATTRIBUTES)));
725 #ifdef WITH_WATCH8BIT
726 addnwstr((wchar_t *) & c, 1);
732 #ifdef WITH_WATCH8BIT
733 if (wcwidth(c) == 0) {
736 if (wcwidth(c) == 2) {
741 oldeolseen = eolseen;
744 if (flags & WATCH_COLOR)
753 /* harvest child process and get status, propagated from command */
754 if (waitpid(child, &status, 0) < 0)
755 xerr(8, _("waitpid"));
757 /* if child process exited in error, beep if option_beep is set */
758 if ((!WIFEXITED(status) || WEXITSTATUS(status))) {
759 if (flags & WATCH_BEEP)
761 if (flags & WATCH_ERREXIT) {
762 mvaddstr(height - 1, 0,
763 _("command exit with a non-zero status, press a key to exit"));
775 int main(int argc, char *argv[])
779 char *interval_string;
782 int command_length = 0; /* not including final \0 */
783 watch_usec_t next_loop; /* next loop time in us, used for precise time
785 #ifdef WITH_WATCH8BIT
786 wchar_t *wcommand = NULL;
787 int wcommand_characters = 0; /* not including final \0 */
788 #endif /* WITH_WATCH8BIT */
790 static struct option longopts[] = {
791 {"color", no_argument, 0, 'c'},
792 {"differences", optional_argument, 0, 'd'},
793 {"help", no_argument, 0, 'h'},
794 {"interval", required_argument, 0, 'n'},
795 {"beep", no_argument, 0, 'b'},
796 {"errexit", no_argument, 0, 'e'},
797 {"chgexit", no_argument, 0, 'g'},
798 {"exec", no_argument, 0, 'x'},
799 {"precise", no_argument, 0, 'p'},
800 {"no-title", no_argument, 0, 't'},
801 {"no-wrap", no_argument, 0, 'w'},
802 {"version", no_argument, 0, 'v'},
806 #ifdef HAVE_PROGRAM_INVOCATION_NAME
807 program_invocation_name = program_invocation_short_name;
809 setlocale(LC_ALL, "");
810 bindtextdomain(PACKAGE, LOCALEDIR);
812 atexit(close_stdout);
814 interval_string = getenv("WATCH_INTERVAL");
815 if(interval_string != NULL)
816 interval = strtod_nol_or_err(interval_string, _("Could not parse interval from WATCH_INTERVAL"));
819 getopt_long(argc, argv, "+bced::ghn:pvtwx", longopts, (int *)0))
826 flags |= WATCH_COLOR;
831 flags |= WATCH_CUMUL;
834 flags |= WATCH_ERREXIT;
837 flags |= WATCH_CHGEXIT;
849 interval = strtod_nol_or_err(optarg, _("failed to parse argument"));
852 precise_timekeeping = 1;
858 printf(PROCPS_NG_VERSION);
868 if (interval > UINT_MAX)
875 command_argv = &(argv[optind]);
877 command = xstrdup(argv[optind++]);
878 command_length = strlen(command);
879 for (; optind < argc; optind++) {
881 int s = strlen(argv[optind]);
883 command = xrealloc(command, command_length + s + 2);
884 endp = command + command_length;
886 memcpy(endp + 1, argv[optind], s);
887 /* space then string length */
888 command_length += 1 + s;
889 command[command_length] = '\0';
892 #ifdef WITH_WATCH8BIT
893 /* convert to wide for printing purposes */
894 /*mbstowcs(NULL, NULL, 0); */
895 wcommand_characters = mbstowcs(NULL, command, 0);
896 if (wcommand_characters < 0) {
897 fprintf(stderr, _("unicode handling error\n"));
901 (wchar_t *) malloc((wcommand_characters + 1) * sizeof(wcommand));
902 if (wcommand == NULL) {
903 fprintf(stderr, _("unicode handling error (malloc)\n"));
906 mbstowcs(wcommand, command, wcommand_characters + 1);
907 #endif /* WITH_WATCH8BIT */
911 /* Catch keyboard interrupts so we can put tty back in a sane
914 signal(SIGTERM, die);
916 signal(SIGWINCH, winch_handler);
918 /* Set up tty for curses use. */
921 if (flags & WATCH_COLOR) {
924 use_default_colors();
927 flags &= ~WATCH_COLOR;
934 if (precise_timekeeping)
935 next_loop = get_time_usec();
938 if (screen_size_changed) {
940 resizeterm(height, width);
942 /* redrawwin(stdscr); */
943 screen_size_changed = 0;
948 #ifdef WITH_WATCH8BIT
949 output_header(wcommand, wcommand_characters, interval);
951 output_header(command, interval);
952 #endif /* WITH_WATCH8BIT */
954 if (run_command(command, command_argv))
958 if (precise_timekeeping) {
959 watch_usec_t cur_time = get_time_usec();
960 next_loop += USECS_PER_SEC * interval;
961 if (cur_time < next_loop)
962 usleep(next_loop - cur_time);
964 if (interval < UINT_MAX / USECS_PER_SEC)
965 usleep(interval * USECS_PER_SEC);