0: Writing a function to be used like a program ----------------------------------------------- Shell functions can be treated very much like programs. You can use them in pipes, give them data on STDIN, get data on STDOUT, and use their return code. I have occasionally had need to remove all comments from a file or a stream, so I'll build a function which removes all comments and can be used two different ways. Here's what the function will do: - it will accept an optional (single) argument which should be a filename - if no argument is specified, it'll use the file found on STDIN - if no argument is specified and STDIN is a terminal, it'll simply exit non-zero (politeness case) This means we can use the function in the following ways: 0> $ strip_comments < /etc/hosts || echo Ooops. 1> $ strip_comments /etc/hosts || echo Ooops. 2> $ strip_comments || echo Ooops. 3> $ strip_comments /etc/ghosts || echo Ooops. In cases 0 and 1, you should see the file with all empty and comment lines removed. Case 0 tests that the function can handle a file connected to its STDIN. Case 1 shows that the function can handle a filename specified as an argument. Cases 2 and 3 should both show the word "Ooops." Case 2 is our (possibly over-designed) politeness case, and there will be no output other than the "Ooops" word, but case 3 is interesting! Here, we have provided a filename that does not exist, so, everything runs along just fine until we hit the "grep". When grep runs, it tries to open the nonexistent file "/etc/ghosts" (Do you have ghosts on your system? Hopefully, just the writing ones.) The grep utility will exit non-zero when it tries to open the missing file, and when it exits non-zero, the function strip_comments exits non-zero. You have now seen how to write a function which can be used almost exactly like a program. There are probably a bunch of clever ways to modify the strip_comments function to accept multiple filenames. Here's the commented function: function strip_comments () { # -- bash allows us to use the shorthand filename /dev/stdin # local stdin=/dev/stdin # -- we accept zero or one arguments (ignoring all others); if # the function is called with an argument, we treat it as a # a filename, if no argument, then we assume /dev/stdin # local file="${1:-$stdin}" # -- now, we check to see if there was no filename specified and # if the function is connected to a terminal--if so, then OOPS! # test "$file" = "$stdin" \ && tty --silent \ && return 1 # -- finally, run the grep. the return code of last command # executed in the function is the return code of the function # grep -Ev '^$|^([[:space:]]+)?#' "$file" } And finally, here's what it should look like stripping itself: $ strip_comments < strip_comments function strip_comments () { local stdin=/dev/stdin local file="${1:-$stdin}" test "$file" = "$stdin" \ && tty --silent \ && return 1 grep -Ev '^$|^([[:space:]]+)?#' "$file" }