#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)
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 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
/etc/profile
(if it exists, which it does on a Mac)./etc/bash.bashrc
, ~/.bash_profile
, ~/.bash_login
and ~/.profile
. It uses the first one that exists and is readable.~/.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:
You need to include the !
(sha-bang) in order to invoke the script. This is also called hash bang.
#!/bin/bash
goes at the beginning of the file and tells your system that this file is a set of commands to be fed to the command interpreter indicated (which is bash)#!
, a magic number, a special marker that designates a file type (an executable shell script)/bin/bash
that points to the shell location (note: /bin is where Linux keeps a lot of other ready to run programs like echo
, kill
)###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.
$
is used when doing assignment, e.g. STR="Hello world!"
$
is used when referencing a variable, e.g. echo $STR
local
to create a local variable (e.g. in a function) like local STR="HELLO"
echo "$BASH_VERSION"
So how does this all work? It goes from:
Bash code falls into a few general categories:
()
(always empty)###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
[ ]
these angular brackets mean an optional field|
the pipe means OR, especially when inside brackets or parenthesisAdditional details can be here
The breakdown of a simple command is:
[ var=value ... ] name [ arg ... ] [ redirection ... ]
This means:
[ var=value ...]
means that before a command is run, we can assign some environment variables like name='Will'
[ arg ]
means any command arguments[ redirection ]
means changing what the file descriptor points to (e.g. one process’ standard out might point to another process’ standard in)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:
time
keyword is used to find out how long a command took!
I’m not sure|
is the pipe symbol, which connects the standard output of the first command to the standard in of the second command|&
is the symbol to say we want the standard output AND standard error of the first command to connect to the standard in of the second command; normally do not want thisSample 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:
control-operator
tells bash what to do when executing the command before it (e.g. ;
is to just run the command and wait for it to end)An example of a control-operator
is:
||
run the command before it normally would and move to the next command ONLY IF the command before it FAILED. If there is no failure, the second command does not run. This can be used to show error messages.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 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
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
You can put more than one command on the same line, just use a &
to separate commands. &
runs commands concurrently
echo hello & echo there
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
is a switch statement
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
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
)