More information please see man bash
, it has comprehensive information.
This blog collects the commonly used code snippets based on my daily work, also do summary from related stackoverflow topics.
set builtin
Usually I use set -x
for debugging purpose, today I see a new statement set -ex
. What is this and what is set in Bash? 后来又知道了很多,见awesome list中的bash tutoral.
The Set Builtin, in short, set
allows you to change the values of shell options and set the positional parameters, or to display the names and values of shell variables.
set -e
, causes the shell to exit if any subcommand or pipeline returns a non-zero status. This tells bash that it should exit the script if any statement returns a non-true return value. The benefit of using -e
is that it prevents errors snowballing into serious issues when they could have been caught earlier.
But sometimes set -e
may not be good, see these two posts:
What does ‘set -e’ do, and why might it be considered dangerous?
这个回答很有启发,用哪种方法还得看具体场景。一定要考虑清楚。
get path of running script
1 | curpath=$(dirname $(readlink -f $0)) |
readlink -f $0
will follow every symlink in every component of the given name recursively and get the canonical
path. A single file existing on a system can have many different paths that refer to it, but only one canonical
path, canonical
gives a unique absolute path for a given file. That means even though you call a script in it’s current directory, readlink -f $0
will give you the absolute path!
dirname $0
cut the script name to get the calling path, the path is relative not absolute.
run script in it’s driectory
Sometimes we want to run script in it’s folder by ./xxx.sh
. we can check that:
1 | SCRIPT_PATH=$(dirname $0) |
create tmp file to store log
Create a temporary file or directory, this temp file is owned and grouped by the current user. Aside from the obvious step of setting proper permissions for files exposed to all users of the system, it is important to give temporary files nonpredictable filenames, for example:
1 | # $$: current PID |
For regular use, it may be more wise to avoid /tmp and create a /tmp under its home.
it will randomly generate 6 characters to replace XXXXXX
. You may need to delete the tmp file when script exits, for example, use trap
:
1 | function exitHook { |
Actually, you can get random number from
1 | echo $RANDOM |
you can also seed it to generate reproducible sequence: https://stackoverflow.com/questions/42004870/seed-for-random-environment-variable-in-bash
if condition
List of test command condition Or check manual man test
.
The test command, it can be written as[]
or test expression
, [[ ]]
is modern format, it supports regular expression =~
for string. which one if preferred: test
is traditional (and part of the POSIX specification for standard shells, which are often used for system startup scripts), whereas [[ ]] is specific to bash (and a few other modern shells). It’s important to know how to use test since it is widely used, but [[ ]] is clearly more useful and is easier to code, so it is preferred for modern scripts.
1 | ## don't double quote regexp |
其他test 的变量operands 一般用double quote括起来,防止值为空的时候出错.
对于file system, 主要检测-e
, -f
, -d
, -L
, -r -w -x
, etc. 还有更多的检测选择,参考man.
对于string 则主要就是检测-n
, -z
, =
, ==
, !=
, =~
, >
, <
.
For comparing integers, -eq
, -ne
, -ge
, -gt
, -le
, -lt
. Or use (( xxx ))
, this is a compound command designed for integers:
1 | INT=-3 |
==
or =
, !=
and =~
are used for string comparision:
1 | # sth does not exist? or using -z |
or
1 | # True if the length of "STRING" is zero. |
1 | # directory does not exist? |
对于logial operators, 有2种模式,一种是在command内部使用,比如: test
(-a
, -o
, !
), [[ ]]
, (())
(&&
||
!
):
1 | if [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]] |
Since all expressions and operators used by test are treated as command arguments by the shell (unlike [[ ]]
and (( ))
), characters that have special meaning to bash, such as <, >, (, and ), must be quoted or escaped.
一种是外部使用的, provided by bash, for example: [[ ]] && [[ ]] || [[ ]]
, [[ ! xxx ]]
. They obey short circuit rule.
Tips: 对于简单的if-condition, 可以替换为形如:
1 | # chaining commands |
select loop
The select loop provides an easy way to create a numbered menu from which users can select options. It is useful when you need to ask the user to choose one or more items from a list of choices.
Note that this loop was introduced in ksh and has been adapted into bash. It is not available in sh.
1 | # PS3 is designed for select command |
When select you can use index number or literal, if no break
, it will loop forever.
If want case to match more than one terms, use ;;&
instead of ;;
at end of each case. The addition of the ;;&
syntax allows case to continue to the next test rather than simply terminating.
input password and confirm
Must not show password user input:
1 | echo "****************************************************************" |
script input parameters
1 | if [ $# -eq 0 ]; then |
Note there are 2 shift in one case, after each
shift
,$#
minus 1.
function
The function refers to passed arguments by their position (not by name), that is $1
, $2
, and so forth. $0
is the name of the script itself.
1 | function example() |
Need to call your function after it is declared.
1 | example "p1" "p2" |
Show functions:
1 | ## list all function names |
Export functions, to make it available to subshells, similarly to export variables:
1 | ## -xf: export a function |
log message
1 | LogMsg() |
1 | LogMsg "[INFO] ..." |
Actually, this style [INFO] [2019-10-11 15:59:26-0081] ...
it better.
check last command result
1 | echo_success_failure() { |
run as root
1 | effective_uid=`id -u` 2>/dev/null |
IFS and read array
The default value of IFS contains a space, a tab, and a newline character. Convert string to array with specific delimiter, for example:
1 | string="item1:item2:item3" |
This version has no globbing problem, the delimiter is set in $IFS
(here is space), variables quoted. Don’t forget to do sanity check after converting.
1 | ${array[0]} ===> item1 |
Why we use here string rather than pipeline, for example:
1 | echo "${string}" | read |
这是不行的,因为pipeline 的本质是subshell, 但是read 需要更改当前parent shell的内容的。这里read 实际上在更改了subshell中$REPLY的内容,一旦command 结束,subshell就没了, parent shell 并没有变化.
此外,验证输入的正确性也很重要,一般用[[ =~ ]]
regular expression 去检测了.
Actually if the string use spaces as delimiter, we can loop items directly:
1 | string="item1 item2 item3" |
loop array
break
and continue
can be used on loop. 还要注意当do
写在下一行的时候,do
前面不需要;
.
1 | declare -a array=("element1" "element2" "element3") |
declare
or typeset
are an explicit way of declaring variable in shell scripts.
In BASH it is safer to quote the variable using ""
for the cases when $i
may contain white spaces or shell expandable characters.
If you want to use index of array element
1 | # get length of an array |
If we use declare
to define a integer variable:
1 | declare -i x=10 |
Until loop continues until it receives a zero exit status.
1 | count = 1 |
In ZSH shell, you can use foreach loop:
1 | ## () is a must |
Another index loop using seq
:
1 | for i in $(seq 1 10) |
read file
1 | # read 3 fields a line, line by line from distros.txt file |
chmod
chmod
recursively for directory and it’s content
1 | chmod -R 0755 <target directory> |
Or only add executable for file
1 | find . -name '<file name>' -type f | xargs chmod +x |
1 | -rwxr-xr-x ... |
pass parameters to script for read
Read can read from keyboard input or file or pipeline: read [-options] [variables...]
. If no variable name is supplied, the shell variable $REPLY
contains the line of data. If read receives fewer than the expected number, the extra variables are empty, while an excessive amount of input results in the final variable containing all of the extra input.
1 | # pass parameters to read command |
Other options:
1 | # -p: prompt |
setup ssh password-less
Idempotence:
1 | ssh-keyscan -H ${remote} >> ~/.ssh/known_hosts |
recursive call
1 | example() |
tee command
tee
command reads the standard input and writes it to both
the standard output and one or more files, -a
flag used to append output to existing file, if no -a
, tee will create the file if not exist.
1 | LogMsg() |
1 | # 注意这里tee 有2个方向的输出,可以用来检查pipeline的中间输出是什么 |
statement block
这个很有意思,之前都没见过: {}
Statement block in shell script
do something after reboot
1 |
|
monitor CPU load
1 |
|