Writing Bash Scripts

Throughout this book, you’ve worked with the Bash shell. You’ve used various commands and programs to work with files, directories, text, and even with data over a network.

A Bash script is a file that contains a list of commands you want the computer to execute. When you run the script, the commands will execute one at a time until all of the commands are run. You can use any command you’d normally type in your terminal, which means you can use everything you’ve learned about the command-line interface in a script. But Bash scripts support more than just commands. You can use variables, conditional statements, loops, and arrays. You can get input from files, pass command-line arguments in, or even prompt the user for input for a more interactive experience.

To explore these concepts further, you’re going to create a script that generates the files and directories for a new website project. When you’re done, you’ll be able to issue a single command to bootstrap a new project.

But before you dive into the website creation program, you’ll get comfortable with creating and running Bash scripts by writing the obligatory “Hello World” program.

Switch to your home directory and create a new file called hello_world.sh. The .sh extension isn’t required, but it’s handy when developing because text editors use it to enable syntax highlighting for shell scripting.

 $ ​​cd
 $ ​​nano​​ ​​hello_world.sh

Add the following statement to the file, which uses the echo command to print “Hello World!”:

 echo ​"Hello World!"

When you run this script, it will execute the script’s contents and print the result to the screen.

Rather than saving the file and quitting nano to run the script, press Ctrl+o to save the file. Then suspend nano to the background with Ctrl+z. This will let you jump right back into the file once you’ve tested it.

At the prompt, use the bash command to run the script:

 $ ​​bash​​ ​​hello.sh
 Hello World!

Now modify the program’s message so that when you run the program, it produces the following output:

 Hello World!
 This is my first Bash script!

To get that output, you’ll use an additional echo statement. Bring nano back to the foreground:

 $ ​​fg

Then add the new statement to the file:

 echo ​"Hello World!"
»echo ​"This is my first Bash script!"

Save the file, suspend nano, and run the script again:

 $ ​​bash​​ ​​hello.sh
 Hello World!
 This is my first Bash script!

Now let’s make this an executable script so you don’t have to run it with the bash command. You saw this a few times back in Chapter 6, The Shell and Environment when you made some quick scripts. Modify the script by adding this line at the top:

»#!/usr/bin/env bash
 echo ​"Hello World!"

This line is called a “shebang.” It’s a comment that tells the operating system where to find the interpreter for this script. Save the changes to your script, suspend nano, and then use chmod to make the script executable:

 $ ​​chmod​​ ​​+x​​ ​​hello.sh

Now run the script. Remember, to run a script in the current directory, you have to prefix it with the characters ./, which makes it clear that you want to run the script that exists in the current directory.

 $ ​​./hello.sh
 Hello World!
 This is my first Bash script!

That’s a basic script. Let’s dive deeper and build out that website generator. Use fg to bring nano to the foreground again and then exit the editor.

Automating File and Directory Creation

One of the best things about automation is that you can put in a little extra work up front and get a lot of reward out of that work down the road.

A typical website has a home page, at least one style sheet, a folder for images, and most likely, some JavaScript files. So let’s use Bash to automate that. You’ll have the script create a root directory for the web site, an index.html file, a scripts directory, an images directory, and a stylesheets directory. And you’ll drop a style.css file in the stylesheets directory for good measure:

 mysite
 ├── images
 ├── index.html
 ├── scripts
 └── styles
  └── style.css

Using what you learned in Chapter 4, Working with Files and Directories, you can probably write this script already. Your first approach might look like this:

 mkdir -p mysite/{images,scripts,styles}
 touch mysite/styles/style.css
 cat << ​'​EOF​' > mysite/index.html
 <!DOCTYPE html>
 <html lang="en-US">
  <head>
  <meta charset="utf-8">
  <title>My Website</title>
  <link rel="stylesheet" href="styles/style.css">
  </head>
  <body>
  </body>
 </html>
 EOF

That would work quite well, but you can make it even more flexible by using variables so you are not repeating website throughout the script. This will also make it easier to change the name of the directory later, which you will do shortly.

Create the file new_site.sh and make it executable:

 $ ​​touch​​ ​​new_site.sh
 $ ​​chmod​​ ​​+x​​ ​​new_site.sh

Now open the script with nano:

 $ ​​nano​​ ​​new_site.sh

Add the following content to the file:

 #!/usr/bin/env bash
 
 set -e
 
 directory=​"mysite"
 
 mkdir -p ​${​directory​}​/{images,scripts,styles}
 touch ​${​directory​}​/styles/style.css
 cat << ​'​EOF​' > ​​${​directory​}​​/index.html
 <!DOCTYPE html>
 <html lang="en-US">
  <head>
  <meta charset="utf-8">
  <title>My Website</title>
  <link rel="stylesheet" href="styles/style.css">
  </head>
  <body>
  </body>
 </html>
 EOF

The script starts with the shebang line, followed by set -e, which tells Bash that it should exit as soon as some statement fails. Without this option, Bash will attempt to run every command, even if previous ones errored out.

It then initializes a variable called directory with the name of the directory it’s going to create to hold the files for the site. In this case, it’s using a hardcoded value: mysite. It then uses the directory variable in the paths when it creates the directories and files. Remember that you have to prefix the variable name with the dollar sign. If you don’t, Bash will use the literal word directory instead of the value the variable contains. And since we’re mixing it with other characters in a string, it’s best to use ${directory} here instead of $directory.

Save the file with Ctrl+o. Now test out the script. Suspend nano with Ctrl+z.

Once you’re back at a prompt, run the script:

 $ ​​./new_site.sh

You won’t see any output from the script unless you’ve made a typo when creating the script. But you can verify that it worked by using the tree command to view the mysite directory:

 $ ​​tree​​ ​​mysite
 mysite/
 ├── images
 ├── index.html
 ├── scripts
 └── styles
  └── style.css
 
 3 directories, 2 files

Bring nano back to the foreground so you can keep editing your script:

 $ ​​fg

The script works, but it’s not very flexible. Let’s customize the script further by having it prompt for the directory name.

Making an Interactive Script

Currently, the script uses the hardcoded value of mysite for the name of the new website. All of your websites won’t have the same name, so you can make the script more flexible by having it prompt you for the directory you want to create.

Modify the variable declaration in your script so it uses the read command to prompt for the directory and assign the value for the directory variable. Then, wrap each use of ${directory} with double quotes so you can support situations where a user might enter a directory name with spaces.

 #!/usr/bin/env bash
 
 set -e
 
»read -p ​"Enter the name of the directory to create: "​ directory
»mkdir -p ​"​​${​directory​}​​"​/{images,scripts,styles}
»touch ​"​​${​directory​}​​"​/styles/style.css
»cat << ​'​EOF​' > "​​${​directory​}​​"/index.html

The read command displays the prompt and pauses the script’s execution. The user can then type the directory they want to create. When they press the Enter key, the value gets stored in the directory variable and the script continues.

You might be wondering why we don’t just wrap the whole line with double quotes like you’ve done in previous examples. The reason here is that the brace expansion on the mkdir line wouldn’t work. You could use double quotes around the other lines without issue, but it’s best to be consistent. We’re using the double quotes so we support situations where the variable has spaces in its value, and we control the rest of the path.

Save the file again, and suspend nano with Ctrl+z. Then execute the script again. When prompted for a value, enter mysite2:

 $ ​​./new_site.sh
 Enter the name of the directory to create: mysite2

Verify the new structure was created:

 $ ​​tree​​ ​​mysite2
 mysite2/
 ├── images
 ├── index.html
 ├── scripts
 └── styles
  └── style.css
 
 3 directories, 2 files

This works reasonably well, but you might run into problems if someone presses Enter without entering a name. If they did, the script would try to create files in the root of the filesystem, because $directory would be an empty string. That’s probably not the behavior you want.

Let’s add logic to the script that checks if they left the directory name blank. If they did, you’ll stop processing the script.

In Creating Shortcuts with Aliases and Functions, you wrote a function that used conditional logic to change how a function behaves. You can apply the same approach here by checking to see if the $directory variable is empty.

The expression [ -z "$directory" ] tests to see if the directory variable is empty. Bring nano back to the foreground with fg and add this code to evaluate the expression and exit the script if the user didn’t enter anything:

 #!/usr/bin/env bash
 
 set -e
 
 read -p ​"Enter the name of the directory to create: "​ directory
»if​ [ -z ​"​$directory​"​ ]; ​then
» ​echo ​"You didn't enter anything. Exiting."
» exit 1
»fi
 mkdir -p ​"​​${​directory​}​​"​/{images,scripts,styles}

In addition to stopping the script, we return an exit code, which ensures that this script can play nice with other programs and commands. Those commands can check if this script executed with errors and respond appropriately.

Save the script, suspend nano, and run the script again. Press Enter when prompted for the directory:

 $ ​​./new_site.sh
 Enter the name of the directory to create:
 You didn't enter anything. Exiting.

The current version of the script is friendly, but a lot of the power from the CLI comes from the fact that many commands aren’t interactive, which means they can be automated easily. So let’s modify this script so you can pass the directory name as an argument to the script. You’ll modify the script to check if the script was called with any arguments. If it was, then you can assign the first argument to the directory variable instead of asking the user for it.

Bring nano to the foreground again and modify your script like this:

 #!/usr/bin/env bash
 
 set -e
 
»if​ [ $# -eq 0 ]; ​then
  read -p ​"Enter the name of the directory to create: "​ directory
 
 if​ [ -z ​"​$directory​"​ ]; ​then
  ​echo ​"You didn't enter anything. Exiting."
  exit 1
 fi
»else
» ​directory=$1
»fi
 mkdir -p ​"​​${​directory​}​​"​/{images,scripts,styles}

The expression [ $# -eq 0 ] checks that the number of arguments passed to the script is zero. If that’s the case, the script executes the original behavior and asks for the name. And if they don’t provide it, the script still bails out with an exit code. But if arguments were passed, the script takes the first argument ($1) and assigns it to the value of directory. The rest of the script stays the same.

Save the script. Suspend nano and run the script again with a directory name as the argument:

 $ ​​./new_site.sh​​ ​​mysite3

The mysite3 directory is created without any interaction. Test that you can still create sites interactively and that your script still exits when someone doesn’t enter a directory name. Once you’re satisifed that things work as you expect, bring nano to the foreground and exit the editor.

This script becomes even more usable if you move it to /usr/local/bin so it’s a system-wide tool:

 $ ​​sudo​​ ​​mv​​ ​​new_site.sh​​ ​​/usr/local/bin/new_site

Now you or any other user can run the new_site command anywhere on the filesystem.

With Bash scripts, you can take the commands you’ve used throughout this book and create complex workflows. Before you move on to the final chapter of the book, you’ll write one more script that ties a few concepts together.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.216.160