16

Is there a way for running a shell script with echoing the commands but w/o actually executing them?

Let's say I have script removing a file whose name is stored in a variable:

#!/bin/bash
set -v
FN="filename"
rm -f ${FN}

Adding set -v will echo the following commands before execution:

$ ./scr.sh
FN="filename"
rm -f ${FN}

Now, I want to see the flow of this script w/o actually removing the file. IOW, I want to prevent any effect on the external environment and filesystem. Is this possible?

I could wrap all commands with an echo command, but it is tiring for a long script.

1
  • I'm not a bash-scripting expert, but if I would write this with another programming language, I would store all commands in an array. this way it should be easy to loop through and either execute the commands or print the out. Mar 16, 2014 at 20:34

7 Answers 7

9

There's no way to step through a script to see how it would execute without actually doing that. In your example, there are no if statements or loops. But in real scripts, there are often lots of conditional statements. Which branch will get taken will often depend on what happened when the shell ran the previous command. If it doesn't run the command, there's no way for the shell to know what output it would have generated or what the return code would have been, which the next conditional branch or assignment statement might depend on.

If the point is that you'd like to scrutinize a script and know what it does before you run it, that's not a bad idea. But realistically, about the best way to do that is by just browsing the file with less or vi or something similar.

Added

If you're developing a script and you're wanting to step through it the first time, testing the logic but not actually doing any damage if you've got a bug, the solution I'm often able to use is to modify only the statements that could do any damage by pasting an echo onto the front.

This often works in typical real-life scripts because (a) generating the lists of items you'll iterate over or the value to which you'll set a variable can often be generated without changing the filesystem and (b) it's usually sufficient to assume that if you did run the command, it would succeed.

2
  • Thanks. It would surprise me if there is a way, exactly due to the reason you mentioned, but I decided to give it a try anyway. The reason for this is that I am developing a script that moves files across many machines in a network and performs some actions on the remote hosts. I'd like to see a flow of the script before actually committing the changes to the filesystem(s). It would be just as tedious to go over all the remote machines to correct file errors...
    – ysap
    Jan 16, 2013 at 19:57
  • 1
    An addition: To step through the script, you can start the script with trap read debug which will do a read after every executed line, and you can then step through it slowly with enter (this is useful if you have a really long script and want to test it for the first time).
    – netigger
    Apr 14, 2015 at 8:55
9

I think you are going to have to echo - however if you put the command in a variable you can turn it on and off. Also, the command can be a function and as sophisticated as you want.

#!/bin/bash
echo 'Debug'
  D=echo
  :>| testy
  ls -l testy
  $D rm -f testy
  ls -l testy
echo $'\n\nLive'
  D=
  :>| testy
  ls -l testy
  $D rm -f testy
  ls -l testy

==================================================

!$ > ./conditional.sh
Debug
-rw-rw-r-- 1 nobody nobody 0 2013-01-18 15:28 testy
rm -f testy
-rw-rw-r-- 1 nobody nobody 0 2013-01-18 15:28 testy

Live
-rw-rw-r-- 1 nobody nobody 0 2013-01-18 15:28 testy
ls: cannot access testy: No such file or directory
1
  • 1
    I required quotes in the echo/don't-echo commands, and then had to use D=eval in the live part. But nice answer!
    – Jasper
    Apr 7, 2014 at 7:49
5

You an trace the flow using the -x option for bash, but this would still execute the commands.

The best you could do is to just put echo or comment out the commands that have external effects like the rm. At least you wouldn't have to change every line.

2
  • Thanks. I mentioned that option in my question, but it does not really solve my problem.
    – ysap
    Jan 16, 2013 at 19:58
  • Sorry, I thought you meant echoing every line, -x would reduce this to a few commands and show you the evaluations.
    – parkydr
    Jan 16, 2013 at 20:06
4

I do not know any particular method for dry run, but we can use some precautions for understanding an unknown script.

  1. Use a chroot environment to run your debug script. It will act as a sandbox and will provide protection against deleting/modifying main system files.
  2. Use bash -n <script> for syntax check. From gnu bash manual https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html

-n Read commands but do not execute them; this may be used to check a script for syntax errors. This option is ignored by interactive shells.

  1. Read the script at least once. You can use echo statement for debug as mentioned in answer by user191016 .
    • Look out for suspicious or danger prone code.
    • e.g. related to rm, commands affecting /dev/sda etc.
  2. Always try to run scripts as normal user, avoid running unknown scripts as root.
  3. You can define alias to make command that can modify files as interactive at start of your script Example alias rm="rm -i" alias cp="cp -i" alias mv="mv -i" For stream editors like sed sed -i (-i modifies the file in place without -i it does a dry run for sed , check man page for details) You can copy the full command and run. It just before the actual command it will show the output on screen then use command to wait for user input to continue. Example sed -i <pattern> Replace it with sed <pattern> read -p "Press any key..." sed -i <pattern>

Also it is always suggested to keep backup points in your system so in case of emergency data can be restored.

4

Here's a little construct I like to use:

#!/bin/sh

verbose=false
really=true

# parse arguments
while [ $# -ge 1 ]
do
  case "$1" in
    -v) verbose=true ;;
    -n) really=false; verbose=true ;;
  esac
  shift
done

doCmd() {
  if $verbose; then echo "$@"; fi
  if $really; then "$@"; fi
}

doCmd make foo
doCmd rm -rf /tmp/installdir
doCmd mkdir /tmp/installdir
doCmd whatever
doCmd blah blah
3

Do set –n, or append n to your existing set –v command.  But since this causes the shell not to evaluate any commands, not even if or while, you might not get too good a view of the operational flow.

1
  • Thanks. This is a good start, but as you mentioned, it does not let me see the actual flow - where in my real script I have a loop over a list of remote hosts.
    – ysap
    Jan 16, 2013 at 20:01
2

If you want to prevent modifications from happening, you can run the script as a user without privileges:

sudo -u nobody ./scr.sh

In your example, this will execute FN="filename" as usual. While it technically executes the command rm -f ${FN} as well, nothing will happen if nobody doesn't have the necessary permissions to delete the file.

Of course, this will cause problems with conditional workflow. The fact that the rm command failed might affect further parts of the script.

3
  • Things we will never understand: Why root is required to run as nobody...
    – mejdev
    Aug 8, 2014 at 19:25
  • User "nobody" isn't spevial to the OS, but you can add an entry in the sudoers file that allows passwordless sudo for "nobody".
    – Dennis
    Aug 8, 2014 at 21:05
  • I dunno. Seems that the script would terminate because of some error or another before it finished. Aug 5, 2016 at 21:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .