There are a couple of things we need to keep in mind as we create shell scripts for our own use or for the use of others.
Where should I keep my scripts?
If you are writing lots of shell scripts, and especially if you want others to have access to them, you should decide on a central location for your scripts. Some system administrators create a scripts library in a /usr/local/scripts directory, or someplace similar. If you are making such a library simply for personal use you can create a directory somewhere in your home directory and place all of your scripts there.
Also, make sure that your script names are not the same as Linux system commands. If that happens whichever appears first in your PATH variable will be the one that gets executed. An easy way to solve this is to add a unique suffix at the end of your script name.
Do I have to specify the entire path every time to run my shell script?
It can be a real hassle to continually type the entire path to your script, or having to cd to the directory where it is located to run it from there. The solution is to set your PATH environment variable to include the directory where your scripts are stored.
I keep my scripts in a scripts directory within my home directory. Then I edit my PATH variable. Each user should have a PATH variable set in their .bash_profile, or some systems place it in .bashrc. If it is not in either of these locations, then you are running with the default system PATH. No problem!! It is quite easy change your PATH.
$ cd
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:
This is a typical PATH for a Linux user. To change this we open .bash_profile (I do not have this file so I edit .bashrc) with your favorite text editor and add the following line:
export PATH="$HOME/scripts:$PATH"
Save the changes, close the editor, and source whichever file you just edited
$ source .bashrc
$ echo $PATH
/home/pbmac/scripts:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:
Notice that my script library is now at the beginning of my PATH.
If you decide to create a system wide script library so that all users can run the shell scripts you write then you have to change the system wide PATH. This is usually found in either the /etc/environment file or the /etc/profile file. To make this change simply edit the file where PATH variable is define, on my system it is the /etc/environment file, and change the PATH line to include your system's script library location, for instance /usr/local/scripts:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/scripts"
Then source whichever file you just edited
$ source /etc/environment
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/scripts
Again, notice that the /usr/local/scripts has now been added to our PATH. Every user who logins in will now have this directory in their PATH.
Can I execute a shell script from another script?
One last question that is often asked, "Can I run one of my shell scripts from another script?". The answer is "Yes". It is actually quite easy, especially when you have the script location set in the PATH as we discussed above.
We simply run the shell script that we are writing, and include a line that runs the library script that we already had developed and placed in our handy dandy library. Let's take a look at an example ...
$ cat myscript
#! /usr/bin/env bash
echo "Hello from myscript"
# The following line calls another script found in my library
two_script
echo "We are done"
We also have a new script, called two_script, that resides in our library directory which is now in our PATH ...
$ cat two_script
#! /usr/bin/env bash
echo "This is two_script"
exit 1
So, if we execute myscript as it is above we will see the output:
$ ./myscript
Hello from myscript
This is two_script
We are done
We can follow the echo statements that produce the output. Notice the first echo output is from myscript, the second output is from two_script, and the last output is again, from myscript.
Now if we change myscript to execute two_script using the source command we see something different about the output ...
$ cat myscript
#! /usr/bin/env bash
echo "Hello from myscript"
# The following line calls another script found in my library
source two_script
echo "We are done"
$ ./myscript
Hello from myscript
This is two_script
Notice we are missing the "We are done" output. Why is that? Well, if we go back to what we learned about the source command - that it actually runs the shell script in the same shell as the calling script - it makes sense. If we do not spawn a separate shell, which is the case with the source command, then when two_script executes the exit statement it exits the original shell. In the first instance two_script actually spawns a separate shell to run the two_script in and it is that shell that ends when the exit command is run. So if we call either:
$ ./myscript
or use
$ bash myscript
a new shell is spawned to run the script, and when that shell exits, control is returned to the myscript. Using "dot space dot-slash" or the source command we will execute two_script in the same shell and the exit of two_script will exit out of that shell back to the command prompt.
BONUS: How to execute a shell script in debug mode
Most scripts are obviously much more complex that our simple myscript. It can be difficult at times to figure out what is going on. We do have the ability to debug scripts, as shown below.
$ bash -x ./myscript
Notice we added an '-x' argument to the command line. This '-x' tells bash to run in debug mode.
Let's add a few lines to myscript to try and get a better understanding of what this can do for us.
$ cat myscript
#! /usr/bin/env bash
number=1 + 2
echo "Number is: $number"
echo "Hello from myscript"
two_script
echo "We are done"
$ cat two_script
#! /usr/bin/env bash
echo "Uname is: " `uname`
echo "This is two_script"
exit 1
We have added two lines to myscript and one line to two_script. Now when we run the shell script we get output
$ ./myscript
./myscript: line 2: +: command not found
Number is:
Hello from myscript
Uname is: Linux
This is two_script
We are done
But what does that error message mean? If we simply run the shell script in debug mode it is easy to see
$ cat myscript
#! /usr/bin/env bash
number=1 + 2
echo "Number is: $number"
echo "Hello from myscript"
bash -x two_script
echo "We are done"
Notice we add a line to run two_script in debug mode as well, even though there is not an error in that script, this will enable debug mode. If we did not run the two_script in debug mode we would simply NOT see any debug output for that script. When we run myscript in debug mode we can easily find the error: All of the debug output is preceded by a '+' character.
$ bash -x myscript
+ number=1
+ + 2
myscript: line 2: +: command not found
+ echo 'Number is: '
Number is:
+ echo 'Hello from myscript'
Hello from myscript
+ bash -x two_script
++ uname
+ echo 'Uname is: ' Linux
Uname is: Linux
+ echo 'This is two_script'
This is two_script
+ exit 1
+ echo 'We are done'
We are done
Right away we can see that the line where we were trying to add 1+2 is failing. Notice that the line which is suppose to read "number=1+2" is broken into to separate line by the shell interpreter, so our debug output looks like:
+ number=1
+ + 2
myscript: line 2: +: command not found
The interpreter is seeing the '+' as a new command, not as an arithmetic operator. To resolve this syntax error we must edit the addition statement
number=`expr 1 + 2`
and this resolves the issue and the script runs properly.
There are other script debug utilities, but the capabilities built into the shell can go a long way towards resolving many simple bugs.