DEV Community

Back2Basics
Back2Basics

Posted on

Shell Scripting Back to Basics

Shell Scripting

1. Variables naming conventions recommended

  • file="filename.txt" no spaces allowed between '='
  • house_color, source_file
  • constants written in UpperCase Example: read only FILE="filename.txt"
  • varialbe outside of scope to be used then use exports .

2. Functions:

functions can start with function keyword or without it.

calculate_area() {
}
clone_repo() {
}
Enter fullscreen mode Exit fullscreen mode

Expanding Variables

The below both versions works well.

#!/bin/bash
var="value of var"
echo ${var}
Enter fullscreen mode Exit fullscreen mode
#!/bin/bash
var="value of var"
echo $var
Enter fullscreen mode Exit fullscreen mode

The difference come when appending additional string in the end. Expanding variables with {} should be used based on need.

#!/bin/bash

height=70
echo "Your height is = $heightcm" # throws error
echo "Your height is = ${height}cm" # works good
Enter fullscreen mode Exit fullscreen mode

Without the quotes the variable string will split by field seperators here space.

#!/bin/bash
string="One Two Three"
for element in ${string}; do
    echo ${element} # or echo $element
done
Enter fullscreen mode Exit fullscreen mode

O/P:

One
Two
Three

Now try enclosing with the double quotes. The string will not split out and will be act as entire string as one element.

#!/bin/bash
string="One Two Three"
for element in "${string}"; do
    echo ${element} # or echo $element
done
Enter fullscreen mode Exit fullscreen mode

O/P:

One Two Three

For the below scenario we want server names seperately.

#!/bin/bash
readonly SERVERS="server1 server2 server3"
for server in ${SERVERS}; do
    echo "${server}.worker-node"
done
Enter fullscreen mode Exit fullscreen mode

O/P:

server1.worker-node
server2.worker-node
server3.worker-node

Recommended to use "" expanding variables:

  1. Directory paths, filenames
  2. Assigning URLs to variables
  3. When in doubt, use double quotes :)

Let revisit basic concepts of linux commands, loops, performing tasks. Shell scripting is much more powerfull. It follows the top down approach and only when we can it gets executed ( calling functions).

# list items
ls
pwd
cd
mkdir
Enter fullscreen mode Exit fullscreen mode

Lets say there is some command will throws the errors to break the script over there we have declare "set -x" in the top of script.

Usage of [[ ]] for conditional controls.

#!/bin/bash
if [[ 3 > 4 ]]; then
   echo " three greater than four";
fi
Enter fullscreen mode Exit fullscreen mode

Shell script has 4 loop types

  1. While
  2. for
  3. until similar to do-while loop

While loop

#!/bin/bash
i=1
while [[ $i -le 3 ]]; do
    echo "Iteration $i"
    i=$((i+1))
done
Enter fullscreen mode Exit fullscreen mode

For loop

for i in {1..3}; do
# for i in $(seq 1 3); do using the sequence as sub-shell the output from it is treated input to i
   echo "Iteration $i"
done
Enter fullscreen mode Exit fullscreen mode

Until Loop

#!/bin/bash

i=3
until [[ $i -eq 0 ]]; do
    echo "Iteration $i"
    i=$((i-1))
done
Enter fullscreen mode Exit fullscreen mode

To import the other script file

There were sometimes we need to import variables from other files(.sh)

# conf file
name="Ubuntu"
Enter fullscreen mode Exit fullscreen mode

Example

#!/bin/bash
source .conf # sourcing the conf file
echo "${name}"
Enter fullscreen mode Exit fullscreen mode

what if the conf file doesnt exists. We need to checkpoint in the script

#!/bin/bash
readonly CONF_FILE=".conf"
if [[ -f ${CONF_FILE} ]]; then
   source "${CONF_FILE}"
else
   name="Bob"
fi

echo "${name}"
exit 0
Enter fullscreen mode Exit fullscreen mode

Functions

Functions encapsulates code. Allows re-usability.
Here is the script that takes backup without function

#!/bin/bash
mkdir backup
cd backup
cp -r "file.txt" .
tar -czvf backup.tar.gz *
echo "Backup complete!!"
exit 0
Enter fullscreen mode Exit fullscreen mode

now convert to a function

perform_backup() {
  mkdir backup
  cd backup
  cp -r "${1}" .
  tar -czvf backup.tar.gz *
  echo "Backup complete!!"
}
perform_backup ${1}
exit 0
Enter fullscreen mode Exit fullscreen mode

Cloning a git repo

#!/bin/bash
git_url=${1}

clone_git() {
  git clone ${1}
}
find_files() {
  find . -type f | wc -l
}

clone_git "${git_url}"
find_files
Enter fullscreen mode Exit fullscreen mode

Declaring variables local to a function. Here the variable local is scoped under my_function.

#!/bin/bash
my_function() {
  local var1="Hello"
  echo "${var1}"
}

my_function
Enter fullscreen mode Exit fullscreen mode

Command Line Arguments

Image description
Pass the argument which needs to add dynamically and it keeps changing

#!/bin/bash
git clone "${1}"
find . -type f | wc -l
Enter fullscreen mode Exit fullscreen mode

clone_project.sh [git-url]

Sometimes remembering these cmdline args bit hard.
so use the "shift" keyword then each time used the next usage will be the next argument serially it act as $1

#!/bin/bash
echo "First arg: $1"
shift
echo "Second arg: $1"
shift
echo "Third arg: $1"
Enter fullscreen mode Exit fullscreen mode

./shift-example.sh arg1 arg2 arg3

O/P:

First arg: arg1
Second arg: arg2
Third arg: arg3

Each Process running on the terminal will associated to the tty session PID. If you want to keep the programme running even after closing the terminal run by "nohup ./script.sh &".
Each Tab in terminal creates its own session with the tty PID as root PID.

Killing processes by PID:

Image description

To check the systemcall made by the process by PID

strace -Tfp 99838
Enter fullscreen mode Exit fullscreen mode

Options:
T - Timing info
f - Child Process
p - Parent Shells PID

Built-in Commands

Built-in commands dont generate the PID

Image description

Some Exercise Questions:

Looping 1 to 99 odd numbers

#!/bin/bash
for((i=1;i<100;i+=2)); do
  echo $i
done
Enter fullscreen mode Exit fullscreen mode

Reading one line of string

#!/bin/bash
read name;
echo "Welcome ${name}";
Enter fullscreen mode Exit fullscreen mode

Arithematic operations

#!/bin/bash
read x
read y
if [[ $x -ge -100 && $x -le 100 && $y -ge -100 && $y -le 100 ]]; then
    echo "$((x+y))"
    echo "$((x-y))"
    echo "$((x*y))"
    if [[ $y -ne 0 ]]; then
        echo "$((x/y))"
    else
        echo "Not possible"
    fi
else
    echo "No correct"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Comparing Two numbers

#!/bin/bash
read x
read y
if [[ $x -gt $y ]]; then
    echo "X is greater than Y"
fi
if [[ $x -lt $y ]]; then
    echo "X is less than Y"
fi
if [[ $x -eq y ]]; then
    echo "X is equal to Y"
fi
Enter fullscreen mode Exit fullscreen mode

Comparing characters

#!/bin/bash
read ch

if [[ $ch == "y" || $ch == "Y" ]]; then
    echo "YES";
fi

if [[ $ch == "N" || $ch == "n" ]]; then
    echo "NO";
fi
Enter fullscreen mode Exit fullscreen mode

OR

#!/bin/bash
read ch

case $ch in
    [yY])  echo “YES” ;;
    [nN])  echo “NO” ;;
    *)   echo “No input” ;;
esac
Enter fullscreen mode Exit fullscreen mode

Evaluating arithmetic expression with floating point 3 decimal places

#!/bin/bash
read expression
result=$(echo "scale=4; $expression" | bc -l)
#for getting 4 decimal places scale is used
result=$(printf "%.3f" $result) # for 3 decimal places rounded
echo $result
Enter fullscreen mode Exit fullscreen mode

Avg of column values in a file

#!/bin/bash

# Compute the average using awk
avg=$(awk '{sum+=$1} END {print sum/NR}' log.txt)

echo "Average: $avg"
Enter fullscreen mode Exit fullscreen mode

OR

#!/bin/bash

# Initialize sum and count
sum=0
count=0

# Read each line in the file
while read -r number; do
    sum=$((sum + number)) # Add the current number to sum
    count=$((count + 1)) # Increment the count
done < log.txt

# Calculate the average
if [ $count -gt 0 ]; then
    avg=$((sum / count))
    echo "Average: $avg"
else
    echo "No numbers in the file"
fi
Enter fullscreen mode Exit fullscreen mode

AVG N numbers

read n
result=0
for((i=0;i<$n;i++)) do
    read num
    result=$(echo " $result + $num "|bc)
done
r=$(echo "$result / $n" | bc -l)
printf "%0.3f" "$r"
Enter fullscreen mode Exit fullscreen mode

Print 3rd char of each line

#!/bin/bash
while read line; do
    echo "${line}" | cut -c3
done
Enter fullscreen mode Exit fullscreen mode

Print 2nd position till 7th position of each line

#!/bin/bash
while read line; do
    echo "${line}" | cut -c 2-7
done
Enter fullscreen mode Exit fullscreen mode

Print 2nd position and 7th position of each line of input

#!/bin/bash
while read line; do
    echo "${line}" | cut -c 2,7
done
Enter fullscreen mode Exit fullscreen mode

Print first 4 characters of each line of string

#!/bin/bash
while read line; do
echo "${line}" | cut -c 0-4
done
Enter fullscreen mode Exit fullscreen mode

Print characters from 13th position to till the end

#!/bin/bash
while read line; do
    echo "${line}" | cut -c 13-
done
Enter fullscreen mode Exit fullscreen mode

C7

Print 4th word of each line

#!/bin/bash
while read line; do
    echo "${line}" | cut -d ' ' -f 4
done
Enter fullscreen mode Exit fullscreen mode

Print first 3 words in each line

#!/bin/bash
while read line; do
    echo "${line}" | cut -d ' ' -f -3
done
Enter fullscreen mode Exit fullscreen mode

Print words with tab delimeter from 2nd word to till end of sentence

#!/bin/bash

while read line; do
    # Extract everything from the second field onward, assuming tab as the delimiter
    echo "$line" | cut -d $'\t' -f 2-
done
Enter fullscreen mode Exit fullscreen mode

Print first 20 lines of text file

#!/bin/bash

head -20 $1
Enter fullscreen mode Exit fullscreen mode

Print first 20 character of text file

#!/bin/bash

head -c 20 $1
Enter fullscreen mode Exit fullscreen mode

Print from 12 to 22 lines in text file

#!/bin/bash

head -n 22 $1 | tail -n 11
Enter fullscreen mode Exit fullscreen mode

Print last 20 lines in text fille

#!/bin/bash
tail -n 20 $1
Enter fullscreen mode Exit fullscreen mode

Print last 20 character of text file

#!/bin/bash
tail -c 20 $1
Enter fullscreen mode Exit fullscreen mode

Replace () with [] in a text file

#!/bin/bash
cat $1 | tr '()' '[]'
Enter fullscreen mode Exit fullscreen mode

Remove lower case characters from the line

#!/bin/bash
tr -d 'a-z'
Enter fullscreen mode Exit fullscreen mode

replace multiple consecutive spaces with single space

#!/bin/bash
while read input; do 
    echo $input | tr -s " "  # s is squeeze
done
Enter fullscreen mode Exit fullscreen mode

Sort in lexicographical/dictionary order of text file

#!/bin/bash
cat $1 | sort -d
Enter fullscreen mode Exit fullscreen mode

Sort in reverse lexicographical/dictionary order of text file

#!/bin/bash
cat $1 | sort -r
Enter fullscreen mode Exit fullscreen mode

Sort numeric,float numbers in a file

#!/bin/bash
cat $1 | sort -n
Enter fullscreen mode Exit fullscreen mode

Sort numeric,float numbers in a file

#!/bin/bash

cat $1 | sort -nr
Enter fullscreen mode Exit fullscreen mode

Sort tab seperated file having tabluar data descending, by column2(k2)

#!/bin/bash
sort -rnk2 -t $'\t'
Enter fullscreen mode Exit fullscreen mode

Sort tab seperated file having tabluar data descending, by column2(key =2nd column) monthly temp avgs.

sort -t$"|" -rnk2
Enter fullscreen mode Exit fullscreen mode

Remove Consecutive repetitions in a file

#!/bin/bash
uniq $1
Enter fullscreen mode Exit fullscreen mode

Consecutive duplicates with count,element removing leading spaces

!/bin/bash

uniq -c | sed 's/^[[:space:]]*//'

Consecutive duplicates with count,element removing leading spaces, ignore case

!/bin/bash

uniq -ci | cut -c7-

Print only uniq line

!/bin/bash

uniq -u

Replace new lines with tab

tr "\n" "\t"

OR

paste -s

repeat the file 3 times

paste - - -

AWK

  1. https://www.thegeekstuff.com/2010/02/awk-conditional-statements/#google_vignette
  2. https://www.thegeekstuff.com/2010/01/awk-introduction-tutorial-7-awk-print-examples/

Your task is to identify the performance grade for each student. If the average of the three scores is 80 or more, the grade is 'A'. If the average is 60 or above, but less than 80, the grade is 'B'. If the average is 50 or above, but less than 60, the grade is 'C'. Otherwise the grade is 'FAIL'.

#!/bin/bash
cat marks.txt
awk '{
if (($2+$3+$4)/3 >= 80)
print $0,":","A";
else if (($2+$3+$4)/3 >= 60 && ($2+$3+$4)/3 < 80)
print $0,":","B";
else if (($2+$3+$4)/3 >= 50 && ($2+$3+$4)/3 < 60)
print $0,":","C"; 
else
print $0,":","FAIL";
}' 
Enter fullscreen mode Exit fullscreen mode

Concatenate every 2 lines of input with a semi-colon.

awk 'ORS=NR%2?";":"\n"' 
Enter fullscreen mode Exit fullscreen mode

ORS is output record seperator we are choosing based on condition.

NR is count of rows

awk{ print $1.$2 }’ test.log
Enter fullscreen mode Exit fullscreen mode

it will concatenate the column 1 and 2

mimicing grep with awk

awk{/test/ print $1 }’ test.log
Enter fullscreen mode Exit fullscreen mode

it will print only lines with test matching in column 1

atleast one lower case letter matching in each line

awk ‘/[a-z]/  { print }’ test.log
Enter fullscreen mode Exit fullscreen mode

Every line starts with a number

awk ‘/^[0-9]/ { print }’ test.log
Enter fullscreen mode Exit fullscreen mode

Every line ends with a number

awk ‘/[0-9]$/ { print }’ test.log
Enter fullscreen mode Exit fullscreen mode

Print line that contain 123 in column 1

awk{
  if ($1 ~ /123/)
    print 
}’ test.log
Enter fullscreen mode Exit fullscreen mode

print line contains number in column 1

awk{
  if ($1 ~ /[0-9]/)
    print 
}’ test.log
Enter fullscreen mode Exit fullscreen mode

Check if all the three columns in each row are not empty atleast

#!/bin/bash
awk '{
if( $2 =="" || $3 =="" || $4 =="" )
print "Not all scores are available for",$1
}'
Enter fullscreen mode Exit fullscreen mode

Students pass/fail check

awk '{
    avg = ($2 + $3 + $4)/3
    if ( avg >=50 )
        print $1,":","Pass"
    else
        print $1,":","Fail"
}'
Enter fullscreen mode Exit fullscreen mode

GREP

https://www.thegeekstuff.com/2009/03/15-practical-unix-grep-command-examples/#google_vignette

https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_02.html

REGEX
https://www.gnu.org/software/sed/manual/html_node/Regular-Expressions.html

Find the lines which contianer only these words no case sensitive

egrep -wi "the|that|then|those" 
Enter fullscreen mode Exit fullscreen mode

credit cards digits are groups of 4 with 4 digits each group.

We need to print those numbers having consecutive digits same more than one time either seperated by space or without space. ( is used to make Basic regular expression to capture the group [0-9] and \s* is for spaces and \1 for back reference group ([0-9])

grep '\([0-9]\)\s*\1'
Enter fullscreen mode Exit fullscreen mode

Print the lines that doesnt contain the “that” word case-insensitive

grep -iv "that"
Enter fullscreen mode Exit fullscreen mode

Print the lines that does contain the “the” word case-insensitive

grep -wi "the"
Enter fullscreen mode Exit fullscreen mode

SED

grep -n “maths”  dump.log
Enter fullscreen mode Exit fullscreen mode

it give the matched lines with line number.

grep  -n “maths”$ dump.log
Enter fullscreen mode Exit fullscreen mode

it give the matched lines in end of line with “maths” with line number.

grep  -n  ^“maths” dump.log
Enter fullscreen mode Exit fullscreen mode

it give the matched lines in start of line with “maths” with line number.

$ !! -c 
Enter fullscreen mode Exit fullscreen mode

this will give the last ran command and its output

grep -n “maths”  dump.log -c 
Enter fullscreen mode Exit fullscreen mode

this will print the output that how many times the “maths” exists with count.

grep  -n “ma..” dump.log
Enter fullscreen mode Exit fullscreen mode

match expression is ma

grep -n ^”[ab]” dump.log
Enter fullscreen mode Exit fullscreen mode

match expression is words that match start with either a or b.


For aa, ab, bb, ba - ^”[ab][ab]”
atleast one number – “[0-9]”
digit followed by letter any case - “[0-9][a-zA-Z]”

case-insensitive replace thy with your globally

sed 's/\bthy\b/your/Ig'
Enter fullscreen mode Exit fullscreen mode

Highlight the thy within {}

sed -e 's/thy/{&}/Ig'
Enter fullscreen mode Exit fullscreen mode

mask first three groups of credit card numbers

awk '{
    print "**** **** ****",$4
}'
Enter fullscreen mode Exit fullscreen mode

using regex

sed 's/\([0-9]\{12\}\)[0-9]\{4\}/\1****/g'
Enter fullscreen mode Exit fullscreen mode

reorders the credit card numbers of 4 groups of 4 digits each.

sed -E 's/([0-9]{4})([0-9]{4})([0-9]{4})([0-9]{4})/\4 \3 \2 \1/g'
Enter fullscreen mode Exit fullscreen mode

Explanation:

1. ([0-9]{4} ): This matches a block of 4 digits followed by a space.
    ◦ ([0-9]{4}) captures 4 digits, and the space after it is also captured.
    ◦ The whole expression will match four groups of 4 digits with spaces.
2. \4 \3 \2 \1: This specifies the order of the captured groups in the replacement part:
    ◦ \4: Refers to the fourth captured group (the last 4 digits).
    ◦ \3: Refers to the third captured group (the third 4 digits).
    ◦ \2: Refers to the second captured group (the second 4 digits).
    ◦ \1: Refers to the first captured group (the first 4 digits).
   This changes the order of the groups, swapping them as required.
3. g: The g at the end ensures that the substitution is applied globally to all occurrences in the line.
Enter fullscreen mode Exit fullscreen mode

Reading country names line by line and inserting into an array. Printing 3rd index element

i=0
countries=[]
while read line; do
    countries[i]=$line
    i=$((i+1))
done
echo "${countries[3]}"
Enter fullscreen mode Exit fullscreen mode

Print count of elements in the input

i=0
countries=[]
while read line; do
    countries[i]=$line
    i=$((i+1))
done
echo "${#countries[@]}"
Enter fullscreen mode Exit fullscreen mode

Print the elements replacing the first capital letter with “.”

i=0
countries=()
while read line; do
    if [[ $line =~ ^[A-Z] ]]; then
        countries[i]=".${line:1}"
        i=$((i+1))
    fi
done
echo "${countries[@]}"
Enter fullscreen mode Exit fullscreen mode

Print the number that occurs only once in a duplicate elements array.

#!/bin/bash

# Read the number of integers
read n

# Read the array of integers
read -a arr

# Initialize a variable to hold the result
result=0

# XOR all the numbers
for num in "${arr[@]}"; do
    result=$((result ^ num))
done

# Output the number that occurs only once
echo $result
Enter fullscreen mode Exit fullscreen mode

Array :

Reference Doc

delete first 100 lines in a file:

sed -i '1,100d' filename
Enter fullscreen mode Exit fullscreen mode

start with Cap letter and followed by 2 letters:

egrep -o "\b[A-Z][a-z]{2}\b" /etc/nsswitch.conf > /home/bob/filtered1 
Enter fullscreen mode Exit fullscreen mode

5 digit number in a file:

egrep -o  '[0-9]{5}'  /home/bob/textfile > /home/bob/number
Enter fullscreen mode Exit fullscreen mode

Count the lines start with number 2:

egrep -c '^2' /home/bob/textfile
Enter fullscreen mode Exit fullscreen mode

Count the lines begin with “Section” in a file:

egrep -ic '^Section' /home/bob/testfile
Enter fullscreen mode Exit fullscreen mode

word “man” exact match lines:

egrep   "\bman\b" /home/bob/testfile > /home/bob/man_filtered
Enter fullscreen mode Exit fullscreen mode

extract last 500 lines in a file:

tail -500 /home/bob/textfile  > /home/bob/last
Enter fullscreen mode Exit fullscreen mode

Reference Doc

Top comments (0)