Writing Your First Shell Script

#shell#shell-scripting#zsh#environment variables#$PATH#$HOME

What are shell scripts? What are example use cases for them?

Basically, shell scripts are small programs you can create that can execute any number of commands in the order you wrote them in.

As I mentioned, I wanted to avoid having to run the same 4 commands over and over again. For some context, I recently launched this blog on my personal website, which involved taking my code and reworking it a tad to work with Jigsaw, a static-site generator (SSG) based on PHP and Laravel functionality. One of its features is that you can have seaparete "builds" for your staging/development and live production code (the code that is out there in the world for others to see). In order to use this functionality, there are 4 steps to get from "just written code" all the way to the live production website:

  • Build the files for the production environment
  • Add those files via git add
  • Commit those files
  • Push those files to your production code base

While this list isn't the most onerous and lengthy list imaginable, I was having to do each of these one by one every time I made any sort of change to my site's code that I wanted to have appear in the live production environment. I was saving time by using the up arrow to traverse the history of commands already inputed, but I still wanted to save keystrokes and have it all happen in one go.

Enter shell scripting! While I'd made one in the past, I never got it to work. This time around, I stuck with the research until I was able to solve my problem.

Let's go ahead and write a new one to show the basic functionality.

Setting Up a Home For Your Scripts

Ok, this part was tricky. There wasn't one right answer from what I could tell. Some people said one way was best for "portability", others said another way was best depending on if it was single or multiple-user access. We'll go ahead with the single-user route.

In my search, the locations I came across were:

  • ~/.local/bin
  • $HOME/bin
  • /usr/local/bin

I imagine there are more, but these are the ones I came across. Let's define some terms that I wasn't sure about.

The '~', known as a tilde, is a "shortcut" that is used to denote the home directory of the user. So, for a user named myuser01 it would be Users/myuser01/.... When you first open up your terminal, by default you are located within your home directory:

Screenshot of command-line prompt Note: yours might look a bit different depending on the shell emulator you're using

The one I ended up going with was the second one: $HOME/bin because it most closely resembled the file/document organization I'm most used to and didn't involve any hidden files or folders. And as a newer developer, I'm learning the importance of KISS as a first pass through (Keep It Simple Silly), and then going deeper when/if the need/interest arises.

What exactly is $HOME? Its an environment variable -- basically a system variable used to hold any sort of configuration that can be used by a program, in this case the shell emulator -- and it refers to your machine's home directory. So, if we run echo $HOME in our terminal, it prints /Users/myuser01 to the screen.

To get to your home directory you can just open up a new terminal window and you should automatically be placed there like we saw earlier. Alternatively, you can also type in the cd command (change directory) by itself from anywhere in your file system it will take you to your home directory.

Alright, so now that we're there we'll need to create a bin folder (short for binaries) if one does not already exist. You can check if it already exists by running the command ls (which lists the contents of the folder you're currently in except for any hidden files) in the terminal. So, if it isn't already there run mkdir bin, mkdir meaning make directory.

Once you have your bin folder in your $HOME directory, we can move on to creating the script.

Creating The Script

Now that we're ready to actually build the script, let's go ahead and create the file where we'll write out the commmand to print "Hello, World!" to the screen.

Go ahead and run touch sayHelloWorld.sh (make sure you're in your bin folder -- you can run pwd to see your present working directory and it should look like /Users/myuser01/bin). We're using the extension .sh, though from my research there are other file extensions we could go with but they don't matter all that much for our purposes here.

Once created, you can either open the file using the built-in Vim text editor (which is my go to though there are some basic text traversals commands you'd need to first learn), or, you can use Nano, another built-in text editor that is easier to use, or any other text-editor of your choice.

When you have it open, the first thing you want to write in the file is what's known as a shebang #! -- why it's called that isn't important but I did read that it could be because the octothorp # is known as a sharp in musical notation, and the exclamation point ! is sometimes referred to as a "bang", so it's all short for sharp-bang. The important part is that we need it to specify which program should be used to actually run the script. For this script we'll go ahead and use bash, so our first line in the script will be:

#!/bin/bash

Next, we'll go ahead and write out each of the command that we want to have ran when we execute the script:

#!/bin/bash

echo "Hello, World!"

And that's it! We now have our shell script written out. However, before we can actually execute it, there are a few more steps to go through.

Running Your Script

In order to actually run our script, we'll need to establish the permissions to do so:

chmod +x sayHelloWorld.sh

Let's break that down. First, we have chmod, which stands for "change mode", which is a command used to change the access permissions to a given resource. The + refers to adding a permission, and the x refers to the "execute" permission specifically. So, this command reads as "change the access mode for sayHelloWorld.sh by giving the current user the ability to execute it."

Now we're ready to run it! In it's current state, based on what we've done, you can go ahead and run the script like so:

/Users/myuser01/bin/sayHelloWorld.sh

You should then see Hello, World! printed out in your terminal window!

Notice, however, that we have to specify the script's location in our system everytime we want to run it. In order to be able to run it by just calling sayHelloWorld.sh, we need to edit our $PATH, which is another environment variable which holds a colon-separated list of directories that your terminal will look through when you try executing a command, amongst other things. If the location of a command (program) is in your $PATH, you will not need to specify its location everytime you want to use it. So, let's go ahead and take care of that.

Well go ahead and open up a special file that allows us to customize our terminal's behavior, including setting environment variables (which is what we're going for here). Depending on what your machine's default is, you'll open up either ~/.zshrc or ~/.bashrc or even ~/.bash_profile, but the technicalities of which and why are out of the scope of this article. As of macOS 10.15 Catalina, the default shell is now zsh. So, we'll go ahead and open ~/.zshrc.

In it, you should be able to see #export PATH=$HOME/bin:/usr/local/bin:$PATH. If this is your first time opening up this file, it should appear as is, and the # means that the line is commented out and not read by your interpreter. You simply remove the # and then you should be all set to go!

Just to clarify, it's $HOME/bin that is making all of this possible, as that is where your script lives! Remember from earlier, where we talked about /Users/myuser01 being $HOME? There you go!

And lastly, whenever we make any changes to ~/.zshrc, we need to "refresh" our terminal session. You can do this by typing in the command source ~/.zshrc. Once done, you should now be able to simply type in the name of the script and it will run!

Congratulations, you've written your first shell script!

Bonus: Passing Arguments Into Your Scripts

As an extra piece, I'd like to show you how you can beef up your script by passing arguments in.

The way it works is that in your script you can use $ and a number starting from 1, so $1, $2, $3, and so on and so forth, as placeholders for any arguments you may include when you run your file. Check it out:

#!/bin/bash

echo "Hello, $1"

Now, when you execute the script you can include an argument after the script name, like so:

testScript.sh Alyssa

You'll then see Hello, Alyssa printed to your terminal! You can have any number of arguments, and the order in which you include them after the script name is the order they'll be applied in your script. So, first argument jumps into $1, then the next one would take the place of $2, and so on.

This is a very simple example, but I'm sure you can imagine how intricate and powerful a script could become when you make use of arguments! And that's not all. You can use loops, conditionals, and much more! I've only scratched the surface myself. As I mentioned at the beginning, I had a need for a script to run 4 individual commands with a single call, I didn't know how to go about it, and so I learned. Working through this process has definintely given me plenty of ideas for other things I could try scripting to save myself time and keystrokes.

I sincrely hope this article has been helpful for you. Thank you for reading.

TR


References