---
# System prepended metadata

title: Bash
tags: [interview]

---

---
tags: interview
---
# Bash
#### echo
```bash
# print in new line
$ echo -e "hello \nworld"
# print with a tab
$ echo -e "hello \tworld"
# Omit a character (prefix)
$ echo -e "hello\bbuvanesh"
hellbuvanesh
# omit a character (suffix)
$ echo -e "hello\ebuvanesh"
hellouvanesh
```
#### I/O redirection & special characters
```bash=
# redirect STDOUT
$ ps aux > output.txt
# redirect STDOUT & STDERR
$ find / -name *foo* &> output.txt
# redirect STDERR
$ find / -name *foo* 2> output.txt
```
File descriptor 0 is STDIN
File descriptor 1 is STDOUT
File descriptor 2 is STDERR

Usage of ||, &&, ;, &:
```
"A ; B" Run A and then B, regardless of success of A
"A && B" Run B if A succeeded
"A || B" Run B if A failed
"A &" Run A in background.
```
Empty a file `> important_file`
#### if elif else
```bash=
if [[ "${UID}" -eq 0 ]]
 then
  echo "you're root"
 else
  echo "you're NOT root"
  exit 1
 fi
```
Check if a variable is defined or not
```bash=
if [[ -z "$1" ]]
then
  echo "variable not defined"
 else
  echo "variable defined"
 fi
 ```
Find if the given input is file or directory
```bash
if [[ -d $1 ]]
then
  echo "$1 is a directory"
  
elif [[ -f $1 ]]
then
  echo "$1 is a file"
fi
```
check if you've a write permission
```bash
if [[ -w $1 ]]
then
  echo "You have a write permission to this file"
else
  echo "You don't have write permission to this file"
fi
```
check if the string is empty
```bash
if [[ -n $string ]]
then
  echo "The string is not empty"
fi
```
"and" and "or" operator
```bash
a=1
b=10
if [[ $a -gt 2 ]] && [[ $b -gt 6 ]]
then
        echo "numbers are within range"
elif [[ $a -lt 2 ]] || [[ $b -gt 6 ]]
then
        echo "either one is true"
fi
```
if elif else
```bash=
if [[ $1 = "start" ]]
then
  echo "starting..."
  
elif [[ $1 = "not" ]]
then
  echo "not starting"
  exit 1
  
else
  echo "not a valid option"
fi
```
#### Expressions
eq, ge, gt, le, lt, ne
Refer: `man test`

Special characters
&&
`$ ls |grep foo && echo "success"`
echo will be executed only if the first one returns exit 0.
#### Cron
```bash=
*   *    *             *     *           = 5 stars
min 
hour 
day of month  
month 
day of week
```
Examples
```bash=
# for every 15 mins
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=linuxsbk@gmail.com
*/15 * * * *

# for every hr
0 * * * *

# at 2 pm everyday
0 2 * * *

# everyday at 12 am
0 0 * * *

# update system on 1st day of each month 4:25 am 
25 04 1 * * /usr/bin/dnf -y update

#25 =min
#04 = hr (4am)
#01 = on 1st day of every month

# send email of the output
MAILTO=linxusbk@gmail.com
```
Anacron
Anacron program handles the cron that didn't run. 
#### Calculation

1. Using **let**
```bash=
value=5
let value=value + 1
```
2. Using $(( ))
```bash=
echo $(( 5 + 5))
```
3. Using `expr`
```bash=
expr 1 + 5
```
4. Using `bc`
```bash=
bc <<< 5 + 5
# OR
echo "5 + 5" |bc
# <<< is here string
```
#### Here document
1. Write to a file
```bash=
$ cat >> test.txt << EOF
> hello
> hi
> EOF
```
2. Echo multiple lines
```bash=
$ cat << EOF
  this is line number 1
  this is line number 2
  EOF
 # this prints above lines
```
3. Very useful one for working with ftp**
```bash=
lftp localhost <<EOL
ls
get file.txt
bye
EOL

echo "The file is downloaded"
```
#### Substitution Operators
Substitution operators allows to manipulate values of variables in an easy way.
* Ensures that variable exists: `${VAR:-word}`
* Set default values for variables: `${VAR:=word}`
* Catch errors if values doesn't set for a variable: `${VAR:?message}`
* Remove portions of variable values: `${VAR:offset:length}`, In other words ``${VAR:starting_place:ending_place}``

Example 1:
```bash=
$ cat script.sh
# Won't set the value for the variable, but just prints
echo ${DATE:-today}
echo $DATE

$ sh -x  script.sh 
+ echo today
today
+ echo
```
Example 2:
```bash=
# Sets the default value for the variable
$ cat script.sh
echo ${DATE:=today}
echo $DATE

$ sh -x  script.sh
+ echo today
today
+ echo today
today
```
Example 3:
```bash=
$ cat script.sh
echo ${DATE:?variable is not set}

$ sh -x script.sh
-bash: DATE: variable is not set
```

Example 4:
```bash=
DATE=$(date +%d-%m-%y)
echo the day is ${DATE:0:2}

Output:
the day is 05

# Another example
VAR=123456789
echo ${VAR:2:5}

Output:
34567
```
#### for
```bash=
for i in {100..115}
do
  ping -c $i 192.168.56.$i
done
```
#### tr
1. convert lower characters to upper
```bash=
echo hello |tr [:lower:] [:upper:]

# upper to lower
echo hello |tr [:upper:] [:lower:]
```
Use **exit** code while reporting errors!
 Reverse use **!**
 `if ! [[ "${UID}" -eq 0 ]]`
2. Move words separated by `:` to a new line: `echo $PATH |tr ':' '\n'`

#### shift
Shift removes 1st argument and makes 2nd argument as 1st argument.
```bash=
#!/bin/bash
# Showing the use of shift

echo "The arguments provided are $*"
echo "The value of \$1 is $1"
echo "The entire string is $*"
shift

echo "The new value of \$1 is $1"
echo "The entire string now is $*"

$ sh input.sh one two three
The arguments provided are one two three
The value of $1 is one
The entire string is one two three

The new value of $1 is two
The entire string now is two three
```

 Find how many arguments are passed to a script - **$#**
 ```
 cat howmany_parameters.sh
 echo "You passed $# parameters"
 ```
 
#### while and until
 while
 ```bash
 count=5
 while [ $count -gt 5 ]
 do
   echo $count
   (( count++ ))
   # OR let count=count+1    
 done
 ```
 Useful example with while and sleep:
 Wait until a log file contains the text 'Exception'

 ```bash=
 while ! grep Exception
 do
   date
   sleep 1
 done
 ```
 until
 ```bash
 count=5
 until [ $count -gt 5 ]
 do
   echo $count
   let count=count+1
 done
 ``` 

#### sed
```bash!
# delete empty lines
sed -E '/^$/d' file.txt

# remove double quotes from lines
cat foo | sed 's/\"//g'

# print 5th line in file
sed -n 5p file.txt

# Delete 5th line in file
sed -i '5d' file.txt

# Delete last line of the file
sed -i '$d' file

# Make a backup file while replacing
sed -i.bak 's/old/new/g' foo.log

# Remove lines which are matching with a pattern\
sed -i '/example string/d' file.txt

# Print line number 5 upto end
sed -n '5,$p' file.txt

# Delete from line 2 to line 10 and also delete line number 15
sed -i '15d;2,10d' file.txt

# Regexp for finding a specific string and replacing the entire line - .*
# finds line that contains foo, and replaces the entire line with just bar
sed -i -e '/foo/c bar' file

sed -i 's/^something.*/something new/g'

####Regular expressions####
^  - matches the character(s) at the beginning of a line
&  - matches the character(s) at the end of the line

sed -ne '/^dog/p' animals.txt - print lines only start with dog
sed -ne '/dog$/p' animals.txt - print lines only end with dog
sed -ne /^dog$/p' animals.txt - print lines only start and ends with dog (without mentioning file (animals.txt) it will wait for text)
sed -ne /^dog$/Ip' -> here I used to tell sed -> don't consider whether the characters are lower or higher case (similar to grep -i option)
```
#### tcpdump
```bash!
# Target specific network interface
tcpdump -i eth0
# List all available interfaces
tcpdump -D
# for more
https://danielmiessler.com/blog/tcpdump
```
##### Reference
1. Great list of sed usage - http://sed.sourceforge.net/sed1line.txt



Redirect stdout and stderr - `&>`

Reverse the input provided by the user
```
echo $@ |rev
```
#### Arrays
```bash=
array=("hello" "buvanesh" "kumar")
echo ${array[2]}
# will print kumar

# To print all the arrays in the list
echo ${array[@]}

# To print all the indexs in the list
echo ${!array[@]}

# Arrays in string form
array=(Buvanesh Lisa Caleigh Pavel Tomas)
echo ${array[1]}
Buvanesh

# replace item
array[1]=Shaunom
echo ${array[@]}
Buvanesh Shayn Caleigh Pavel Tomas

# count index 
array=(Buvanesh Lisa Caleigh Pavel Tomas)
echo ${#array[@]}
5
```

Best to include this on top of the script - `set -eou`
Debugging shell script - `set -x` 

Exit if any of the prev command fails - `set -e`

Exit if any varialbes undefined - `set -u`

Exit if a command fails in a pipe - `set -o`


Find modified files in last 3 days 
```
find / -type f -mtime -3 -exec ls {} \;
```

#### Trap
```bash
set -x

trap "echo -e '\e[1;31mPlease do kinit before running this script!\e[0m'; exit 1" ERR INT TERM
klist -s
trap - ERR

release=$1
package=$2
owner=$3
setup_or_branch_git $release $package
brew add-pkg --owner=$owner $release $package

# Example
# release = rhel-8.0.0
# package = thermald
# owner = prarit
# Ref: RCM-50722

```

#### case
```bash
case $1 in
	start)
		echo "starting"
		;;
	status|state|--status|--state)
		echo "status"
		;;
	stop)
		echo "stopping"
		;;
	*)
		echo "not a valid statement"
		;;
esac
```
Another eample:
```bash
read -p "Enter a character: " var
case $var in
   [a-z])
        echo "You entered a lower case alphabet." ;;
   [A-Z])
        echo "You entered an upper case alphabet." ;;  
   [0-9])
        echo "You entered a digit." ;;
   ?)
        echo "You entered a special symbol." ;;
   *)
        echo "You entered more than one characters."
esac
```
#### function
```
function function_name{
  ls -l 
}

function_name
```
OR
```
function_name() {
  echo "hello world"
}

function_name
```
#### cut
```bash=
# Remove first two columns
cut -d " " -f 3- foo
## -d " ": use a single space as the delimiter (cut uses TAB by default)
## -f: specify fields to keep
## 3-: all the fields starting with field 3

# Get first character from all the lines of the file
cut -c 1 /etc/passwd

# Print characters from 5th position to 9th position in all the lines
cut -c 5-8 /etc/passwd

# Print all the characters starting from 5th position
cut -c 5- /etc/passwd

# Print first 4 characters from all the lines
cut -c -4 /etc/passwd

# Print 1st, 4th and 8th character
cut -c 1,4,8 /etc/passwd

# Print 2nd column separated by tab
echo -e 'one\ttwo\tthree' |cut -f 2

# Separated by comma
echo -e 'one,two,three' | cut -d , -f 2

# prints 3rd column
echo -e 'one two three' | cut -d " " -f 3

# Change output delimiter for cut and awk
cut -d : -f 1,5 --output-delimiter , /etc/passwd
```
#### diff
1. Print if there is a difference between two files
```bash
diff -rq file1 file2
Files file1 and file2 differ
```
2. Print common lines and diffs between two files
```bash
 diff -u file1 file2        
--- file1	2020-06-05 16:53:31.757459971 +0530
+++ file2	2020-06-05 16:53:43.167411642 +0530
@@ -1,3 +1,3 @@
-hey buvanesh
-how are you
+hey ravi
+how are you?
 ok ok
# + = present in file2
# - = present in file1
# <no-sign> = common line in both files
```
#### du
```bash=
# find directry size of each
du -h --max-depth=1
```
#### AWK
```bash=
# Remove empty and whitespaces in starting line of the file

$ cat foo.txt 
test
    new
   file
buvi
$ cat foo.txt |awk '{$1=$1;print}'
test
new
file
buvi

# Change output delimiter use -v OFS=,
awk -F : -v OFS=, '{print $1,$3}' /etc/passwd

# Print 3rd column which has value greater than 500
awk -F : '$3 > 500' /etc/passwd

# Search and print
awk -F : '/lisa/ { print $4 }' /etc/passwd
#^ it will search for the line having lisa, and it will print 4th column in the line.

# Customize the awk output
awk -F : '{print "column one:"$1 " column three:" $3}' /etc/passwd
awk -F : '{print "UID: "$3 ";LOGIN:" $1}' /etc/passwd

# Print last field
awk -F : '{print $NF}' /etc/passwd

# Print 2nd column from the last field
awk -F : '{print $(NF -1)} /etc/passwd'
```
#### grep
Refer: https://antonz.org/grep-by-example
Print lines ends with `w`
```
grep w$ /file
```
Print lines that starts with `first` and ends with `last`
```bash
cat file.txt |grep ^first |grep last$
# NOT WORKING
grep '^first,last$' /file
```
Grep multiple string
```
netstat -nutl |grep -vR '^Active|^Proto' 
```
Print the line number as well
```bash
$ grep -n string file.txt
1: file something
6: some file ok
```
Print files which has only the matching string
```
$ grep -l -R something
$ ack -l something
```
#### find
```bash=
#find and replace all the occurrence of foo with bar in a path folder
find /path -type f -exec sed 's/foo/bar/g' {} \;

# find only directories with the filename hello
find / -name hello -type d

# find files based on their permissions (777) and find the opposite
find / -type f -perm 0777
find / -type f ! -perm 777 

# find all the empty files and directories
find / -type f -empty
fnd / -type d -empty 

# find all the executable files
find / -perm /a=x

# find and delete all the files that ends with log
find /path -name '*.log' -exec rm -rf {} \;
# OR
find /path -name '*.log' |xargs rm -rf

# Find the files with *bar* except the ones having *foo* in its filename
find . ! -name '*foo*' -name '*bar*'
# find files and remove its part of its name
find . -name 'y2mate.com - *' |while read video
do
  mv $video ${video/"y2mate.com - "/}
done
# here it removes y2mate.com - from all the filenames

# find files and rename its part of filename
find . -name '*.log' |while read file
do
  mv $file ${file/.log/.LOG}
done

# find files and list their permissions
find . -name '*foo*' -ls 

# find files which are not owned by anyone
find . -nouser

# list files which are owned by bsivasub and has permission of 4000 (4 denotes suid)
find . -perm /4000 -user bsivasub -ls

# find executables which are not installed by package managers
# 
# find files which are changed in last 15 mins
find /etc -cmin -60

# find files which are modified within 5 days
find . -type f -mtime -5 -exec ls {} \;

# find files which are modified for more than 5 days
find . -type f -mtime +5 -exec ls {} \;

# find files which are modified exactly 5 days ago
 find . -type f -mtime 5 -exec ls {} \;

# mtime, atime, ctime
ctime: Many times this is understood as a creation time but that wrong.
Ctime is change time of file stats such as ownership, what permissions it has etc.

mtime: File modification time. 
Value of mtime is updated when content of file changes.

atime. File access time. 
Value of atime is modified when file is opened. 
```
#### sort
```bash=
## Sort by alphabets
sort /file

## Reverse 
sort -r /file

## Sort by number
sort -n /file

## Print only uniq numbers
sort -un /file

## Sort specific column 
sort -n -t : -k 3 /etc/passwd
```
#### uniq
```bash
# Find how many times a line is repeated
uniq -c /path
```

Get top 3 most accessed resource from apache log
```bash
cat access_log |cut -d '"' -f 2 |awk '{print $2}' |uniq -c |sort -rn |head -n 2
```
#### set
```bash
line="shell:scripting:is:fun"
IFS=:
set $line

echo $1
echo $2
```
An another example:
```bash
read -p "Enter username: " username
line=$(grep $username /etc/passwd)

IFS=:
set $line

echo "Username: $1 \nUser ID: $3 \nHome Folder: $6"
```
### nmcli
```bash
# get IP address, gateway, DNS, and domain of the machine
$ nmcli con show
$ nmcli device show <device-got-from above output>
IP4.ADDRESS[1]:                         10.0.1.24/24
IP4.GATEWAY:                            10.0.1.1
IP4.DOMAIN[1]:                          ec2.internal
IP4.DNS[1]:                             10.0.0.2
```
### Redirection
redirect stderr
```
ls / 2> /var/err.log
```

Discard stdout and stderr
```
ls / &> /dev/null
```
Discard stderr
```
ls / 2> /dev/null
```
### Exercises
Exercise 1:
```
if ! [[ $UID = 0 ]] 
then
  echo "Please run the script with sudo or as root"
  exit 1
elif [[ $# -lt 1 ]]
then
  echo "Please provide atleast one argument. 
        Usage info: script [user-name]"
  exit 1
elif [[ $# -eq 1 ]]
then
  echo "Creating a new user named $1"
  useradd $1
fi
shift
echo "Parameters passed additionally are: $@"
```
Exercise 2:
```
log(){
  if [[ $VERBOSE = 'true' ]]
  then
    echo $@
  fi
  logger -t script.sh $@
}

backup_file() {
  if [[ -f $1 ]]
  then
    log "Backing up $1 to /var/tmp/$(basename $1).$(date +%F-%N)\"
    cp -p $1 "/var/tmp/$(basename $1).$(date +%F-%N)\"
  else
    return 1
fi
}

VERBOSE=true
log 'Hello!'
log 'This is fun!'

backup_file '/etc/passwd'
```

Exercise 3:
```
read CHAR
if [[ ($CHAR = Y) || ($CHAR = y) ]]
then
  echo "YES"
elif [[ ($CHAR = N) || ($CHAR = n) ]]
then
  echo "NO"
fi
```

Exercise 4:
Find and remove files
```
remove_file (){
	while read -r file; do
		rm -rf $file || true
	done
}

find . -name hello |remove_file

# read -r => read handles / as a special character, to ignore use -r option.
```

Sample from work
```bash
#!/bin/bash

COMPOSE_REMOVE="/usr/bin/compose-remove"

ShowHelp () {
    echo "
This script performs nightly clean-ups of old composes of RHEL 8.

Available options:

-d | --dry-run  Debug. Only print commands that would be executed.
-h | --help     Help. Shows this help.
-v | --verbose  Simply more output.
"
    exit
}

LoggingDebug () {
    if [[ "$VERBOSE" ]]; then
        echo "$@"
    fi
}
RemoveCompose () {
    if [[ "$DRY_RUN" ]]; then
        while read -r filename; do
            LoggingDebug "Would delete $filename"
            "$COMPOSE_REMOVE" --force --test "$filename" || true # Ignore errors
        done
    else
        while read -r filename; do
            LoggingDebug "Removing $filename"
            "$COMPOSE_REMOVE" --force "$filename" || true # Ignore errors
        done
    fi
}

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -h|--help) ShowHelp;;
        -d|--dry-run) DRY_RUN=true;;
        -v|--verbose) VERBOSE=true;;
        *) echo "Unknown parameter: $1" >&2 ; exit 1 ;;
    esac
    shift
done

#
find /mnt/nigtly -maxdepth 1 -name "something*" -mtime +10 | RemoveCompose

```
Addition
1. ``expr``
2. ``echo $(( X + Y))``

### Articles
https://askubuntu.com/a/707753/869835
*** A very good article about set options in shell
https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/ 

 ### Questions
 1. Find how many parameters are passed to a script
 2. Delete last line
 3. Delete content of a file
    ```bash
    cat /dev/null > file.txt
    # OR
    > file.txt
    ```
Exampls
1. Ref: https://raw.githubusercontent.com/coreos/etcd-operator/master/example/rbac/create_role.sh
```bash=
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail

ETCD_OPERATOR_ROOT=$(dirname "${BASH_SOURCE}")/../..

print_usage() {
  echo "$(basename "$0") - Create Kubernetes RBAC role and role bindings for etcd-operator
Usage: $(basename "$0") [options...]
Options:
  --role-name=STRING         Name of ClusterRole to create
                               (default=\"etcd-operator\", environment variable: ROLE_NAME)
  --role-binding-name=STRING Name of ClusterRoleBinding to create
                               (default=\"etcd-operator\", environment variable: ROLE_BINDING_NAME)
  --namespace=STRING         namespace to create role and role binding in. Must already exist.
                               (default=\"default\", environment variable: NAMESPACE)
" >&2
}

ROLE_NAME="${ROLE_NAME:-etcd-operator}"
ROLE_BINDING_NAME="${ROLE_BINDING_NAME:-etcd-operator}"
NAMESPACE="${NAMESPACE:-default}"

for i in "$@"
do
case $i in
    --role-name=*)
    ROLE_NAME="${i#*=}"
    ;;
    --role-binding-name=*)
    ROLE_BINDING_NAME="${i#*=}"
    ;;
    --namespace=*)
    NAMESPACE="${i#*=}"
    ;;
    -h|--help)
      print_usage
      exit 0
    ;;
    *)
      print_usage
      exit 1
    ;;
esac
done

echo "Creating role with ROLE_NAME=${ROLE_NAME}, NAMESPACE=${NAMESPACE}"
sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" \
  -e "s/<NAMESPACE>/${NAMESPACE}/g" \
  "${ETCD_OPERATOR_ROOT}/example/rbac/cluster-role-template.yaml" | \
  kubectl create -f -

echo "Creating role binding with ROLE_NAME=${ROLE_NAME}, ROLE_BINDING_NAME=${ROLE_BINDING_NAME}, NAMESPACE=${NAMESPACE}"
sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" \
  -e "s/<ROLE_BINDING_NAME>/${ROLE_BINDING_NAME}/g" \
  -e "s/<NAMESPACE>/${NAMESPACE}/g" \
  "${ETCD_OPERATOR_ROOT}/example/rbac/cluster-role-binding-template.yaml" | \
  kubectl create -f -

```
2. Create users from LDAP output, ask user for confirmation before creating the users
```bash=
VAR="cn=lisa,dc=example,dc=com
cn=linda,dc=example,dc=com
cn=laura,dc=example,dc=com"

for i in $VAR
do
  USER=$(echo $i |awk -F = '{print $2}' |awk -F , '{print $1}')
  echo $USER
done

AddUser(){
  for user in $VAR
  do
    USER=$(echo $user |awk -F = '{print $2}' |awk -F , '{print $1}')
    useradd $USER
    echo "$USER added successfully"
  done
}
echo "Can I go ahead and create these users? (Y/n)"
read answer

case $answer in
  yes|YES|y|Y) AddUser ;;
  no|n|NO|N) echo "Not adding any users" && exit ;;
esac

```
3. Create a script (using getopts) to create users
```bash=
# script that creates users using preferred options
# usage: use -a to add home dir
#        use -b to make the user member of group 100
#        use -c to specify shell

while getopts abc: opt
do
  case $opt in
	  a) VAR1=-m ;;
	  b) VAR2="-g 100" ;;
	  c) VAR3="-s $OPTARG" ;;
	  *) echo "usage: $1 [-a] [-b] [-c shell] username"
esac
done

if [ $# -lt 1 ]
then
	echo "usage: $1 [-a] [-b] [-c shell] username"
	exit 1
fi

echo "The current arguments are set to $*"
shift $((OPTIND -1))
echo "Now the current arguments are set to $*"
echo "useradd $VAR1 $VAR2 $VAR3 $1"
```