So, since we're now super awesome scripting professionals... We excel at following examples, making empty files, and toggling the executable flag on scripts, right? Well, dear reader - it's time to expand on that knowledge!

Lets start off with making our script a little fancier with some variables:

This is part three in a multi-part series. Go back and take a look at both part one and part two, if you're feeling lost or want some background on what this article is about.

Variables

Variables. Because why type the same thing over and over when you can declare it once and be done with it?

Think of a variables as temporary storage for data that you want to use somewhere in your script. A variable in bash can contain a number, a character, a string of characters, or even other variables. We can store a lot of different kinds of things in a variable. They're super useful, even on small scripts.

Let's take a look at the following example script: (variable_example01.sh)

#!/usr/bin/env bash

name="Joe"
city="Indianapolis"

echo "Hiya ${name}."
echo "How's the weather in ${city} today?"

If you run the script on the command line, the following is returned:

Hiya Joe.
How's the weather in Indianapolis today?

In the case above, we're assigning two variables - name and city.

  • name is set to Joe
  • city is set to Indianapolis
  • echo is just spitting out our predefined text with our variable assignments tossed in. (For more on echoes, see the next section)

We assign variables in our script using variable=something.

var="what now?"

Since it's really easy to make a variable that breaks our script (if we're not careful), we need to remember the following:

  • A variable in single quotes is treated as a literal string, not as a variable

  • Variables in double quotes are treated as variables

  • To return the value held in a variable, you have to provide a dollar sign

  • A variable without the dollar sign only provides the name of the variable - ie:

    myvar="This is my variable"
    echo myvar
    
    $myvar
    

    To be safe, we should always enclose our variable assignment in double quotes to avoid any odd issues that might pop up in more complicated scripting.

Best practices for variables

  • Always enclose your variables in double quotes and watch out with your spacing. Here are some examples of things that work, and some things that don't:

    # GOOD - This is how we want to do it
    myvar="This is my variable"
    
    # EH, NOT SO GOOD - This works, but you'll run into issues occasionally*
    # if you're not double quoting your variable
    myvar=This is my variable
    
    # NOPE - Don't put a space after the equal sign
    myvar= This is my variable
    
    # NOT THIS EITHER - This may work, but mind your spaces**
    myvar= "This is my variable"
    
    # DUDE, NO - Same... Watch those spaces
    myvar ="This is my variable"
    
    # WHAT ARE YOU DOING? - Yeah. Collapse those spaces!
    myvar = "This is my variable"
    

    (* If you aren't sure what might be in a variable, it's safer to quote it. A variable might be empty, contain spaces (or any whitespace really), or special characters (wildcards). Not quoting strings with spaces often leads to the shell breaking apart a single argument into many.)

    (** In bash, arguments are passed as whitespace separated words. Adding additional spaces may make bash interpret your variable assignment differently than you are expecting. It’s safer to just omit any additional spaces.)

  • Name your variables in lowercase. Historically, environmental and shell variables are uppercase, and user-defined variables are lowercase. Everyone has their own preference, but to avoid conflicts, stick to using lowercase:

    # YES - Do this.
    myvar="This is my variable"
    
    # NO - Uppercase is for system/env/shell variables
    MYVAR="This is my variable"
    
    # NOPE, GROSS - I mean, you can do this, but why?
    MyVar="This is my variable"
    
  • Enclose variables to prevent unnecessary expansion down the line:

    # YEP. Use those curly braces ALL THE TIME.
    mynewvar="${myvar} and this is my new variable"
    
    # WORKS, but not all the time
    mynewvar="$myvar and this is my new variable"
    
    # MIGHT NOT WORK? Who knows?
    mynewvar=$myvar and this is my new variable
    

Quick primer on how variables expand

#!/usr/bin/env bash

myvar="This is my variable"

echo '${myvar}'

single quotes = literal string
output: ${myvar}

#!/usr/bin/env bash

myvar="This is my variable"

echo "${myvar}"

double quotes = variable
output: This is my variable

#!/usr/bin/env bash

myvar="This is my variable"

echo myvar

no dollar sign = only the name of the variable
output: myvar

#!/usr/bin/env bash

myvar="This is my variable"

echo $myvar

dollar sign = return variable value
output: This is my variable

The above works, but is just a demonstration of how the $ works with variables. You should usually be enclosing your variable in double quotes and using curly braces { } in quoting a variable for expansion in an echo/printf command. For more on this (since I'm only scratching the surface), check out this article on linux.com.

Putting variables to work (for us)

So, it's time to dust off the script that we made (or touched, if you will) in part 2. Go ahead and open it up in your preferred editor and copy+paste in the following (replacing what we already had in there): (myscript_v01.sh)

#!/usr/bin/env bash

filepath="$HOME/Desktop/"
filename="mysecondnewfile.txt"

touch ${filepath}${filename}

Using what we've learned above about variables, we're making some small changes to our original script:

  • We've added a filepath variable for the output directory
  • We've added a filename variable for the output filename
  • We've switched our original touch command to use the variables that we've defined

So, once you run the script again (after putting these new values in), we should have a file called mysecondnewfile.txt sitting on our desktop now. If you want to change either the path or the name of the output, it's as easy as editing the variable at the top of the script. While it might not seem like much of a savings in our tiny script, it becomes extremely useful once you get into much longer/complicated scripts.

Note: $HOME/Desktop/ should work out of the box on any Mac/Linux-based environment. You may have to adjust this to your own preferred path if you're using something different.

echo

Both the echo and printf commands are extremely useful. They are THE cornerstone of making your scripts more user-friendly. They can both be styled, added to, and used to compliment other tasks in our scripts. We're going to use the echo command to relay some information to the user in our script: (myscript_v02.sh)

#!/usr/bin/env bash

filepath="$HOME/Desktop/"
filename="mysecondnewfile.txt"

touch ${filepath}${filename}

echo "I'm an echo. Enjoy your new file - now with content!"
echo "This is my test file." > "${filepath}${filename}"

So, in the example above, we're doing a few new things here.

  • echo "I'm an echo. Enjoy your new file - now with content!" simply prints the line that you specify here (in quotes) to the console, and bumps your cursor to the next line.

  • echo "This is my test file." > "${filepath}${filename}" is using the echo command to put our text (in quotes) INTO the file that we've specified in our variables. The > operator tells the script interpreter that we want to echo our text into our file, instead of showing it on the console.

Replace your existing code with the code above and re-run your script. You should see your first echo command on the console AND your test file should now contain some actual content. BAM... scripting!

Exercises in echoes

If you'd like to expand on your options when using echo a bit, you can add some/all of the following code to the bottom of your script. Try it and see what spits out on your console! (myscript_v03_echo.sh)

#!/usr/bin/env bash

filepath="$HOME/Desktop/"
filename="mysecondnewfile.txt"

echo "I'm an echo. Enjoy your new file - now with content!"
echo "This is my test file." > "${filepath}${filename}"

# To give your output a little more space, you can simply use
# an echo to print an empty line to the console
# ----------------------------------------------------------------------
echo
echo
echo

# Example 1
# regular echo command - "\n" typically defines a new line in a script
# ----------------------------------------------------------------------
echo
echo "------------------------------------------------------"
echo "I'm a line of text, you\nknow... for testing.\n"
echo "I'm a second line of text."
echo "------------------------------------------------------"
echo

# Example 2
# echo -n: Trailing newline is omitted
# ----------------------------------------------------------------------
echo
echo "------------------------------------------------------"
echo -n "I'm a line of text,\nyou know... for testing.\n"
echo -n "I'm a second line of text."
echo "------------------------------------------------------"
echo

# Example 3
# echo -E: Disable interpretation of backslash escaped characters
# ----------------------------------------------------------------------
echo
echo "------------------------------------------------------"
echo -E "I'm a line of text,\nyou know... for testing.\n"
echo -E "I'm a second line of text."
echo "------------------------------------------------------"
echo

# Example 4
# echo -e: Enable interpretation of backslash escaped characters
# ----------------------------------------------------------------------
echo
echo "------------------------------------------------------"
echo -e "I'm a line of text,\nyou know... for testing.\n"
echo -e "I'm a second line of text."
echo "------------------------------------------------------"
echo

So, to explain a bit about what we just did, we're using the same two lines here for testing on each echo flag. I have wrapped each of the lines with extra echo statements and an echo with some dashes, so it's a little more obvious what's happening with each as they're printed to your console. See the # above each block for a quick description of what's happening in each block.

Your output should look like this:

I'm an echo. Enjoy your new file - now with content!




------------------------------------------------------
I'm a line of text, you\nknow... for testing.\n
I'm a second line of text.
------------------------------------------------------


------------------------------------------------------
I'm a line of text,\nyou know... for testing.\nI'm a second line of text.------------------------------------------------------


------------------------------------------------------
I'm a line of text,\nyou know... for testing.\n
I'm a second line of text.
------------------------------------------------------


------------------------------------------------------
I'm a line of text,
you know... for testing.

I'm a second line of text.
------------------------------------------------------
  • Example 1 is your regular old echo with no flags. This will print everything in the quotes literally. Notice that the \n does nothing here and is printed along with the rest of the text.

  • Example 2 is using the -n flag. Since echo always prints to a single line and then bumps the next command down to the next line, this makes your echo commands with -n print all on the same line. In this case, our two text lines are both using -n, so the lines that follow them are all bumped up and start directly after our echo command.

  • Example 3 is using the -E flag, which disables ALL escaped characters. Any \ characters will be shown as literal text in your output. Sometimes (but not always), this is the same behavior of just using echo without any additional flags.

  • Example 4 is using the -e flag, which enables ALL escaped characters, and doesn't print them in literal text. In our example, the \n bumps the first line down when it encounters the escaped \n, and again at the end of the first line - creating an extra line after our first line.

Echoes make building readable scripts a no-brainer. You should at least add a simple echo command to everything that you write, for at least a little bit of visual feedback to the user. No one wants a script that does its thing and offers you ZERO output, right?

There are lots of ways that you can use flags with the echo command. Feel free to explore and test them out yourself!

printf

Now, what's this about printf, you say?

To compare, here's a quick example of swapping out echo with printf in our script: (myscript_v03_printf.sh)

#!/usr/bin/env bash

filepath="$HOME/Desktop/"
filename="mysecondnewfile.txt"

printf "I'm a printf statement. Enjoy your new file - now with content!"
printf "This is my test file." > "${filepath}${filename}"

# Unlike echo, printf needs an argument
# ==========================================
#
# printf arguments:
# ------------------------------------------
# %c - character
# %d - decimal (integer) number (base 10)
# %e - exponential floating-point number
# %f - floating-point number
# %i - integer (base 10)
# %o - octal number (base 8)
# %s - a string of characters
# %u - unsigned decimal (integer) number
# %x - number in hexadecimal (base 16)
# %% - print a percent sign
# \% - print a percent sign

# These don't print anything to the console
# ------------------------------------------------------------------------
printf ""
printf "\n"

# This prints an empty line to the console
# ------------------------------------------------------------------------
printf "%s\n"

# This will throw an error of 'invalid option', because printf
# is expecting an argument
# ------------------------------------------------------------------------
printf "------------------------------------------------------"


# ====================================================================== #
#   We're going to put some echoes in each examples below, just for
#   the simplicity of making a new line. It will make the prinf output
#   a little bit more obvious
# ====================================================================== #


# Example 1 - regular printf command
# Each "\n" will actually create a new line
#
# Otherwise, printf statements will continue where previous prinf left
# off, as seen on the third and fourth lines. There is no space between
# the third and fourth line outputs.
# ------------------------------------------------------------------------
echo
printf "I'm a line of text, you\nknow... for testing.\n"
printf "I'm a second line of text."
echo
printf "This is a third line of example text."
printf "And this is the fourth line, which is pretty great."
echo

# Example 2
# printf using a string argument with \n (new line)
#
# This will print all \n instances as literal inside of the quotes. The
# \n specified in the printf statement will automatically insert the new
# line at the end of the printf statement for that specific line.
# ------------------------------------------------------------------------
echo
printf '%s\n' "------------------------------------------------------"
printf '%s\n' "I'm a line of text,\nyou know... for testing.\n"
printf '%s\n' "I'm a second line of text."
printf '%s\n' "------------------------------------------------------"
echo

# Example 3
# printf (without arguments), printing a variable
#
# This will print our variable "line" before and after the lines of text
# Notice and we're using the %s (string) before our divider dashed line.
# If we don't do this, printf will try to use our dashes as an argument
# delimiter and will throw an error. We're also adding a \n to add a new
# line at the end of our variable.
# ------------------------------------------------------------------------
line="%s------------------------------------------------------\n"

echo
printf "${line}"
printf "I'm a line of text,\nyou know... for testing.\n"
printf "I'm a second line of text."
printf "${line}"
echo

# Example 4
# printf to equal our original echo example
#
# In order to replicate Example 4 in myscript_v03_echo.sh using a printf
# statement, we'll have to do the following:
# ------------------------------------------------------------------------
printf "\n"
printf "%s------------------------------------------------------\n"
printf "I'm a line of text,\nyou know... for testing.\n"
printf '%s\n' "I'm a second line of text."
printf "%s------------------------------------------------------\n"
printf "\n"

printf offers a lot more formatting options than our typical echo command. In order to achieve what we did in our initial echo script (myscript_v03_echo.sh), there is a little more setup/work involved. We'll definitely be using printf down the line, however, once we expand our script with further customizations.

echo vs printf

So, which one should I use?

I could get into the various reasons that one would use echo vs printf, but for our purposes, echo is easier. echo is great for basic commands. IMHO, it's what it does best. You could always substitute printf in for each echo command and get the same functionality, but with the caveat that it will require more syntax to achieve the same goal, while echo keeps things simple.

Use whichever one you're more comfortable with, mix and match, or use neither. It's ultimately up to you. You are the one that decides on how YOU want to structure your script output.

For a good rundown of echo vs printf, check out this thread over at Stack Exchange.

Functions

A function is a snippet of code that performs a specific task. In our case, we're going to use functions to make our script more streamlined (ie: look better.)

Basically, we'll be creating a new function that will perform the actions specified in our original script and later, we'll be calling the function (name) at the bottom of our modified script to run our actions.

Functions are super handy when scripts get larger in size/complication. Functions can be placed in external files and they are really simple to reuse throughout your script. This makes it easier to break apart an otherwise big and complicated task into smaller, reusable pieces.

Since functions are a huge topic, for now we’re only going to scratch the surface with some basics. We’ll be focusing on using basic functions to make our scripts cleaner. (I’ll be going through other, more elaborate uses for functions in a later part of this series.)

Let's go ahead and create a function to replace our echo commands that we currently have in our script: (myscript_v04.sh)

#!/usr/bin/env bash

filepath="$HOME/Desktop/"
filename="mysecondnewfile.txt"

function dothething() {  
  echo "--------------------------------------------------------"
  echo "  I'm an echo. Enjoy your new file - now with content!  "
  echo "This is my test file." > "${filepath}${filename}"
  echo "--------------------------------------------------------"
  echo
}

# call our function
# ----------------------------------------------------------------------
dothething

Run your script in your terminal again. It should do the exact same thing that we were doing before, only now, it's broken up into bite sized chunks.

The magic in using functions is that we only have to call the function name (dothething) in order for our script to work. This is one of the main building blocks in writing neatly organized and/or complicated scripts. You're already well on your way to writing a full-blown application.

Bravo.

Notice that I've added additional spaces in front/back of the second echo in our function? Since this literally prints the string that I've put in, we have some nice spacing around our output, rather than it being left-aligned. In the future, we'll be going through what it will take to do this automatically, and other ways to make our script output look awesome.

Oh my

That was a lot to digest, right? So, we've covered a lot up to this point. As someone starting out, it's definitely a lot to take in, but it all starts to fit together once you grasp these core concepts.

So as a refresher:

  • variables - use these to declare a name/path/whatever for later reuse
  • echo/printf - use one/both of these to output your variable/string/dividers
  • functions - put your script elements in neat little wrappers that you can reuse

Next up in part 4, we'll be dealing with structures, external sourcing, and more customization. Stay tuned!