Shell Script ============ One of the coolest part of UNIX command is that we can easily script series of commands for automating tasks or simply to avoid retyping a series of commands. Once again, this part of the tutorial is not mandatory to do the projects. But having at least a basic understanding of shell scripting is advised. If you do not plan on using the shell for other tasks, you can go over this part quickly, not focusing on the details. Create a Simple Script ---------------------- First, save the following code into a file named "hello.sh": .. code-block:: shell #!/bin/bash echo "hello world" The first line of the script indicate which program will execute the script. In this case, it is "bash". Next, we need to give the script permission to execute. This is done with the following command: .. code-block:: shell $ chmod u+x hello.sh .. note:: The ``u`` stands for "user", ``u+x`` means "add the execute permission to the user". It is more secure than simply using ``+x``, which would give the permission to everyone. Finally, to execute the script: .. code-block:: shell $ ./hello.sh hello world If you modify the script as follows, output will also be changed: .. code-block:: shell #!/bin/bash echo "hello world" echo "good morning" echo "good afternoon" echo "good evening" Let's execute it again. And that case, you will have: .. code-block:: shell $ ./hello.sh hello world good morning good afternoon good evening Variables --------- Variables in shell scripts are defined by the following syntax: .. code-block:: shell $ n=5 $ echo $n 5 $ h="Hi you" $ echo "The equivalent for $h in french is Salut toi" The equivalent for Hi you in french is Salut toi .. important:: Do not put spaces around the equal sign when defining a variable. These are **local variables**. You have already seen some **environment variables** (set by the system) like ``HOME``, ``PATH``, etc. The shell also has its own **shell variables**. For example, ``RANDOM`` is a variable that contains a random number. .. code-block:: shell $ echo $((RANDOM % 5)) > rands.txt $ echo $((RANDOM % 5)) >> rands.txt $ echo $((RANDOM % 5)) >> rands.txt $ echo $((RANDOM % 5)) >> rands.txt $ echo $((RANDOM % 5)) >> rands.txt $ cat rands.txt 0 1 1 0 4 .. note:: - The ``>`` operator is used to place the standard output of a command into a file (replacing all of its content). - The ``>>`` operator is used to append the standard output of a command to a file. - The ``$((...))`` syntax is used to perform arithmetic operations. Mathematical operations ----------------------- To perform mathematical operations, you can either do it directly in the shell: .. code-block:: shell $ echo $((5 * 10)) 50 $ n=5 $ echo $((n * 10)) 50 $ echo $(((n * 10) % 5)) 0 Or use some external tools like ``bc``: .. code-block:: shell $ echo "5 * 10" | bc 50 The Exit Code ------------- Each command or group of commands returns an exit code. The ``?`` variable is a shell variable that contains the exit code of the last command: - If the command was successful, the exit code is 0. - If an error occurred, the exit code is different from 0. .. note:: Each program decides itself which exit code to return, therefore the meaning of "an error occurred" is not always the same. For example, if ``grep`` did not find the string you were looking for, it will return 1, even though it is not really an error. .. code-block:: shell $ echo "Hello INFO0940" Hello INFO0940 $ echo $? 0 $ cp /etc /etc cp: /etc is a directory (not copied). $ echo $? 1 Conditions ---------- It is possible to use conditions in shell scripts. Let's see some example and explain the new notions for each. **Example 1:** .. code-block:: shell #!/bin/sh file="$1" if grep "hello" $file --quiet then echo "hello is present !" fi You can launch this script with the following command: .. code-block:: shell $ ./script.sh ANY_FILE_YOU_WANT - ``$1`` is the first argument given to the script. (``$0`` is the string corresponding to the script/command itself.) - ``--quiet`` is an option for grep which will not display the result of the search. - ``if`` will check the return code of the given command. If it is 0, the condition is true. In this case, ``grep`` will only return 0 if the string is present in the file. **Example 2:** .. code-block:: shell #!/bin/sh grep "hello" file.txt > /dev/null if [ $? -eq 0 ] then echo "hello is present !" fi - ``/dev/null`` is a special file that discards all data written to it. - ``[`` is actually not part of the shell syntax, but a command that returns 0 if the condition is true. **Example 3:** .. code-block:: shell #!/bin/sh if [ $1 -lt 10 ] then echo "$1 is smaller than 10" else echo "$1 … thats too much" fi - ``-lt`` is a comparison operator which means "less than". (it is an argument of the ``[`` command. See ``man [``.) .. note:: ``[`` is posix compliant (meaning it will work on any shell). If you prefer, you can use ``[[`` which is part of the shell syntax. ``[[`` is more intuitive and has some additional features, but is less portable. Loops ----- Let's now see how to use ``for`` and ``while`` loops. **Example 4:** .. code-block:: shell #!/bin/sh for n in $(seq 10) do echo "$n" done - ``seq 10`` will generate a sequence of numbers from 1 to 10. - ``for`` will create a variable ``n`` that will take the value of each word after ``in``. It would have been the same to use ``for n in 1 2 3 4 5 6 7 8 9 10`` **Example 5:** .. code-block:: shell #!/bin/sh for file in ./* do stat $file done - ``stat`` will display information about the given file. **Example 6:** .. code-block:: shell #!/bin/sh n=0 while [ $n -lt 10 ] do echo "$n" n=$((n + 1)) done Online Resources ---------------- If you want to learn more about shell scripting, here are some resources: - Play with shell: `https://www.learnshell.org `_ - Learn shell scripts: `https://www.shellscript.sh `_ Exercise -------- If you want some practice, here is an exercise for you. (Otherwise, you can jump to the last section: `Getting Kernel Information `_) Let's build a shell script which computes the total size of files in a folder specified by the first argument of the script. We only want to consider files (not directories). We provide you some hints to help you. Expected output:: $ ./filesize.sh DIRECTORY_NAME Total size of files in the directory is 8 KB Hint 1: Getting argument(s) .. raw:: html
See hint .. code-block:: shell #!/bin/bash echo "argument 1" $1 echo "argument 2" $2 .. raw:: html
Hint 2: Getting the size of a file in KB .. raw:: html
See hint .. code-block:: bash $ du -s FILE_NAME or .. code-block:: bash $ ls -la FILE_NAME You will see that ``ls`` and ``du`` do not give the same size. The size given by ``ls`` is the exact size of the file, while the size given by ``du`` is the size of the file on disk (which is not the same). Why is it not the same? You will understand this during the theoretical lecture on Mass Storage (lecture 8). .. raw:: html
Hint 3: Using loop .. raw:: html
See hint .. code-block:: shell #!/bin/bash nb_words=0 for word in $(echo -n "I sorted my tea bags using an infusion sort algorithm") do nb_words=$((nb_words+1)) done echo "Number of words: $nb_words" .. raw:: html
Hint 4: Printing only files in a directory .. raw:: html
See hint .. code-block:: shell #!/bin/bash files=`find . -maxdepth 1 -type f` for f in ${files[@]} do echo $f done or .. code-block:: shell #!/bin/bash for f in $(find . -maxdepth 1 -type f) do echo $f done .. raw:: html
Hint 5: Arithmetic computation .. raw:: html
See hint .. code-block:: shell #!/bin/bash one=1 two=2 ret=$(($one+$two)) echo "${one} + ${two} is ${ret}" ret=$(($one*$two)) echo "${one} * ${two} is ${ret}" .. raw:: html
Hint 6: showing N-th column .. raw:: html
See hint .. code-block:: shell #!/bin/bash cat /proc/cpuinfo echo echo "----- only showing 1st column -----" echo cat /proc/cpuinfo | awk '{ print $1 }' .. raw:: html
**Solution:** This is only one possible solution. There are many ways to solve this problem. .. raw:: html
See answer .. code-block:: shell #!/bin/sh total_size=0 for file in $(find $1 -maxdepth 1 -type f) do file_size=$(ls -la $file | awk '{print $5}') # or file_size=$(du $file | awk '{print $1}') total_size=$((total_size+file_size)) done echo "Total size of files in the directory is $total_size KB" .. note:: It is not necessary to use ``exit 0`` at the end of the script, as the script will return the exit code of the last command executed (and if it was successful, it will return 0). .. raw:: html
Note that to be rigorous, you should check if an argument is provided to the script and if it is indeed a directory. This can be done by placing the following code before your loop: .. raw:: html
See answer .. code-block:: shell if [ $# -ne 1 ] then echo "Please provide an argument." echo "Usage: ./filesize.sh DIRECTORY_NAME" exit 1 fi if [ ! -d $1 ] then echo "The first argument should be a directory." echo "Usage: ./filesize.sh DIRECTORY_NAME" exit 2 fi .. raw:: html