Logo

Bash Scripting, Part 2

15 min read
Lesson slides
1 / 16

Operating Systems I - Lesson 12

Bash Scripting, Part 2

Repeat work with loops, organize logic into functions, call one script from another, build menus, manipulate strings, and move data through redirection and pipes.

By the end of this lesson you will be able to write Bash scripts that repeat work with loops, organize logic into functions, call one script from another, build interactive menus, manipulate strings, and read or write files through redirection and pipes.

Objectives

  • Repeat work with for over a word list, while with a counter, and until; update counters with (( )).
  • Write functions in both declaration forms, pass arguments, keep variables local, and hand a result back through a global variable.
  • Run one script from another with source, bash, eval and exec, and know how each differs.
  • Build a menu with select, process command-line arguments with case, and handle strings with IFS, read -a, ${var^^} and tr.
  • Read a file line by line, write with > and >>, and chain sort, grep and wc with pipes.

Prerequisites

  • The previous lesson on Bash scripting: the shebang line, making a script executable, variables, read, $1/$2, $?, exit, $(( )) arithmetic, bc, and if/elif/else with [[ ]].
  • A Linux terminal (a local install, a virtual machine, or WSL).
  • The bc calculator, needed for one function example. If it is missing, install it with your package manager.
  • A single working directory for all the scripts in this lesson, since one task's script calls another task's helper scripts by name.

"Create the script" below means: paste the listing into a text editor, save it with a .sh extension, then run it with bash name.sh (or make it executable once and run ./name.sh afterwards). Two short steps in this lesson deliberately repeat scripts from the previous lesson: they are review, placed where this material builds on them.

Concept 1: Loops

Definition. A loop repeats a block of commands. Bash has three loop keywords, for, while and until, and every loop body sits between do and done.

Purpose. Any work that must happen once per item (each day of the week, each word of a sentence, each number from 1 to 20) is written once inside the body and the loop runs it as many times as needed.

How it works. for takes the next word from a list on every pass and stops when the list is used up. while and until test a condition before every pass:

flowchart TD
    A[Start] --> B{Condition}
    B -- "while: true / until: false" --> C[Run the body between do and done]
    C --> D["Update the counter, for example counter++"]
    D --> B
    B -- "while: false / until: true" --> E[Continue after done]

The only difference between while and until: while runs as long as the test succeeds, until as long as it fails.

for over a word list

The most common form walks through a list of words, one per iteration:

#!/bin/bash
# Read a weekday name in each iteration of the loop
for day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday
do
    if [[ $day == 'Monday' || $day == 'Thursday' ]]
    then
        echo "Meeting on $day at 9:30 am"
    elif [[ $day == 'Tuesday' || $day == 'Wednesday' || $day == 'Friday' ]]
    then
        echo "Training on $day at 11:00 am"
    else
        echo "$day is Holiday"
    fi
done
  • for day in ...: day is the loop variable. On each pass it takes the next word from the list after in.
  • do / done: the body. Everything between them runs once per word.
  • The if/elif/else inside the body compares the current $day with string values, so each day gets its own message.

for over the words of a sentence

The list after in does not have to be a set of separate names. A sentence typed there is split on its spaces, and each word becomes one iteration, which is how a for loop reads string data word by word:

#!/bin/bash
# Read each word of a text by using for loop
for value in Bash Programming for the Beginners
do
    echo $value
done
  • for value in Bash Programming for the Beginners: the five words form the list; value receives them one at a time.
  • echo $value: prints the current word on its own line, so the output is Bash, Programming, for, the, Beginners, one per line.

while with a counter

#!/bin/bash
echo "Print the numbers which are even and divisible by 5"
counter=1
while [ $counter -le 20 ]
do
    if [[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]
    then
        echo "$counter"
    fi
    ((counter++))
done
echo "Done"
  • counter=1: the loop variable must be set before the loop, or the test fails immediately.
  • while [ $counter -le 20 ]: the body runs while counter is less than or equal to 20.
  • [[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]: -eq inside [[ ]] evaluates both sides as arithmetic, so $counter%2 becomes, for example, 10%2, which is 0. % is the remainder operator.
  • ((counter++)): adds 1 to counter. Forgetting this line gives an infinite loop; press Ctrl+C if that happens.

until

#!/bin/bash
n=20
until [ $n -lt 0 ]
do
    if [[ $n%2 -gt 0 ]]
    then
        echo $n
    fi
    ((n=$n-1))
done
  • until [ $n -lt 0 ]: the body runs until n becomes less than 0, in other words while n is 0 or more.
  • [[ $n%2 -gt 0 ]]: a remainder greater than 0 means n is odd, so the script prints the odd numbers counting down from 19 to 1.
  • ((n=$n-1)): the counter goes down instead of up. This is the same step as the pre-decrement ((--N)) seen earlier.

Concept 2: Functions

Definition. A function is a named block of commands that you define once and call as many times as you need. Calling it is the same as typing its name as a command.

Purpose. When the same block is needed in several places of a script, a function holds it once, and each place calls the name instead of repeating the lines.

How it works. The definition gives the name, empty parentheses and the body in braces. A call is the bare name, optionally followed by values; inside the function those values are $1, $2, exactly as command-line arguments are for a script. A result comes back either as text the function prints or as a variable the function sets and the caller reads afterwards:

flowchart LR
    A[Main script] -- "call: calculate_area 5, the 5 becomes $1" --> B[Function body]
    B -- "sets a global variable, for example return_str" --> A
    B -- "prints a line with echo" --> T[Terminal]

Declaration forms, arguments and local

#!/bin/bash
function print_message()
{
    echo "Bash programming for beginner"
}
 
function ret_strdata()
{
    return_str="Learn bash programming step by step"
}
 
function calculate_area()
{
    local radius=$1
    area=$(echo $radius*$radius*3.14 | bc)
    echo "Area of the circle is $area"
}
 
print_message
ret_strdata
echo $return_str
echo "Enter the radius value"
read rad
calculate_area $rad
  • function print_message() { ... }: the first declaration form. The keyword function is optional, so print_message() { ... } is the second form and means exactly the same thing. Both need the empty parentheses and the braces.
  • ret_strdata sets return_str with no local. Variables are global by default, so the main script can print it after the call: the "return through a global variable" style, which is how a function hands text back in Bash.
  • calculate_area receives its argument the way a script does: the first value after the function name becomes $1. local radius=$1 copies it into a variable that exists only inside the function; without local, a function that sets a name used by the main script overwrites it.
  • $(echo $radius*$radius*3.14 | bc): (( )) handles integers only, so the decimal multiplication is piped to bc, and $( ) captures what bc prints. A bare * can be expanded into file names by the shell; here the pattern 5*5*3.14 matches no file, so it reaches bc unchanged, but quoting the expression as "$radius * $radius * 3.14" is the safe habit.
  • print_message: ret_strdata, calculate_area $rad: the calls. Definitions must come before the calls; Bash reads top to bottom and does not know a function until it has passed its definition.

Scope: global by default, local on request

The local keyword from the listing above deserves its own demonstration, because the difference is invisible until a function and the main script use the same name:

#!/bin/bash
counter=100
show_local()
{
    local counter=5
    echo "Inside function counter = $counter"
}
 
show_local
echo "Outside function counter = $counter"
  • counter=100: a global variable set by the main script.
  • local counter=5: inside the function, a second, separate counter that exists only until the function returns.
  • show_local then echo "Outside ...": the function prints 5; the main script still prints 100, because the global one was never touched. Remove the word local and both lines print 5.

Between whole scripts, a result travels as an exit code instead: a script ends with exit 0 or exit 1, and the script that ran it checks $?.

Concept 3: Calling one script from another

Definition. A script can start another script the way you start it from the prompt. Four commands do this, and they differ in which shell runs the second script and what happens to the first one.

Purpose. Splitting work into several files keeps each one short, and the caller decides whether the second file's variables should survive, whether arguments are passed, and whether the caller continues afterwards.

How it works. A child shell is a second bash process started by the first one; it gets its own copy of the variables, and everything it sets disappears when it exits. Only source avoids the child shell:

flowchart TD
    S[callpro.sh in shell A] --> S1["source add.sh: same shell, variables stay"]
    S --> S2["bash subtract.sh 50 20: child shell B, returns to A"]
    S --> S3["eval bash multiply.sh $a $b: build the command text, then run it"]
    S --> S4["exec full/path/divide.sh 30: shell A replaced, nothing after runs"]
  • source file: reads the file's commands into the current shell; any variable the file sets is still there afterwards. source receives the name as a file argument, so a bare add.sh in the current directory works.
  • bash file args: starts a child shell. Arguments become $1, $2 inside it; its variables vanish when it finishes. Like source, bash takes the name as a file argument, so a bare subtract.sh works.
  • eval bash file $a $b: eval first joins its arguments into one line of text, then runs that text as a command. It matters when the command text is only built at run time, for example when the whole command lives in a variable. With plain variables, as in the examples here, the result is identical to typing bash multiply.sh 6 7; the listing shows eval only for its syntax.
  • exec file args: replaces the running script with the target program, so the lines after exec never execute. exec looks its target up in PATH exactly like a typed command name, so a bare divide.sh fails with exec: divide.sh: not found; a full path or a ./divide.sh form both work. $PWD holds the same text the pwd command prints: the full path of the current directory.

Concept 4: Menus with select and arguments with case

Definition. select name in list prints the list as a numbered menu, shows the prompt #?, reads a number, and stores the matching word in name. case compares one value against several patterns and runs the branch of the first match.

Purpose. select gives a script an interactive menu in one line. case replaces a long if/elif chain when one value must be matched against many fixed words.

How it works. select is a loop: it repeats until the script exits, so the body tests for an "Exit" choice and calls exit 0:

flowchart TD
    A[Print the numbered list] --> B["Show the #? prompt"]
    B --> C[Read a number]
    C --> D[Store the matching word in language]
    D --> E{Is it Exit?}
    E -- yes --> F[exit 0]
    E -- no --> G[echo the selected word]
    G --> B

Combined with cut, case handles key=value arguments:

for arg in "$@"
do
    key=$(echo $arg | cut -f1 -d=)
    value=$(echo $arg | cut -f2 -d=)
    case $key in
        name) echo "Student's name = $value";;
        mark) echo "Obtained mark = $value";;
        *)
    esac
done
  • "$@": all command-line arguments, each kept as one word even if it contains spaces (so name="Ahmed Ali" arrives intact).
  • cut -f1 -d=: cut the text at = (-d= sets the delimiter) and keep field 1; -f2 keeps field 2.
  • case $key in ... esac: each pattern ends with ), each branch ends with ;;, and *) is the default that matches anything. Here the default branch is empty, so an unknown key is silently ignored.

Concept 5: Strings

Definition. A string is a piece of text held in a variable. Bash has no separate string functions; the common operations are done with quoting, expansions and small commands.

Purpose. Scripts join text, compare it, split it into words and change its case constantly: building a message, checking an answer, reading a line of input word by word.

How it works.

  • Concatenation: place the variables next to each other, echo "$string1$string2".
  • Comparison: if [ "$text" == "Python" ]. Quote the variable so an empty input does not break the test.
  • Splitting and case change are the two new operations, each shown by a listing below.

Splitting a string into an array

An array is one variable that holds several values, numbered from 0: arr[0] is the first element, "${arr[@]}" lists every element as separate words. read -a arr stores each word of its input as the next element of arr.

#!/bin/bash
echo "Enter a string value"
read text
IFS=' '
read -a arr <<< "$text"
for value in "${arr[@]}";
do
    printf "$value\n"
done
  • read text: the whole typed line lands in text.
  • IFS=' ': sets the Internal Field Separator, the character read splits on, to a space.
  • read -a arr <<< "$text": <<< feeds the string to read as if it were typed, and -a stores each word as the next element of arr.
  • for value in "${arr[@]}";: the loop walks the elements, one per pass. The trailing ; before the line break is optional and harmless.
  • printf "$value\n": printf prints exactly its format string, so \n must be written to get a newline; echo adds one on its own. Typing Learn Bash programming prints the three words on three lines.

The sentence loop in Concept 1 split words in the same way, but its words were fixed in the script; this listing splits text typed at run time.

Changing case

#!/bin/bash
text1='[email protected]'
echo "${text1^^}"
text2='Bash Programming Basics'
echo $text2 | tr '[:upper:]' '[:lower:]'
  • ${text1^^}: Bash's own uppercase expansion; it prints [email protected].
  • echo $text2 | tr '[:upper:]' '[:lower:]': tr turns every uppercase character coming through the pipe into lowercase, giving bash programming basics. Quote the bracket expressions so the shell does not treat them as filename patterns.

Concept 6: Files and pipes

Definition. Standard output is what a command prints to the terminal; standard input is what it reads from the keyboard. Both can be redirected: > and | take the output somewhere else, read and | feed the input from somewhere else.

Purpose. Redirection lets a script keep results in a file and read them back later; a pipe lets several commands act as one without a temporary file.

How it works. Reading a file:

#!/bin/bash
echo "Reading file using cat command"
content=`cat hardware.txt`
echo $content
 
echo "Reading file line by line using loop"
filename='hardware.txt'
while read line;
do
    echo $line
done<$filename
  • content=`cat hardware.txt`: backticks are the older spelling of $( ); the whole file lands in one variable. echo $content without quotes prints it as one line, because the newlines become spaces.
  • while read line;: read line succeeds as long as a line is left, so the while stops at the end of the file. The trailing ; before the line break is optional, exactly as in the for loop of Concept 5.
  • done<$filename: redirects the file into the whole loop, so every read takes the next line from the file instead of the keyboard, and each line prints separately.

Writing: echo $str1 > test.txt creates or overwrites the file; echo $str2 >> test.txt appends.

A pipe | sends the standard output of one command into the standard input of the next:

flowchart LR
    A[marks.txt] --> B[sort] -- sorted lines --> C["grep 'Keya'"] -- matching lines --> D[wc -l] --> E[2]

Task 1: Four loops in one script

  1. Create loops.sh with the four loops of Concept 1 assembled into one file:
#!/bin/bash
# Read a weekday name in each iteration of the loop
for day in Monday Tuesday Wednesday Thursday Friday Saturday Sunday
do
    if [[ $day == 'Monday' || $day == 'Thursday' ]]
    then
        echo "Meeting on $day at 9:30 am"
    elif [[ $day == 'Tuesday' || $day == 'Wednesday' || $day == 'Friday' ]]
    then
        echo "Training on $day at 11:00 am"
    else
        echo "$day is Holiday"
    fi
done
 
# Read each word of a text by using for loop
for value in Bash Programming for the Beginners
do
    echo $value
done
 
echo "Print the numbers which are even and divisible by 5"
counter=1
while [ $counter -le 20 ]
do
    if [[ $counter%2 -eq 0 && $counter%5 -eq 0 ]]
    then
        echo "$counter"
    fi
    ((counter++))
done
echo "Done"
 
n=20
until [ $n -lt 0 ]
do
    if [[ $n%2 -gt 0 ]]
    then
        echo $n
    fi
    ((n=$n-1))
done
  1. Run bash loops.sh. Expected output:
Meeting on Monday at 9:30 am
Training on Tuesday at 11:00 am
Training on Wednesday at 11:00 am
Meeting on Thursday at 9:30 am
Training on Friday at 11:00 am
Saturday is Holiday
Sunday is Holiday
Bash
Programming
for
the
Beginners
Print the numbers which are even and divisible by 5
10
20
Done
19
17
15
13
11
9
7
5
3
1
  1. Change until [ $n -lt 0 ] to until [ $n -lt 10 ] and run again. Expected: the last block now stops at 11, because the loop ends as soon as n drops below 10.
  2. Remove the ((counter++)) line from the while block, run the script, and press Ctrl+C after a second. Expected: "Done" never prints, because counter stays at 1 forever. Put the line back.

Task 2: Functions, local variables and exit codes

  1. Create func.sh from the three-function listing in Concept 2 and run bash func.sh, typing 5 at the prompt. Expected output:
Bash programming for beginner
Learn bash programming step by step
Enter the radius value
5
Area of the circle is 78.50
  1. Create scope.sh from the show_local listing in Concept 2 and run bash scope.sh. Expected output:
Inside function counter = 5
Outside function counter = 100
  1. Delete the word local in show_local and run again. Expected: both lines now show 5, because the function overwrote the script's global variable.
  2. Review from the previous lesson: a script hands a result to its caller through an exit code. Create the two files below, then run bash second.sh twice, typing 55 and then 110.

first.sh:

#!/bin/bash
echo "Enter a numeric value"
read n
if [[ $n -le 100 ]]
then
    exit 0
else
    exit 1
fi

second.sh:

#!/bin/bash
bash "first.sh"
if [ $? -eq 1 ]
then
    echo "The input number is greater than 100"
else
    echo "The input number is less than or equal to 100"
fi

Expected for the two runs:

Enter a numeric value
55
The input number is less than or equal to 100
Enter a numeric value
110
The input number is greater than 100

second.sh never sees the number, only the exit code of first.sh.

Task 3: Calling scripts with source, bash, eval and exec

  1. Create the four helper scripts. Each listing is a complete file.

add.sh:

#!/bin/bash
a=60
b=40
((result=$a+$b))
echo "The addition of $a+$b=$result"

subtract.sh:

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

multiply.sh:

#!/bin/bash
((result=$1*$2))
echo "The multiplication of $1 and $2 is $result"

divide.sh:

#!/bin/bash
a=$1
b=2
((result=$a/$b))
echo "The division of $a by $b is $result"
  1. Make the four scripts executable, since exec needs its target to be executable.
  2. Create callpro.sh. exec looks up its target like a typed command, so it needs the full path of divide.sh; $PWD supplies the path that pwd prints, so the script works no matter which directory it is created in:
#!/bin/bash
script1="add.sh"
script2="subtract.sh"
script3="multiply.sh"
script4="$PWD/divide.sh"
 
source "$script1"
echo "After source, result is still visible here: $result"
 
bash $script2 50 20
 
echo "Enter the value of a"
read a
echo "Enter the value of b"
read b
eval bash $script3 $a $b
 
exec $script4 30
echo "This line never runs because exec replaced the shell"
  1. Run bash callpro.sh and enter 6 then 7. Expected output:
The addition of 60+40=100
After source, result is still visible here: 100
The subtraction of 50-20=30
Enter the value of a
6
Enter the value of b
7
The multiplication of 6 and 7 is 42
The division of 30 by 2 is 15

The second line shows 100 because source ran add.sh in the same shell. The eval line prints the same as bash multiply.sh 6 7 would, as Concept 3 explains. The final echo never appears because exec replaced that shell with divide.sh.

  1. Change source "$script1" to bash "$script1" and run again. Expected: the second line ends right after the colon, because add.sh ran in a child shell (a second bash process) whose result was discarded when it exited.
  2. Change script4="$PWD/divide.sh" to script4="divide.sh" and run again. Expected last line: callpro.sh: line 18: exec: divide.sh: not found, because exec searched PATH and the current directory is not in it. Restore the $PWD form.

Task 4: A menu and a command-line calculator

  1. Create menu.sh:
#!/bin/bash
echo "Select your favorite language"
select language in C# Java PHP Python Bash Exit
do
    if [[ $language == "Exit" ]]
    then
        exit 0
    else
        echo "Selected language is $language"
    fi
done
  • select language in C# Java PHP Python Bash Exit: the six words are the menu; the chosen word lands in language.
  • if [[ $language == "Exit" ]] then exit 0: the only way out of the loop.
  • echo "Selected language is $language": any other choice is echoed and the #? prompt returns.
  1. Run bash menu.sh, type 3, then 6. Expected:
Select your favorite language
1) C#
2) Java
3) PHP
4) Python
5) Bash
6) Exit
#? 3
Selected language is PHP
#? 6

The menu prints once, the #? prompt returns after every choice, and 6 ends the script through exit 0.

  1. Review: create cl1.sh, which reads three arguments: a number, an operator and a number. x stands for multiplication because a bare * on the command line would be expanded into file names.
#!/bin/bash
echo "Argument values are: $1 $2 $3"
operand1=$1
operand2=$3
operator=$2
 
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))
fi
echo -e "Result is = $result"

echo -e enables backslash escapes such as \n inside the text; here the text has none, so it makes no visible difference and plain echo would print the same line.

  1. Run it four times: bash cl1.sh 6 + 3, bash cl1.sh 6 - 3, bash cl1.sh 6 x 3, bash cl1.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
  1. Create cl2.sh from the case listing in Concept 4, with #!/bin/bash and echo "Argument values are: $1 $2" as its first two lines. Run bash cl2.sh name="Ahmed Ali" mark=90. Expected:
Argument values are: name=Ahmed Ali mark=90
Student's name = Ahmed Ali
Obtained mark = 90
  1. Modify the empty default branch: replace the line *) with *) echo "Unknown argument: $key";;. Run bash cl2.sh name="Ahmed Ali" mark=90 age=20. Expected:
Argument values are: name=Ahmed Ali mark=90
Student's name = Ahmed Ali
Obtained mark = 90
Unknown argument: age

The first line still shows only two arguments because it prints $1 $2; the loop over "$@" sees all three, and age reaches the *) branch.

Task 5: Strings and files

  1. Create split.sh from the splitting listing in Concept 5. Run it and type Learn Bash programming. Expected:
Enter a string value
Learn Bash programming
Learn
Bash
programming

The input has three words, so the array has three elements and the loop prints three lines.

  1. Create case.sh from the case-change listing in Concept 5 and run bash case.sh. Expected:
[email protected]
bash programming basics
  1. Create hardware.txt with one word per line: Monitor, Keyboard, Mouse, Scanner, Printer. Create readfile.sh from the Concept 6 listing and run bash readfile.sh. Expected:
Reading file using cat command
Monitor Keyboard Mouse Scanner Printer
Reading file line by line using loop
Monitor
Keyboard
Mouse
Scanner
Printer
  1. Create writefile.sh, run it, and type Hello Bash then Second line:
#!/bin/bash
echo "Enter some text"
read str1
echo $str1 > test.txt
echo "Enter some other text"
read str2
echo $str2 >> test.txt
echo `cat test.txt`
  • read str1 then echo $str1 > test.txt: the first text creates (or overwrites) the file.
  • read str2 then echo $str2 >> test.txt: the second text is appended as a new line.
  • echo `cat test.txt`: prints the file's content; the unquoted backticks turn the two lines into one, as in Concept 6.

Expected session:

Enter some text
Hello Bash
Enter some other text
Second line
Hello Bash Second line

Then run cat test.txt and expect the two lines on separate rows: > created the file and >> appended to it. Run the script again with different words: the file holds only the new pair, because > overwrote it.

Task 6: Pipes

  1. Create marks.txt with exactly these six lines (columns separated by spaces or tabs):
Asraf   CSE-409         79
Kabir   CSE-304         95
Keya    CSE-101         67
Asraf   CSE-304         88
Keya    CSE-409         90
Asraf   CSE-101         92
  1. Do the job the long way, three commands and a temporary file:
sort marks.txt
grep 'Keya' marks.txt > temp.txt
wc -l temp.txt

Expected output of the three commands:

Asraf   CSE-101         92
Asraf   CSE-304         88
Asraf   CSE-409         79
Kabir   CSE-304         95
Keya    CSE-101         67
Keya    CSE-409         90
2 temp.txt

sort orders the lines alphabetically (the three Asraf lines by course code), grep writes the two Keya lines into temp.txt, and wc -l counts them.

  1. Do it the short way: sort marks.txt | grep 'Keya' | wc -l. Expected output: 2. No temp.txt is needed; each output flows straight into the next command.
  2. Change the pipeline to count Asraf instead. Expected: 3.

Summary

  • for word in list walks a list, whether the list is a set of names or the words of a sentence; while runs while a test succeeds; until while it fails. Every body sits in do ... done.
  • Update counters with ((counter++)) or ((n=$n-1)); a loop without an update never ends.
  • Functions are declared as name() { } with or without the function keyword, must be defined before they are called, and receive arguments as $1, $2. Variables are global by default; local keeps one inside the function. A function hands text back by setting a global variable; whole scripts hand a result back with exit N, read through $?.
  • source keeps the called script's variables; bash runs it in a child shell; eval builds the command text first and behaves like bash when the text is plain; exec replaces the current shell for good and needs a full path such as $PWD/divide.sh.
  • select builds a numbered menu with the #? prompt and loops until exit; case ... esac matches patterns, *) being the default; cut -f1 -d= splits key=value.
  • Strings: concatenate by adjacency, compare with [ "$a" == "$b" ], split into an array with IFS plus read -a and walk it with "${arr[@]}", change case with ${var^^} or tr. printf needs an explicit \n.
  • Read files with while read line; do ...; done<file, write with > (overwrite) and >> (append), and chain commands with |, which links one command's standard output to the next command's standard input.