Bash Shell

De Marijan Stajic | Wiki
Version datée du 21 mai 2024 à 13:23 par Marijan (discussion | contributions)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

Basic informations

Types

There exist multiple different types of shells in Linux :

  • Bourne Shell (sh)
  • C Shell (csh or tcsh)
  • Korn Shell (ksh)
  • Z Shell (zsh)
  • Bourne Again Shell (bash)

All of them serve a common purpose, which is to facilitate communication between the user and the operating system.

To check the shell being used, we have to use the following command:

marijan$ echo $SHELL

/bin/bash

Bash is probably the most popular shell because of these features.

It's possible to change the default shell using the following command :

marijan$ chsh

Enter the new value, or press enter for the default
         Login Shell [/bin/bash]: /bin/sh

It's important to specify /bin/ before the type of shell you want to use.

Features

Bash has multiple interesting features that make him the most used type of shell. Here is some examples :

  • Auto-Completion : When you are typing a command, you don't really need to type in the entire command. Indeed, you can type the beginning of the command or the name of your file or folder, and then press tab to auto-complete. Here is a example with the command ls :
marijan$ ls Docu [PRESS TAB] Documents
  • Alias : You can create an alias if you would like to save time or if it's easier for you to remember some commands. For instance, here we have created an alias for the date command :
marijan$ alias dt=date
marijan$ dt
Thu 21 Mar 2024 04:09:44 PM CET
  • History : We can view the history of commands by using the following command :
marijan$ History
ls Documents
alias dt=date
dt

Variables

Variables are used to store values and to print it in the shell, you have to use the dollar ($) symbol.

You can create your own variable and set it to a sentence :

marijan$ var1='Hello my name is Marijan'
marijan$ echo $var1
Hello my name is Marijan

If you would like to include special characters, you have to use double quotes :

marijan$ var2="H3ll0 H0w @re y0_u ?"
marijan$ echo $var2
H3ll0 H0w @re y0_u ?

To include your variable in a sentence in your shell, you have to enclose the variable in brackets :

marijan$ Here is a big sentence ${var2}¨
Here is a big sentence H3ll0 H0w @re y0_u ?

Define a variable as shown here apply only to the current shell session. After a restart, the value will be deleted. If you want to make them persistent, you have to add them to the file ~/.profile or ~/.pam_environment.

Environment

Environment variables are used to store information about the user's login session. To see a list of all of these variables, we can use the following command :

marijan$ env
SHELL=/bin/bash
LANGUAGE=en_US:en
PWD=/root
LOGNAME=root
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/root
LANG=en_US.UTF-8
SSH_CONNECTION=212.147.110.1 51872 172.16.0.15 22
XDG_SESSION_CLASS=user
TERM=xterm
USER=marijan
SHLVL=1
XDG_SESSION_ID=17
XDG_RUNTIME_DIR=/run/user/0
SSH_CLIENT=212.147.110.1 51872 22
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SSH_TTY=/dev/pts/0
_=/usr/bin/env

To set an environment variable, you have to use the export command and usually, all of the environment variables are in uppercase :

marijan$ export MARIJAN=msa

To change the information displayed on the prompt, we have to modify the value of the default variable PS1. Indeed, there exist several options, for instance :

\d: Date of the day ;
\h: Name of the machine ;
\H: Full name of the machine (with the domain) ;
\t: Current time ;
\u: Name of the user ;
\w: The current working directory ;
\W: The basename of the current directory. ;

By default, PS1 shows only \u (name of the user) and \h (name of the machine) :

marijan@debian-vm:~$

It's also possible to change this information by altering the prompt string directly, without using specific options. For example :

marijan@debian-vm:~$ PS1="debian-server"
debian-server:~$

By the way, [~] represents the present working directory and $ is the user prompt symbol.

Scripting

To save time and increase productivity by setting up a solution for automating repetitive or complex tasks on a Linux system, you can use Shell Scripts. Here is a list of a few tasks you can accomplish with them:

  • Automating Daily Backups ;
  • Automating Installations ;
  • Periodically Monitoring the System ;
  • Raising Alarms and Sending Notifications ;

If you want to set up scripts with Shell, you have to know the basics of Linux.


Théorie sur les systèmes d'exploitation Linux

Run

Once the script is created, you have two ways to run it. The first option is to execute the script using this following command for example in bash :

marijan$ bash marijan-first-script.sh

If your system is already using Bash, you don't have to specify this. It depends on which shell you are using. Follow the second option to check.

The second option, if you want to run your script as a command, is to remove the .sh extension (a good practice because most Linux commands don't include an extension) and inform the operating system that you intend to run this script.

So to know if here is a command or not, the operating system looks at the paths configured in the environnement variable $PATH to locate the executable or script for that command.

marijan$ echo $PATH

If you are not currently using a superadmin user, you need to authorise execute permissions for the user on the script.

marijan$ ls -l /home/marijan/marijan-first-script
-rw-rw-r-- 1 marijan marijan 24 Mar 14:35 marijan-first-script

To do this, simply run the following command :

marijan$ chmod +x /home/marijan/marijan-first-script
marijan$ ls -l /home/marijan/marijan-first-script
-rwx-rwx-r-x marijan marijan 24 Mar 14:35 marijan-first-script

Command Line Arguments

When you want to assign a value to a variable without editing the entire script, as it needs to be done differently each time, you can use command line arguments.

Let's take an example with the following script :

marijan$ vim create-and-launch-rocket
mission_name=saturne_mission

mkdir $mission_name

rocket-add $mission_name
rocket-start-power $mission_name
rocket-internal-power $mission_name
rocket-start-sequence $mission_name
rocket-start-engine $mission_name
rocket-lift-off $mission_name

rocket_status=$(rocket-status $mission_name)

In this script, the value of mission_name needs to be changed each time the script is executed. To avoid modifying the value directly in the script, you can replace the value of mission_name with $1.

marijan$ vim create-and-launch-rocket
mission_name=$1

mkdir $mission_name

rocket-add $mission_name
rocket-start-power $mission_name
rocket-internal-power $mission_name
rocket-start-sequence $mission_name
rocket-start-engine $mission_name
rocket-lift-off $mission_name

rocket_status=$(rocket-status $mission_name)

$1 represents the argument on the command line following the script name.

marijan$ create-and-launch-rocket(=$0) moon_mission(=$1)

The number "(=$0)" is not on the command line! this is just a example for understanding what I mean.

You can do that with any numbers ($2, $3, $4, $...) by using the same method.

Input

If you want to define the value of a variable using a prompt, you can use the read command in your shell script.

marijan$ vim create-and-launch-rocket
read mission_name

mkdir $mission_name

rocket-add $mission_name
rocket-start-power $mission_name
rocket-internal-power $mission_name
rocket-start-sequence $mission_name
rocket-start-engine $mission_name
rocket-lift-off $mission_name

rocket_status=$(rocket-status $mission_name)

This will ask you to enter a name to define the value of the variable.

To specify to the user exactly what they need to write, you can add text using the -p option :

marijan$ vim create-and-launch-rocket
read -p "Enter mission name:" mission_name

mkdir $mission_name

rocket-add $mission_name
rocket-start-power $mission_name
rocket-internal-power $mission_name
rocket-start-sequence $mission_name
rocket-start-engine $mission_name
rocket-lift-off $mission_name

rocket_status=$(rocket-status $mission_name)

marijan$ create-and-launch-rocket
Enter mission name:

Operators

Arithmetic

The arithmetic operators allow operations on data, particularly calculations.

Symbol Meaning
+ Addition
- Subtraction
\* Multiplication
/ Division

To use arithmetic operations, you need to use the following commands:

marijan$ expr 1 + 1
3

You can also perform these operations with variables :

marijan$ $A=6
marijan$ $B=3
marijan$ $A / $B
3

To perfom these operations on a programming language like C, you have to use double parentheses :

marijan$ echo $(( A * B ))
18

Comparaison

To compare a variable and a value, you can use operators such as :

Example Description
["abc" = "abc" ] If string1 is exactly equal to string2
["abc" != "abc" ] If string1 is not exactly equal to string2
[ 5 -eq 5 ] If number1 is equal to number2
[ 5 -ne 5 ] If number1 is not equal to number2
[ 6 -gt 5 ] If number1 is greater than number2
[ 5 -lt 6 ] If number1 is less than number2

These operators are generic; you can find them in various scripting languages such as PowerShell.

Specific to Bash, here are some unique operators:

Example Description
"abcd" = *bc* If string2 contains a part of string1
[[ "abc" = "ab[cd]" ]] or "abd" = "ab[cd]" ]] If the third character of abc is c or d
[[ "abc" > "bcd" ]] If "abc" comes after "bcd" when sorted in alphabetical (lexographical) order
[[ "abc" < "bcd" ]] If "abc" comes before "bcd" when sorted in alphabetical (lexographical) order

To use these specific operators, you have to use double brackets instead of just one.

To check if two conditions are true before executing a command, you can use the double ampersand (&&) operator :

[ COND 1 ] && [ COND 2 ]

To check if either of the conditions is true, you can use the double pipe (||) operator :

[ COND 1 ] || [ COND 2 ]

If you are using operators specific to Bash, you can also use double brackets ([[ ]]).

There are operators for file as well :

Example Description
[ -e FILE ] If file exists
[ -d FILE] If file exists and is a directory
[ -s FILE ] If file exists and has size greater than 0
[ -x FILE ] If the file is executable
[ -w FILE ] If the file is writeable

Conditional Logic

If, Elif and Else

To execute a part of the code only for a specific condition, use the following structure:

if [ $rocket_status = "failed" ]
then
    rocket-debug $mission_name
fi

Here, the structure begins with if followed by the value of the variable you want to control in bracket. In this example, if the value of the variable is equal to failed, the subsequent commands will be executed. To run the condition, you must use then after the if. To close the block, use fi (which is if reversed).

You must to add spaces between the variable, the equal sign, and the value.

If you want to display something when the variable used in the condition has a different value, you can use else if, which is written as elif in Bash :

if [ $rocket_status = "failed" ]
then
    rocket-debug $mission_name

elif [ $rocket_status = "success" ]
then
    echo "This is successful"
fi

It has the same structure as if, you specify the value of the variable used in the condition and add then followed by the commands. To close it, you also use fi.

For the rest, if the condition is different from the two specified conditions, you can use else :

if [ $rocket_status = "failed" ]
then
    rocket-debug $mission_name

elif [ $rocket_status = "success" ]
then
    echo "This is successful"

else
    echo "This is not failed or successful"
fi

Loops

For

If you need to run the same command multiple times, you should use for loops. Specifically, if you need to run the same command with different values each time, use for loops.

for mission in saturn-mission mars-mission lunar-mission
do
    create-and-launch-rocket mission
done

First, define the variable that contains the missions. In this case, it will be saturn-mission, mars-mission, lunar-mission for mission.

Then, use the keyword do to begin the loop and execute the commands you want to run. By using the loop with the mission content, you avoid repeating the same commands multiple times.

To finish, close the loop with done.

For the content of the default variable for the For loops, you can include a file instead of write each words :

marijan$ cat mission-names.txt
lunar-mission
mars-mission
saturn-mission
switzerland-mission
vtx-mission

for mission in $(cat mission-names.txt)
do
    create-and-launch-rocket mission
done

You just need to use the cat command followed by the file path. A best practice is to use $ and parentheses to reference the file, rather than just writing cat mission-name.txt.

You can also run numbers by specifying the numbers directly. To run all the numbers between two specified numbers, you can use the following syntax :

for mission in {0..100}
do
    create-and-launch-rocket mission
done

You just need to use brackets and include the two numbers with two periods (..) between them.

The for loop can also consist of three distinct parts. The first part represents the initial value of the loop's main variable. The second part corresponds to the condition that, when met, allows the third part to increase the value of that variable.

for (( mission = 0 ; mission <= 10; mission++ ))
do
    create-and-launch-rocket mission
done

Here, we have an initial value of 0, a condition that states that as long as we are strictly less than 10, we need to increment by 1. You have to put all of these in parenthese.

While

To avoid repetition in your script when waiting for something before proceeding to the next step, you can use while loops.

while [ $rocket_satuts = "launching" ]
do
    sleep 2
    rocket_status=roocket-status $mission_name
done

For example, while the variable $rocket_status is set to launching, the script will remain in the loop. Once $rocket_status changes to a different value, such as success or failed, the loop will end.

You can also create interaction with the user to automate some processes :

while true
do
        echo "1. Shutdown"
        echo "2. Restart"
        echo "3. Exit menu"
        read -p "Enter your choice: " choice

        if [ $choice -eq 1 ]
        then
                echo "Shutdown of the system."
                sleep 1
                echo "Shutdown of the system.."
                sleep 1
                echo "Shutdown of the system..."
                sleep 1
                shutdown now

        elif [ $choice -eq 2 ]
        then
                echo "Reboot of the system."
                sleep 1
                echo "Reboot of the system.."
                sleep 1
                echo "Reboot of the system..."
                sleep 1
                shutdown -r now
        elif [ $choice -eq 3 ]
        then
                break
        else
                continue
        fi
done

In this scenario, the user is prompted to choose a number between 1 and 3. If the user doesn't make a selection, the while loop will continue prompting them to choose. However, if the user presses 3, the loop will end by a break and setting the while condition to false.

Case Statements

To avoid using conditional logic for choosing between several options in Bash, you can use case statements to optimise the code.

while true
do
        echo "1. Shutdown"
        echo "2. Restart"
        echo "3. Exit menu"
        read -p "Enter your choice: " choice

        case $choice in
                1) shutdown now
                   ;;
                2) shutdown -r now
                   ;;
                3) break
                   ;;
                *) continue
                   ;;
        esac
done

Here, the conditional logic is replaced by a case statement that includes a choice variable, which is set when the prompt asks the user to choose between options 1, 2, and 3.

You just have to write case followed by the variable and in. Then define each case option with a number, separated by semicolons (;;). Finally, close the structure by using esac.

Functions

To avoid repetition in a script, you can use functions. For example, if you need to repeat a message multiple times throughout the script, you can define it in a function. Then, if you need to update the message, you can simply update the function.

function welcome_message() {
    echo "Bienvenue sur mon wikipédia"

}

welcome_message
Bienvenue sur mon wikipédia

To create a function, define the function name followed by parentheses(), and then enclose the code in bracket.

Shebang

Different types of shells may not be compatible with all scripts. For instance, a script written for Bash might not work with Dash.

To avoid this problem, you can specify the shell to use when running the script or define the correct shell in the $PATH variable.

Alternatively, you can use the shebang at the beginning of the script to indicate which shell the script should use, eliminating the need to define the right $PATH or specify the shell type each time you execute the script.

#!/bin/bash
for mission in {0..10}
do
    create-and-launch-rocket mission
done

To do this, add #!/bin/bash at the top of the script. This way, you don't need to specify the shell type when executing the script.

Exit Codes

When you enter a command in Linux, if the command is misspelled or does not exist, the shell should return an error.

This is due to the exit code status. If the command is incorrect, the exit code will be greater than 0, indicating failure. However, if the command is correct, the exit code will be 0, signifying success.

It is advisable to implement checks for the exit code in your script to handle errors appropriately.

if [ $rocket_status = "failed" ]
then
    rocket-debug $mission_name
    exit 1

elif [ $rocket_status = "success" ]
then
    echo "This is successful"
fi

To set up a exit code, you just have to add exit 1 in your script.

Shell check

If you want to check your code for potential issues, you can install ShellCheck.

marijan$ apt-get install shellcheck

Then, execute the script file (.sh). This will help you identify any potential errors.

marijan$ shellcheck menu.sh

In menu.sh line 10
    rea -p "Enter your choice: " choice
    ^-- SC2148: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.