Shell Script #1 - (Comments, Here Document, and Debugging)
We wrote our script. We ran it. No issues. Okay...
We wrote our script. We ran it, but it didn't work. Why? Where did we encounter an error? Or. We wrote our script. Three months later, we got an error. We opened it, looked at it. We didn't understand anything. The classic programming problems...
The code examples are shared in my GitHub repository: Shell Scripting 101
Comments
In Bash scripts, we use the "#" symbol for comments. As you remember, shebang also starts with this character. Well, not exactly. Shebang starts with "#!". These two are interpreted differently.
For multi-line comments, we will use "here document". In the script below, you can see the comment lines. Notice that the first line is always the shebang.
#! /bin/bash
#A simple listing script
#While listing, it shows hidden files, permissions, and inode numbers.
#It shows file sizes in "human-readable" format (K, M, G, etc.).
#Display a message to the user:
echo "The contents of the current directory are listed below:"
ls -alhi
Output of our script:
[root@localhost bashscript]# ./yorumlar.sh
Bulunduğunuz dizinin içeriği aşağıda listelenmiştir:
toplam 12K
16797781 drwxr-xr-x. 2 root root 47 Eki 28 18:30 .
16797761 dr-xr-x---. 8 root root 4,0K Eki 28 14:53 ..
16802432 -rwxr--r--. 1 root root 126 Eki 28 15:07 echo_ornegi.sh
16802435 -rwxr--r--. 1 root root 541 Eki 28 18:30 yorumlar.sh
[root@localhost bashscript]#
Here Document
A here document is a block of code used for a special purpose. It acts as a form of I/O redirection. This allows you to feed an interactive command. Let's try to explore this with an example.
With the wc program, we can see the line, word, and size information of a file provided as input. To see only the line count, we can use the "-l" flag.
As seen above, there are 17 lines in the "yorumlar.sh" file. So, what if we want to provide pre-written data from the script as input to a program? This is where "here document" comes to our rescue. Let's examine the following example script:
We sent the content defined within the "input (girdi)" block to the "wc -l" program. Let's think about this in the context of "mail". We can use this technique to directly attach text prepared in the script to the body of an email.
[root@localhost bashscript]# cat word_count.sh
#! /bin/bash
wc -l <<girdi
Test
Örnek veri
Deneme
girdi
[root@localhost bashscript]# ./word_count.sh
3
[root@localhost bashscript]#
Similarly, if we send this block to a large empty space, we can essentially create a multi-line comment.
[root@localhost bashscript]# cat yorumlar.sh
#! /bin/bash
#Basit bir listeleme script'i
#Listeleme işini yaparken gizli dosyaları, izinleri ve inode numaralarını gösterir.
#Dosya boyutlarını "human-readable" formatta (K, M, G gibi) gösterir.
<<yorumlar
Bu blokta kalan içerik, tamamen yorum olarak kalacaktır.
Bu sayede birden fazla satıra yayılmış yorumlar oluşturabilirsiniz.
Bunu yaparken her satırın başına # eklemenize de gerek olmaz.
yorumlar
#Kullanıcıya bir mesaj göster:
echo "Bulunduğunuz dizinin içeriği aşağıda listelenmiştir:"
ls -alhi
[root@localhost bashscript]# ./yorumlar.sh
Bulunduğunuz dizinin içeriği aşağıda listelenmiştir:
toplam 24K
16797781 drwxr-xr-x. 2 root root 88 Eki 28 23:53 .
16797761 dr-xr-x---. 8 root root 4,0K Eki 28 14:53 ..
17514307 -rw-r--r--. 1 root root 2 Eki 28 22:33 a
16821756 -rw-r--r--. 1 root root 5 Eki 28 22:28 ali
16802432 -rwxr--r--. 1 root root 126 Eki 28 15:07 echo_ornegi.sh
16802431 -rwxr--r--. 1 root root 58 Eki 28 23:53 word_count.sh
16802435 -rwxr--r--. 1 root root 541 Eki 28 18:30 yorumlar.sh
[root@localhost bashscript]#
Debugging in Scripts
Let me share 3 options from the Bash program's man page first. Then, we'll discuss what they do.
-n: Read commands but do not execute them. This may be used to check a shell script for syntax errors. This is ignored by interactive shells. -v: Print shell input lines as they are read. -x: After expanding each simple command, for command, case command, select command, or arithmetic for command, display the expanded value of PS4, followed by the command and its expanded arguments or associated word list.
Let's start.
[root@localhost bashscript]# ls
a debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]# cat debug.sh
#! /bin/bash
echo "Ben bu dosyayı silecek miyim sence?"
rm a
[root@localhost bashscript]# bash -n debug.sh
[root@localhost bashscript]# ls
a debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]#
Let's examine the above example step by step:
- I listed the directory with
ls. There is a file named "a". - I read the "debug.sh" script using
cat. This script attempts to print a message to the screen and delete the "a" file. - Using
bash -n ./debug.sh, I gave the script to the bash program in the current directory. With the-nflag, I told bash to just read the commands without executing them. - When I checked again with
ls, I saw that the "a" file was not deleted. Moreover, no message was printed on the screen.
Now, let's make the script faulty and try again.
[root@localhost bashscript]# cat debug.sh
#! /bin/bash
ehco "Ben bu dosyayı silecek miyim sence?
remove a
[root@localhost bashscript]# bash -n ./debug.sh
./debug.sh: line 3: `"' için eşleşme aranırken beklenmedik dosya sonu
./debug.sh: line 5: sözdizimi hatası: beklenmeyen dosya sonu
[root@localhost bashscript]#
- I read my script with
cat. The word "echo" was written as "ehco" and "rm" as "remove". There are typos in this script. - I checked the script with
bash -n. - Errors were shown as output. Let's assume that I did a long task. If the script had failed somewhere after many steps were completed correctly, things could have gotten complicated. Fortunately, with
bash -n, we were able to see that the script wouldn't work before even starting the job.
Now, let's move on to another example:
[root@localhost bashscript]# ls
a debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]# cat debug.sh
#! /bin/bash
echo "Ben bu dosyayı silecek miyim sence?"
rm a
[root@localhost bashscript]# bash -x ./debug.sh
+ echo 'Ben bu dosyayı silecek miyim sence?'
Ben bu dosyayı silecek miyim sence?
+ rm a
[root@localhost bashscript]# ls
debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]#
- I checked with
ls, and the "a" file is there. - I read it with
cat. There doesn't seem to be any typos. - I used the xtrace feature to have the script show "what it understood from what I wrote." Each line was first shown to us, then executed.
- After checking with
lsagain, I saw that the script had deleted the "a" file.
"...what it understood from what I wrote..." Doesn't this sound a bit vague? Here's another example:
[root@localhost bashscript]# ls
a debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]# cat debug.sh
#! /bin/bash
echo "Ben bu dosyayı silecek miyim sence?"
echo "Bu işlemin yapıldığı tarih: $(date)"
rm a
[root@localhost bashscript]# bash -x ./debug.sh
+ echo 'Ben bu dosyayı silecek miyim sence?'
Ben bu dosyayı silecek miyim sence?
++ date
+ echo 'Bu işlemin yapıldığı tarih: Sal Kas 3 16:50:02 +03 2020'
Bu işlemin yapıldığı tarih: Sal Kas 3 16:50:02 +03 2020
+ rm a
[root@localhost bashscript]# ls
debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]#
ls. I saw the "a" file.
2. I read it with cat. In the script, "echo" and "date" are nested together in some place. "What I wrote" was an echo with a date. But what does "it understand"?
3. I ran the script with bash -x.
4. It understood the first echo as it is. It showed it, ran it, and we saw the result.
5. In the ++date line, we see that it first said, "I need to run the date command to get the date," then it placed the output of date into the echo and ran it.
6. We can also confirm by checking with ls that the file was deleted.
Is this enough? No:
[root@localhost bashscript]# ls
a debug.sh echo_ornegi.sh word_count.sh yorumlar.sh
[root@localhost bashscript]# bash -v ./debug.sh
#! /bin/bash
echo "Ben bu dosyayı silecek miyim sence?"
Ben bu dosyayı silecek miyim sence?
echo "Bu işlemin yapıldığı tarih: $(date)"
Bu işlemin yapıldığı tarih: Sal Kas 3 16:54:09 +03 2020
rm a
[root@localhost bashscript]#
- I checked with
ls. I saw the "a" file. - I wanted the relevant line to be shown to me during script execution with
bash -v. - During the execution, I saw that the code of my script was displayed before it was executed. This way, we can see which line produced which output.
Can these tools work together? Of course. To explain better, I'll number this part:
1- [root@localhost bashscript]# bash -xv debug.sh
2- #! /bin/bash
3- echo "Ben bu dosyayı silecek miyim sence?"
4- + echo 'Ben bu dosyayı silecek miyim sence?'
5- Ben bu dosyayı silecek miyim sence?
6- echo "Bu işlemin yapıldığı tarih: $(date)"
7- ++ date
8- + echo 'Bu işlemin yapıldığı tarih: Sal Kas 3 16:56:40 +03 2020'
9- Bu işlemin yapıldığı tarih: Sal Kas 3 16:56:40 +03 2020
10- rm a
11- + rm a
[root@localhost bashscript]#
With bash -xv, I requested both xtrace and verbose. What did I write, what did you understand, and what did you execute?
- Number 2: Shebang.
- Number 3: The
echoI wrote. - Number 4: The version with a
+, which is the form understood by bash. - Number 5: The output of the above
echo. - Number 6: The
echoI wrote. - Number 7: The line starting with
++, where bash said "first I need to rundate". - Number 8: After
dateran, the new form of theecho. I didn't write this, bash turned it into this. - Number 9: The output of the
echothat bash transformed from my intended form. - Number 10: The
rmI wrote. - Number 11: The line starting with
+, which is the form bash understood.
Conclusion
Shell scripts, especially when run with root privileges, can have catastrophic effects. However, they can also allow you to create incredibly powerful tools for automating repetitive tasks. Therefore, you must ensure that your scripts will work in any environment and under any circumstances.
Even if you cannot ensure this, you still have the chance to perform the necessary checks for your script to work.