Skip to content

Download C04-Practice-EN.pdf
Download SLIDE-C04-Practice-EN.pdf

How to have a beautiful source code

The return code

You have to control the return code ($?). In function you must use the command return <ReturnCode>, and in the main function you must use the command exit <ReturnCode>

Beware the return command immediately exits the function. The exit command terminates the script (even if the exit command is inside a function)

Check your input

However the input data is retrieved, it should always be checked.

  • if a file must be read, it must already be known whether the file exists
  • if you have to do an arithmetic operation, you have to know if it's numbers (pay attention to the division by 0)
  • ...

A lot of checking exist in test command (see Comparison rules).

Indentation

Do you prefer this code :

function action () { echo -ne "${1}\t";shift;${*};rc=${?};\
[ ${rc} -eq 0 ] && echo '[OK]' || \
echo '[KO]';return ${rc};}

or this ?


function action () {
    echo -ne "${1}\t"
    shift
    ${*}
    rc=${?}
    [ ${rc} -eq 0 ] && echo '[OK]' || echo '[KO]'
    return ${rc}
}

I prefer the second script. The intention is a matter of taste. But it must at least be homogeneous throughout the script. remember to ventilate your code (an empty line is not expensive, but it makes the code more readable).

Comments

Add a lot of comments in your script. Why ?

  • if you take your code after 6 months, you will understand it more easily
  • if you give your code to someone else, they will understand it more easily
  • the user of your code will understand these errors more easily
  • ...

Script Construction

All project in test / production enivronement are based on requirement.

This requierments are necessary to ve sure that the final creation do what we/they want.

Requierement

this script aims to classify the former students present during a meeting

Here we ll create a script with this requierement :

  • The script name is : alumni-reunion.sh

  • The script must parse a csv file given in the first arguement

  • the csv file format is : NAME;SURNAME;YEAR;LEVEL (file path : /data/admin/list/student-list)

  • for each line in the csv file the script the script ask : "SURNAME NAME is present ? (y/n)"


  • The answer have to be y or n and you don't need to use Enter key to valid

  • The script have to create a directory with the date (ex : /data/admin/result/2022-09-27)

  • Each time the answer is y or n you have to append in the first arguement in a new file (dir path : /data/admin/result/YYYY-MM-DD/)

  • The results files are sorted by the group [year][level] (ex : /data/admin/result/YYYY-MM-DD/student-list_2020-M1) .

  • The format of the contents files is : NAME;SURNAME;YEAR;LEVEL;[here|absent]

  • The script end with a sumury for all created files and the sum of present/absent count (need a header : FILE | present | absent |

Algo

Classic Function

The classic functions needed in the script are the following:

  • Check or Action to verify each command

  • Help or Usage to print information about the script himself

  • ArgCheck to check the arguement or the type of arguement

Specific Function

We need for each script to analyse the requierments to translate each of them in a function or a bash code.

  • LineToVar : translate each column line in a specific variable (Name=[...] Surname=[...])

  • Question : ask to the user the question "SURNAME NAME is present ? (y/n)" and echo the result translated in STDOUT of the function

  • NameFileOutput : Create the variable output file /data/admin/result/student-list_YYYY-LEVEL with the data of LineToVar and the result of Question

  • Resume : Print all the created file with the sum of present/absent count (like : FILE : 12 2 )

Output

Output (First start)

isen@astier_g_client ~ $ ./alumni-reunion.sh list-student.csv 

* Create environement /data/admin/result/2022-09-28 : OK
KADE Anthony is present ? (y/n) : y
LILIAN Giles is present ? (y/n) : y
[...]
ASIA Petty is present ? (y/n) : n

FILE                    |       present |       absent
-------------------------------------------------------------
student-list_2013-M2    |       0       |       1
student-list_2018-M2    |       1       |       0
student-list_2020-M2    |       1       |       0
-------------------------------------------------------------
TOTAL                   |       2       |       1

Output (restart)

isen@astier_g_client ~ $ ./alumni-reunion.sh /data/admin/list/student-list
/data/admin/result/2022-09-28 exist \
do you want to continue (delete all data) (y/n) : y
* Clean environement : OK
KADE Anthony is present ? (y/n) : y
LILIAN Giles is present ? (y/n) : y
[...]
ASIA Petty is present ? (y/n) : n

FILE                    |       present |       absent
-------------------------------------------------------------
student-list_2013-M2    |       0       |       1
student-list_2018-M2    |       1       |       0
student-list_2020-M2    |       1       |       0
-------------------------------------------------------------
TOTAL                   |       2       |       1

Function

REMINDER : ALL function have to be on the top of the script before the main !!!

Help

The Help function is only here to print information and exit. If something go wrong you can use this function to exit the script

# Print the help and exit
function Help() {
        echo "$(basename $0) [absolute or relative path file]"
        [[ ! -z $1 ]] && [[ $(let $1) ]] && Exit=$1 || Exit=0
        exit ${Exit}
}

Action

The Action function get 2 arguement :

  • what we need to print
  • The command to exec (no stdout/stderr)

# Print info exec and status
function Action () {
        # Print the first arguement without \n in the end (-n)
        echo -ne "\n\* $1 : " 
        # Shift to the left 
        shift
        # execute all the argument like a classic command but redirect in /dev/null
        $* &> /dev/null
        ResultExec=$?
        # Check the result and print OK/Failed
        if [[ ${ResultExec} -eq 0 ]]
                then
                        echo OK
                else
                        echo Failed; Help ${ResultExec}
        fi
}

Check

You can used Check or Action but Check is used differently.

function Check() {
        HaveToExit=$2
        [[ ${HaveToExit} -eq 1 ]] && $Help ${ResultExec}
        if [[ $1 -eq 0 ]]
                then
                        echo OK
                else
                        echo Failed 
                        Help $1
        fi
}
# Exemple :
echo -n 'Is it OK ? : '
true
Check $? 0

ArgCheck

Check the environement :

  • is the first arguement is a file
  • is the output directory exist
  • is the output directory creation is OK

function ArgCheck() {
        # Check the first arg of the script and directory output data
        [[ ! -f $1 ]] && echo "$1 is not a file" && Help 1
        if [[ -d ${DirOutput} ]]; then
        while [[ $Qans != "y" ]] && [[ $Qans != "n" ]]
            do
                echo -e "\n"
                read -p  "${DirOutput} exist \
do you want to continue (delete all data) (y/n) : " -n1 Qans
            done
        # Clean environement
        [[ $Qans == "n" ]] && exit || \
        Action "Clean environement" "1" rm -rf ${DirOutput}/*
        fi
} 

NameFileOutput

Create the variable which contain the ouput file for each line of the input file

# Create the varname of the output file
function NameFileOutput(){
        FileName=student-list_${1}-${2}
        echo ${DirOutput}/${FileName}
}

Question

For each line in the input file we need to ask the question present/absent

function Question() {
        qName=$1
        qSurname=$2
        Answer=""
        Result=""
        # Check if the data is y or n
        while [[ -z ${Result} ]]
                do
                        read -p "$Name $Surname is present ? (y/n) : " \
            -n1 Answer </dev/tty
                        [[ ${Answer} == "y" ]] && Result=present
                        [[ ${Answer} == "n" ]] && Result=absent
                done
        echo $Result
}

LineToVar

Parse the all files, get data and call other function


# Analyse for all line
function LineToVar(){
        while read -r Line
                do
                        echo -e "\n"
                        # Gen variable environement for each line
                        Name=$(echo $Line | cut -d";" -f1)
                        Surname=$(echo $Line | cut -d";" -f2)
                        Year=$(echo $Line | cut -d";" -f3)
                        Level=$(echo $Line | cut -d";" -f4)
                        Result=$(Question "${Name}" "${Surname}")
                        OutputFile=$(NameFileOutput "${Year}" "${Level}")
                        # output data in the outputfile
      echo "${Name};${Surname};${Year};${Level};${Result}" >> ${OutputFile}

                done  < $1
}

Resume

Output the resume of all created output files


function Resume() {
        echo -e "\n\nFILE\t\t\t|\tpresent\t|\tabsent"
        echo  "-------------------------------------------------------------"
        # find all output file
        for FileOutput in $(find ${DirOutput} -type f \
                -name "student-list_*")
                do
                        # Gen variable for each file
                        FileName=$(basename $FileOutput)
                        Present=$(cat ${FileOutput} | grep present|wc -l)
                        Absent=$(cat ${FileOutput} | grep absent|wc -l)
                        TotPresent=$((TotPresent+Present))
                        TotAbsent=$((TotAbsent+Absent))
                        echo -e "$FileName\t|\t${Present}\t|\t$Absent"
                done
        echo  "-------------------------------------------------------------"
        # print total 
        echo -e "TOTAL\t\t\t|\t${TotPresent}\t|\t$TotAbsent"
}

Main

Main call function and create some variable for all the scripts and functions

######## MAIN ########
Date=$(date "+%Y-%m-%d")

DirOutput=/data/admin/result/${Date}

ArgCheck $1

[[ ! -d ${DirOutput} ]] && \
Action "Create environement ${DirOutput}" "1" mkdir -p ${DirOutput}

LineToVar $1

Resume

Main and fucntion file

You can separate the main file and all function with source

#!/bin/bash

source $(realpath $(dirname $0))/fct_classic
source $(realpath $(dirname $0))/fct_specific

ArgCheck $1

[[ ! -d ${DirOutput} ]] && \
Action "Create environement ${DirOutput}" "1" mkdir -p ${DirOutput}

LineToVar $1

Resume