bash

Posted by neverset on October 24, 2020

common usages

check available shell

cat /etc/shells
#check current shell
echo $SHELL

multiline comments

use : ‘ and ‘ for multiline comments

Here Document

The content between the two delimiters (Here Document Content part) is passed to cmd as an input parameter

cat << EOF
First Line
Second Line
Third Line EOF
EOF

conditional statement

if [$num -gt 5] && [$num -lt 10] 
then
    echo "in range 5 and 10"
elif [$num -lt 5] || [$num -gt 10]
    echo "not in range 5 and 10"
else
    echo "not equal!"
fi

for loop

for i in {1..5}
#for i in $(seq 1 2 10)
do 
    echo $i
done

string processing

#length of string
echo $(#str_name)
#cat string
newstring=$str1$str2

arithmetics

echo $((num1 + num2))
#or
echo $(expr $num1 - $num2)

echo $((num1 * num2))
#or
echo $(expr $num1 \* $num2)

#convert hex to decimal
echo "obase=10; ibase=16; $hex" | bc

declare statement

declare [-aixr] variable
-a: Define the following variable as an array -i: Define the following variavle as an integer number -x: The usage is the same as export, which is to turn the following variable into an environment variable -r: Set a variable to read-only. Reading the variable cannot change the content, nor can it unset

check file and directory

if [-d $dir]
then
    echo "it is a directory"
fi
if [-f $file]
then
    echo "it is a file"
fi

filter

grep

#check if pattern exists in logfile
grep -q $pattern $logfile
#print a line not containing the pattern
grep -v $pattern $logfile
#print a line ignoring case in pattern
grep -i $pattern $logfile
#print a line after teh matched pattern
grep -A1 $pattern $logfile
#print a line before teh matched pattern
grep -B1 $pattern $logfile

awk

#print entire file contents
awk '{print}' $filename
#print lines containing pattern
awk '/root/ {print}' $filename
#print 3th columns of the line containing pattern
awk '/root/ {print $3}' $filename

sed

#replace first matching of root with other word groot
sed "s/root/groot/" $filename
#replace all matching 
sed "s/root/groot/g" $filename
#replace and write change to file
wed -i "s/root/groot/g" $filename

array

#define array
$ declare -A userdata
$ userdata[name]=seth
$ userdata[pass]=8eab07eb620533b083f241ec4e6b9724
$ userdata[login]=`date --utc +%s`
#view entire array
$ typeset -A
#remove item from array
$ unset userdata[pass]
$ unset "userdata[*]"

Tips

start line

#!/usr/bin/env bash

arguments

if [[ $# != 2 ]];then
    echo "Parameter incorrect."
    exit 1
fi

double quotes

#!/bin/sh
#as known there is an a.sh file in current folder
var="*.sh"
echo $var
echo "$var"
#output
a.sh
*.sh

use main

#!/usr/bin/env bash
func1(){
    #do sth
}
func2(){
    #do sth
}
main(){
    func1
    func2
}
main "$@"

default variable scope is global

#!/usr/bin/env bash
var=1
func(){
    var=2
}
func
echo $var
#output
2

return value

return value of function can only be integer, but there are some work-arounds for string return value

func(){
    echo "2333"
}
res=$(func)
echo "This is from $res."

Indirect reference

VAR1="2323232"
VAR2=VAR1
eval $VAR1=233
echo ${!VAR1}

heredocs to file

write multiline contents to file

cat>>/etc/rsyncd.conf << EOF
log file = /usr/local/logs/rsyncd.log
transfer logging = yes
log format = %t %a %m %f %b
syslog facility = local3
EOF

path

script_dir=$(cd $(dirname $0) && pwd)
script_dir=$(dirname $(readlink -f $0 ))

keep code short

#bad
cat /etc/passwd | grep root
#good
grep root /etc/passwd

#bad
find . -name '*.txt' |xargs sed -i s/233/666/g
find . -name '*.txt' |xargs sed -i s/235/626/g
find . -name '*.txt' |xargs sed -i s/333/616/g
find . -name '*.txt' |xargs sed -i s/233/664/g
#good
find . -name '*.txt' |
xargs sed -i "s/233/666/g;s/235/626/g;s/333/616/g;s/233/664/g"

Parallelization

parallelization with “&” and “wait” commands

func(){
    #do sth
}
for((i=0;i<10;i++))do
    func &
done
wait
find . -name '*.txt' -type f | xargs grep 2333
#if there is space in .txt name then
find . -type f|xargs -i echo '"{}"'|xargs grep 2333

standard input

$#–>number of input parameter $@–>all input parameters

switch

select cart in amazon flipkart local
do 
    echo "$cart is selected"
    case $cart in
    amazon)
        echo "this is amazon";;
    flipkart)
        echo "this is flipkart";;
    local)
        echo "this is local";;
    *)
        echo "no option, please reselect.."
    esac
done ### while

which [true]
do
    read -t 3 key
    if [$? -eq 0]
    then
        echo "terminating scipt..."
        exit
    else
        echo "wait for input..."
    fi
done

source

you can import function defined in other bash script using source, and use that function in that script

parse config file

#conf.cfg
user="tim"
#conf_parse.sh
MYCONFIG="conf.cfg"
function parse_config(){
    CONFIG=$1
    cat $CONFIG | cut -d"=" -s -f1,2 > /tmp/temp.cfg
    source /tmp/temp.cfg
}
parse_config $MYCONFIG

curl

#GET
curl -s http://test_url.com
#POST
curl -k -X POST http://test_url.com --data @data.json -H "Content-Type: application/json"
#use jq to handle response
curl -s http://test_url.com | jq
curl -s http://test_url.com | jq -r .data[0] > response.json

debugging

-x will print out command in bash file line by line

bash -x test_debug.sh

bash history

History control

#set history upper limit
export HISTSIZE=3000
#force Bash to exclude commands starting with empty space
export HISTCONTROL=$HISTCONTROL:ignorespace
#avoid duplicate entries
export HISTCONTROL=$HISTCONTROL:ignoredups

Remove command from history

history | tail
history -d <history command id>
#clear entire session history
history -c

BATS test

BATS is used to test bash script

installation

git submodule init
git submodule add https://github.com/sstephenson/bats test/libs/bats
git submodule add https://github.com/ztombol/bats-assert test/libs/bats-assert
git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
git add .
git commit -m 'installed bats'

to clone a git repo with BATS module you need:

git clone --recurse-submodules

refactor bash files

bash script should be splited into small functions and putting all to be execuated function in run_main for better test execuration.

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]
then
  run_main
fi

bats test file

test files are .bats files in test folder, by adding following to test files and chmod +x will make them direct execuable.

#!/usr/bin/env ./test/libs/bats/bin/bats
load 'libs/bats-support/load'
load 'libs/bats-assert/load'

@test "requires CI_COMMIT_REF_SLUG environment variable" {
    unset CI_COMMIT_REF_SLUG
    assert_empty "${CI_COMMIT_REF_SLUG}"
    run some_command
    assert_failure
    assert_output --partial "CI_COMMIT_REF_SLUG"
}

bats test file supports loading bash libs

load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'helpers'
load 'docker_mock'

mock function

function cat() { echo "THIS WOULD CAT ${*}" }
export -f cat