There’s a lot of tools and things to learn when it comes to networks, cloud, IaC, and so forth. There are a few that I understand to be fundamental: python, networking, Linux, and…scripting. So, even though I am loving learning about all things cloud and networking, I am going to shift to deep dive into shell scripting. I’m going to post my journey here, in hopes it might be useful, but also to serve as a reference for myself going forward. I worked my way through this excellent resource for this exercise.
First, we looked at how the shell parses arguments before the program executes. This was the code run to create first2.sh:
#!/bin/sh
# This is a comment!
echo "Hello World" # This is a comment, too!
echo "Hello World"
echo "Hello * World"
echo Hello * World
echo Hello World
echo "Hello" World
echo Hello " " World
echo "Hello "*" World"
echo `hello` world
echo 'hello' world
The output for that was this:
Hello World
Hello World
Hello * World
Hello first.sh first2.sh my-script.sh World
Hello World
Hello World
Hello World
Hello * World
./first2.sh: line 11: hello: command not found
world
hello world
Next up: variables.
- There can’t be spaces around the “=” sign.
- A variable can hold only one value, so a string with several values must be encapsualted with quotes.
- The built-in shell command read reads a line from standard input into the given variable: ex: read MY_NAME can be used to capture a name and used later in the program.
- We looked at variable scope: a child process/shell will need a parent variable to be exported to access it. Example:
myvar2.sh script:
#!/bin/sh
echo "MYVAR is: $MYVAR"
MYVAR="hi there"
echo "MYVAR is: $MYVAR"
Now playing with those variables:
Davids-MacBook-Air:Bash davide$ MYVAR=hello
Davids-MacBook-Air:Bash davide$ ./myvar2.sh
MYVAR is:
MYVAR is: hi there
Davids-MacBook-Air:Bash davide$ export MYVAR
Davids-MacBook-Air:Bash davide$ ./myvar2.sh
MYVAR is: hello
MYVAR is: hi there
We have to use sourcing to allow environment changes back from a script by sourcing the script, which runs the script within the interactive shell vs spawing another shell- use the “.” (dot) command to source. We don’t have to export the variable. Example:
Davids-MacBook-Air:Bash davide$ MYVAR=hello
Davids-MacBook-Air:Bash davide$ echo $MYVAR
hello
Davids-MacBook-Air:Bash davide$ . ./myvar2.sh
MYVAR is: hello
MYVAR is: hi there
Davids-MacBook-Air:Bash davide$ echo $MYVAR
hi there
Davids-MacBook-Air:Bash davide$
- Be careful of using variables when used within a script to create a file, for example, use curly brackets to enclose the variable
- Usage of wildcards , escape characters
",$,`, and\are still interpreted by the shell, even when they’re in double quotes.- The backslash (
\) character is used to mark these special characters so that they are not interpreted by the shell, but passed on to the command being run (for example,echo).
- for loop:
#!/bin/sh
for i in hello 1 * 2 goodbye
do
echo "Looping ... i is set to $i"
done
- while loop:
#!/bin/sh
INPUT_STRING=hello
while [ "$INPUT_STRING" != "bye" ]
do
echo "Please type something in (bye to quit)"
read INPUT_STRING
echo "You typed: $INPUT_STRING"
done
- if..then…else
if [ ... ]
then
# if-code
elif [ something_else ]; then
echo "Something else"
else
# else-code
fi
// or this
if [ ... ]; then
# do something
fi
- test using ‘[‘ character/command
if SPACE [ SPACE "$foo" SPACE = SPACE "bar" SPACE ]
- There is a simpler way of writing
ifstatements: The&&and||commands give code to run if the result is true, or false, respectively. - case statement, make sure to use the right bracket after the option that is understood, like this:
#!/bin/sh
echo "Please talk to me ..."
while :
do
read INPUT_STRING
case $INPUT_STRING in
hello)
echo "Hello yourself!"
;;
bye)
echo "See you again!"
break
;;
*)
echo "Sorry, I don't understand"
;;
esac
done
echo
echo "That's all folks!"
variables: $@ = all params $# num of params $1-… given params
code:
Davids-MacBook-Air:Bash davide$ cat testTest.sh
#!/bin/sh
echo "I was called with $# parameters"
echo "My name is $0"
echo "My first parameter is $1"
echo "My second parameter is $2"
echo "All parameters are $@
output
Davids-MacBook-Air:Bash davide$ ./testTest.sh one two three four
I was called with 4 parameters
My name is ./testTest.sh
My first parameter is one
My second parameter is two
All parameters are one two three four
- Also, $$ is the PID of current running shell
- $! is the PID of the last run BG process
and these…
Cheating with awk
Consider wc, which counts the number of characters, lines, and words in a text file. Its output is:
$ wc hex2env.c 102 189 2306 hex2env.c
If we want to get the number of lines into a variable, simply using:
NO_LINES=`wc -l file`
which would read in the whole line.
Because the output is space-padded, we can’t reliably get the number 102 into the string. Instead, we use the fact that awk works similarly to scanf in C – it strips unwanted whitespace. It puts these into variables $1 $2 $3 etc. So we use this construct:
NO_LINES=`wc -l file | awk '{ print $1 }'`
The variable NO_LINES is now 102.
Cheating with sed
Another handy utility is sed – the stream editor. Perl is very good at dealing with regular expressions, the shell isn’t. So we can quickly use the s/from/to/g construct by invoking sed.For example:
sed s/eth0/eth1/g file1 > file2
changes every instance of eth0 in file1 to eth1 in file2.
If we were only changing a single character, tr would be the tool to use, being smaller and therefore faster to load.
Another thing that tr can’t do, is remove characters from a file:
echo ${SOMETHING} | sed s/"bad word"//g
This removes the phrase “bad word” from the variable ${SOMETHING}. It may be tempting to say, “But grep can do that!” – grep only deals with whole lines. Consider the file:
This line is okay. This line contains a bad word. Treat with care. This line is fine, too.
Grep would remove the whole second line, leaving only a two-line file; sed would change the file to read:
This line is okay. This line contains a . Treat with care. This line is fine, too.