A `bash` script is a sequence of command that are executed one by one.
Most of the time we just execute one command and wait for the result to then make a decision and execute an other command.
We can however create a sequence of commands on the command line.
```
(baseline-) ➜ ~ echo hello world
hello world
(baseline-) ➜ ~ date
Wed 16 Mar 2022 07:06:02 PM CET
(baseline-) ➜ ~ cal
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
(baseline-) ➜ ~ echo hello world date cal
hello world date cal
(baseline-) ➜ ~ echo hello world; date; cal
hello world
Wed 16 Mar 2022 07:06:19 PM CET
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
```
A very visually similar result, but completely different operation, can be obtained by replacing the `;` with `&` or `&&`.
`&` will launch a new process and send it to the background, `&&` will **evaluate** the return status of your process and only continue **if** the status was `0`, or in other words successful.
```
(baseline-) ➜ ~ echo hello world & date & cal
[1] 3075524
hello world
[2] 3075525
[1] - 3075524 done echo hello world
Wed 16 Mar 2022 07:06:33 PM CET
[2] + 3075525 done date
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
(baseline-) ➜ ~ echo hello world && date && cal
hello world
Wed 16 Mar 2022 07:06:40 PM CET
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
(baseline-) ➜ ~
```
This should make you think we can create quite complicated logical flows in `bash` and you're right!
But first things first, let's create our first script!
```
(baseline-) ➜ bash file test.sh
test.sh: Bourne-Again shell script, ASCII text executable
(baseline-) ➜ bash ls -l test.sh
-rwxr-xr-x 1 waldek waldek 39 Mar 16 19:13 test.sh
(baseline-) ➜ bash cat test.sh
#!/bin/bash
echo hello world
cal
date
(baseline-) ➜ bash ./test.sh
hello world
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Wed 16 Mar 2022 07:27:22 PM CET
(baseline-) ➜ bash bash test.sh
hello world
March 2022
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Wed 16 Mar 2022 07:27:25 PM CET
(baseline-) ➜ bash
```
If you observe the output above you can conclude multiple things.
* the `test.sh` file is a simple text file
* it has executable permissions for the owner, group and others
* we can execute the sequence of commands in the script in two ways
*`./test.sh`
*`bash test.sh`
* the content is a series of commands but with a *weird* first line
That first line is called a [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) and is a way of explaining which interpreter understands the lines that follow.
If you venture out into the python universe you'll encounter the same norm but with a different path to an interpretor, often `/bin/python3` or `/usr/bin/env python3`.
The later is a sort of shortcut that points to the local python installation, even if it's not in a standard location.
A shebang is not necessary for a script to function but is **highly** advised.
The small *password* checker we made before could use some [conditional logic](https://en.wikipedia.org/wiki/Conditional_(computer_programming)).
We can easily implement this in `bash` with the following syntax.
```bash
#!/bin/bash
my_pass="supersecret"
read -s -p "what is your secret password? " pass
echo
if [ $pass == $my_pass ]; then
echo "access granted!"
fi
```
The `[ $pass == $my_pass ]` is the actual *evaluation* and will **always** evaluate to either `true` or `false`.
If the statement is `true`, the following code get's executed.
If **not**, currently nothing happens.
We can introduce a second keyword `else` to handle this.
```bash
#!/bin/bash
my_pass="supersecret"
read -s -p "what is your secret password? " pass
echo
if [ $pass == $my_pass ]; then
echo "access granted!"
else
echo "access denied..."
fi
```
The script above gives us the following behaviour.
```
waldek@helloworld:~$ bash test.sh
what is your secret password?
access denied...
waldek@helloworld:~$ bash test.sh
what is your secret password?
access granted!
waldek@helloworld:~$
```
We can add a bit more complexity to our possible branches with the `elif` keyword.
This keyword allows us to construct a second and third branch of execution.
Consider the sentence below.
> If you are younger than 27 you are still young so if you're older than 27 you're considered old, but if you are 27 on the dot your life might be at risk!
This sentence can be converted to a conditional logic block as follows.
```bash
#!/bin/bash
read -p "how old are you? " age
echo
if [ "$age" -lt "27" ]; then
echo "you are so young! enjoy it"
elif [ "$age" -gt "27" ]; then
echo "you're sooo old!"
elif [ "$age" -eq "27" ]; then
echo "your life might be at risk..."
else
echo "I'm not sure I understand you."
fi
```
There is a little problem here though!
We can input anything we want, not only numbers, and this creates some error messages.
Bash is a bit special, compared to a language like `python3`, because it doesn't *crash* on an error.
It just *keeps going*.
```
waldek@helloworld:~$ bash test.sh
how old are you? helloworld
test.sh: line 6: [: helloworld: integer expression expected
test.sh: line 8: [: helloworld: integer expression expected
test.sh: line 10: [: helloworld: integer expression expected
I'm not sure I understand you.
waldek@helloworld:~$
```
We can check if the input is *really* a number and redirect the error to `/dev/null`.
If the number is not a real number we can't continue so we `exit` the script.
Every command you execute on the command line has an **exit code**.
You can [read up](https://en.wikipedia.org/wiki/Exit_status) a bit on what they are but the most important things to know are:
* the code is always a **number**
* on our systems it's a [uint8](https://en.wikipedia.org/wiki/Integer_(computer_science)#Common_integral_data_types) which means a value between **0** and **255**
* the [**convention**](https://tldp.org/LDP/abs/html/exitcodes.html) is that `0` means **success**, everything else is an **error**
In your bash shell, the variable `$?` always references the *last* exit status code.
We can discover it's behaviour as follows.
```
waldek@debian:~$ ls does_exist
does_exist
waldek@debian:~$ echo $?
0
waldek@debian:~$ ls does_not_exist
ls: cannot access 'does_not_exist': No such file or directory
waldek@debian:~$ echo $?
2
waldek@debian:~$ echo $?
0
waldek@debian:~$
```
The **first**`echo $?` prints the exit code of `ls does_exist`.
The **second** prints the exit code of the *failed* command `ls does_not_exist`.
The **third** prints the exit code of the `echo $?` that failed!
## `test`
The presence of exit codes means we can **evaluate** their value and make **decisions** based on the outcome.
The main workhorse for this is a builtin called `test`.
```
waldek@debian:~$ whatis test
test (1) - check file types and compare values
waldek@debian:~$
```
I **highly** recommend you take some time to read the `man test`.
Because the convention of exit codes is *no news, good news* there are two tiny programs that just serve to output `true` and `false`, where true is `0` and false is `1`.
A little demonstration.
```
waldek@debian:~$ which true
/usr/bin/true
waldek@debian:~$ which false
/usr/bin/false
waldek@debian:~$ man true
waldek@debian:~$ whatis true
true (1) - do nothing, successfully
waldek@debian:~$ whatis false
false (1) - do nothing, unsuccessfully
waldek@debian:~$ true
waldek@debian:~$ echo $?
0
waldek@debian:~$ false
waldek@debian:~$ echo $?
1
waldek@debian:~$ test true == true
waldek@debian:~$ echo $?
0
waldek@debian:~$ test true == false
waldek@debian:~$ echo $?
1
waldek@debian:~$
```
Again, I highly advise you to read the `man test`.
If you did this then the following will make a lot of sense.
The table below is taken from the bash reference manual you can find [here](https://www.gnu.org/software/bash/manual/bash.html#Bash-Conditional-Expressions).
| <divstyle="width:300px">command</div> | status |
| --- | --- |
| -a file | True if file exists. |
| -b file | True if file exists and is a block special file. |
| -c file | True if file exists and is a character special file. |
| -d file | True if file exists and is a directory. |
| -e file | True if file exists. |
| -f file | True if file exists and is a regular file. |
| -g file | True if file exists and its set-group-id bit is set. |
| -h file | True if file exists and is a symbolic link. |
| -k file | True if file exists and its "sticky" bit is set. |
| -p file | True if file exists and is a named pipe (FIFO). |
| -r file | True if file exists and is readable. |
| -s file | True if file exists and has a size greater than zero. |
| -t fd | True if file descriptor fd is open and refers to a terminal. |
| -u file | True if file exists and its set-user-id bit is set. |
| -w file | True if file exists and is writable. |
| -x file | True if file exists and is executable. |
| -G file | True if file exists and is owned by the effective group id. |
| -L file | True if file exists and is a symbolic link. |
| -N file | True if file exists and has been modified since it was last read. |
| -O file | True if file exists and is owned by the effective user id. |
| -S file | True if file exists and is a socket. |
| file1 -ef file2 | True if file1 and file2 refer to the same device and inode numbers. |
| file1 -nt file2 | True if file1 is newer (according to modification date) than file2, or if file1 exists and file2 does not. |
| file1 -ot file2 | True if file1 is older than file2, or if file2 exists and file1 does not. |
| -o optname | True if the shell option optname is enabled. The list of options appears in the description of the -o option to the set builtin (see The Set Builtin). |
| -v varname | True if the shell variable varname is set (has been assigned a value). |
| -R varname | True if the shell variable varname is set and is a name reference. |
| -z string | True if the length of string is zero. |
| -n string string | True if the length of string is non-zero. |
| string1 == string2 | True if the strings are equal. When used with the [[ command, this performs pattern matching as described above (see Conditional Constructs). ‘=’ should be used with the test command for POSIX conformance. |
| string1 = string2 | True if the strings are equal. When used with the [[ command, this performs pattern matching as described above (see Conditional Constructs). ‘=’ should be used with the test command for POSIX conformance. |
| string1 != string2 | True if the strings are not equal. |
I'll be the first to admit that the syntax of `bash` can be confusing and is rarely reader friendly.
A nice, but brief, explication of the nuances of single and double brackets can be found in [this](https://unix.stackexchange.com/questions/306111/what-is-the-difference-between-the-bash-operators-vs-vs-vs) stack overflow post.
The double bracket command are called [compound commands](https://www.gnu.org/software/bash/manual/html_node/Compound-Commands.html).
Up until now all our scripts run from top to bottom, executing each line as they are read.
Most, if not all, programming languages offer some form of looping to **repeat** lines of code either based on a **condition** or for a predefined number of *items*.
## `while` loop
```bash
#!/bin/bash
counter=0
while [[ counter -lt 10 ]]; do
echo $counter
(( counter++ ))
done
```
### Read from a file with a `while` loop
Below you can see I have two files, one is my *shopping list*, the other my script.
The script loops over each item I have to buy and prints me a verbose message.
```
waldek@debian:~$ cat shopping.list
6 eggs
1 bread
2 milk
4 pasta
waldek@debian:~$ bash test.sh shopping.list
you need to buy: 6 eggs
you need to buy: 1 bread
you need to buy: 2 milk
you need to buy: 4 pasta
waldek@debian:~$
```
One way to do this is with a `while` loop.
The syntax is a follows.
```bash
#!/bin/bash
while read line; do
echo "you need to buy: $line"
done < $1
```
The syntax is not what I would call *pretty* but it is quite effective.
The **file**`$1` is redirected **into** the `while` loop which performs a `read`.
**When** there are no more lines in the file, the condition **fails** and the loop exits.
### Read from a pipe with a `while` loop
We can use a *very* similar construct to read data from a pipe.
The utilisation is as follows.
```
waldek@debian:~$ cat shopping.list | ./test.sh
you need to buy: 6 eggs
you need to buy: 1 bread
you need to buy: 2 milk
you need to buy: 4 pasta
waldek@debian:~$
```
And the code that does this as such.
```bash
#!/bin/bash
while IFS= read line; do
echo "you need to buy: $line"
done
```
What on earth is this `IFS=`?
I'm glad you [asked](https://unix.stackexchange.com/questions/184863/what-is-the-meaning-of-ifs-n-in-bash-scripting)!
## `for` loop
Ah, my favorite loop!
While the `while` loop runs as long as a condition is `true`, the `for` loop **iterates** over *items*.
The simplest way to visualize it would be as follows.
For each *friend* in my list of friends I'll say hello.
```
waldek@debian:~$ ./test.sh
these are my friends: adam camille alice bob steve
hello adam!
hello camille!
hello alice!
hello bob!
hello steve!
waldek@debian:~$
```
The syntax to achieve this goes as follows.
```bash
#!/bin/bash
friends="adam camille alice bob steve"
echo "these are my friends: $friends"
for f in $friends; do
echo "hello $f!"
done
```
### Counter loops with `for`
We can do a counter loop as well via a `for` loop.
An easy way to do this is by using the `seq` program.
Remember to read the `man seq` for more information.
```bash
#!/bin/bash
for number in $(seq 0 10); do
echo "number is $number"
done
```
### Finicky behaviour
`bash` is not the prettiest language and the difference between single and double quotes can be a bit confusing.
```bash
#!/bin/bash
for animal in dog cat horse 'racing horse' whale; do
echo "$animal says: I'm an animal..."
done
```
Which gives the following output.
```
waldek@debian:~$ ./test.sh
dog says: I'm an animal...
cat says: I'm an animal...
horse says: I'm an animal...
racing horse says: I'm an animal...
whale says: I'm an animal...
waldek@debian:~$
```
# Coding challenge - pipe or argument plus action!
Can you expand the previous coding challenge, where you perform a conditional logic on the input of the script and actually *use* the incoming data?
[This](https://gitea.86thumbs.net/waldek/python_course_doc) repository has a twenty day course to learn python written by me.
The main file you need to follow is [this](https://gitea.86thumbs.net/waldek/python_course_doc/src/branch/master/learning_python3.md) one.
Some practical exercises can be found [here](https://gitea.86thumbs.net/waldek/linux_course_doc/src/branch/master/modules/qualifying/exercise_python.md) together with the needed [source files](https://gitea.86thumbs.net/waldek/linux_course_doc/src/branch/master/modules/qualifying/assets/files.tar.gz).
## Vim as an IDE
I made a tutorial on the essentials of [vim customization](https://gitea.86thumbs.net/waldek/linux_course_doc/src/branch/master/modules/qualifying/learning_vim_configuration.md).
My real world configuration can be found at [this](https://gitea.86thumbs.net/waldek/vimrc) repository.