Linux :: Shell Scripts

Shell Scripts

overall

#!/bin/bash
bash -c 'scripts' # run inline scripts
!! # repeat last command
$! # most recent background command
$$ # PID of current shell

sh -x file # debugging; trace execution
set -x # turn on and off(+x) trace mode

(...) # commands in subshell without affecting current env
{ ...; } # commands in current shell; notice the spaces and ;

set arg1 arg2 arg3 # set $1 $2 and $3

.   # dot command, same as source

here doc

# - would eliminate any tabs befor text
# use "_EOF_" if necessary
cat <<- _EOF_
    text
_EOF_

variables

# no space in definition
var=foo
readonly VAR # ro
readonly -p # list
unset var # remove 
CONST="string"

$var # same as ${var}
# use "" to prevent shell from expanding the var
"$var"

$(command)

$((expression)) # arithmetic expansion
## use awk for float calculation
echo $num1 $num2 $num3 | awk '{ print $1*$2/$3 }'
awk 'BEGIN{ printf("%.3g", 1024*8096) }'

# array
declar -a array=($(ls))
array=(1 2 a b c)   # within () separated by space
echo ${array[0]}
echo ${array} # in sh, this is the same as ${array[0]}
${#array[*]} #number of elements
${#array[i]} # length of ith element

# passing and returning array to function
# only the first item will be passed if using $array name
# use ${array[@]} instead
function function_name {
    local newarray
    newarray=("$@")
    echo ${newarray[@]}
}
# necessary to use () when calling the function
var=($(function_name ${array[@]})) 

string manipulation

# convert to array
# use IFS to change default delimiter (space)
array_type=($string_type)
echo ${array_type[@]}

# case conversion
# does not work in zsh
,,  to lowercase
^^  to uppercase
~~  transpose case
,   first letter to lowercase
^   first letter to uppercase
${old_string^^}

# string slicing
{String:START:LENGTH}
{String:START}  # to the end

# reverse
echo "$string" | rev

# search and replace
{STRING/X/Y}
{STRING//X/Y}   # replace all

# remove string
${var#pattern} # chop the shortest match for pattern from the front of var's value 
${var##pattern} # chop the longest match 
${var%pattern} # chop shortest from end
${var%%pattern} # chop longest from end
# example
FILENMAE=/path/to/file.txt
${FILENAME##*/} #file.txt

${var:-value} # if unset or empty, default to value
${var:+value} # if not null, substitute with value
${var:=value} # if unset, default to value and set var to value
${var:?value} # if unset, 
${#var} # number of char

reading and printing

read var1 var2
read # $REPLAY is default answer
read -r # prevent \ from interpreting 
read -p "prompt text" # with prompt text
read -s -p "enter password: " # sensitive
read -n # specify length of input

#for trimming leading and trailing whitespaces in input.
# [StackOverflow](https://stackoverflow.com/questions/57497365/what-does-the-bash-read-d-do)
# IFS='' prevent any trimming of leading and trailing whitespaces
# -d '' use NUL as delimiter
IFS='' read -r -d '' variable << EOF
text
EOF

echo -n text
printf "format" arg1 arg2...
printf "%+d\n%+d\n" 10 -10 #+10 \n -10

conditions

if [[ $var == string ]]; then
   : # null command; do nothing 
    -eq # for numbers
elif [[ $foo != string ]]; then
    command
fi

case $var in 
    case1 ) command
    ;;
    case2 | case3 ) command
    ;;
    * )  command ;;

esac

for NUMBER in 0 1 2 3 5 6 7; do
    { cmmand }
done

for ((a=1; a<10; a++)); do
    command
    continue # skip the rest of the loop
done

while [[ "$number" -lt 10 ]]; do 
    command
    break # break out of the loop; break n: nth innermost loop
done

function

function_name()
{
    local var   # define local var
    exit n # exit the program
    return n # complete the function
}

function name
{
    return value    # value between 0 and 255
}

# type in CLI
function name { command; } # need ;

var=$(function_name)    # assigns the return value to a var

exit status: 0 success; non zero: error

$# # the number of arguments
$* # all arguments
$@ # all arguments
$0 # current program name
$1 # 1st arugment
${10} # for arguments > 10, use {}
$? # exit status of the last command: 0 succuss; non zero: error
shift # ${n} value shifted to ${n-1}; $# decrease by one
shift 3 # shift 3 times

quotes

# single quote: ignores all special char
# double quote: ignore most char other than
    $ # var name substitutions
    ` # back quotes, not recommended, use $() instead
    \ # backslashes, \n new line; \$ ignore $, \", \'

test


[ -f filename ] # regular file
-e # file exists; same as -a
-s # file is not zero size
-f # regular file
-d # a directory
-h # sym link -L
-r/w/x # file has read/write/exc permission
-n # not null
-z # is null

# integer comparison
-eq
-ne
-gt
-lt
-ge
-le

[[ test ]] && {action} || {action}

-a # AND; lower precedence than string/integer operators
-o # OR; lower precedence than -a

[[ ! -f "$file1" -a -f "$file2" ]]
[[ \( "$count" -ge 10 \) -o \( "$count" -lt 1 \) ]] # need \ for (), and spaces surround parentheses - treated as argument


test "$var" = "string" # must have space before and after = coz they are arguments; using double quote to cover when $var is null

regular expression (regex)

.   # matches any single char
?   # preceding item matches no or one char; extended expression
*   # preceding item matches no or more char
+   # preceding item matches one more more times; extended
-   # range [a-z]
\b  # matches the empty string at the edge of a word
\B  # matches the empty string provided it's not at the edge of a word
\<  # match the empty string at the beginning of a word
\>  # matches the empty string at the end of word
^   # caret, matches start of string; also negate in a range
$   # matches end of string
()  # groups; extended
\   # escape special char: .*[]^${}\+?|()
|   # logical OR, no space between pipe sympbol; extended

# extended
{n} # matches n times, p{2}
{n,m}   # matches n and no more than m times
{n,}    # matches or more than n times

[abc]   # matches either one
[a-z]   # range
[0-9][0-9][0-9] # multiple
[a-fm-z]    # multiple range
[^oi]t  # negate either char

# examnple to verify email
^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$

# special char classes
[[:alpha:]]	# Pattern for any alphabetical character, either upper or lower case.
[[:alnum:]]	# Pattern for 0–9, A–Z, or a–z.
[[:blank:]]	# Pattern for space or Tab only.
[[:digit:]]	# Pattern for 0 to 9.
[[:lower:]]	# Pattern for a–z lower case only.
[[:print:]]	# Pattern for any printable character.
[[:punct:]]	# Pattern for any punctuation character.
[[:space:]]	# Pattern for any whitespace character: space, Tab, NL, FF, VT, CR.
[[:upper:]]	# Pattern for A–Z upper case only.

file manipulation

# cut and paste
cut -cchars file
cut -c1,13,50 data 
cut -c10-40 data
cut -c5- data # till the end
cut -ddchar -ffields file
cut -d: -f1,6 /etc/passwd # delimiter :, first and sixth field

paste var1 var2 # seprated by tab
paste -dchar var1 var2 # specify delimiter
paste -d'+' file1 file2
paste -s names # all in one line
ls | paste -d' ' -s - # read names and paste into one line

# find
find /path -maxdepth 1 -name name -type d -printf "%T+ %f\n"
# this will find the current root path (. or full path name) 
# %T time
# %p path: ./name
# %f file name only

# grep: text matching and printing
# sed: text transformation and substitution
# awk: everything else

# grep
grep PATTERN files
grep -E # extended regex
grep -e PATTERN #  use pattern; -f FILE: use patterns from FILE
env | grep ^HO # begins with HO
grep UNIX intro # same as sed -n '/UNIX/p' intro
grep [tT]he # either the or The
grep -i the # ignore case
grep '[A-Z]' list
grep '[0-9]' data
grep '[A-Z]...[0-9]' list # five char, start with letter and end with digit
grep '\.pic$' file # end with .pic
grep -v 'UNIX' intro # reverse: does not contain UNIX
grep -l 'PATTERN' files # list files that contain PATTERN
grep -n 'PAT' files # print line number


# sed
sed options 'flag/regxp/flag' file
# options
-e 'scripts1; script2'  # seperated by ; or new line
-f scriptfile   # seperated by new line
-n  # quiet, does not produce output for each command
-i[SUFFIX]  # in place, make backup if extension supplied

# substitution 's/pattern/replacement/flags'
# default behavior for 's' is replace first occurrence for each line
sed 's/char1/char2/' data   # substitute flag
# flags
's/char1/char2/2'   # replace the second occurrence rather than first
's/char1/char2/g'   # global
sed -n 's/char1/char2/p'   # print out the line that contains a matching pattern, used with -n
's/char1/char2/w newfile'   # save to file
cat somefile.txt | sed 's/ *$//' > new_file # remove extra blank spaces at the end of each line; $ for end of line

# insert, append and change
sed 'i\new line' # insert "new line" before the data
sed '3i\new line'   # insert "new line" before line 3
sed '3a\new line'   # append "new line" after line3
sed 'a\new line'    # append to end of the data
sed '3c\new line'   # change line 3
sed '/UNIX/c\new line'  # change the line containing UNIX to "new line"
sed '2,3c\new line' # change BOTH lines 2 and 3 to "new line"
sed '2,3y/123/789' # change 1 to 7, 2 to 8, and 3 to 9 for lines 2 and 3

# lines
# address: single number; n1,n2: range; n1,: from n1
[address]command
sed -i '1,5s/c1/c2/' file # replace c1 with c2 from lines 1-5 and save to the same file
sed -n '3,p' intro # print from the third lines forward
sed '/^$/d' intro # delete any blank lines   

# pattern filter
/pattern/command # command only applies to matching pattern lines
sed '/UNIX/d' intro # delete all lines containing UNIX
sed -n '/UNIX/p' intro # print lines containing UNIX
sed '/User/s/bash/csh/' /etc/passwd # replace bash with csh for User
sed -i '/en_US.UTF-8 UTF-8/s/# *//' /etc/locale.gen # remove # and its trailing spaces
sed s/^ *//g        # remove all leading whitespaces

# group commands with {}
sed '3,${
    s/brown/greeen/
    s/lazy/active/
    }'  datafile
}


# awk - a symlink to gawk in Arch
awk options '/regex/{ program }' file
# default behavior: reads a line, executes the program script
# options
-F fs   # specifies file separators, defaut whitespace or tab
-f file # file name to read the program (not the file to read data)
-v var=value    # defines var
-mf N   #  max numbe rof fields
-mr N   # max record size
-W  keyword # 
# examples
awk -F: '/user/{ print $1 }' /etc/passwd
awk -F: -f scriptfile datafile
# field var
$0  # whole line
$1  # 1st field; $n: nth field by FS char
# program - seperate commands by ; or new line
awk '{ $2="newchar"; print $0 }'   # replace second field with newchar

# run script prior to or after reading data
# can have multiple BEGIN or END, only run once; but normal action will run for each line
awk 'BEGIN { print "Header Information" } 
    /test/{ print $0 } 
    END { print "END" }' data

## tricks
# [remove all leading whitespaces](https://unix.stackexchange.com/questions/102008/how-do-i-trim-leading-and-trailing-whitespace-from-each-line-of-some-output)
awk '{ $1=$1; print $1 }'

# tr
cat file | tr from-char to-char # replace characters
tr -d char # delete char from STDIN; sed 's/ //g' file

# sort
sort data
sort -u data # eliminate duplicate lines
sort -r # reverse
sort -o # same as >
sort -k3n -t: /etc/passwd # skip third filed delimiter :

# uniq
uniq file # remove duplicate lines
uniq -d file # list duplicate lines
sort /etc/passwd | cut -f1 -d: | uniq -d
uniq -c # count