William Liu

Bash Scripting


#Summary

These are notes for shell scripting, specifically for Bash, (aka the Bourne-Again shell, a replacement for the Bourne shell). At its heart, a bash script is just a list of system commands stored in a file (usually with the convention of ending in .sh, but doesn’t have to since Linux is an extensionless system)


##Intro to Bash

Bash code, like any program, is executed by the Linux kernel (if it is marked as executable through chmod +x). We can save it to a file or run in a termainl.

chmod + x hello.sh  # mark hello.sh as an executable program
./hello.sh  # tell bash to start the hello.sh program

##Shells and Profiles

Shells and Profiles go hand in hand. The idea is that when our shell starts up (and depending on how it starts up), it looks at our profiles for different settings.

###Shells

First off, there’s a lot of different shells. By default, most systems use the Bash shell. There’s others like csh, zsh, sh, each with slightly different syntax. For these notes, I’m only talking about bash. Now you also have a lot of different profiles; you have your .bash_profile, your .profile, your etc/bash.bashrc. What is going on?!

####Shell Mode (Interactive)

When you open up your terminal, you can run bash commands one at a time; this is the shell’s interactive mode

  1. When you start bash as an interactive login shell, bash reads and executes from /etc/profile (if it exists, which it does on a Mac).
  2. Often this startup script calls other files, like /etc/bash.bashrc, ~/.bash_profile, ~/.bash_login and ~/.profile. It uses the first one that exists and is readable.
  3. When a bash login shell exits, it reads ~/.bash_logout (if it exists).

####Shell Mode (Non-Interactive)

When you run a script, its just running a bunch of bash commands one after another; this is the shell’s non-interactive mode. By default, these bash scripts end in .sh (e.g. myscript.sh), but don’t have to because Linux is an extensionless system.

###Profiles

So what’s the difference between say .bash_profile and .profile? Bash profile is specific to bash while profile is used for more generic shells (e.g. not dependent on the bash shell) Remember how I said depending how your shell starts up? Besides the interactive login shell, you can also start as a non-login interactive shell; this then reads .bashrc and does NOT read .bash_profile I use bash so it doesn’t matter if I put customizations in .bash_profile or .bashrc, but you should be aware of this if you do use other shells.

##Symbols

Here’s a few good to know symbols:

###! the sha-bang

You need to include the ! (sha-bang) in order to invoke the script. This is also called hash bang.

###prompt >

When bash doesn’t have enough information to know what to do when a command succeeds or not, it will prompt with >.

###Comments

# to start a line off with comments or you can put after code You cannot put code after a comment

###Variables

You can assign values to variables.

##Basics of how Bash fits in

So how does this all work? It goes from:

##Structure of a Bash Command

Bash code falls into a few general categories:

###How to read a bash synopsis

Just like in man pages, you want to know how to read the SYNOPSIS (e.g. man ls) and see SYNOPSIS

Additional details can be here

###Simple Commands

The breakdown of a simple command is:

[ var=value ... ] name [ arg ... ] [ redirection ... ]

This means:

Note that running in terminal is not the same as running in a script (e.g. var=value)

Sample code to print out a string

echo "Hello world!"

###Pipelines

The breakdown of a pipeline is:

[ time [-p]] [ ! ] command [ [|]|[|&] command2 ... ]

This means:

Sample code to reverse a line of text:

echo Hello | rev

###Lists

A list is a sequence of commands. The breakdown of a list is:

command control-operator [ command2 control-operator ... ]

This means:

An example of a control-operator is:

Sample code:

$rm hello.txt || echo "Couldn't delete hello.txt." >&2
rm: hello.txt: No such file or directory
Couldn't delete hello.txt 

###Compound Commands

Compound commands are commands with special syntax inside them. They behave as a single big command in a command list (e.g. a block of commands). Inside are a bunch of subcommands. The breakdown is:

if list [ ;|<newline> ] then list [;|<newline>] fi

So why use a compound command? It affects say the || command in determining if an error happened or not.

Sample code:

# both lines of code do the same operation, first is a compound command in a command list
if ! rm hello.txt; then echo "Couldn't delete hello.txt." >&2; exit 1; fi
rm hello.txt || { echo "Couldn't delete hello.txt." >&2; exit 1; }

###Coprocesses

A coprocess allows you to run a command asynchronously (i.e. in the background). The breakdown is:

coproc [ name ] command [ redirection ... ]

Here we start an asynchronous process named auth that calls tail on a log file (first line). We then write a message showing the latest authentication attempt/log. The script continues and reads from the coprocess pipe (each time getting next line from tail).

Sample code:

coproc auth { tail -n1 -f /var/log/auth.log; }
read latestAuth <&"${auth[0]}"
echo "Latest authentication attempt: $latestAuth"

###Functions

A function is a temporary new command that you can later invoke. The breakdown is:

name () compound-command [ redirection ]

We have a function name that is always followed by an empty set of parenthesis (). Then add in a compound-command that gets executed when the function is called.

Sample code:

exists() { [[ -x ${type -P "$1" 2>/dev/null) ]]; }
exists gpg || echo "Please call GPG." <&2

##Process Management with ; and &

The bash shell can run commands using two ways, batch (Unix) and concurrent mode, which is basically synchronous and asynchronous

###; as batch mode

You can put more than one command on the same line, just use a ; (semicolon) to separate commands. ; runs commands one after another

echo hello; echo there

###& for concurrent mode

You can put more than one command on the same line, just use a & to separate commands. & runs commands concurrently

echo hello & echo there

##Control Flow

You can control the flow of bash scripts with if, case, and select statements.

###if statement

You can run specify if statements with the syntax:

if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi

####if example

if [ TEST-COMMANDS -ne 0 ]
  then
    echo "TEST"
fi

####if example with last command

if [ $? - eq 0 ] 
  then echo "Last command exited successfully"
fi

###case statements

case is a switch statement

###select statements

select creates a prompt ($PS3, by default) and allows user to select from a prompt

####select example

#!/bin/bash
PS3='Choose your favorite vegetable: '
select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas"
do
  echo
  echo "Your favorite beggie is $vegetable"
  break
done
exit

####select in a function example

#!/bin/bash
PS3='Choose your favorite vegetable: '
echo
choice_of()
{
select vegetable
# [in list] omitted, so 'select' uses arguments passed to the function
do
  echo
  echo "Your favorite veggie is $vegetable."
  break
done
}

choice_of beans rice carrots radishes rutabaga spinach
exit 0

Useful Example Commands

Empty a file with: cat /dev/null myfile.log date to get the date (e.g. Thu Aug 23 15:51:48 MDT 2018) date -d “last month” ‘+%Y-%m’ # (e.g. 2018-07)