Basics for running your own scripts

Computec Media GmbH

Computec Media GmbH

Dot Before Slash

Many Linux beginners stumble over the fact that you need to precede any calls to your own scripts or programs with a ./ combination. What's up with these dot-slashes?

The ./ character duo – a dot-slash – should be familiar to most experienced Linux users as a command prefix. However, Linux beginners may have a tough time getting used to it when running a program or script from the current shell directory. What's behind this cryptic practice, and why is it always necessary? I will shed a little light on the topic in this article.

Before I dive into the matter, I'll step back and talk a little about the Linux shell and the $PATH environment variable. A shell is essentially a simple program providing a text interface for entering commands that the shell ultimately executes. Apart from the widespread Bash Bourne shell, there are other, simpler ones such as the Zsh shell. However, I will be focusing on the Bash shell exclusively.

When calling a command, the shell executes either a built-in one or starts an executable program or script that's located somewhere on your hard drive. The first group includes command such as cd , echo , kill , or alias . These are part of the shell itself and don't require an independent program file. In contrast, executable programs such as mv and less or larger applications such as gedit or firefox are mostly included as program files in the /usr/bin/ directory.

When you enter a shell command, the shell first searches in the location set by the $PATH environmental variable for the executable program (Listing 1). The sequence of directories determined therein is what matters. If the shell is looking for the foo file and finds it in the /usr/local/bin directory set shown in Listing 1, it executes the program as /usr/local/bin/foo – even if /usr/bin/ also contained a foo file. The shell parses the paths in order and uses the first one listed – an important thing to keep in mind.

Listing 1

Shell Search

$ echo $PATH
/usr/local/sbin:/usr/local/bin:\
  /usr/sbin:/usr/bin:/sbin:/bin:/usr/games

Executing Scripts

Now I'll return to ./ and talk somewhat about executing programs and scripts. Suppose you want to call a self-written script or a downloaded binary program. You call the script sample.sh and put it in the tmp/ directory of your home directory. Without much fuss, you can use cd to move to the directory, make the script executable, and use ls to see that the executable bit is truly set. You call up the script, and, voilà, nothing happens. The shell returns a command not found error (Listing 2).

Listing 2

Command Not Found

tux@computer:~$ cd tmp
tux@computer:~/tmp$ chmod +x sample.sh
tux@computer:~/tmp$ ls -al sample.sh
-rwxr-xr-x 1 tux tux 61 2010-02-03 15:28 sample.sh
tux@computer:~/tmp$ cat /sample.sh
#!/bin/bash
echo "This is only a sample script."
tux@computer:~/tmp$ sample.sh
sample.sh: command not found

But, the script is in the right location and has the proper file privileges. Why doesn't the shell want to run it? The answer is in the previously described $PATH variable. Take another look at it in Listing 1.

The shell discovers that nowhere in the list of directories set in the $PATH variable is there an example.sh file. Searching its own internal commands fails as well. The shell really should look into the current directory, but it doesn't. Instead, it suspends the program search immediately after checking the paths and its internal commands.

Therefore, the sample.sh script should really be in one of the directories set in $PATH or else you need to prefix the call with the full path to the file. Listing 3 provides three path options you could use.

Listing 3

Path Options

tux@computer:~/tmp$ /home/tux/tmp/sample.sh
This is only a sample script.
tux@computer:~/tmp$ $HOME/tmp/sample.sh
This is only a sample script.
tux@computer:~/tmp$ ~/tmp/sample.sh
This is only a sample script.

As you can see, there are a few ways to handle it, such as by preceding the path with a tilde (~ ), which is shorthand for the user's home directory. The shortest way to describe the current directory is with a simple dot (two dots go to the parent directory, something you will already be familiar with from using the cd .. command). Therefore, moving to the example.sh file's location and using a dot-slash (./ ) before its name is enough to run it (Listing 4).

Listing 4

Using ./

tux@computer:~$ cd tmp
tux@computer:~/tmp$ ./sample.sh
This is only a sample script.

Security Before Convenience

The question might be, why isn't the current directory in the path? It works that way at the Microsoft prompt. It would save a lot of typing and confusion especially for Linux beginners. The reason is simple: It's all about security.

Suppose someone gives you a USB stick containing a whole lot of useful files you wish to copy using shell commands. That's not hard. You simply use cd to move to the directory on the USB stick and use mv to move the files to your hard drive (Listing 5).

Listing 5

Using mv

tux@computer:~$ cd /media/usb-stick
tux@computer:/media/usb-stick$ mv sample.* /where/ever/desired

What if the USB stick also had a mv file with the content in Listing 6? If the shell didn't ignore the current directory, the malicious code in mv would take effect instead of the desired /usr/bin/mv command. Instead of saving the files to the hard drive, your entire home directory would disappear without your even knowing it – until it's too late.

Listing 6

Malicious mv

#!/bin/bash
echo "mv me and I'll delete you!"
rm -rf $HOME

Slippery Path

The shell thus makes sure that the same, well-defined commands get executed each time, unless you explicitly request something else with the dot-slash combo. If you're still not a fan of this method, you can always add the current directory in the $PATH variable. Although I will tell you how to do exactly that in the next paragraphs, you do it at your own risk. Ultimately, getting used to using dot-slash is a way better.

To include the current directory into your $PATH , depending on the Linux distribution and shell version, you must tweak the variable with entries in the ~/.profile or ~/.bashrc file in your home directory. Add the export PATH="$PATH:." command at the end of the file (Listing 7, first line) and start a new terminal window. The new path will be like the one on the last line of Listing 7.

Listing 7

New Path

$ echo 'export PATH="$PATH:.' >> ~/.profile
[... new terminal ...]
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:\
  /usr/bin:/sbin:/bin:/usr/games:.

As you can see, the end of the path has a dot for the current directory, so you can omit the dot-slash (Listing 8). With the reference to the current directory at the end of $PATH (remember that Bash parses $PATH in order), you minimize the risk of running into the malicious behavior described earlier.

Listing 8

Reference the Current Directory

tux@computer:~$ cd tmp
tux@computer:~/tmp$ sample.sh
This is only a sample script.

~/bin and /usr/local/bin

However, there's also a so-called canonical way of running scripts without adding path information. The ~/bin/ (i.e., a bin directory in your home directory) and /usr/local/bin/ directories provide meaningful alternatives as used by package management for installing applications. Most distributions add these directories to $PATH automatically so that scripts and programs stored in them are executable without needing a path. You can also create a symbolic link to them from installed applications in /opt .

If the ~/bin/ directory is missing in your home directory, then simply create it, and be sure to add an entry for it in ~/.profile (Listing 9), which the system runs when starting the shell. A newly opened terminal window will then have $HOME/bin/ as the first entry in the path, and scripts in them will be located automatically.

Listing 9

Add ~/bin/

# In case a private bin directory exists
# include it in $PATH
if [ -d "$HOME/bin" ] ; then
  PATH="$HOME/bin:$PATH"
fi

If you want all your system users to run your scripts or small programs, copy them with root privileges to /usr/local/bin/ or symlink them (ln -s <source> <destination> ) there. This directory is always in $PATH and the package manager never puts data or files in it, so that they don't run afoul of your script. The directory remains a safe haven.