Shell Scripting part3

A function may return a value in one of four different ways:

  • Change the state of a variable or variables
  • Use the exit command to end the shell script
  • Use the return command to end the function, and return the supplied value to the calling section of the shell script
  • echo output to stdout, which will be caught by the caller just as c=`expr $a + $b` is caught
[maxwell@oracle-db-19c shell_20230320]$ cat function.sh
#!/bin/sh
# A simple script with a function...

add_a_user()
{
  USER=$1
  PASSWORD=$2
  shift; shift;
  #Having shifted twice, the rest is now comments... ?
  COMMENTS=$@
  echo "Adding user $USER..."
  echo useradd -c "$COMMENTS" $USER
  echo passwd $USER $PASSWORD
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}


###
# Main body of script starts here
###

echo "Start of script..."
add_a_user bob letmein Bob Holness the presenter
add_a_user fred badpassword Fred Durst the singer
add_a_user bilko worsepassword Sgt. Bilko the role model
echo "End of Script..."
[maxwell@oracle-db-19c shell_20230320]$ 

Scope of Variables

Programmers used to other languages may be surprised at the scope rules for shell functions. Basically, there is no scoping, other than the parameters ($1, $2, $ @, etc).

The $@ parameters are changed within the function to reflect how the function was called. The variable x, however, is effectively a global variable – myfunc changed it, and that change is still effective when control returns to the main script.

$ ./scope.sh a b c
Script was called with a b c
x is 1
I was called as : 1 2 3
x is 2
$
$ cat scope.sh
#!/bin/sh

myfunc()
{
  echo "I was called as : $@"
  x=2

}

### Main script starts here

echo "Script was called with $@"
x=1
echo "x is $x"
myfunc 1 2 3
echo "x is $x"
$ 

Recursion

Functions can be recursive – here’s a simple example of a factorial function:

$ ./factorial.sh
Enter a number:
4
twenty four
Enter a number:
2
2
Enter a number:
5
120
Enter a number:
6
720
Enter a number:
7
5040
Enter a number:
^C
$ cat factorial.sh
#!/bin/sh

factorial()
{
  if [ "$1" -gt "1" ]; then
    i=`expr $1 - 1`
    j = `factorial $i`
    k=`expr $1 \* $j`
    echo $k
  else
    echo 1
  the fi

}


while :
do
  echo "Enter a number:"
  read x
  factorial $x

done
$
$
$ cat common.lib
# common.lib
# Note no #!/bin/sh as this should not spawn
# an extra shell. It's not the end of the world
# to have one, but clearer not to.
#
STD_MSG="About to rename some files..."

rename()
{
 

  # expects to be called as: rename .txt .bak
  FROM=$1
  TO=$2

  for i in *$FROM
  do
    j = `basename $i $FROM`
    mv $i ${j}$TO
  done
           
           
}
$ cat function2.sh
#!/bin/sh
#function2.sh
. ./common.lib
echo $STD_MSG
rename.txt.bak
$
$ cat function3.sh
#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename.html.html-bak
$

Return Codes

#!/bin/sh

adduser()
{
  USER=$1
  PASSWORD=$2
  shift ; shift
  COMMENTS=$@
  useradd -c "${COMMENTS}" $USER
  if [ "$?" -ne "0" ]; then
    echo "Useradd failed"
    return 1
  the fi
  passwd $USER $PASSWORD
  if [ "$?" -ne "0" ]; then
    echo "Setting password failed"
    return 2
  the fi
  echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}

## Main script starts here

adduser bob letmein Bob Holness from Blockbusters
ADDUSER_RETURN_CODE=$?
if [ "$ADDUSER_RETURN_CODE" -eq "1" ]; then
  echo "Something went wrong with useradd"
elif [ "$ADDUSER_RETURN_CODE" -eq "2" ]; then
   echo "Something went wrong with passwd"
else
  echo "Bob Holness added to the system."
fi

grep is an extremely useful utility for the shell script programmer.
An example of grep would be:

$
$ grep -i maxwell /etc/passwd
maxwell:x:1000:1000:maxwell:/home/maxwell:/bin/bash
$ grep -i maxwell /etc/passwd | cut -d: -f1
maxwell
$ vim grep_maxwell.sh
$ chmod 775 grep_maxwell.sh
$
$ ./grep_maxwell.sh
All users with the word "maxwell" in their passwd
Entries are: maxwell
$
$ grep -i maxwell /etc/passwd
maxwell:x:1000:1000:maxwell:/home/maxwell:/bin/bash
$
$ grep -i maxwell /etc/passwd |cut -d: -f1
maxwell
$
$ cat grep_maxwell.sh
#!/bin/sh
maxwell=`grep -i maxwell /etc/passwd | cut -d: -f1`
echo "All users with the word "maxwell" in their passwd"
echo "Entries are: $maxwell"
$ 
$ vim grep_maxwell_tr.sh
$ chmod 775 grep_maxwell_tr.sh
$
$ ./grep_maxwell_tr.sh
All users with the word maxwell in their passwd
Entries are:
MAXWELL
$
$ cat grep_maxwell_tr.sh
maxwell=`grep -i maxwell /etc/passwd | cut -d: -f1`
echo "All users with the word "maxwell" in their passwd"
echo "Entries are: "
echo "$maxwell" | tr ' ' '\012' | tr '[a-z]' '[A-Z]'
$ 

Cheating

Cheating with awk

Consider wc, which counts the number of characters, lines, and words in a text file.

[maxwell@oracle-db-19c shell_20230320]$ PS1="$ " ; export PS1
$
$ wc grep_maxwell.sh
  4 23 150 grep_maxwell.sh
$ 

AWK is a programming language and tools for text processing, available in the shell. AWK processes the input data in line units, and splits, filters and formats the output according to the rules specified by the user.

AWK command consists of three parts: mode (pattern), action (action) and input file (input file). The pattern is used to match certain content in the input data, the action defines the operation to be performed when the pattern matches the input data, and the input file specifies the data source to be processed.

Common AWK uses include:

  1. formatted text data output
  2. Do a text search and replace
  3. Statistics and analysis of text data

If we want to get the number of lines into a variable, simply using:

$ NO_LINES=`wc -l grep_maxwell.sh | awk '{print $1}'`
$ echo ${NO_LINES}
4
$ 

Cheating with sed

Another handy utility is sed – the stream editor. we can quickly use the s/from/to/g construct by invoking sed.

$ touch test.txt
$ vim test.txt
$
$ cat test.txt
This line is okay.
This line contains a bad word. Treat with care.
This line is fine, too.

There is nothing wrong with cheating! Some things the shell just isn't very good at. Two useful tools are sed and awk. Whilst these are two hugely powerful utilities, which can be used as mini-programming languages in their own right, they are often used in shell scripts for very simple, specific reasons.
$
$ sed s /line/lines/g test.txt > test_result.txt
$ cat test_result.txt
This lines is okay.
This lines contains a bad word. Treat with care.
This lines is fine, too.

There is nothing wrong with cheating! Some things the shell just isn't very good at. Two useful tools are sed and awk. Whilst these are two hugely powerful utilities, which can be used as mini-programming languages in their own right, they are often used in shell scripts for very simple, specific reasons.
$ 

sed is a stream editor and tool for text processing in the shell. It can edit, replace, delete, and add to the input data according to the specified rules, and output the results to standard output or files.

A sed command consists of one or more editing commands, each of which contains an address range and an action. The address range specifies the row or range of rows to operate on, and the operation defines the editing action to be performed.

Common sed uses include:

  1. Find and replace text
  2. formatted output of text
  3. Selectively show or delete lines of text

15. Quick Reference

Command Description Example
& amp ; Run the previous command in the background ls & amp;
& amp; & amp; Logical AND if [ "$foo" -ge "0" ] & amp; & amp; [ "$foo" -le "9" ]
|| Logical OR if [ "$foo" -lt "0 " ] || [ "$foo" -gt "9" ]
^ Start of line grep "^foo"
$ End of line grep " foo$"
= String equality (cf. -eq) if [ " $foo" = "bar" ]
! Logical NOT if [ "$ foo" != "bar" ]
$$ PID of current shell echo "my PID = $$"
$! PID of last background command ls & amp; echo "PID of ls = $!"
$? exit status of last command ls ; echo "ls returned code $?"
$0 Name of current command ( as called) echo "I am $0"
$1 Name of current command’s first parameter echo "My first argument is $1"
$9 Name of current command’s ninth parameter echo "My ninth argument is $9"
$@ All of current command’s parameters (preserving whitespace and quoting) echo "My arguments are $@"
$* All of current command’s parameters (not preserving whitespace and quoting) echo "My arguments are $*"
-eq Numeric Equality if [ "$foo" -eq "9" ]
-ne Numeric Inquality if [ "$foo" -ne "9" ]
-lt Less Than if [ "$foo" -lt "9" ]
-le Less Than or Equal if [ "$foo" -le "9" ]
-gt Greater Than if [ "$foo" -gt "9" ]
-ge Greater Than or Equal if [ "$foo" -ge "9" ]
-z String is zero length if [ -z "$foo" ]
-n String is not zero length if [ -n "$foo" ]
-nt Newer Than if [ "$file1" -nt "$file2" ]
-d Is a Directory if [ -d /bin ]
-f Is a File if [ -f /bin/ls ]
-r Is a readable file if [ -r /bin/ls ]
-w Is a writable file if [ -w /bin/ls ]
-x Is an executable file if [ -x /bin/ls ]
( … ) Function definition function myfunc() { echo hello }

IFS

In Shell, IFS is the abbreviation of “Internal Field Separator”. It is an environment variable that specifies the character or string used to separate fields. When parsing commands, the shell uses this variable to determine how to split words and spaces on input lines.

By default, IFS is set to a set of characters including spaces, tabs, and newlines. This means that the shell will use these characters to split command-line arguments and standard input data streams.

The separator can be customized by changing the IFS value in the script or command. For example, IFS can be set to comma “,” to have the shell use commas to split input lines. Note, save the original value before changing IFS, and restore it when you’re done to avoid impacting other parts of your program.

IFS is an environment variable that is commonly used in shell scripts to specify delimiters to process text data. Here are some usages of IFS:

  1. Read file lines and split fields: You can use a while loop to read each line of the file, and on each iteration set IFS to the appropriate delimiter (such as comma or tab), then use the read command to split the lines for the field.
#!/bin/bash
while IFS=',' read -r col1 col2 col3
do
    echo "Column 1: $col1"
    echo "Column 2: $col2"
    echo "Column 3: $col3"
done < data.csv

2. Split string: You can use IFS to split a string into an array. For example, the following code will split a string into an array using spaces as delimiters:

#!/bin/bash
string="Hello World"
IFS=' ' read -ra arr <<< "$string"

for i in "${arr[@]}"
do
   echo "$i"
done

3. Implement command substitution: You can use $(command) or backtick characters to execute commands, and use IFS to split the output into an array. For example, the following code uses IFS to split the output of the df command into an array:

#!/bin/bash
IFS=$'\
' df_output=($(df -h))

for line in "${df_output[@]}"
do
    echo "$line"
done

IFS is a very useful variable that can be used to manipulate text data and implement various shell programming tasks.