/******************************************************************************* * * bgrep.c * * AUTHOR: * Dan Harkless * * COPYRIGHT: * This file is Copyright (C) 2003 by Dan Harkless, and is released under the * GNU General Public License . * * USAGE: * % bgrep [--blank] [... -] [...] * * DESCRIPTION: * I wrote this program because this: * * % find . -type f -exec egrep Pattern {} \; -print * * is such an unsatisfying solution for the problem of looking for a pattern in * hierarchy of files. The results are hard to read because the name of each * file comes at the end of the grep matches and there isn't a blank line in * betweeen each file. The only thing you can look for is the initial "./", * but that's easy to miss. * * GNU grep gives you other output options than the above, due to its -H and -r * options, which will prefix each matching line with ":", but in * some ways that's even worse, since lines will wrap and tabs will be messed * up. * * To avoid these problems, use bgrep: * * % find * -type f -exec bgrep --blank Pattern {} \; * * bgrep calls egrep once per file. If there are no matches, nothing is * printed. If there are matches, then prior each block of matching lines, a * one-line terminal-width boldface banner (the "b" in "bgrep") will be * printed, containing the filename, right-justified, where it's easy to spot * since the text in most files is left-justified. * * The --blank option forces bgrep to print a blank line between each file's * matching output, which it usually only does if there are multiple files * specified on the commandline. Note that if --blank is specified it must be * the first parameter. * * After the optional --blank, it's possible to specify options to be passed to * egrep. To avoid bgrep having to know which egrep options require arguments * (which of course varies per platform), the egrep options must be terminated * by a '-'. For instance: * * % bgrep -i -e - -r *.c * * will do a case-insensitive search for "-r" in the C files of the current * directory. * * COMPILATION: * AIX/Solaris/...: % cc bgrep.c -lcurses -o bgrep * HP-UX 9.x: % cc -Ae bgrep.c -lcurses -o bgrep * HP-UX 10.x: % cc -Ae -I/usr/include/curses_colr bgrep.c -lcur_colr -o bgrep * * DATE MODIFICATION * ========== ================================================================== * 2003-04-15 If an egrep child dies with a signal, abort processing arguments * and exit with that signal number. Likewise, if egrep returns a * status greater than 1 (this signifies an error), abort and return * that same status. Otherwise, if any egrep call returns status 1 * (no matches found), return that when we're done with all files. * Otherwise, return 0 when we're all done. * 2003-04-15 If read() returns -1 and errno is EINTR (i.e. because someone * stopped and restarted bgrep), it means the system call was * interrupted before the data was read -- just re-run read(). * 2003-03-30 Blank lines now come after each file's output block rather than * before; no trailing blank line unless --blank specified. * 2003-03-30 Allow passing options to egrep. Due to this, renamed -b to * --blank, since egrep has a -b option. * 1999-04-09 Updated file to my new convention: all global vars end in _global. * 1998-07-13 It's not possible to put single quotes in single-quoted strings in * the shell, so change ' in egrep pattern or filenames to '"'"'. * 1998-02-24 -b flag forces blank line before each file, even if only one arg. * 1996-10-01 Original. * *******************************************************************************/ #include /* must precede */ #include /* for errno */ #include /* for sigaction(), etc. */ #include /* for fprintf(), etc. */ #include /* for exit(), etc. */ #include /* for strrchr(), etc. */ #include /* must precede */ #include /* for wait() */ #include /* for putp(), etc. */ #include /* for pipe(), etc. */ #define MAX_CONTIGUOUS_SIGNAL_INSTALLATION_FAILURES 32 #define READ_END 0 #define WRITE_END 1 typedef enum { text_mode_Lo_Err = 0, BOLD_MODE, NORMAL_MODE, text_mode_Hi_Err } text_mode_t; char* our_program_name_global; text_mode_t current_mode_global = NORMAL_MODE; void die_curses(const char* routine); void die_syscall(const char* syscall); void die_usage(); void install_signal_handlers(); void mode(text_mode_t new_mode); void signal_handler_fatal(int caught_signal_num); void signal_handler_stop(int caught_signal_num); char* signal_name(int signal_num); void die_curses(const char* routine) { fprintf(stderr, "%s: FATAL: %s() returned ERR.\n", our_program_name_global, routine); exit(EXIT_FAILURE); } void die_syscall(const char* syscall) { mode(NORMAL_MODE); fprintf(stderr, "%s: FATAL: %s: %s.\n", our_program_name_global, syscall, strerror(errno)); exit(EXIT_FAILURE); } void die_usage() { fprintf(stderr, "Usage: %s" " [--blank] [... -] [...]" "\n", our_program_name_global); exit(EXIT_FAILURE); } void install_signal_handlers() { int contiguous_failures = 0, signal_num = 1; struct sigaction fatal_sigaction, stop_sigaction; fatal_sigaction.sa_flags = 0; fatal_sigaction.sa_handler = signal_handler_fatal; sigemptyset(&fatal_sigaction.sa_mask); stop_sigaction.sa_flags = 0; stop_sigaction.sa_handler = signal_handler_stop; sigemptyset(&stop_sigaction.sa_mask); /* Unfortunately there's no MAXSIG/SIGMAX #define that can be depended on to be there. To be portable, we'll just start installing the fatal signal handler at signal 1 (skipping stop and non-fatal and uncatchable signals) and keep going until we've had MAX_CONTIGUOUS_SIGNAL_INSTALLATION_FAILURES. */ while (contiguous_failures < MAX_CONTIGUOUS_SIGNAL_INSTALLATION_FAILURES) { switch (signal_num) { case SIGCHLD: /* default action does not terminate */ case SIGCONT: /* default action does not terminate */ #ifdef SIGINFO case SIGINFO: /* default action does not terminate */ #endif /* SIGINFO */ case SIGKILL: /* uncatchable */ case SIGPWR: /* default action does not terminate */ case SIGSTOP: /* uncatachable */ case SIGURG: /* default action does not terminate */ case SIGWINCH: /* default action does not terminate */ /* Don't install a handler for this signal. */ break; case SIGTSTP: case SIGTTIN: case SIGTTOU: if (sigaction(signal_num, &stop_sigaction, NULL) < 0) die_syscall("sigaction(...stop_sigaction...)"); break; default: /* Try to install the fatal signal handler. */ if (sigaction(signal_num, &fatal_sigaction, NULL) == 0) contiguous_failures = 0; else contiguous_failures++; } signal_num++; } } void mode(text_mode_t new_mode) { char* new_mode_str; static char* enter_bold_or_standout_mode = NULL; if (new_mode != current_mode_global) { if (enter_bold_or_standout_mode == NULL) if (enter_bold_mode != NULL) enter_bold_or_standout_mode = enter_bold_mode; else enter_bold_or_standout_mode = enter_standout_mode; if (new_mode == BOLD_MODE) new_mode_str = enter_bold_or_standout_mode; else /* new_mode == NORMAL_MODE */ new_mode_str = exit_attribute_mode; if (putp(new_mode_str) == ERR) die_curses("putp()"); fflush(stdout); current_mode_global = new_mode; } } void signal_handler_fatal(int caught_signal_num) { char* caught_signal_name = signal_name(caught_signal_num); mode(NORMAL_MODE); fprintf(stderr, "%s: FATAL: Caught signal %d%s.\n", our_program_name_global, caught_signal_num, caught_signal_name); exit(EXIT_FAILURE); } void signal_handler_stop(int caught_signal_num) { text_mode_t mode_on_entry = current_mode_global; mode(NORMAL_MODE); raise(SIGSTOP); mode(mode_on_entry); } char* signal_name(int signal_num) { /* The return value of this function is intended to be printed out just to the right of a printed signal number. For nonportable signals, we return the empty string. If all systems defined sys_siglist[], this wouldn't be necessary. */ switch (signal_num) { case SIGABRT: return " (SIGABRT)"; case SIGALRM: return " (SIGALRM)"; case SIGBUS: return " (SIGBUS)"; case SIGCHLD: return " (SIGCHLD)"; case SIGCONT: return " (SIGCONT)"; case SIGFPE: return " (SIGFPE)"; case SIGHUP: return " (SIGHUP)"; case SIGILL: return " (SIGILL)"; #ifdef SIGINFO case SIGINFO: return " (SIGINFO)"; #endif /* SIGINFO */ case SIGINT: return " (SIGINT)"; case SIGIO: return " (SIGIO)"; case SIGKILL: return " (SIGKILL)"; case SIGPIPE: return " (SIGPIPE)"; case SIGPROF: return " (SIGPROF)"; case SIGPWR: return " (SIGPWR)"; case SIGQUIT: return " (SIGQUIT)"; case SIGSEGV: return " (SIGSEGV)"; case SIGSTOP: return " (SIGSTOP)"; case SIGTERM: return " (SIGTERM)"; case SIGTRAP: return " (SIGTRAP)"; case SIGTSTP: return " (SIGTSTP)"; case SIGTTIN: return " (SIGTTIN)"; case SIGTTOU: return " (SIGTTOU)"; case SIGURG: return " (SIGURG)"; case SIGUSR1: return " (SIGUSR1)"; case SIGUSR2: return " (SIGUSR2)"; case SIGVTALRM: return " (SIGVTALRM)"; case SIGWINCH: return " (SIGWINCH)"; default: return ""; } } int main(int argc, char** argv) { char force_blank_line_before = FALSE; char* last_slash_ptr = strrchr(argv[0], '/'); char** egrep_argv; int bgrep_exit_status = 0; int egrep_argc, egrep_filename_index, i, j; int first_filename_index, pattern_index; int first_option_index = -1, last_option_index = -1;/* 2nd -1 for warn*/ if (last_slash_ptr == NULL) our_program_name_global = argv[0]; else our_program_name_global = last_slash_ptr + 1; if (argc < 3) die_usage(); i = 1; if (strcmp(argv[i], "--blank") == 0) { force_blank_line_before = TRUE; i++; } if (argv[i][0] == '-') { while (i < argc && strcmp(argv[i], "-") != 0) { if (first_option_index == -1) first_option_index = i; last_option_index = i++; } i++; } if (i >= argc - 1) die_usage(); pattern_index = i++; first_filename_index = i; /* Set up the argument array for egrep. */ egrep_argc = 1 /* command */ + 1 /* pattern */ + 1 /* file */; if (first_option_index != -1) egrep_argc += (last_option_index - first_option_index) + 1; egrep_argv = malloc(sizeof(*egrep_argv) * (egrep_argc + 1 /* NULL */)); egrep_argv[0] = "egrep"; i = 1; if (first_option_index != -1) { j = first_option_index; while (j <= last_option_index) egrep_argv[i++] = argv[j++]; } egrep_argv[i++] = argv[pattern_index]; egrep_filename_index = i++; egrep_argv[i] = NULL; /* Load the terminfo entry for our terminal. */ if (setupterm(NULL, STDOUT_FILENO, NULL) == ERR) die_curses("setupterm"); install_signal_handlers(); /* wait until we've done setupterm() for this */ /* Loop through the filenames specified on the commandline. */ for (i = first_filename_index; i < argc; i++) { char buf[256]; /* size is arbitrary and immaterial */ int child_packed_status, fork_return, output_banner = 0, read_len; int pipe_des[2]; /* Create a pipe for communication from child to parent. We don't use popen() because we want to avoid shell interpretation of arguments (or quoting hassles to avoid that). */ if (pipe(pipe_des) != 0) die_syscall("pipe()"); fork_return = fork(); if (fork_return < 0) /* fork() failed. */ die_syscall("fork()"); else if (fork_return == 0) { /* We're the child. Close the read end of the pipe (unlike with the case of the parent not closing the write end of the pipe, I'm not sure what would happen if we didn't do this, but it's convention...). */ if (close(pipe_des[READ_END]) != 0) die_syscall("close(pipe_des[READ_END])"); /* Connect stdout to the write end of the pipe. */ if (pipe_des[WRITE_END] != STDOUT_FILENO) { /* can this ever be? */ if (dup2(pipe_des[WRITE_END], STDOUT_FILENO) < 0) die_syscall("dup2()"); /* Close the original descriptor for write end of the pipe. */ if (close(pipe_des[WRITE_END]) != 0) die_syscall("close(pipe_des[WRITE_END])"); } /* Run egrep. */ egrep_argv[egrep_filename_index] = argv[i]; execvp("egrep", egrep_argv); /* If execvp() returned, it failed. */ die_syscall("execvp(\"egrep\"...)"); } /* We're the parent. Close the write end of the pipe so we'll be able to detect EOF. */ if (close(pipe_des[WRITE_END]) != 0) die_syscall("close(pipe_des[WRITE_END])"); while ((read_len = read(pipe_des[READ_END], buf, sizeof(buf))) != 0) { if (read_len < 0) { /* read() had an error. */ if (errno != EINTR) /* The error wasn't EINTR (system call interrupted due to the user stopping and then restarting bgrep), but rather something fatal. Die. */ die_syscall("read()"); } else { /* read() got a block of data. */ if (!output_banner) { int hyphen_columns = columns - strlen(argv[i]) - 2; mode(BOLD_MODE); for (j = 1; j <= hyphen_columns; j++) putchar('-'); fputs("> ", stdout); fputs(argv[i], stdout); putchar('\n'); mode(NORMAL_MODE); output_banner = 1; } write(STDOUT_FILENO, buf, read_len); } } if (output_banner && (force_blank_line_before || i < argc - 1)) putchar('\n'); /* skip a line between files */ /* Reap the child. */ if (wait(&child_packed_status) < 0) die_syscall("wait()"); /* Check the return status, and abort this for() loop if appropriate. */ if (WIFSIGNALED(child_packed_status)) { int child_signal = WTERMSIG(child_packed_status); fprintf(stderr, "%s: FATAL: egrep received signal %d%s.\n", our_program_name_global, child_signal, signal_name(child_signal)); exit(child_signal); } else { /* WIFEXITED(child_packed_status) */ int child_return = WEXITSTATUS(child_packed_status); if (child_return == 1) bgrep_exit_status = 1; else if (child_return != 0) { fprintf(stderr, "%s: FATAL: egrep returned status %d.\n", our_program_name_global, child_return); exit(child_return); } } /* Done with the pipe. */ if (close(pipe_des[READ_END]) != 0) die_syscall("close(pipe_des[READ_END])"); } return bgrep_exit_status; }