Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a bash script with various if statements based on command line arguments I pass in when calling it. Having some kind of output as to what commands are being run is helpful to confirm the flow through all those if statements, but my current solution is giving me too much information.

Using set -v in the script was somewhat helpful to see commands printed to the screen as they were run in the script, however I get too many commands. It's almost like an entire copy of the script.

I want output that shows what commands are being run, but I don't want to see comments, new lines, expressions in if statements, etc.

Is there a way I can pass all possible output generated by the -v option through a regex first before being printed? Or some other solution to get bash to only output commands of a certain "type" (e.g. that are using executables and not just bash specific statements, comments etc.?)

[1] http://stackoverflow.com/questions/257616/sudo-changes-path-why was quite helpful on this and is where I got the suggestion for the set -v usage.


A similar (but not identical) script to the one I'm running:


#get verbose command output
set -v


if [ "$env" == "dev" ]; then
    python ascript.py

if [ "$env" == "prod" ]; then

    #launching in prod will most likely fail if not run as root. Warn user if not running as root.
    if [ $EUID -ne 0 ]; then
        echo "It doesn't look like you're running me as root. This probably won't work. Press any key to continue." > /dev/stderr
        read input

    #"stop" any existing nginx processes
    pkill -f nginx

    nginx -c `pwd`/conf/artfndr_nginx.conf


I want only 2 possible sets of output lines from this script. The first:

python ascript.py

The second:

pkill -f nginx
nginx -c /some/current/directory/conf/artfndr_nginx.conf
share|improve this question
Of course you can parse it but we can't help unless you show us the script and explain which parts of the set -v output you want and which ones you don't. – terdon Dec 27 '13 at 13:59
up vote 8 down vote accepted

When I write more complex bash scripts, I use a little function to run commands that will also print the commands run into a logfile:

    ## print the command to the logfile
    echo "$@" >> $LOG
    ## run the command and redirect it's error output
    ## to the logfile
    eval "$@" 2>> $LOG 

Then, in my script, I run commands like this:

runthis "cp /foo/bar /baz/"

If you don't want a command printed, just run it normally.

You can either set the $LOG to a filename or just remove it and print to stdout or stderr.

share|improve this answer
+1 Also I was able to run this inside my script by simply prepending "important" commands with a short-named version of the function, so the lines look something like v python ascript.py without having to enclose in quotations and lose my vim code highlighting – Trindaz Dec 27 '13 at 14:19
@Trindaz the quotes are there for when you need to pass variables in your commands, if the variables contain spaces you might have problems otherwise. – terdon Dec 27 '13 at 14:23
eval ..... || ok=1 : will set ok to "1" only when "eval ..." fails ?? Maybe you meant "&&" ? And if you meant that, add a "ok=0" before the eval line, so it's "reset" each time. Or simply rename "ok" into "error" ? it seems that's what was meant here. So in the end: eval "$@" 2>> "$LOG" && error=0 || error=1 – Olivier Dulac Dec 27 '13 at 14:50
@OlivierDulac, in the version of this I use, I have an ok variable that will stop the script if any command fails. Since that was not relevant here, I removed it but forgot to delete the || ok=1. Thanks, fixed now. – terdon Dec 27 '13 at 14:53

Use a sub-shell, i.e:

( set -x; cmd1; cmd2 )

For example:

( set -x; echo "hi there" )


+ echo 'hi there'
hi there
share|improve this answer

I've seen methods used similar to @terdon's. It's the beginnings of what higher level programming languages call loggers, and offer as full blown libraries, such as log4J (Java), log4Perl (Perl) etc.

You can get something similar using set -x in Bash as you've mentioned but you can use it to turn up the debugging just a subset of commands by wrapping blocks of code with them like so.

$ set -x; cmd1; cmd2; set +x


Here's a one liner pattern you can use.

$ set -x; echo  "hi" ;set +x
+ echo hi
+ set +x

You can wrap them like this for multiple commands in a script.

set -x
set +x



Most people are oblivious but Bash also has a log4* as well, Log4Bash. If you have more modest needs this might be worth the time to set it up.

log4bash is an attempt to have better logging for Bash scripts (i.e. make logging in Bash suck less).


Here are some examples of using log4bash.

#!/usr/bin/env bash
source log4bash.sh

log "This is regular log message... log and log_info do the same thing";

log_warning "Luke ... you turned off your targeting computer";
log_info "I have you now!";
log_success "You're all clear kid, now let's blow this thing and go home.";
log_error "One thing's for sure, we're all gonna be a lot thinner.";

# If you have figlet installed -- you'll see some big letters on the screen!
log_captains "What was in the captain's toilet?";

# If you have the "say" command (e.g. on a Mac)
log_speak "Resistance is futile";


If you want what I would classify as more of the full power of a log4* framework then I'd give Log4sh a try.


log4sh was originally developed to solve a logging problem I had in some of the production environments I have worked in where I either had too much logging, or not enough. Cron jobs in particular caused me the most headaches with their constant and annoying emails telling me that everything worked, or that nothing worked but not a detailed reason why. I now use log4sh in environments where logging from shell scripts is critical, but where I need more than just a simple "Hello, fix me!" type of logging message. If you like what you see, or have any suggestions on improvements, please feel free to drop me an email. If there is enough interest in the project, I will develop it further.

log4sh has been developed under the Bourne Again Shell (/bin/bash) on Linux, but great care has been taken to make sure it works under the default Bourne Shell of Solaris (/bin/sh) as this happens to be the primary production platform used by myself.

Log4sh supports several shells, not just Bash.

  • Bourne Shell (sh)
  • BASH - GNU Bourne Again SHell (bash)
  • DASH (dash)
  • Korn Shell (ksh)
  • pdksh - the Public Domain Korn Shell (pdksh)

It's also been tested on several OSes, not just Linux.

  • Cygwin (under Windows)
  • FreeBSD (user supported)
  • Linux (Gentoo, RedHat, Ubuntu)
  • Mac OS X
  • Solaris 8, 9, 10

Using a log4* framework will take some time to learn but it is worth it if you have more demanding needs from your logging. Log4sh makes use of a configuration file where you can define appenders and control the formatting for the output that will appear.


#! /bin/sh
# log4sh example: Hello, world

# load log4sh (disabling properties file warning) and clear the default
# configuration
LOG4SH_CONFIGURATION='none' . ./log4sh

# set the global logging level to INFO
logger_setLevel INFO

# add and configure a FileAppender that outputs to STDERR, and activate the
# configuration
logger_addAppender stderr
appender_setType stderr FileAppender
appender_file_setFile stderr STDERR
appender_activateOptions stderr

# say Hello to the world
logger_info 'Hello, world'

Now when I run it:

$ ./log4sh.bash 
INFO - Hello, world

NOTE: The above configures the appender as part of the code. If you like this can be extracted out into its own file, log4sh.properties etc.

Consult the excellent documentation for Log4sh if you need further details.

share|improve this answer
Thanks for the added notes, but the main problem I have with that is all the set commands i'd need to introduce, alternating around comments etc, so just having a function at the top of my script, with a single character function call prepended to all "important" lines in the script seemed neater to me for now. (single character because the function has a single character name) – Trindaz Dec 27 '13 at 15:06
@Trindaz - sorry I hadn't finished my answer yet. Take a look at log4bash if you have more needs that the function that terdon gave. – slm Dec 27 '13 at 15:07
@Trindaz - I do something similar from time to time, the other approach I've used is to wrap echo in my own function, mecho, and then pass a switch into the program called -v for verbose when I want to turn things on off. I also can control it with a 2nd argument switch which specifies the function's name, so I have 2 axis on which to control the logging. This is often the gateway to wanting log4bash though. – slm Dec 27 '13 at 15:09
@Trindaz set -x prints commands as they are executed. It doesn't print comments. set -x is practical for debugging (unlike set -v which isn't very useful). Zsh has better output for set -x than bash, for example it shows which function is currently being executed and the source line number. – Gilles Dec 27 '13 at 15:39
Thanks @Gilles that's true, but it did give me the if expression expansions, which was overkill in this case – Trindaz Dec 27 '13 at 15:40

I wanted to print the command in color before running it. Here is a function to do that:

xc() {
  awk '
    x = "\47"
    printf "\33[36m"
    while (++i < ARGC) {
      y = split(ARGV[i], z, x)
      for (j in z) {
        printf z[j] ~ /[^[:alnum:]%+,./:=@_-]/ ? x z[j] x : z[j]
        if (j < y) printf "\\" x
      printf i == ARGC - 1 ? "\33[m\n" : FS
  ' "$@"


share|improve this answer

This is a revised version of Steven Penny's neat function. It prints its arguments in color and quotes them as needed. Use it to selectively echo the commands you want to trace. Since quotes are output, you can copy printed lines and paste them to the terminal for immediate re-execution while you are debugging a script. Read the first comment to know what I changed and why.

xc() # $@-args
  cecho "$@"
cecho() # $@-args
  awk '
    x = "\047"
    printf "\033[36m"
    while (++i < ARGC) {
      if (! (y = split(ARGV[i], z, x))) {
        printf (x x)
      } else {
        for (j = 1; j <= y; j++) {
          printf "%s", z[j] ~ /[^[:alnum:]%+,./:=@_-]/ ? (x z[j] x) : z[j]
          if (j < y) printf "\\" x
      printf i == ARGC - 1 ? "\033[m\n" : FS
  ' "$@"

Example usage with output:

# xc echo "a b" "c'd" "'" '"' "fg" '' " " "" \# this line prints in green

echo 'a b' c\'d \' '"' fg '' ' ' '' '#' this line prints in green

a b c'd ' " fg # this line prints in green

The second line above prints in green and can be copy-pasted to reproduce the third line.

share|improve this answer
Why I changed it @Steven-Penny 's original xc is clever and he deserves all credits for it. I noticed some problems with it, but I couldn't comment his post because I don't have enough reputation. So I made a suggested edit to his post but the reviewers rejected it. I post it here as an answer though I'd would have preferred to be able to edit Steve Penny's own answer. – stepse May 5 at 12:18
What I changed Fixed: printing null strings - they weren't printed. Fixed: printing strings that include % - they caused awk syntax errors. Replaced for (j in ...) with for (j = 0, ...) because the former doesn't guarantee the order of array traversal (it's awk implementation dependent). Added 0 to octal numbers for portability. – stepse May 5 at 12:18

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.