最後更新: 2021-07-22
目錄
- if
- Compound Comparison (OR, AND)
- case
- for
- while
- until
- 加數
- Echo mulit-line into a file
- Function
- 一行過行多個 Script
- read
- Set
- Integer limit
- Store stderr in a variable
- Integer Comparison
- "--"
- String as Command (eval)
- Useful Bash Script
- (…) 與 $(…)
- Command substitution($(), ``)
- Single Quote
- Script 的真實 Path
- bash glob
- Doc
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