Running shell scripts from Bash

How to save programs as files and execute them with bash

Running commands from a script

As we get into more complicated computing tasks, the commands will be too numerous and verbose to type in manually and execute line-by-line – remember that once you've hit Enter, you can't go backwards at the interactive prompt.

So this is where we learn a new technique: writing shell scripts. Instead of typing a series of commands into the command-line interpreter, we type them into a text file. We then save that text file. And then we tell bash to execute the commands that are in that text file. which we refer to as a script.

Pretend that the file my_script.sh contains the following commands:

echo "Hello there"
echo "My name is $(whoami)"
echo "Nice to meet you"

Executing that script involves invoking the bash program at the command-line and giving it the name of the script, my_script.sh

user@host:~$ bash my_script.sh
Hello there
My name is dun
Nice to meet you

Using the nano text-editor to create a text file

A brief segue here: We can (and sometimes do) create quickie text files from the command-line. To create the aforementioned my_script.sh, for example:

user@host:~$ echo 'echo "Hello there"' > my_script.sh
user@host:~$ echo 'echo "My name is $(whoami)"' >> my_script.sh
user@host:~$ echo 'echo "Nice to meet you"' >> my_script.sh

Did that look ugly and awkward? That's because it is. And it's prone to all the problems you have before, in which one typo on a previous line will cause you to start all over again.

Opening nano

To open nano, simply type in nano at the prompt:

user@host:~$ nano

However, I like passing in the name of the file that I intend to create. By default, nano will choose that argument as the filename to save to. If that filename already exists, nano reopens the file for editing.

user@host:~$ nano myfile.txt

Either way, nano is a servicable text editor. Sure, it'll feel primitive compared to anything you've used since, say the Apple ][…but it's significantly more comfortable and forgiving than the prompt. You can move up and down previously-entered lines of text, for example.

img

When you're done entering in commands (or whatever you want, it is just a text editor, after all), hit Ctrl-X to exit. nano will then ask you, in the most confusing way possible, if you want to save your changes. Type y, which should then prompt nano to specify the filename. Hit enter, and you'll be back at the prompt.

Here's an animated GIF of me creating a script by entering nano, writing out the commands, saving it, and then executing it from the prompt:

img

Making reusable scripts

If my_script.sh seems lame, that's because it is. The commands it runs are simple. And every time we run it, it does the same thing.

What if we made a more friendly version of it? We'll call it, my_friendly_script.sh. And this is how it should be used:

user@host:~$ bash my_friendly_script.sh Pat
Hello there, Pat
My name is dun
Nice to meet you, Pat

See how the script adapted to the presence of Pat? Or to any other name we give it? Having our script do different things based on command-line arguments is the first step to making it more reusable.

Numbered parameters for arguments

So, when editing the code that's in my_friendly_script.sh, how do we refer to what was passed as a command-line argument, i.e. Pat?

By convention, special variables, or parameters, are reserved for command-line arguments: 1 is for the first argument, 2 is for the second, and so forth.

So to print Pat to standard output, we refer to $1:

echo "Hello there, $1"
echo "My name is $(whoami)"
echo "Nice to meet you, $1"

Read the tutorials, How to write a basic shell script and How to write reusable shell scripts

How to execute scripts in the background

Read the tutorial, How to run a process in the background

How to make executable scripts

The immediate benefit of turning one of our plain old shell scripts into an executable shell script is this difference in running the script:

Instead of this syntax:

user@host:~$ bash my_script.sh

We can simply do this:

user@host:~$ ./my_script.sh

Giving permission for a script to execute

In fact, let's try the above command for executing my_script.sh: the prepending of the ./ is how we tell Bash, run this my_script.sh (and not another executable my_script.sh somewhere else in the file system):

user@host:~$ ./my_script.sh
-bash: ./my_script.sh: Permission denied

OK, bash didn't like that. What does permission mean? As you've seen yourself, the my_script.sh file is simply a text file, with no real powers over any other collection of plaintext, until we pass it as an argument to bash, in which case, the bash program executes the text as commands.

Contrast that with echo, which is more than just a text file, but more relevant to our topic, has permission to__ execute. As soon as we type echo and then hit Enter, a _program actually runs. There's no need to tell bash, "Hey, can you read this word of text I wrote, and then execute it as a program?"

We want that kind of authority, that permission to execute, for my_script.sh.

And this is how we do it:

user@host:~$ chmod a+x my_script.sh

And that's it. Read more about the chmod command on your own, if you'd like. But we can now try to execute my_script.sh without explicitly invoking bash:

user@host:~$ ./my_script.sh
Hello there
My name is dtown
Nice to meet you

We can even rename my_script.sh to just my_script to give it more of an elite look, and because bash honestly doesn't care what you name your files or what extensions you give it:

user@host:~$ mv my_script.sh my_script
user@host:~$ ./my_script
Hello there
My name is dtown
Nice to meet you

What about that ./ that we have to include? We can remove that to, by moving my_script into a directory that's part of the bash environment's executable PATH. You can read more about that here.

The shebang

Because we're executing my_script in the Bash shell, by default, bash is implicitly invoked to interpret the commands inside my_script.

However, later on, when we create scripts in the Python language to execute from Bash, we need to specify in the very first line of the script: we want to use Python to run this script. So when we run that Python script, the Bash interpreter reads that line and knows, "This script is meant to be run by the Python interpreter, so I'll pass it along"

That first line that says, "Hey, use this program to interpret what's in this script", that's called the shebang.

And for scripts that are meant to be run by bash, it looks like this:

#!/usr/bin/env bash

Again, it's optional to add to our my_script. But it's a good habit to get into.

Software Carpentry has a great guide on shell scripts.

Check out the class textbook for more details on creating reusable and executable scripts.