[bash] How to echo shell commands as they are executed

In a shell script, how do I echo all shell commands called and expand any variable names?

For example, given the following line:

ls $DIRNAME

I would like the script to run the command and display the following

ls /full/path/to/some/dir

The purpose is to save a log of all shell commands called and their arguments. Is there perhaps a better way of generating such a log?

This question is related to bash shell sh posix trace

The answer is


shuckc's answer for echoing select lines has a few downsides: you end up with the following set +x command being echoed as well, and you lose the ability to test the exit code with $? since it gets overwritten by the set +x.

Another option is to run the command in a subshell:

echo "getting URL..."
( set -x ; curl -s --fail $URL -o $OUTFILE )

if [ $? -eq 0 ] ; then
    echo "curl failed"
    exit 1
fi

which will give you output like:

getting URL...
+ curl -s --fail http://example.com/missing -o /tmp/example
curl failed

This does incur the overhead of creating a new subshell for the command, though.


To allow for compound commands to be echoed, I use eval plus Soth's exe function to echo and run the command. This is useful for piped commands that would otherwise only show none or just the initial part of the piped command.

Without eval:

exe() { echo "\$ $@" ; "$@" ; }
exe ls -F | grep *.txt

Outputs:

$
file.txt

With eval:

exe() { echo "\$ $@" ; "$@" ; }
exe eval 'ls -F | grep *.txt'

Which outputs

$ exe eval 'ls -F | grep *.txt'
file.txt

Another option is to put "-x" at the top of your script instead of on the command line:

$ cat ./server
#!/bin/bash -x
ssh user@server

$ ./server
+ ssh user@server
user@server's password: ^C
$

For zsh, echo

setopt VERBOSE

And for debugging,

setopt XTRACE

Combining all the answers I found this to be the best

exe(){
    set -x
    "$@"
    { set +x; } 2>/dev/null
}
# example
exe go generate ./...

{ set +x; } 2>/dev/null from https://stackoverflow.com/a/19226038/8608146


I use a function to echo and run the command:

#!/bin/bash
# Function to display commands
exe() { echo "\$ $@" ; "$@" ; }

exe echo hello world

Which outputs

$ echo hello world
hello world

For more complicated commands pipes, etc., you can use eval:

#!/bin/bash
# Function to display commands
exe() { echo "\$ ${@/eval/}" ; "$@" ; }

exe eval "echo 'Hello, World!' | cut -d ' ' -f1"

Which outputs

$  echo 'Hello, World!' | cut -d ' ' -f1
Hello

You can execute a Bash script in debug mode with the -x option.

This will echo all the commands.

bash -x example_script.sh

# Console output
+ cd /home/user
+ mv text.txt mytext.txt

You can also save the -x option in the script. Just specify the -x option in the shebang.

######## example_script.sh ###################
#!/bin/bash -x

cd /home/user
mv text.txt mytext.txt

##############################################

./example_script.sh

# Console output
+ cd /home/user
+ mv text.txt mytext.txt

set -x will give you what you want.

Here is an example shell script to demonstrate:

#!/bin/bash
set -x #echo on

ls $PWD

This expands all variables and prints the full commands before output of the command.

Output:

+ ls /home/user/
file1.txt file2.txt

$ cat exampleScript.sh
#!/bin/bash
name="karthik";
echo $name;

bash -x exampleScript.sh

Output is as follows:

enter image description here


You can also toggle this for select lines in your script by wrapping them in set -x and set +x, for example,

#!/bin/bash
...
if [[ ! -e $OUT_FILE ]];
then
   echo "grabbing $URL"
   set -x
   curl --fail --noproxy $SERV -s -S $URL -o $OUT_FILE
   set +x
fi

Type "bash -x" on the command line before the name of the Bash script. For instance, to execute foo.sh, type:

bash -x foo.sh

For csh and tcsh, you can set verbose or set echo (or you can even set both, but it may result in some duplication most of the time).

The verbose option prints pretty much the exact shell expression that you type.

The echo option is more indicative of what will be executed through spawning.


http://www.tcsh.org/tcsh.html/Special_shell_variables.html#verbose

http://www.tcsh.org/tcsh.html/Special_shell_variables.html#echo


Special shell variables

verbose If set, causes the words of each command to be printed, after history substitution (if any). Set by the -v command line option.

echo If set, each command with its arguments is echoed just before it is executed. For non-builtin commands all expansions occur before echoing. Builtin commands are echoed before command and filename substitution, because these substitutions are then done selectively. Set by the -x command line option.


According to TLDP's Bash Guide for Beginners: Chapter 2. Writing and debugging scripts:

2.3.1. Debugging on the entire script

$ bash -x script1.sh

...

There is now a full-fledged debugger for Bash, available at SourceForge. These debugging features are available in most modern versions of Bash, starting from 3.x.

2.3.2. Debugging on part(s) of the script

set -x            # Activate debugging from here
w
set +x            # Stop debugging from here

...

Table 2-1. Overview of set debugging options

    Short  | Long notation | Result
    -------+---------------+--------------------------------------------------------------
    set -f | set -o noglob | Disable file name generation using metacharacters (globbing).
    set -v | set -o verbose| Prints shell input lines as they are read.
    set -x | set -o xtrace | Print command traces before executing command.

...

Alternatively, these modes can be specified in the script itself, by adding the desired options to the first line shell declaration. Options can be combined, as is usually the case with UNIX commands:

#!/bin/bash -xv

Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to shell

Comparing a variable with a string python not working when redirecting from bash script Get first line of a shell command's output How to run shell script file using nodejs? Run bash command on jenkins pipeline Way to create multiline comments in Bash? How to do multiline shell script in Ansible How to check if a file exists in a shell script How to check if an environment variable exists and get its value? Curl to return http status code along with the response docker entrypoint running bash script gets "permission denied"

Examples related to sh

How to run a cron job inside a docker container? I just assigned a variable, but echo $variable shows something else How to run .sh on Windows Command Prompt? Shell Script: How to write a string to file and to stdout on console? How to cat <<EOF >> a file containing code? Assigning the output of a command to a variable What does set -e mean in a bash script? Get specific line from text file using just shell script Printing PDFs from Windows Command Line Ubuntu says "bash: ./program Permission denied"

Examples related to posix

How to make parent wait for all child processes to finish? Kill all processes for a given user What is the proper #include for the function 'sleep()'? What is /dev/null 2>&1? How to kill all processes with a given partial name? What can lead to "IOError: [Errno 9] Bad file descriptor" during os.system()? How to use nanosleep() in C? What are `tim.tv_sec` and `tim.tv_nsec`? Converting year and month ("yyyy-mm" format) to a date? CRON job to run on the last day of the month Checking if a file is a directory or just a file

Examples related to trace

When tracing out variables in the console, How to create a new line? How can I debug git/git-shell related problems? How to echo shell commands as they are executed Logging best practices How can I add (simple) tracing in C#?