Logo

Bash Scripting, Part 1

32 min read
Lesson slides
1 / 17

Operating Systems I - Lesson 11

Bash Scripting, Part 1

Write, permit and run a Bash script that takes input, makes decisions, and does arithmetic.

By the end of this lesson you will be able to write, permit and run a Bash script that takes input, makes decisions, and does arithmetic.

Objectives

  • Explain what a Bash script is and why its first line is #!/bin/bash.
  • Create a script file from the terminal, make it executable with chmod, and run it both as bash script.sh and as ./script.sh.
  • Store values in variables and read them back with $.
  • Take input from the keyboard with echo followed by read, and from the command line with the positional parameters $1, $2, $3.
  • Read the exit status of the last command from $? and return a status from a script with exit.
  • Do whole-number arithmetic with $(( )) and (( )), and decimal arithmetic with bc.
  • Make decisions with if, elif and else using the test brackets [[ ]], and recognize the older [ ] form when you meet it.

Prerequisites

  • A Linux terminal.
  • echo: cat and the > / >> redirection operators, plus file permissions and chmod (symbolic u+x and numeric 755).
  • A text editor (nano is enough); every script here can also be created with echo and redirection.
  • The bc calculator, used later in this lesson. If bc --version says command not found, install it with sudo apt install bc.

Background

Every script in this lesson is introduced with a file name. To reproduce the output shown, save the listing under that name (with nano name.sh, or with echo and redirection as shown below) and run bash name.sh. The tasks reuse several of these files.

Concept 1: What a Bash Script Is

Definition. Bash is the default command language of Linux. A Bash script is a plain text file holding the same commands you type at the prompt, one per line, executed top to bottom by the bash program.

Purpose. Any task you repeat at the prompt can be written once into a script and executed with a single command. A script can also take input, make decisions and report results, which makes it a small program.

How it works. When you run a script, a new bash process starts, reads the file line by line and executes each line exactly as if you had typed it. The first line tells the system which interpreter to use.

Concept 2: Creating and Running a Script

The shebang line. The first line of every script in this lesson is:

#!/bin/bash

#! (the "shebang") followed by the absolute path of the interpreter tells the kernel: "run this file with /bin/bash". The two characters #! must be the very first characters of the file. Three shapes you will meet:

  • #! /bin/bash: with a space after #!, still works; the space is allowed.
  • A misspelled path such as #!/bin/bas names an interpreter that does not exist. ./script.sh refuses to start and prints a message such as cannot execute: required file not found (the exact wording depends on the Bash version).
  • Any character before #!, for example .#!/bin/bash, turns line 1 into an ordinary command that does not exist. The run prints line 1: .#!/bin/bash: No such file or directory and the file is no longer a proper Bash script.

Creating the file. You can open an editor, or build the file with echo and redirection. Remember the two operators:

  • > creates the file or overwrites its content.
  • >> appends a line to the end of the file (creating it if it does not exist).
echo '#!/bin/bash' > hello.sh
echo 'echo "Hello from FCIS"' >> hello.sh
cat -n hello.sh

Line by line:

  1. echo '#!/bin/bash' > hello.sh creates the file with the shebang as line 1. Single quotes stop the shell from interpreting the !.
  2. echo 'echo "Hello from FCIS"' >> hello.sh appends line 2; the outer single quotes carry the inner double quotes into the file unchanged.
  3. cat -n hello.sh shows the file with line numbers so you can check line 1.

Expected output:

     1  #!/bin/bash
     2  echo "Hello from FCIS"

Two ways to run it. The two commands differ in one important way: permissions.

CommandWhat happensNeeds execute permission?
bash hello.shYou start bash yourself and give it the file as an argument. The shebang is ignored because you already chose the interpreter.No, read permission is enough.
./hello.shYou ask the kernel to run the file as a program. The kernel reads the shebang and starts /bin/bash for you.Yes, the file must have x.

A new file created with echo typically has permissions -rw-rw-r-- (on some systems -rw-r--r--; the group column depends on the system default). Either way there is no x anywhere, so ./hello.sh fails with Permission denied until you add the execute bit:

chmod u+x hello.sh

chmod u+x adds execute permission for the owner (u), turning -rw-rw-r-- into -rwxrw-r--. The numeric form chmod 755 hello.sh (owner rwx, group r-x, others r-x) is the common choice for scripts shared with other users.

The ./ in ./hello.sh tells the shell that the file is in the current directory.

flowchart LR
    A[Write the script file] --> B{"First line is #!/bin/bash?"}
    B -- no --> A
    B -- yes --> C[chmod u+x script.sh]
    C --> D[./script.sh]
    A --> E[bash script.sh]
    E --> F[bash reads the file line by line]
    D --> F
    F --> G[Output on the terminal]

The diagram shows the two paths: bash script.sh works as soon as the file exists, while ./script.sh needs a correct shebang and the execute bit.

Concept 3: Comments

Definition. A comment is text inside the script that bash ignores at execution time. It exists only for the human reader.

Purpose. Comments explain what a block does and why, so that you (or a colleague) can understand the script months later.

How it works. A # starts a single-line comment; everything after it on that line is ignored. For several lines, Bash has no dedicated syntax, but the idiom : ' ... ' works. : is a built-in command that does nothing and returns success. The single quotes turn everything up to the next ' into one argument, which : discards. Because of that, the comment text itself must not contain a ': a stray apostrophe (as in isn't) ends the comment early and the run fails with unexpected EOF while looking for matching.

Save the following as comment.sh:

#!/bin/bash
#Take a number as input
echo "Enter a number"
read a
: '
Check the input number is
less than 10 or greater than 10 or equal to 10
'
if [[ $a -lt 10 ]]
then
echo "The number is less than 10"
elif [[ $a -gt 10 ]]
then
echo "The number is greater than 10"
else
echo "The number is equal to 10"
fi

Line by line:

  1. #!/bin/bash is the shebang. Note that the shebang is the one line beginning with # that is NOT a comment for the kernel; for bash itself it is still a comment, which is why the two roles never conflict.
  2. #Take a number as input is a single-line comment.
  3. echo "Enter a number" prints a prompt.
  4. read a waits for a line of keyboard input and stores it in the variable a.
  5. : ' opens the multi-line comment: the two text lines and the closing ' together form the single argument that : throws away.
  6. if [[ $a -lt 10 ]] tests whether a is less than 10 (-lt); then starts the branch body.
  7. elif [[ $a -gt 10 ]] is a second test, greater than (-gt), checked only if the first was false.
  8. else catches the remaining case (equal to 10), and fi closes the whole statement.

Run bash comment.sh three times, answering 3, 10 and 90. Expected output of the three runs (your typed answer is on the line after each prompt):

Enter a number
3
The number is less than 10
Enter a number
10
The number is equal to 10
Enter a number
90
The number is greater than 10

Concept 4: Printing with echo

Definition. echo is the command used in Bash to print output on the terminal. It prints its arguments followed by a newline.

Purpose. Scripts use echo for every message: prompts before a read, results after a calculation, and error messages.

How it works. Each echo writes its text and then moves the cursor to the next line. The option -n suppresses that final newline, so whatever is printed next continues on the same line.

Save the following as echo_test.sh:

#!/bin/bash
#Print the first text
echo "Print text with a new line"
#Print the second text
echo -n "Print text without a new line"

Line by line: the first echo prints its text and moves to a new line; the second prints its text and stays on that line, so the next shell prompt appears glued to it. Expected output of bash echo_test.sh:

Print text with a new line
Print text without a new line$

The $ at the end is your shell prompt (on Ubuntu it looks like user@host:~$), not part of the script's output.

Concept 5: Variables

Definition. A variable is a named place that stores a value (text or number).

Purpose. Variables let a script remember input, hold intermediate results and reuse a value in several places without retyping it.

How it works. Assignment uses name=value with NO spaces around = and NO $. Reading the value uses $name. Variables can be created at the prompt or inside a script. Type these two lines at the prompt:

mystr="I like bash programming"
echo $mystr

Line 1 stores the text in mystr; line 2 expands $mystr to its value before echo runs. Expected output:

I like bash programming

Quoting matters when you print a variable:

  • Inside double quotes, $name is replaced by its value: echo "$mystr" prints the text.
  • Inside single quotes nothing is replaced: echo '$mystr' prints literally $mystr.

Two variables written back to back are simply joined (this is how strings are concatenated). Save the following as concat.sh:

#!/bin/bash
string1="I like "
string2="Bash Programming"
echo "$string1$string2"

Line by line: two strings are stored, then printed one after the other inside a single pair of double quotes. Expected output of bash concat.sh: I like Bash Programming.

A variable can also hold a number and be compared with another value. Save the following as var.sh:

#!/bin/bash
echo "Enter a number"
read a
b=100
if [[ $a -eq $b ]]
then
echo "Numbers are equal"
else
echo "Numbers are not equal"
fi

Line by line: the typed number lands in a; b=100 stores a number the same way as a string; [[ $a -eq $b ]] is true only when the two values are equal as numbers (-eq, explained in Concept 10); one of the two messages is printed. Run bash var.sh twice, answering 56 and then 100. Expected output of the two runs:

Enter a number
56
Numbers are not equal
Enter a number
100
Numbers are equal

Concept 6: Reading Keyboard Input

Definition. read variable pauses the script, waits for the user to type a line and press Enter, and stores that line in the variable.

Purpose. It is how a script asks its user for a value instead of having the value fixed in the file.

How it works. read prints nothing by itself, so it is always paired with an echo on the line before it: the echo shows the question, the read collects the answer. Save the following as ask.sh:

#!/bin/bash
echo "Enter your name"
read name
echo "Enter your age"
read age
echo "$name is $age years old"

Line by line: line 2 prints the first question on its own line; line 3 stores the typed name; lines 4 and 5 do the same for the age; line 6 prints both values inside one string. Expected terminal session for bash ask.sh (each answer sits on the line after its question):

Enter your name
Student1
Enter your age
20
Student1 is 20 years old

Concept 7: Positional Parameters

Definition. Positional parameters are the words typed after the script name on the command line. Inside the script they are available as $1 (first), $2 (second), $3 (third).

Purpose. They let the user pass values without being asked interactively, which is what makes a script usable from other scripts.

How it works. The shell splits the command line on spaces and assigns each word to the next number before the script starts. Save the following as subtract.sh:

#!/bin/bash
a=$1
b=$2
((result=$a-$b))
echo "The subtraction of $a-$b=$result"

Line by line: $1 and $2 are copied into a and b; ((result=$a-$b)) evaluates the subtraction (see Concept 9); the message prints all three values. Running bash subtract.sh 50 20 prints:

The subtraction of 50-20=30

The parameters can also be used directly, as in ((result=$1*$2)), without copying them first.

Concept 8: Exit Status and $?

Definition. Every command finishes with an exit status: an integer where 0 means success and any other value means failure or a specific condition. The special variable $? holds the exit status of the most recently executed command.

Purpose. A script can end with exit N to report a result to whoever ran it, and the caller can read that number from $? and decide what to do next.

How it works. Every command overwrites $?, so read it immediately after the command you care about, or copy it into a variable. Save the following as check.sh; it prints nothing about its decision and reports it only through exit:

#!/bin/bash
echo "Enter a numeric value"
read n
# Return 0 when the value is less than or equal to 100, 1 otherwise
if [[ $n -le 100 ]]
then
exit 0
else
exit 1
fi

Line by line: after reading n, -le tests "less than or equal"; exit 0 ends the script with status 0 (success); exit 1 ends it with status 1, which this script uses to mean "n was greater than 100". Any non-zero number is allowed and its meaning is decided by whoever writes the script. Nothing after exit runs.

Read the status at the prompt, twice, answering 55 and then 110:

bash check.sh
echo $?
bash check.sh
echo $?

Expected output of the four commands:

Enter a numeric value
55
0
Enter a numeric value
110
1

The echo $? must come right after bash check.sh; any command placed between them would replace the 0 or 1 with its own status. A second script can call check.sh the same way and turn $? into a message. The hand-off looks like this:

sequenceDiagram
    participant C as caller.sh
    participant K as check.sh
    participant U as Keyboard
    C->>K: bash check.sh
    K->>U: Enter a numeric value
    U-->>K: n
    alt n <= 100
        K-->>C: exit 0
    else n > 100
        K-->>C: exit 1
    end
    Note over C: $? now holds 0 or 1
    C->>C: if [ $? -eq 1 ] print "greater than 100" else print "less than or equal to 100"

The decision travels only through the exit status: check.sh never prints the result, and caller.sh never sees the number typed. One of the tasks below builds caller.sh and then breaks it on purpose.

Concept 9: Arithmetic

Definition. Bash does not evaluate 2+3 on its own: to the shell that is a plain string. Arithmetic must be requested explicitly.

Purpose. Scripts count, add up input values and convert units; every calculator-like task in this lesson needs one of the arithmetic forms below.

How it works. The simple way is double parentheses, in two forms: $(( expression )) is replaced by the value, so it can sit inside a string or on the right of =; (( expression )) evaluates and assigns in place, as in ((result=$a-$b)) or (( N += 10 )). Inside $(( )) and (( )) a variable may be written with or without $: $((age + 1)) and $(($age + 1)) give the same result. Double parentheses work with whole numbers only: $((7 / 2)) gives 3, because the fractional part is dropped. Bash also has the let and expr commands and the external bc calculator; this lesson uses bc, the one that understands decimals.

Save the following as arith.sh:

#!/bin/bash
# Calculate the sum
result=$((50+25))
# Print summation value
echo "sum = $result"
 
# Calculate the division
result=$((50/25))
# Print division value
echo "division = $result"
 
# Assign a value to N
N=10
# Doing pre-increment
((--N))
# Print the value of N
echo "Value after decrement = $N"
 
# Using shorthand operator
(( N += 10 ))
# Print the value of N
echo "Value after adding 10 = $N"

Line by line: $((50+25)) computes 75 and stores it; $((50/25)) computes 2; ((--N)) subtracts 1 from N, so N becomes 9 (((N--)) used alone on a line does the same); (( N += 10 )) adds 10 in place, giving 19. Expected output of bash arith.sh:

sum = 75
division = 2
Value after decrement = 9
Value after adding 10 = 19

When decimals matter, hand the expression to bc through a pipe. A circle-area calculation with radius 5 is:

area=$(echo $radius*$radius*3.14 | bc)

echo prints the expression 5*5*3.14, the pipe | feeds that text to bc, and bc prints 78.50. $( ... ) is command substitution: it runs the command inside and is replaced by that command's output, which is how the printed answer lands in the variable area. One of the tasks below puts both forms side by side.

Concept 10: Decisions with if, elif and else

Definition. An if statement runs a block of commands only when a condition is true. elif adds further conditions that are tried in order, and else runs when none of them was true.

Purpose. Decisions are what turn a list of commands into a program: the same script prints a different message, or takes a different path, depending on the input.

Structure.

if CONDITION
then
    commands
elif OTHER_CONDITION
then
    commands
else
    commands
fi

Each if or elif line is followed by then (on the next line, or on the same line after a ;), and the statement ends with fi.

The test brackets. The condition is written between brackets, and there are two kinds. [[ ... ]] is the Bash-only form: it accepts && and || directly between two tests and does not break when a variable is empty. [ ... ] is the older form that works in every shell, but it needs its variables quoted ("$text") and cannot take && inside the brackets. The rule for this lesson: write [[ ]]; [ ] appears in two listings only so that you recognize it. Both need a space after the opening bracket and before the closing one. Inside them:

OperatorMeaningExample
-eqnumbers equal[[ $a -eq $b ]]
-lt, -lenumber less than / less or equal[[ $n -le 100 ]]
-gt, -genumber greater than / greater or equal[[ $age -ge 18 ]]
==strings equal[[ $operator == '+' ]]
&&, ||both conditions / either condition (inside [[ ]])[[ $age -ge 18 && $code -eq 1100 ]]

Numeric operators compare values as numbers; == compares text character by character. Save the following as movie.sh:

#!/bin/bash
 
echo "Enter your code"
read code
echo "Enter your age"
read age
 
if [[ $age -ge 18 && $code -eq '1100' ]]
then
echo "You are eligible to see the movie"
else
echo "You are not eligible to see the movie"
fi

Line by line: two values are read; the condition is true only when age is at least 18 AND code equals 1100; one of the two messages is printed. The single quotes around '1100' are harmless here because -eq converts both sides to numbers; they are not needed, and the tasks in this lesson write numbers bare. Run bash movie.sh twice, with code 1100 and age 5, then code 1100 and age 45. Expected output of the two runs:

Enter your code
1100
Enter your age
5
You are not eligible to see the movie
Enter your code
1100
Enter your age
45
You are eligible to see the movie

String comparison uses the same shape with ==. Save the following as compare.sh; it is written with the older [ ] brackets:

#!/bin/bash
echo "Enter any string value"
read text
#Check the input data is equivalent to "Python"
if [ $text == "Python" ]; then
echo "You like Python."
else
echo "You like PERL"
fi

Line by line: the typed text is compared with the word Python; note then on the same line after ;. Input PERL prints You like PERL; input Python prints You like Python..

An added note, not part of the listing above: run bash compare.sh once more and press Enter without typing anything. The line becomes [ == "Python" ] and Bash complains [: ==: unary operator expected before printing You like PERL. Writing "$text" inside [ ], or using [[ $text == "Python" ]], makes the empty input pass quietly. That is why every task script in this lesson uses [[ ]].

flowchart TD
    S[read mark] --> C1{"mark >= 85?"}
    C1 -- yes --> A[print Grade: A]
    C1 -- no --> C2{"mark >= 75?"}
    C2 -- yes --> B[print Grade: B]
    C2 -- no --> C3{"mark >= 65?"}
    C3 -- yes --> C[print Grade: C]
    C3 -- no --> C4{"mark >= 50?"}
    C4 -- yes --> D[print Grade: D]
    C4 -- no --> F[print Grade: F]
    A --> E[fi]
    B --> E
    C --> E
    D --> E
    F --> E

The flowchart is the if/elif/else chain used in the grade-classification task below: conditions are tested top to bottom, the first true one wins, and exactly one branch runs.

Lab Tasks

Work inside a fresh, empty directory so the script files created below do not mix with anything else.

Task 1: Create, Permit and Run Your First Script

  1. Build the file with echo and redirection:

    echo '#!/bin/bash' > hello.sh
    echo '# My first Bash script' >> hello.sh
    echo 'echo "Hello from FCIS"' >> hello.sh
    echo 'echo -n "No newline here. "' >> hello.sh
    echo 'echo "Same line."' >> hello.sh

    Line by line: line 1 creates the file with the shebang (> overwrites, as in Concept 2); line 2 appends a single-line comment (Concept 3); line 3 appends an echo that prints its text and moves to a new line; line 4 appends an echo -n that prints its text and stays on the same line (Concept 4); line 5 appends a second echo whose text therefore lands on the same line as the previous one. Each echo at the prompt is wrapped in single quotes so that the !, the # and the inner double quotes reach the file unchanged.

  2. Check the content and the permissions:

    cat -n hello.sh
    ls -l hello.sh

    Expected output (the owner, group, size and date will be yours):

         1  #!/bin/bash
         2  # My first Bash script
         3  echo "Hello from FCIS"
         4  echo -n "No newline here. "
         5  echo "Same line."
    -rw-rw-r-- 1 student student 104 Nov 20 10:00 hello.sh

    The group column may read r-- instead of rw- on your machine; what matters is that no x appears anywhere.

  3. Run it with the interpreter named explicitly:

    bash hello.sh

    Expected output:

    Hello from FCIS
    No newline here. Same line.
  4. Try to run it as a program before adding execute permission:

    ./hello.sh

    Expected output:

    bash: ./hello.sh: Permission denied
  5. Add the execute bit and run it again:

    chmod u+x hello.sh
    ls -l hello.sh
    ./hello.sh

    Expected output:

    -rwxrw-r-- 1 student student 104 Nov 20 10:00 hello.sh
    Hello from FCIS
    No newline here. Same line.

    The only difference between steps 4 and 5 is the x in the owner's permissions.

Task 2: Variables and Keyboard Input

  1. Create profile.sh with the following content:

    #!/bin/bash
    # Ask for a name and an age, then build a message
    echo "Enter your name"
    read name
    echo "Enter your age"
    read age
    greeting="Welcome to the OS lab"
    echo "$greeting, $name"
    echo "Next year you will be $((age + 1))"

    Line by line: line 3 prints the first question; line 4 stores the typed name; lines 5 and 6 do the same for the age; line 7 stores a fixed greeting; line 8 prints the greeting and the name joined by a comma; line 9 adds one to the age with $(( )) inside the string (age is written without $ inside the parentheses, which Concept 9 allows).

  2. Run it and answer the two questions with Student1 and 20:

    bash profile.sh

    Expected terminal session (each answer sits on the line after its question):

    Enter your name
    Student1
    Enter your age
    20
    Welcome to the OS lab, Student1
    Next year you will be 21
  3. Change line 8 to single quotes: echo '$greeting, $name'. Run again with the same answers. That line now prints the literal text $greeting, $name: single quotes stop variable expansion. Change it back before continuing.

Task 3: A Calculator Driven by Positional Parameters

  1. Create calc.sh:

    #!/bin/bash
    # Usage: ./calc.sh number operator number
    echo "Argument values are: $1 $2 $3"
    operand1=$1
    operator=$2
    operand2=$3
    if [[ $operator == '+' ]]
    then
        ((result=$operand1+$operand2))
    elif [[ $operator == '-' ]]
    then
        ((result=$operand1-$operand2))
    elif [[ $operator == 'x' ]]
    then
        ((result=$operand1*$operand2))
    elif [[ $operator == '/' ]]
    then
        ((result=$operand1/$operand2))
    else
        echo "Unknown operator: $operator"
        exit 1
    fi
    echo "Result is = $result"

    Line by line: line 3 echoes the three arguments as received; lines 4 to 6 copy them into named variables (the operator is the second word, so $2 goes to operator); the if/elif chain compares the operator string with == and runs the matching (( )) assignment; the else branch reports an unknown operator and leaves with status 1; the last line prints the result. Multiplication is requested with the letter x, so the four accepted operators are +, -, x and /.

  2. Make it executable and run it four times:

    chmod 755 calc.sh
    ./calc.sh 6 + 3
    ./calc.sh 6 - 3
    ./calc.sh 6 x 3
    ./calc.sh 6 / 3

    Expected output:

    Argument values are: 6 + 3
    Result is = 9
    Argument values are: 6 - 3
    Result is = 3
    Argument values are: 6 x 3
    Result is = 18
    Argument values are: 6 / 3
    Result is = 2
  3. Run it with an operator it does not know, then read the exit status:

    ./calc.sh 6 % 3
    echo $?

    Expected output:

    Argument values are: 6 % 3
    Unknown operator: %
    1
  4. Run ./calc.sh 7 / 2. Expected result line: Result is = 3. Write in one sentence why the answer is not 3.5 (the reason is in Concept 9).

  5. Two inputs the script does not guard against. Run ./calc.sh 6 / 0 and then ./calc.sh 6 - (third argument missing). Expected output:

    Argument values are: 6 / 0
    ./calc.sh: line 18: ((: result=6/0: division by 0 (error token is "0")
    Result is = 
    Argument values are: 6 - 
    ./calc.sh: line 12: ((: result=6-: syntax error: operand expected (error token is "-")
    Result is = 

    A missing parameter is simply empty, so $operand2 disappears from the expression; in both runs the (( )) line fails, result is never assigned, and the last echo prints an empty value. Adding checks for these cases is left for the next lesson.

Task 4: Whole Numbers with Double Parentheses, Decimals with bc

  1. Create area.sh:

    #!/bin/bash
    # Circle area with double parentheses and with bc
    echo "Enter the radius"
    read radius
     
    # 1) double parentheses: whole numbers only
    square=$((radius * radius))
    echo "radius squared = $square"
    rough=$((radius * radius * 3))
    echo "rough area (pi taken as 3) = $rough"
     
    # 2) bc: decimals allowed
    area=$(echo $radius*$radius*3.14 | bc)
    echo "Area of the circle is $area"
     
    # integer division drops the fraction
    echo "7 / 2 with double parentheses = $((7 / 2))"
     
    # decrement and shorthand operators
    N=10
    ((--N))
    echo "Value after decrement = $N"
    (( N += 10 ))
    echo "Value after adding 10 = $N"

    Line by line: the radius is asked for with echo and stored with read; $(( )) squares it and then multiplies by 3, a whole-number stand-in for pi; bc receives 5*5*3.14 through the pipe and returns the decimal answer, which command substitution stores in area; the 7 / 2 line shows the dropped fraction; the last block repeats the decrement and += shorthand from Concept 9.

  2. Run it with radius 5:

    bash area.sh

    Expected output:

    Enter the radius
    5
    radius squared = 25
    rough area (pi taken as 3) = 75
    Area of the circle is 78.50
    7 / 2 with double parentheses = 3
    Value after decrement = 9
    Value after adding 10 = 19
  3. Change the rough= line to use the real value of pi inside the double parentheses: rough=$((radius * radius * 3.14)). Run again with radius 5. Expected output for that line:

    area.sh: line 9: radius * radius * 3.14: syntax error: invalid arithmetic operator (error token is ".14")
    rough area (pi taken as 3) = 

    Double parentheses stop at the decimal point; this is the reason the area line goes through bc. Restore the 3.

Task 5: Returning an Exit Code from One Script and Catching It in Another

  1. Create check.sh exactly as listed in Concept 8:

    #!/bin/bash
    echo "Enter a numeric value"
    read n
    # Return 0 when the value is less than or equal to 100, 1 otherwise
    if [[ $n -le 100 ]]
    then
        exit 0
    else
        exit 1
    fi

    Line by line: a number is read; -le tests it against 100; the script terminates immediately with status 0 or 1, and prints nothing about the decision. The decision travels only through the exit status.

  2. Create caller.sh, the left-hand participant in the Concept 8 sequence diagram:

    #!/bin/bash
    # Run check.sh, then read its exit code from $?
    bash check.sh
    code=$?
    echo "check.sh returned $code"
    if [ $code -eq 1 ]
    then
        echo "The input number is greater than 100"
    else
        echo "The input number is less than or equal to 100"
    fi

    Line by line: bash check.sh runs the first script (which asks for the number); code=$? saves the exit status before the next echo overwrites $?; the if uses the older [ ] brackets with -eq to turn the code into a message (this is the second and last [ ] listing in the lesson; code is never empty here, so no quotes are needed).

  3. Run the caller twice, answering 55 and then 110:

    bash caller.sh
    bash caller.sh

    Expected output for the two runs:

    Enter a numeric value
    55
    check.sh returned 0
    The input number is less than or equal to 100
    Enter a numeric value
    110
    check.sh returned 1
    The input number is greater than 100
  4. Read $? directly instead of saving it. Delete BOTH the line code=$? and the line echo "check.sh returned $code", and change the test to if [ $? -eq 1 ], so that nothing stands between bash check.sh and the if. Run with 110. Expected output:

    Enter a numeric value
    110
    The input number is greater than 100

    It still works because $? is read right after bash check.sh. Now insert one line, echo "checking...", between bash check.sh and the if, and run with 110 again. Expected output:

    Enter a numeric value
    110
    checking...
    The input number is less than or equal to 100

    The message is wrong because $? now holds the status of the echo, which is 0. Explain this in one sentence, then restore the step 2 version of the file. (If you delete only code=$? and keep the echo "check.sh returned $code" line, that echo is the command that overwrites $?: the run prints check.sh returned with an empty value followed by the wrong message.)

Task 6: Grade Classification with if, elif and else

  1. Create grade.sh:

    #!/bin/bash
    # Classify a mark and check exam eligibility
    echo "Enter your mark (0-100)"
    read mark
    echo "Enter your attendance percentage"
    read attendance
     
    if [[ $mark -ge 85 ]]
    then
        echo "Grade: A"
    elif [[ $mark -ge 75 ]]
    then
        echo "Grade: B"
    elif [[ $mark -ge 65 ]]
    then
        echo "Grade: C"
    elif [[ $mark -ge 50 ]]
    then
        echo "Grade: D"
    else
        echo "Grade: F"
    fi
     
    if [[ $mark -ge 50 && $attendance -ge 75 ]]
    then
        echo "You passed the course"
    else
        echo "You did not pass the course"
    fi

    Line by line: two values are asked for with echo and stored with read; the first if chain is the flowchart from Concept 10, tested from the highest threshold down so that a mark of 90 stops at the first branch and never reaches -ge 75; the second if combines two numeric tests with && inside [[ ]], so both must hold to print the pass message.

  2. Make it executable and run it four times. Each pair below is (mark, attendance): the first number answers the mark question, the second answers the attendance question. The pairs are (90, 80), (70, 90), (40, 100) and (60, 50):

    chmod u+x grade.sh
    ./grade.sh
    ./grade.sh
    ./grade.sh
    ./grade.sh

    Expected output, one block per run:

    Enter your mark (0-100)
    90
    Enter your attendance percentage
    80
    Grade: A
    You passed the course
    Enter your mark (0-100)
    70
    Enter your attendance percentage
    90
    Grade: C
    You passed the course
    Enter your mark (0-100)
    40
    Enter your attendance percentage
    100
    Grade: F
    You did not pass the course
    Enter your mark (0-100)
    60
    Enter your attendance percentage
    50
    Grade: D
    You did not pass the course
  3. Move the whole -ge 50 branch (its elif line, its then and its echo "Grade: D") to the top of the chain as the if, and make the old -ge 85 branch an elif. Run with mark 90 and attendance 80. Expected first line after the two prompts: Grade: D. Explain in one sentence why the order of branches matters when the conditions overlap. Restore the original order.

  4. Modify the script so that it also accepts the mark as a positional parameter: replace the first two lines of the body (echo "Enter your mark (0-100)" and read mark) with the single line mark=$1, and run ./grade.sh 88, answering 80 for attendance. Expected output:

    Enter your attendance percentage
    80
    Grade: A
    You passed the course

Summary

  • A Bash script is a text file of commands executed top to bottom by bash; its first line, #!/bin/bash, names the interpreter. #! /bin/bash also works, a misspelled path or a character before #! does not.
  • bash script.sh needs only read permission; ./script.sh needs the execute bit (chmod u+x or chmod 755) and a correct shebang, and the ./ tells the shell to look in the current directory.
  • # starts a single-line comment; : ' ... ' is the idiom for a multi-line comment, and the text inside it must not contain a '.
  • Assign with name=value (no spaces, no $); read with $name; double quotes expand variables, single quotes do not.
  • Keyboard input is an echo for the question followed by read var for the answer; $1, $2, $3 receive the words typed after the script name.
  • Every command leaves an exit status in $? (0 = success); a script sets its own with exit N, and $? must be read immediately because the next command overwrites it.
  • $(( )) and (( )) do whole-number arithmetic (7 / 2 gives 3, and 3.14 is a syntax error inside them); echo expression | bc handles decimals; let and expr exist but are not used in this lesson.
  • if / elif / else / fi chooses exactly one branch; conditions live in [[ ]] with spaces inside the brackets; use -eq -lt -le -gt -ge for numbers, == for strings, and && / || inside [[ ]] to combine tests. The older [ ] needs quoted variables and cannot combine tests inside the brackets.
  • The next lesson covers the rest of the material: for, while and until loops, functions, calling one script from another with source, bash, eval and exec, menus with select, case and $@, string manipulation, reading and writing files, and pipes.