Bash Shell Script

最後更新: 2021-07-22

目錄


if

# number of input arguments the script was passed

if [ $# -eq 0 ]; then
    echo "No arguments supplied"
fi

if ... else

    if [ "$exist" = "" ]; then
        echo $username
        $ADDUSER $username
        echo $i | $CHANGEPASSWD
    else
        echo $i | $CHANGEPASSWD
    fi

* 只用一個 "="

if .. elif .. else

If [ conditional expression1 ]
then
    statement1
    statement2
    .
elif [ conditional expression2 ]
then
    statement3
    statement4
    .
.
.
else
    statement5
fi

P.S.

The no-op command in shell is ":" (colon)

 


Compound Comparison (OR, AND)

 

-a  (AND), -o (OR)

e.g.

if [ "$expr1" -a "$expr2" ]; then
    ...
fi

&&, ||

 * These are similar to the Bash comparison operators && and ||

要在 "[[ ... ]]" 內才用到

e.g.

[[ condition1 && condition2 ]]

Notes

建議 var 前加個 x, 以免 var 是 NULL 時出 error (應用: 當不是 TEST 時加 "#" 即可)

#!/bin/bash

TEST=--dry-run
/bin/false

if [ $? = 0 ] && [ x$TEST = x ]; then
    echo "Run"
fi

 


case

 

case "$1" in
        start)
            start
            ;;
        stop)
            stop
            ;;
        *)
            echo $"Usage: $0 {start|stop|restart|condrestart|status}"
            exit 1
esac

 


for

 

一行一行讀入

for (( c=1; c<=5; c++ ))
do
   echo "Welcome $c times"
done

Loop in list:

# 一行一個 Item

mymodule="authn_dbd_module
authn_dbm_module
unique_id_module
userdir_module"

for m in $mylist
do
    echo "$m"
done

# 一行多個 Item

Port="5201 5202 5203 5204"
PidFolder=/root/iperf3

killall iperf3
sleep 1
for p in $Port
do
        iperf3 -s -p $p -D -I $PidFolder/$p.pid
done

Stop the loop:

  • continue
  • break

 


Breaking out of multiple loop levels

 

continue 2  # Continue at loop on 2nd level, that is "outer loop".

Demo: list.sh

#!/bin/bash

SkipFolder="_X _x _bak list.txt list.sh"
mkdir a b c _X _bak

# list 方便處理 !!
ls -1 > list.txt

while read i; do
        for s in $SkipFolder; do
                if [ x$i == x$s ]; then
                        continue 2
                fi
        done
        echo $i
done < list.txt

rm list.txt

while

 

一行一行讀入

while read line
do 
    echo $line
done < "myfile.txt"

小心空格

以下的 for loop 的內容有空格, 所以會出事

#!/bin/bash
for i in $( ls ); do
    echo item: $i
done

Remark

 * 它們是不一樣的 !!

#!/bin/bash
var="a1 b2 c3"
for i in $var; do
    echo item: $i
done
#!/bin/bash
for i in "a1 b2 c3"; do
    echo item: $i
done

用其他符號來做分隔:

while IFS='|' read -r _col1 _col2 _col3
do
   echo "C1: $_col1, C2: $col2, C3: $_col3"
done < "$_input"

Loop

while true
do
    statement(s)
    sleep 1
done

until

 

If the condition evaluates to false, commands are executed.

until [CONDITION]
do
  [COMMANDS]
done

 


加數

 

a=$(($a+1))

 


Echo mulit-line into a file

 

<1>

cat > ./outfile <<DELIM
line one
line two
DELIM

<2>

#!/bin/bash

text="this is line one
this is line two
this is line three"
echo "$text"

 


Function

 

寫法1:

function function_name() {
    command...
}

# function_name 後的 () 可有可無

function function_name {
    command...
}

 

寫法2:

# "()" 必須加
function_name ()
{
    command...
}

寫法3: Define a function on one line

ls(){ ls -1; }

說明

"{"

It is not automatically recognized as a special.

So you need whitespace after it

"}"

It needs to be the start of a command.

So if it's not on its own line, you need ";" to terminate the previous command

Call:

hello

Remark

 * function definition must precede the "first call" to it.

code:

f1 ()
{
  f2
}

f2 ()
{
  echo "Function \"f2\"."
}

f1        # Function "f2" is not actually called until this point

Functions may not be empty (containing only)

not_empty ()
{
  # ":" = null command
  :
}

* when a function is defined multiple times, the final version is what is invoked.

Functions with parameters

# $1 $2 $3 ...

function arg_test() {
    echo $1
}

arg_test "Hello World"

# check parameter

func2 () {
   if [[ -z $1 ]]                           # Is parameter #1 zero length?
   then
     echo "-Parameter #1 is zero length.-"  # Or no parameter passed.
   else
     echo "-Parameter #1 is \"$1\".-"
   fi
}

Function 的 return

return from a function go back to the instruction after the call return is optional and it's implicit at the end of the function

bash function 的 return 是 function 的 exit status

它只可以 return 數字 ( 0 ~ 255, 0 meaning "success" )

"return" & "exit" is "shell builtin"    # Checking: type exit

#!/bin/bash

retfunc()
{
    echo "this is retfunc()"
    return 1
}

exitfunc()
{
    echo "this is exitfunc()"
    exit 1
}

retfunc
echo "We are still here"
exitfunc
echo "We will never see this"

 


一行過行多個 Script

 

假設有目錄

-rwx------ 1 root root 23 Dec 15 15:00 1.sh
-rwx------ 1 root root 23 Dec 15 15:03 2.sh
-rwx------ 1 root root 23 Dec 15 15:03 3.sh

一句行晒怇地

for cmd in `ls *.sh`; do ./$cmd &; done;

 


read

 

BASH shell builtin

read from the stdin, or from the file,
split into words as described above under Word Splitting(IFS),
and the first word is assigned to the first varname, and so on.

The backslash character (\) may be used to remove any special meaning for the next character

Syntax

read [opts] varname

# Word Splitting(IFS). Default 係空格.

read v1 v2 v3
echo "v1: $v1"
echo "v2: $v2"
echo "v3: $v3"

# Display PROMPT, without a trailing newline

-p PROMPT

i.e.

read -p "Please enter your name: " name
echo "Hello, $name"

 

# Cause read to timeout and return failure
# it has no effect when reading from regular files.

-t TIMEOUT

read -t 3 -p "Please enter your name: " name
echo "Hello, $name"                            # 3 秒後就執行此行

# If input is coming from a terminal, characters are not echoed. <= Silent mode

-s

read -s -p "Password? " pw
if [ x"$pw" == "x1234" ]; then echo OK; else echo Fail; fi

# read returns after reading NCHARS characters

-n NCHARS

# The first character of delim is used to terminate the input line, rather than newline.

-d delim

# Do not treat a Backslash as an escape character.

-r

Example

 


" 與 '

_var="test"

echo "'$_var'"

output

'test'

 


Grouping Commands

 

{}
{ list; }

    Placing a list of commands between curly braces causes the list to be executed in the current shell context.
    No subshell is created. The semicolon (or newline) following list is required.

 


set

 

-o opt

Enable / Disable 某 option

"+": Enable; "-": Disable

 * For all options the opposite of set "-x" is set "+x"

set -e
...
set +e
...
set -e

查看現在設置了的 flags

echo $-

ehB

-e | errexit

Exit immediately if a command exits with a non-zero status.

(which is the opposite of the default shell behaviour, which is to ignore errors in scripts)

This option applies to the shell environment and each subshell environment separately

Preventing an immediate exit

 * Failing commands in a conditional statement will not cause an immediate exit

foo || true         # foo: command not found

Example

# 當不加 "|| true" 時有機會 exit 了

#!/bin/bash
set -e

rm -f /var/log/*-* || true

原因

rm -f /var/log/*-*

rm: cannot remove ‘/var/log/php-fpm’: Is a directory

-x | xtrace

Print a trace of simple commands, for commands, case commands, select commands, and arithmetic for commands

and their arguments or associated word lists after they are expanded and before they are executed.

The value of the PS4 variable is expanded and the resultant value is printed before the command and its expanded arguments.

(Write each command (preceded by the value of the PS4 variable) to  standard error before it is executed)

-u | nounset

Write a message to standard error when attempting to expand a variable that is not set,

and if the shell is not interactive, exit immediately.

-o pipefail

The bash shell normally only looks at the exit code of the last command of a pipeline.

This behavior is not ideal as it causes the -e option to only be able to act on the exit code of a pipeline's last command.

This is where -o pipefail comes in.

This particular option sets the exit code of a pipeline to that of the rightmost command to exit with a non-zero status,

or zero if all commands of the pipeline exit successfully.

#!/bin/bash
set -e

foo | echo "a"                # 'foo' is a non-existing command
echo "bar"                    # echo 會照被執行

# 用了 pipefail

#!/bin/bash
set -eo pipefail

foo | echo "a"               # 'foo' is a non-existing command
echo "bar"                   # 不會行這一行, -e 有預期效果

-E

needs to be set if we want the ERR trap to be inherited by shell functions,

command substitutions, and commands that are executed in a subshell environment.

總結:

=> Safer bash scripts with 'set -euxo pipefail'

Doc

https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html

 


let 與 (( ))

 

let does exactly what (( )) do, it is for arithmetic expressions.
There is almost no difference between let and (( )).

The only difference being let is a builtin (simple command), and (( is a compound command.

returns an exit code according to the truth value of the rightmost expression.

    0 (TRUE) when arg evaluated to not 0 (arithmetic "true")
    1 (FALSE) when arg evaluated to 0 (arithmetic "false")

 


運算

 

x %= y  # x = x % y

 


Integer Limit

 

64 bit OS

echo $((X=2**63-1))

9223372036854775807

echo $((X=2**63))

-9223372036854775808

 


Use alias in shell script

 

alias

alias ls="ls -1"

unalias

unalias ls

Login 時自動載入 alias

# ~/.bashrc

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

Remark

[1]

In bash, aliases are not expanded in shell scripts.

(Aliases are not expanded when the shell is not interactive.)

If you want aliases to be expanded in shell scripts,

the shell script must use a bash-specific shell option

ls.sh

#!/bin/bash
alias ls="ls -1"
shopt -s expand_aliases
ls

bash ls.sh

[2]

For almost every purpose, aliases are superseded by shell functions.

#!/bin/bash
alias myls="ls2"
shopt -s expand_aliases
myls(){ ls -a;}
ls2

Print Variable

“env” command - Will print all defined environment variables

“declare” command - Will print all defined functions (including the built in)

“set” command - Will print all defined and available variables and functions

 


":"colon for bash

 

":" is a shell builtin that is basically equivalent to the true command. It is often used as a no-op

":" is a so-called POSIX special built-in, whereas true is a regular built-in.

Special built-ins are required to be built into the shell;

Regular built-ins are only "typically" built in, but it isn't strictly guaranteed.

There usually shouldn't be a regular program named : with the function of true in PATH of most systems.

 


Store stderr in a variable

 

a=`compare -metric PSNR 1.png tmp.png /dev/null 2>&1`

 


Integer Comparison

 

以下 operator 要在 [ ... ] 內使用

-eq
-ne
-gt    # (("$a" > "$b"))
-ge    # (("$a" >= "$b"))
-lt    # (("$a" < "$b"))
-le    # (("$a" <= "$b"))

 


String Comparison

 

==
<        
>
!=
-z
-n

 


"--"

 

# A "--" signals the end of options and disables further option processing.

script='echo "arg 1 is: $1"'

myvar=test1234

/bin/sh -c "$script" -- "$myvar"                               # arg 1 is: test1234

 


String as Command (eval)

 

# If you want the content of $command to be evaluated as shell code.

command="????"

eval "$command"

If you can't guarantee that $command won't start with "-"

(which would cause eval in some shells like bash to treat it as an option), you may want to run:

eval -- "$command"

i.e.

clc='tee >(md5sum >> $md5_file) | pv -s 1000m -L ${io_speed}m | lz4'

dd bs=1M if=$local_device \
    | eval $clc \
    | ssh $IP -- "lz4 -d | dd bs=1M of=$target_device"

 


Useful Bash Scripts

 

刪除所有 docker

docker ps -a | awk 'FNR>1{print $1}' > docker.txt

while read line; do docker rm $line; done < docker.txt

Other

http://datahunter.org/useful_bash_scripts

 


(…) 與 $(…)

 

(…) parentheses indicate a subshell

(sleep 20; echo test) | telnet 192.168.88.18 25

└─bash─┬─bash───sleep
                └─telnet

$(…) is a command substitution

there is a command inside the parentheses,

and the output from the command is used as part of the command line

 


Command substitution($(), ``)

 

A facility that allows a command to be run and

its output to be pasted back on the command line as arguments to another command.

Syntax: $( ... ), ` ... `

i.e.

#!/bin/bash
vi $(fgrep -l malloc *.c)

Remark

` ... `        # syntax has been criticized as awkward to nest

$( ... ),      # syntax  borrowing from the notational style used for variable substitution

 


Single Quote

 

In single quotes, no escaping is possible.

There is no way how to include a single quote into single quotes.

 


Script 的真實 Path

 

# realpath 可以克服 $0 有機會是 "./application" 及 ln -s 問題

# ln -s /mydata/project/nic-traffic/nic-traffic.sh /usr/bin/nic-traffic

ScriptRootPath=$(dirname $(realpath $0))

# 如果沒有 realpath 就要用

ScriptRootPath="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"

 


Doc

 

 

Creative Commons license icon Creative Commons license icon