Attached is a slightly-reworked version of the earlier patch that adds stderr handling to watch. This version makes the stderr handling default rather than optional. I prefer this version of the patch, because of the principle of least surprise in UI design, reference http://en.wikipedia.org/wiki/Principle_of_least_astonishment . When one runs a command like "watch ls -ld some_file", and then some_file is deleted, one is not expecting a bunch of scrolling errors to obscure the watch command.
I see at http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=225549 that Justin Pryzby thinks dup2(STDOUT_FILENO, STDERR_FILENO) is a bad idea because it loses the ability to discard stderr. Note that it's still possible for users to override the default handling inside the command, i.e.: watch "ls -ld no_such_file 2>/dev/null" So anyone who wants to discard stderr still can. And if the desire to add "discard stderr" as a native watch capability comes up, it's still possible to add a "discard stderr" option even with the default of dup2(1, 2). I'd be happy to do this if a "discard stderr" option is needed for this functionality to be added to watch. - Morty
diff -Nur procps-3.2.7.orig/watch.1 procps-3.2.7/watch.1 --- procps-3.2.7.orig/watch.1 2003-02-09 02:05:25.000000000 -0500 +++ procps-3.2.7/watch.1 2008-08-08 16:26:53.000000000 -0400 @@ -3,12 +3,13 @@ watch \- execute a program periodically, showing output fullscreen .SH SYNOPSIS .B watch -.I [\-dhvt] [\-n <seconds>] [\-\-differences[=cumulative]] [\-\-help] [\-\-interval=<seconds>] [\-\-no\-title] [\-\-version] <command> +.I [\-bdhvt] [\-n <seconds>] [\-\-beep] [\-\-differences[=cumulative]] [\-\-help] [\-\-interval=<seconds>] [\-\-no\-title] [\-\-version] <command> .SH DESCRIPTION .BR watch runs .I command -repeatedly, displaying its output (the first screenfull). This allows you to +repeatedly, displaying its output and errors (the first screenfull). This +allows you to watch the program output change over time. By default, the program is run every 2 seconds; use .I -n @@ -28,7 +29,11 @@ or .I --no-title option turns off the header showing the interval, command, and current -time at the top of the display, as well as the following blank line. +time at the top of the display, as well as the following blank line. The +.I -b +or +.I --beep +option causes the command to beep if it has a non-zero exit. .PP .BR watch will run until interrupted. @@ -84,4 +89,5 @@ .B watch was written by Tony Rems <[EMAIL PROTECTED]> in 1991, with mods and corrections by Francois Pinard. It was reworked and new features added by -Mike Coleman <[EMAIL PROTECTED]> in 1999. +Mike Coleman <[EMAIL PROTECTED]> in 1999. The beep and error handling features were +added by Morty Abzug <[EMAIL PROTECTED]> in 2008. diff -Nur procps-3.2.7.orig/watch.c procps-3.2.7/watch.c --- procps-3.2.7.orig/watch.c 2006-06-17 05:18:38.000000000 -0400 +++ procps-3.2.7/watch.c 2008-08-08 16:35:16.000000000 -0400 @@ -8,6 +8,7 @@ * Mike Coleman <[EMAIL PROTECTED]>. * * Changes by Albert Cahalan, 2002-2003. + * stderr handling and beep option added by Morty Abzug, 2008 */ #define VERSION "0.2.0" @@ -35,13 +36,14 @@ {"differences", optional_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {"interval", required_argument, 0, 'n'}, + {"beep", no_argument, 0, 'b'}, {"no-title", no_argument, 0, 't'}, {"version", no_argument, 0, 'v'}, {0, 0, 0, 0} }; static char usage[] = - "Usage: %s [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] [--no-title] [--version] <command>\n"; + "Usage: %s [-bdhntv] [--beep] [--differences[=cumulative]] [--help] [--interval=<n>] [--no-title] [--version] <command>\n"; static char *progname; @@ -140,17 +142,24 @@ int optc; int option_differences = 0, option_differences_cumulative = 0, + option_beep = 0, option_help = 0, option_version = 0; double interval = 2; char *command; int command_length = 0; /* not including final \0 */ + int pipefd[2]; + int status; + pid_t child; setlocale(LC_ALL, ""); progname = argv[0]; - while ((optc = getopt_long(argc, argv, "+d::hn:vt", longopts, (int *) 0)) + while ((optc = getopt_long(argc, argv, "+bd::hn:vt", longopts, (int *) 0)) != EOF) { switch (optc) { + case 'b': + option_beep = 1; + break; case 'd': option_differences = 1; if (optarg) @@ -191,6 +200,7 @@ if (option_help) { fprintf(stderr, usage, progname); + fputs(" -b, --beep\t\t\t\tbeep if the command has a non-zero exit\n", stderr); fputs(" -d, --differences[=cumulative]\thighlight changes between updates\n", stderr); fputs("\t\t(cumulative means highlighting is cumulative)\n", stderr); fputs(" -h, --help\t\t\t\tprint a summary of the options\n", stderr); @@ -261,11 +271,46 @@ free(header); } - if (!(p = popen(command, "r"))) { - perror("popen"); + /* allocate pipes */ + if (pipe(pipefd)<0) { + perror("pipe"); + do_exit(7); + } + + /* flush stdout and stderr, since we're about to do fd stuff */ + fflush(stdout); + fflush(stderr); + + /* fork to prepare to run command */ + child=fork(); + + if (child<0) { /* fork error */ + perror("fork"); do_exit(2); + } else if (child==0) { /* in child */ + close (pipefd[0]); /* child doesn't need read side of pipe */ + close (1); /* prepare to replace stdout with pipe */ + if (dup2 (pipefd[1], 1)<0) { /* replace stdout with write side of pipe */ + perror("dup2"); + exit(3); + } + dup2(1, 2); /* stderr should default to stdout */ + status=system(command); /* watch manpage promises quoting, so no execvp */ + if (!WIFEXITED(status)) { /* child exits nonzero if command does */ + exit(1); + } else { + exit(WEXITSTATUS(status)); + } } + /* otherwise, we're in parent */ + close(pipefd[1]); /* close write side of pipe */ + if ((p=fdopen(pipefd[0], "r"))==NULL) { + perror("fdopen"); + do_exit(5); + } + + for (y = show_title; y < height; y++) { int eolseen = 0, tabpending = 0; for (x = 0; x < width; x++) { @@ -313,7 +358,18 @@ oldeolseen = eolseen; } - pclose(p); + fclose(p); + + /* harvest child process and get status, propagated from command */ + if (waitpid(child, &status, 0)<0) { + perror("waitpid"); + do_exit(8); + }; + + /* if child process exited in error, beep if option_beep is set */ + if (option_beep && (!WIFEXITED(status) || WEXITSTATUS(status))) { + beep(); + } first_screen = 0; refresh();