Start the bash script with a "she-bang" that tells the system where to locate the bash interpreter:
#!/bin/bashCreate a file called hello_world and populate it with a simple echo command:
$ cat > hello_world
#!/bin/bash
echo "Hello, world!"
# ctrl-dThen change the permissions on the file so everyone can execute it:
$ chmod a+x hello_worldThen you can execute the script with:
$./hello_world
# => Hello, world!To set a global variable (global to the script):
myvar="foo"Note: There are no spaces around the = sign.
If the variable is meant not to change, it is conventional to to indicate that it is a constant with all caps:
MYVAR='foo'If you are in the scope of a function, you can create a local variable like so:
myfunction() {
local myvar="foo"
}Say you have a variable x set to a number. You can make a conditional branch of code on this like so:
x=5
if [ $x = 5 ]
then
echo "x is 5"
elif [ $x = 6 ]
then
echo "x is 6"
else
echo "x is not 5 or 6"
fiNote: There needs to be a space after the [ and a space before ].
This could have been written in less lines like so:
x=5
if [ $x = 5 ]; then
echo "x is 5"
elif [ $x = 6 ]; then
echo "x is 6"
else
echo "x is not 5 or 6"
fiThe [] is called the test command. It can be written in two ways:
test $x = 5[ $x = 5 ]There are a number of file specific features you can use with test:
You can check if a file exists with -e:
if [ -e "$file" ]; then
echo "file exists"
fiSome of the usefule file test options:
| flag | test |
|---|---|
| -e | file exists |
| -L | file exists and is a symbolic link |
| -r | file exists and is readable |
| -w | file exists and is writable |
| -x | file exists and is executable |
| -s | file exists and has a length greater than zero |
| -d | file is a directory |
You can also do tests on strings:
ANSWER=maybe
if [ "$ANSWER" = "maybe" ]; then
echo "The answer is maybe!"
fiSome useful string test options:
| test | description |
|---|---|
| string | string is not null |
| -n string | length of string is greater than 0 |
| -z string | length of string is 0 |
| string1 = string2 | strings are equal |
| string1 == string2 | strings are equal |
| string1 > string2 | string1 is greater than string2 |
| string1 < string2 | string1 is less than string2 |
| string1 != string2 | string1 does not equal string2 |
One example of -z is checking if there is an argument being passed to the script:
if [ -z "$1" ]; then
echo "no arguments"
else
echo "argument passed in: $1"
fiYou can do integer based tests:
MY_NUM=23
if [ $MY_NUM -lt 42 ]; then
echo "My number is less than 42"
fiSome usefule integer comparisons:
| test | description |
|---|---|
| integer1 -eq integer2 | integer1 equals integer2 |
| integer1 -lt integer2 | integer1 is less than integer2 |
| integer1 -gt integer2 | integer1 is greater than integer2 |
| integer1 -ge integer2 | integer1 is greater than or equal to integer2 |
| integer1 -le integer2 | integer1 is less than or equal to integer2 |
| integer1 -ne integer2 | integer1 is not equal to integer2 |
There is a newer test method [[ test ]] that adds a regular expression operator =~ to string tests:
MY_STRING="(415) 555-5555"
if [[ "$MY_STRING" =~ ^\([0-9]{3}\)[[:space:]][0-9]{3}-[0-9]{4} ]]; then
echo "It's a phone number"
fiAnd, there is a test command, (()) for performing arithmetic boolean operations:
if ((1)); then
echo "It is true"
fi
INT=5
if ((INT == 4)); then
echo "It's 5"
elif ((INT > 4)); then
echo "It's greater than 4"
fi
if (( ((INT % 2)) == 0 )); then
echo "It is even"
else
echo "It is odd"
fiYou can use logical operators in tests:
| operation | test | [[]] and (()) |
|---|---|---|
| AND | -a | && |
| OR | -o | |
| NOT | ! | ! |
INT=50
if [[ $INT -ge 55 && $INT -lt 100 ]]; then
echo "Woo hoo"
fiThis could also be written:
INT=50
if [[ $INT -ge 55 -a $INT -lt 100 ]]; then
echo "Woo hoo"
fiHere is a test that checks to see if the user is the root user:
if [[ $( id -u) -eq 0 ]]; then
echo "The current user is root"
fiHere is another one I found in a script
[[ $EUID == 0 ]] || { echo "Must be run as root."; exit; }
Here is a test that checks if the last process worked:
if [ $? -ne 0 ]; then
echo "Problems"
exit $?
fiif [[ $# -ne 0 ]] ; then
echo 'An argument was passed'
fiShell functions and scripts output a exit status number ranging from 0 to 255. 0 is a successful exit status, whereas all other numbers indicate a failure of some kind.
You can get the most recent exit status with the ?$ parameter, like so:
$ ls -l /my/dir
$ echo $?
# => 0
$ ls -l /dir/that/does/not/exist
# ls: /dir/that/does/not/exist: No such file or directory
$ echo $?
# => 1You can manually return an exit code with the exit command:
exit 1You can also use redirection to echo to standard error:
MY_NUM=0
if [ $MY_NUM -eq 0 ]; then
echo "My number can not be zero" >&2
exit 1
fiYou can read from standard input, or from a file if redirection is used.
echo "Please enter a number:"
read number
echo "Your number is $number."Or, you can pass a flag like -s for silent mode, in case you don't want to print out secure information to the screen:
echo "Please enter your password"
echo -n "> "
read -s password
if [[ ${#password} -lt 8 ]]; then
echo "The password is not long enough" >&2
exit 1
else
echo "Cool, that's a super secure password"
fiOther read options:
| flag | effect |
|---|---|
| -a array | assign input to an array |
| -e | use readline to handle input |
| -n number | read number of characters of input |
| -p prompt | display a prompt for a user with the string prompt |
| -t seconds | timeout in seconds |
| -d fd | use input from file descriptor, rather than standard IO |
echo "What's your name"
read -e -t 5 -p "> " name
if [ $name ]; then
echo "Hello, $name"
else
echo "Sorry, not quick enough!" >&2
fiYou can also read multiple values in at once:
echo -n "Enter one or more values > "
read var1 var2 var3 var4 var5
echo "var1 = '$ var1'"
echo "var2 = '$ var2'"
echo "var3 = '$ var3'"
echo "var4 = '$ var4'"
echo "var5 = '$ var5'"You can print out the numbers 1 through 10, using a while loop, like so:
count=1
while [ $count -lt 11 ]; do
echo $count
count=$((count + 1))
doneIn a while loop, you can use the standard break command to break out of the loop, and the continue command to skip to the next iteration of the loop.
Imagine that you have a file named zipcodes.csv containing this contents:
zipcode,plus4,city
94107,0203,SAN FRANCISCO
94106,0234,SAN FRANCISCO
95815,0423,SACRAMENTOYou can read this in like so:
count=1
while read line; do
zipcode=$(echo $line | cut -d "," -f 1)
plus4=$(echo $line | cut -d "," -f 2)
city=$(echo $line | cut -d "," -f 3)
echo -e "line #$count:\t$zipcode\t$plus4\t$city"
count=$((count + 1))
done < zipcodes.csvYou can also pipe information in:
count=1
grep -i "San Francisco" zipcodes.csv | while read line; do
zipcode=$(echo $line | cut -d "," -f 1)
plus4=$(echo $line | cut -d "," -f 2)
city=$(echo $line | cut -d "," -f 3)
echo -e "line #$count:\t$zipcode\t$plus4\t$city"
count=$((count + 1))
doneYou can make multiple choice decisions with case like so:
echo "Choose a number between 1 and 3"
read -p "> " number
case $number in
1) echo "You chose 1"
exit
;;
2|3) echo "You chose 2 or 3"
exit
;;
*) echo "Hey, I said a number between 1 and 3"
exit
;;
esacYou can iterate over a list of items with for like so:
for i in A B C D; do
echo $i
doneYou could also do this with brace expansion:
for i in {A..D}; do
echo $i
doneOr, you could do this with globbing:
for i in *.pdf; do
echo $i
doneYou can also use C style expression loops:
for (( i=0; i < 5; i=i+1 )); do
echo $i
doneIf your script is long running, someone might ctrl-c to stop the process. You can capture this SIGINT signal, like so:
capture_sigint() {
echo 'capturing ctrl-c with trap'
exit
}
trap capture_sigint SIGINT
while true; do
printf '.'
sleep 1
doneNow if you run this script it will continuously print out "." until you hit ctrl-c. When you do, trap captures the SIGINT signal and passes it off to the capture_sigint function.
Note: You need to exit at the end of the function, otherwise script will continue to run.
| Signal | Value | Action | Comment |
|---|---|---|---|
| SIGHUP | 1 | term | Hangup detected on controlling terminal or death of controlling process |
| SIGINT | 2 | term | Interrupt from keyboard |
| SIGQUIT | 3 | core | Quit from keyboard |
| SIGILL | 4 | Core | Illegal Instruction |
| SIGABRT | 6 | Core | Abort signal from abort(3) |
| SIGFPE | 8 | Core | Floating point exception |
| SIGKILL | 9 | Term | Kill signal |
| SIGSEGV | 11 | Core | Invalid memory reference |
| SIGPIPE | 13 | Term | Broken pipe: write to pipe with no readers |
| SIGALRM | 14 | Term | Timer signal from alarm(2) |
| SIGTERM | 15 | Term | Termination signal |
Note: The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
Here is an example where we take the value of "foo" stored in a variable and append "_file" to it:
a="foo"
# echo '$a_file' will not work
echo '${a}_file'You can set a default value for a variable in an expansion with the ${variable:-"default string"} approach:
a="foo"
echo $a
# => "foo"
a=""
echo ${a:-"biz baz bar"}
# => "biz baz bar"You can also do this with ${variable:="default string"}. However, if you use the = sign expansion, the variable will be set to the default value.
You can use an expansion to verify that a variable is set, and exit with an error if not, like so:
echo ${a:?"error message about missing variable"}
# => "error message about missing variable"
echo $?
# => 1You can also substitute a value of a variable in the event that it is set to something:
a="foo"
echo ${a:+"some other value"}
# => "some other value"This will give you the number of characters:
a="Hello, world!"
echo ${#a}
# => 13Lets say we have a quote from Mitch Hedberg:
quote="My fake plants died because I did not pretend to water them."Grab all the characters from n to the end of the string:
echo ${quote:28}
# => I did not pretend to water them.Grab characters from n1 to n2, using ${varable:start_point:offset}:
echo ${quote:3:16}
# => fake plants diedIf you use a negative sign, you can start the grab from the end of the string (notice the space after the :):
echo ${quote: -22}
# => pretend to water them.
echo ${quote: -22:7}
# => pretendecho ${quote//\ /}
# => MyfakeplantsdiedbecauseIdidnotpretendtowaterthem.
echo ${quote//fake/real}
# => My real plants died because I did not pretend to water them.Say we have a variable like this:
file=my_template.html.hamlTo remove the shortest match of a pattern from the leading part of a string. The pattern in this case is like a wildcard used in pathname expansions.
echo ${file#*.}
# => html.hamlTo remove the longest match, use a ##:
echo ${file##*.}
# => hamlTo remove the shortest match from the end of a string:
echo ${file%.*}
# => my_template.htmlTo remove the longest match from the end of a string:
echo ${file%%.*}
# => my_templateecho $((0xff))
# => 255| Notation | Description |
|---|---|
| parameter = value | assigns value to parameter |
| parameter += value | same as parameter = parameter + value |
| parameter -= value | same as parameter = parameter - value |
| parameter *= value | same as parameter = parameter * value |
| parameter /= value | same as parameter = parameter / value |
| parameter %= value | same as parameter = parameter % value |
| parameter++ | same as parameter = parameter + 1 (value incremented after return) |
| parameter-- | same as parameter = parameter - 1 (value incremented after return) |
| ++parameter | same as parameter = parameter + 1 (value incremented before return) |
| --parameter | same as parameter = parameter - 1 (value incremented before return) |
foo=1
echo $((foo++))
# => 1
echo $foo
# => 2
echo $((++foo))
# => 3
echo $foo
# => 3tput allows us to add some general styling characteristics to our terminal. tput is a part of the ncurses package.
There are a number of text effects that you can use with tput:
| argument | effect |
|---|---|
| bold | Start bold text |
| smul | Start underlined text |
| rmul | End underlined text |
| rev | Start reverse video |
| blink | Start blinking text |
| invis | Start invisible text |
| smso | Start "standout" mode |
| rmso | End "standout" mode |
| sgr0 | Turn off all attributes |
| setaf | Set foreground color |
| setab | Set background color |
The colors that you can pass to foreground and background are:
| number | color |
|---|---|
| 0 | Black |
| 1 | Red |
| 2 | Green |
| 3 | Yellow |
| 4 | Blue |
| 5 | Magenta |
| 6 | Cyan |
| 7 | White |
| 8 | Not used |
| 9 | Reset to default color |
To colorize text, I usually create a number of variables that hold the color and reset values. Then I apply then to echo statement strings, like so:
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
COLOR_RESET=$(tput sgr0)
echo "${RED}Here is some red text.${COLOR_RESET}"
echo "${GREEN}Here is some red green.${COLOR_RESET}"
echo "${YELLOW}Here is some red yellow.${COLOR_RESET}"Here is a method for outputing colorized messages:
say() {
echo -e "${2:-$YELLOW}$1${COLOR_RESET}"
}
# say "foo" $RED$ date +'%Y%m%d%H%M%S'You can access all of the args passed to a script with $@. For example this would call anotherscript with all the arguments passed to the script that this line is in.
File: somescript.bash
#!/bin/bash
anotherscript $@So calling it with somescript.bash one two three would in turn call anotherscript with the arguments one two three.
You can also translate the args passed to a script into an array:
args=("$@")
echo args[0]And, you can count the number of arguments being passed in with $#:
echo $## rename the files
$ for f in *.csv; do mv $f $f-full; done
# truncate the files
$ for f in *-full; do head -n 500 $f > $(echo $f | sed -e 's/-full//g'); done
# delete files with -full on the end
$ find . -name '*-full' -delete