---
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"
```