Skip to content
Extraits de code Groupes Projets
Valider ba99d2b2 rédigé par Pierre-Yves Barriat's avatar Pierre-Yves Barriat
Parcourir les fichiers

Merge branch 'feat/add_exercises' into 'master'

Add exercises, some edits

See merge request barriat/learning-bash!1
parents f59befd8 b7a75d70
Aucune branche associée trouvée
Aucune étiquette associée trouvée
1 requête de fusion!1Add exercises, some edits
......@@ -80,32 +80,19 @@ echo $STRING
---
# Shell syntax rules
Shells use 3 **"standard I/O streams"**
- `stdin` is the standard input stream, which provides input to commands
- `stdout` is the standard output stream, which displays output from commands
- `stderr` is the standard error stream, which displays error output from commands
Shell has several **meta-characters** and **control operators**
> `|`, `&`, `>`, `;` , etc.
---
# Bash environment
In a Bash shell many things constitute your environment
- the form of your prompt
- the form of your 'prompt' (what comes left of your commands)
- your home directory and your working directory
- the name of your shell
- functions that you have defined
- etc.
Environment includes many variables that may have been set **by bash** or **by you**
> Access the value of a variable by prefixing its name with `$`
**Access the value of a variable by prefixing its name with `$`**
---
......@@ -119,8 +106,11 @@ Environment includes many variables that may have been set **by bash** or **by y
| `SHELL` | the name of the shell |
| `UID` | the numeric user id of the logged-in user |
So to get the value of `USER` you would use `$USER` in bash code
> You can use special files to control bash variables : `$HOME/.bashrc`
---
# Bash Scripting basics
......@@ -131,13 +121,15 @@ By naming convention, bash scripts end with `.sh`
A good practice is to define a `shebang` : first line of the script, `shebang` is simply an absolute path to the shell interpreter (see `echo $SHELL` result)
> combination of `bash #` and `bang !`
The usual shebang for bash is `#!/bin/bash`
---
### Comments start with `#`
On a line, any characters after `#` will be ignored -- *with the exception of* `#!`
On a line, any characters after `#` will be ignored (with the exception of `#!`)
```bash
echo "A comment will follow." # Comment here.
......@@ -150,7 +142,7 @@ echo "A comment will follow." # Comment here.
- Use something your editor makes easy (**Vim** uses `Tab`)
---
<!-- JDF: je pnseque c'est trop tôt pour ceci
### Command separators
Commands can be combined using **meta-characters** and **control operators**
......@@ -167,11 +159,11 @@ $ cd myfolder || ls # if failed cd to myfolder, `ls` will run
```
---
-->
# Permissions and execution
- Bash script is nothing else just a **text file** containing instructions to be executed sequentially
> by default in Linux, a new text file is **-rw-r--r--** (or 644)
> by default in Linux, a new text file's permissons are **-rw-r--r--** (or 644)
- You can run the script `hello_world.sh` using
* `sh hello_world.sh`
* `bash hello_world.sh`
......@@ -182,6 +174,15 @@ $ cd myfolder || ls # if failed cd to myfolder, `ls` will run
# Hands-on exercise
Your first bash script:
1. create a folder `bash_exercises` and go there
1. use your favourite editor (vim, obviously) to create a new file called `exercise_1.sh`
1. write some code in it to display the current working directory as:
> The current directory is : /home/me/bash_exercises
4. make the file executable
5. run it !
---
# Variables and data types in Bash
......@@ -190,22 +191,29 @@ Variables let you store data : **numeric values** or **character(s)**
You can use variables to read, access, and manipulate data throughout your script
**There are no data types in Bash**
Set the variable values in the following ways :
**You don't specify data types in Bash**
- assign directly : `greeting="Welcome"` or `a=4`
- access the variable value using `$`: `echo $greeting`
- assign based on variable: `b=$a`
---
And then access using `$`: `echo $greeting`
**!!!** no space before or after `=` in the assignation **!!!**
> `myvar=Hello World` :boom: `-bash: World: command not found`
`myvar = "Hello World"` :boom:
---
### Quotes for character(s) `" '`
Double will do variable substitution, single will not
Double will do **variable substitution**, single will not:
```
$ echo "my home is $HOME"
my home is /home/me
$ echo 'my home is $HOME'
my home is $HOME
```
### Command Substitution
......@@ -231,7 +239,7 @@ myvar=$( ls )
* Avoid using **reserved keywords**, such as `if`, `then`, `else`, `fi`, and so on...
* **Never** name your private variables using only **UPPERCASE** characters
* **Never** name your private variables using only **UPPERCASE** characters to avod conflicts with builtins
---
......@@ -242,13 +250,16 @@ myvar=$( ls )
| `+` `-` `\*` `/` | addition, subtraction, multiply, divide |
| `var++` | increase the variable var by 1 |
| `var--` | decrease the variable var by 1 |
| `%` | modulus (Return the remainder after division) |
| `%` | modulus (remainder after division) |
Several ways to go about arithmetic in Bash scripting :
`let`, `expr` or using **double parentheses**
<!-- JDF: probablement pas la bonne place pour ça
Return the length of a variable : `${#var}`
-->
<!-- JDF: a mon avis pas utile
---
- `let` make a variable equal to an expression
......@@ -273,7 +284,7 @@ a=$( expr 10 - 3 )
- double parentheses : return the result of the expression
> it is the **preferred method**
-->
---
```bash
......@@ -298,6 +309,23 @@ echo $b # 16
# Conditional statements
Use:
* `if condition; then` to start conditional block
* `else` to start alternative block
* `elif` to start alternative conition block
* `fi` to close conditional block
The following operaors can be used beween conditions:
* `||` means **OR**
* `&&` mean **AND**
---
# Conditional exemple
(Dont pay too much attention to the details yet)
```bash
#!/bin/bash
echo "Please enter a number: "
......@@ -313,14 +341,28 @@ else
fi
```
> read the **standard input stream** with the `read` command
Read the **standard input stream** with the `read` command
---
### `test` command
| Operator | Description |
| ------------ | --------------- |
| `! EXPRESSION` | The EXPRESSION is false|
| `-n STRING` | The length of STRING is greater than zero |
| `-z STRING` | The lengh of STRING is zero (ie it is empty) |
| `STR1 = STR2` | STRING1 is equal to STRING2 |
| `STR1 != STR2` | STRING1 is not equal to STRING2 |
| `INT1 -eq INT2` | INTEGER1 is numerically equal to INTEGER2 (or `==`) |
| `INT1 -gt INT2` | INTEGER1 is numerically greater than INTEGER2 |
| `INT1 -lt INT2` | INTEGER1 is numerically less than INTEGER2 |
| `INT1 -ne INT2` | INTEGER1 is numerically not equal to INTEGER2 |
---
### Build conditions with the `test` command
```bash
test -s /etc/hosts && echo "Exists and not empty"
test -s /etc/hosts
```
| Operator | Description |
......@@ -334,20 +376,6 @@ test -s /etc/hosts && echo "Exists and not empty"
---
| Operator | Description |
| ------------ | --------------- |
| `! EXPRESSION` | The EXPRESSION is false|
| `-n STRING` | The length of STRING is greater than zero |
| `-z STRING` | The lengh of STRING is zero (ie it is empty) |
| `STR1 = STR2` | STRING1 is equal to STRING2 |
| `STR1 != STR2` | STRING1 is not equal to STRING2 |
| `INT1 -eq INT2` | INTEGER1 is numerically equal to INTEGER2 (or `==`) |
| `INT1 -gt INT2` | INTEGER1 is numerically greater than INTEGER2 |
| `INT1 -lt INT2` | INTEGER1 is numerically less than INTEGER2 |
| `INT1 -ne INT2` | INTEGER1 is numerically not equal to INTEGER2 |
---
### Conditional: light variation
Check an expression in the `if` statement ?
......@@ -394,6 +422,13 @@ esac
# Hands-on exercise
1. In your `bash_exercises` folder create a new bash file called `exercise_2.sh` and make it executable
2. Ask the user for two numbers smaller or equal to 100, put them in variables `NUMBER1` and `NUMBER2`
3. Do the following only if the number are smaller than 100:
4. Check if at least one of the numbers is a multiple of 3, and tell the user
5. Otherwise, check if both numbers are even, and tell the user
6. If the user's numbers ar too big, tell them
---
# Arrays
......@@ -401,7 +436,6 @@ esac
### Indexed arrays
```bash
#!/bin/bash
# Declare an array with 4 elements
my_array=( 'Debian Linux' 'Redhat Linux' Ubuntu OpenSUSE )
# get number of elements in the array
......@@ -423,16 +457,16 @@ echo ${my_array[@]}
By default, a bash array is an indexed array : need to use the `declare` command
```bash
#!/bin/bash
declare -A acronyms
declare -A acronyms # -A for associative array
acronyms[ACK]=Acknowledgement
acronyms[EOF]="End of Frame"
echo ${acronyms[ACK]}
if [ ${acronyms[EOF]+_} ]; then echo "Found"; else echo "Not found"; fi
```
The variable expansion `${MYVAR+ABC}` expands to "ABC" is `MYVAR` is set and to nothing otherwise.
```bash
#!/bin/bash
declare -A countries=( [ALB]=Albania [BHR]=Bahrain [CMR]=Cameroon [DNK]=Denmark [EGY]=Egypt )
echo ${countries[@]}
echo ${!countries[@]}
......@@ -484,7 +518,6 @@ Basic loop structures in Bash scripting :
### Examples
```bash
#!/bin/bash
# Basic while loop
counter=0
while [ $counter -lt 3 ]; do
......@@ -493,7 +526,6 @@ while [ $counter -lt 3 ]; do
done
```
```bash
#!/bin/bash
# Basic until loop
counter=1
until [ $counter -gt 10 ]; do
......@@ -530,31 +562,6 @@ for i in $( cat file.txt )
---
### How to Read a File Line By Line
```bash
#!/bin/bash
# How to Read a File Line By Line
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
```
> by default `read` removes all leading and trailing whitespace characters such as spaces and tabs
<!--
`while IFS= read -r line`
The internal field separator (`IFS`) is set to the empty string to preserve whitespace issues
The `-r` option is used not to allow backslashes to escape any characters
-->
---
```bash
#!/bin/bash
# How to iterate over keys or values of an Array
......@@ -575,6 +582,15 @@ done
# Hands-on exercise
Speed game:
1. Use the following webiste to get a list of random words: https://randomwordgenerator.com and put them together in a variable
2. Register the start time with `date +%s` and put it in a variable `TSTART`
3. Loop over the words and ask the user to give the number of letters. Put the answers in an associative array using the words as keys and the answers as values
4. Register the end time in `TEND`
5. Display the total run time
6. Loop over the associative array to compute the score (number of good answers) and show it to the user.
---
# Arguments - Positional Parameters
......@@ -638,6 +654,34 @@ bash test_arg.sh -f 'John Smith' -a 25 -u john
---
# Input/Output streams
Shells use 3 standard I/O streams
- `stdin` is the standard input stream, which provides input to commands
- `stdout` is the standard output stream, which displays output from commands
- `stderr` is the standard error stream, which displays error output from commands
Shell has several **meta-characters** and **control operators**
> `|`, `&`, `>`, `;`, `<`, etc.
---
# Control operators
| Character | Effect |
| ---- | --- |
| `;` | Normal separator between commands |
| `&&` | Execute next command only if command succeeds |
| `\|\|` | Execute next command only if command fails |
| `&` | Don't wait for result of command before starting next command |
| `\|` | Use output of command as input for the next command |
| `> file_desc` | Send stdandard output of command to file descriptor |
| `< file_desc` | Use content of file descriptor as input |
---
# Redirections
Use the meta-character `>` in order to control the output streams `stdout` and `stderr` for a command or a bash script
......@@ -660,6 +704,31 @@ cat $1 2>&1
---
### How to Read a File Line By Line : input redirection
```bash
#!/bin/bash
# How to Read a File Line By Line
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
```
> by default `read` removes all leading and trailing whitespace characters such as spaces and tabs
<!--
`while IFS= read -r line`
The internal field separator (`IFS`) is set to the empty string to preserve whitespace issues
The `-r` option is used not to allow backslashes to escape any characters
-->
---
# Return codes
Linux command returns a status when it terminates normally or abnormally
......@@ -701,6 +770,7 @@ command && echo "success" || echo "failed"
_files="$@"
[[ "$_files" == "" ]] && { echo "Usage: $0 file1.png file2.png"; exit 1; }
```
<!-- Attention utilisatin du [[ ]] pas encore expliqué ? -->
---
......@@ -728,7 +798,6 @@ Functions must be declared **before** they are used
## Variables Scope
```bash
#!/bin/bash
# Define bash global variable
# This variable is global and can be used anywhere in this bash script
var="global variable"
......@@ -754,13 +823,11 @@ echo $var
Bash functions don’t allow you to return a value when called
After completion, the return value is the status of the last statement (so 0-255)
After completion, the return value is the **status** of the last statement (so 0-255)
Can be specified by using `return` :
It can also be specified manually by using `return` :
```bash
#!/bin/bash
my_function () {
echo "some result"
return 55
......@@ -771,10 +838,10 @@ echo $?
---
Return an arbitrary value from a function : assign the result of the function
Return an arbitrary value (different from a return code) from a function :
* Assign the result of the function
```bash
#!/bin/bash
my_function () {
func_result="some result"
}
......@@ -782,10 +849,9 @@ my_function
echo $func_result
```
Better way is to send the value to `stdout` using `echo`
* Better way is to send the value to `stdout` using `echo`
```bash
#!/bin/bash
my_function () {
local func_result="some result"
echo "$func_result"
......@@ -813,7 +879,7 @@ print_something Mars
```bash
#!/bin/bash
ls () {
command ls -lh
command ls -lh # 'command' keyword prevents ambiguity
}
ls
```
......@@ -822,6 +888,11 @@ ls
# Hands-on exercise
1. Write a script called `exercise_3.sh` expecting **2 arguments**. If not exactly two arguments are provided, exit with an error and show a "usage" message to the user.
2. Write a function taking a **folder path** and an **extension** as arguments and giving the list of matching files to the user
3. Imagine you are running jobs taking data from two folders, each with a dedicated extension. Use the two arguments of the script as the name of the two folders and **get the two lists of files**.
4. Since your work needs to read these files, **check that all files can be read**. If some files cannot be read, display their name to the user.
---
# Shell vs Environment Variables
......@@ -846,12 +917,12 @@ bash test.sh
# Subshells
* subshell is a "child shell" spawned by the main shell ("parent shell")
* subshell is separate instance of the command process, run as a new process
* unlike calling a shell script, subshells inherit the same variables as the original process
* a subshell allow you to execute commands within a separate shell environment = *Subshell Sandboxing*
* A subshell is a "child shell" spawned by the main shell ("parent shell")
* A subshell is a separate instance of the command process, run as a new process
* Unlike calling a shell script, subshells inherit the same variables as the original process
* A subshell allows you to execute commands within a separate shell environment = *Subshell Sandboxing*
> useful to set temporary variables or change directories without affecting the parent shell's environment
* subshells can be used for parallel processing
* Subshells can be used for parallel processing
---
......@@ -871,11 +942,30 @@ Or :
bash -c "command1; command2; command3"
```
> Reminder : variables in a subshell are not visible outside the block of code in the subshell
> Reminder : variables in a subshell are **not** visible outside the block of code in the subshell
---
## Differences between **Sourcing** and **Executing** a script
- source a script = execution in the current shell
> variables and functions are valid in the current shell after sourcing
- execute a script = execution in a new shell (in a subshell of the current shell)
> all new variables and functions created by the script will only live in the subshell
Source a script using `source` or `.`
```bash
source myScript.sh
. myScript.sh
```
> official one is `.` Bash defined `source` as an alias to the `.`
---
### Sourcing Bash scripts
## Example
```bash
#!/bin/bash
......@@ -894,6 +984,7 @@ greeting $COUNTRY
```
```bash
COUNTRY="France"
source myScript.sh
echo $COUNTRY
greeting $COUNTRY
......@@ -901,25 +992,6 @@ greeting $COUNTRY
---
Differences between **Sourcing** and **Executing** a script
- source a script = execution in the current shell
> variables and functions are valid in the current shell after sourcing
- execute a script = execution in a new shell (in a subshell of the current shell)
> all new variables and functions created by the script will only live in the subshell
Source a script using `source` or `.`
```bash
source myScript.sh
. myScript.sh
```
> official one is `.` Bash defined `source` as an alias to the `.`
---
### Running parallel processes in subshells
Processes may execute in parallel within different subshells
......@@ -933,7 +1005,7 @@ job() {
i=0
while [ $i -lt 10 ]; do
echo "${i}: job $job_id"
i=$[$i+1]
(( i++ ))
sleep 0.2
done
}
......@@ -969,10 +1041,6 @@ time ./manager_par.sh
---
# Hands-on exercise
---
# Debug
Tips and techniques for debugging and troubleshooting Bash scripts
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter