基本数据类型
- 变量:
fun() { local local_var="hello, world!" # only in function } # 导出函数 export -f fun export export_var="hello, world!" # 常量 readonly CONST_VAR="hello, world!" # 常量并且导出为环境变量 declare -xr CONST_ENV_VAR='hello, world' str="hello, world!" echo $str echo ${str}
- 数组:
# 声明与赋值 array[0]=val array[1]=val array=([1]=val [0]=val) array=(val val) # 获取数组中元素 ${array[index]} # 获取数组长度 ${#array[@]} ${#array[*]} # 第 index 个元素的长度 ${#array[index]} # 数组全部元素 for i in "${array[@]}" do # do some done # 随机选择数据元素 declare -a server_ips=('10.66.170.71' '10.66.170.72') server_ip=${server_ips[$RANDOM % ${#server_ips[@]}]}
- map:
declare -A animals animals=(["moo"]="cow" ["woof"]="dog") declare -A animals=(["moo"]="cow" ["woof"]="dog") ${animals[moo]}
- ternary condition:
${varname:-word} # 如果varname存在且不为null,返回$varname;否则返回word ${varname:=word} # 如果varname存在且不为null,返回$varname;否则赋值varname=word,并返回word ${varname:+word} # 如果varname存在且不为null,返回word; 否则返回null ${varname:offset:length} # 将$varname看作字符串,返回${varname[offset:offset+length]}
- string substitution:
${variable#pattern} # if the pattern matches the beginning of the variable's value, delete the shortest part that matches and return the rest ${variable##pattern} # if the pattern matches the beginning of the variable's value, delete the longest part that matches and return the rest ${variable%pattern} # if the pattern matches the end of the variable's value, delete the shortest part that matches and return the rest ${variable%%pattern} # if the pattern matches the end of the variable's value, delete the longest part that matches and return the rest ${variable/pattern/string} # the longest match to pattern in variable is replaced by string. Only the first match is replaced ${variable//pattern/string} # the longest match to pattern in variable is replaced by string. All matches are replaced ${#varname} # returns the length of the value of the variable as a character string
条件表达式
-
数字比较:
-eq: [eq]ual to -ne: [n]ot [e]qual -gt: [g]reater [t]han -lt: [l]ess [t]han -ge: [g]reater or [e]qual to -le: [l]ess or[e]qual to
-
字符串比较:
[[ $str1 == $str2 ]] 或 [[ $str1 = $str2 ]] [[ $str1 =~ pattern ]] : pattern是正则表达式(不要加双引号) [[ $str1 != $str2 ]] [[ $str1 < $str2 ]] [[ $str1 > $str2 ]] [[ -z $str ]]: str是空字符 [[ -n $str ]]: str不是空字符
-
逻辑运算:
[[ statement1 && statement2 ]] [[ statement1 || statement2 ]] [[ ! statement ]]
-
文件系统:
[ -f $var ]: 文件存在,并且是regular file [ -d $var ]: 文件存在,并且是目录 [ -e $var ]: 文件存在 [ -a $var ]: 与 -e 相同 [ -s $var ]: 文件存在,并且不是空白文件 [ -c $var ]: 字符设备 [ -b $var ]: 块设备文件 [ -L $var ]: 符号链接 [ -h $var ]: 与 -L 相同 [ -r $var ]: 可读文件 [ -w $var ]: 可写文件 [ -x $var ]: 可执行文件 [ -N $var ]: 文件已经被修改自从它最后被读 [ -O $var ]: 你自己的文件 [ -G $var ]: 你所属组的文件 [ ! -f $var ]: 文件不存在
[
是 linux 命令,等同test
命令;[[ ]]
是 shell 语法
控制语句
请将
; do
、; then
和while
、for
、if
放在同一行
-
条件判断:
#### if if condition; then commands; elif condition; then commands; else commands; fi #### ?: [[ condition ]] && echo "true" || echo "false" #### case case expression in pattern1 ) command1 ... commandN ;; pattern2 ) statements ;; ... *) echo "not found" ;; esac
-
循环:
#### for # list can be a string, or a sequence. string使用IFS作为定界符。 # 生成sequence:`{1..50}`、`{a..z}` for var in list; do commands; # 使用$var done for var in list; do actions; done; # c语言格式风格 for ((i=0; i<10; i++)) { commands; # 使用变量$i } #### while while condition; do commands; done #### until x=0; until [ $x -eq 9 ]; do let x++; echo $x; done #### while read while read line; do # do something with line done < $filepath; #### while read (comment) cur_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" while read line; do if [[ ${line} == \#* || -z ${line} ]]; then echo skip comment line: $line continue fi # echo $line # host_ip=$line # scp -o StrictHostKeyChecking=no -C $cur_dir/down.sh root@${host_ip}:/data/ # ssh -o StrictHostKeyChecking=no -n root@${host_ip} "cd /data; sh down.sh" done < hosts.data #### Infinite while Loop while : do echo "Press <CTRL+C> to exit." sleep 1 done #### Infinite while Loop while true; do echo "Press <CTRL+C> to exit." sleep 1 done
算术运算
- 运算符:
- 加、减、乘、除、取余:
+ - * / %
- 与、或、非、异或:
& | ! ^
- 加、减、乘、除、取余:
- 运算操作:
no1=4; #字符串 no2=5; #字符串 #### let # 使用let时,变量名之前不需在加$ let result=no1+no2; #让字符串做算术运算 echo $result # 其他方式 let no1++ let no1-- let no+=6 let no-=6 #### [] result=$[ no1 + no2 ] result=$[ $no1 + 5 ] #### (()) result=$(( no1 + 50 )) #### expr result=`expr 3+4` result=$(expr $no1 + 5) #### bc result=`echo "$no * 1.5" | bc`
- 进制转换:
# $((N#xxx)) N 表示进制,xxx为改进制下的数值 $((2#110)) $((16#2a))
函数与参数
- 函数定义:
function fname() { statements; } fname() { statements; }
- 执行函数:
# 无参数 fname; # 执行函数并传递参数 fname arg1 arg2;
- 参数表示:
$# # 为参数个数 $0 # 表示当前脚本的名字 $1 $2 ... $n # 分别为第1个、第2个...第n个参数 $@ # 表示所有参数分别被双引号包含,"$1","$2"....; 如果参数中有被双引号包裹起来并含有空格时,使用"$@" $* # 表示所有参数被一对双引号包含, "$1c$2c$3",其中c为IFS的第一个字符 $_ # 代表上一个命令的最后一个参数 $$ # 表示当前的进程号(PID) $! # 表示最后执行的后台命令的PID $? # 表示上一条命令的返回值(通常用0表示正常执行)
- 退出状态
function fname() { statements; return 1; # 虽然定义了 return 关键字,但是这里表示退出状态 } fname echo $? # 退出状态为 1
- 作用域:bash 是动态作用域
i=1 # 全局变量 j=1 # 全局变量 function foo() { echo "foo i=${i}" echo "foo j=${j}" } function bar() { i=2 # 会改变全局变量 i local j=2 # 局部变量,不会改变全局的 j foo # 使用调用时的 i,j 值 } bar echo "global: i=${i}" echo "global: j=${j}"
heredoc
sshpass -p "$kpasswd1" ssh root@$khost1_ipv6 << EOF
mkdir -p $ZK_LOG_DIR
rm -rf /data/zookeeper/* >/dev/null 2>&1
echo "1" > /data/zookeeper/myid
service zookeeper-server restart >/dev/null 2>&1
EOF
$ sql=$(cat <<EOF
SELECT foo, bar FROM db
WHERE foo='baz'
EOF
)
$ cat <<EOF > print.sh
#!/bin/bash
echo \$PWD
echo $PWD
EOF
$ cat <<EOF | grep 'b' | tee b.txt
foo
bar
baz
EOF
heredoc的结尾EOF
不能缩进,必须在行首,可以使用<<-
代替<<
来使用缩进,此时缩进必须使用<tab>
if [ 1 ]; then
cat <<-EOF
indented
EOF
fi
echo Done
引号
- 单引号’: 不对包含字符作任何处理(但是在单引号内,两个单引号被转义为一个单引号,如
'I''m angry!'
) - 双引号”: 对包含字符中的特殊字符(
$、\、"
)作处理; -
反引号`: 执行引用命令,用命令的输出代替`包含的内容
cmd_output=$(COMMANDS) cmd_output=`COMMANDS`
保留命令输出的空格和和换行:
cmd_output="$(COMMANDS)"
,推荐使用$(commands)
而不是`commands`
重定向
- 重定向 stdout:
# 默认重定向 stdout ls > list.txt ls 1> list.txt
- 重定向 stderr:
ls 2> error.log
# 重定向无关信息到/dev/null mkdir newdir 2> /dev/null
- 合并 stdout 与 stderr:
&
发信号通知 Bash1
是目标文件描述符# 将stderr转换为stdout 2 > &1
find /etc -iname "*.service" 1> services.txt 2>&1 find /etc -iname "*.service" &> services.txt
- 标准输入:
# 标准输入 0< # 将文件重定向到命令 $ cmd < file
IFS
IFS(Internal Field Separator)是用于特定用途的定界符,它是存储定界符的环境变量。
data="name,sex,rollno,location"
oldIFS=$IFS
IFS=,
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
IFS的默认值为空白字符(换行符、制表符或者空格)
~
符号
POSIX shell中,~
必须出现在复合表达式的最前面,否则它只是普通字符,不能代表家目录。
# 错误
export PATH=$PATH:~/bin
# 正确
export PATH=$PATH:$HOME/bin
执行方式
sh script.sh
/./script.sh
: 父进程会 fork 一个子进程,shell script 在子进程中执行,非交互模式。- 子进程的虚拟地址空间是父进程的一份拷贝,意味着子进程的环境变量继承自父进程,但是子进程自身对环境的修改(如设置环境变量、跳转到其他目录)只在子进程中生效
- 子进程文件描述符表是父进程的一份拷贝,意味着子进程与父进程共享文件,子进程的输出会在当前 shell 打印。
sh
有以下参数:-n
: 语法检查-x
: 语句逐条跟踪
source script.sh
/. script.sh
: 在原进程中执行,不会 fork 子进程,效果和直接敲里面的命令一样,处于交互模式。exec
: 不启动新的进程,而是用要被执行的命令替换当前的 shell 进程
交互模式/非交互模式
交互模式(interactive mode)是指用户输入bash
或ssh
登录到主机后的那种模式,出现$
的Prompt,等待用户的输入指令。
非交互模式(non-interactive mode)是指使用bash
运行一个命令或脚本,运行完成bash
就退出。
可以通过环境变量$-
中是否有字符i
来测试是否为交互模式
$ [[ $- == *i* ]] && echo "Interactive" || echo "Not Interactive"
Interactive
$ bash -c '[[ $- == *i* ]] && echo "Interactive" || echo "Not Interactive"'
Not Interactive
写一个脚本check_interactive.sh
:
[[ $- == *i* ]] && echo "Interactive" || echo "Not Interactive"
$ source check_interactive.sh
Interactive
$ bash check_interactive.sh
Not Interactive
$ bash -c "source check_interactive.sh"
Not Interactive
bash 配置是针对交互模式的,所以在.bashrc
开头就有判断代码:
[[ $- != *i* ]] && return
Login/Non-Login
- Login:终端登录、ssh 连接、
su --login <username>
- Non-Login:直接运行bash、
su <username>
判断是否为Login:
$ shopt -q login_shell && echo "Login shell" || echo "Not login shell"
Not login shell
$ bash -c 'shopt -q login_shell && echo "Login shell" || echo "Not login shell"'
Not login shell
$ bash --login -c 'shopt -q login_shell && echo "Login shell" || echo "Not login shell"'
Login shell
登录模式:只加载~/.bash_profile
,如果~/.bash_profile
不存在,尝试加载~/.bashrc
非登录模式:只加载~/.bashrc
extglob
shopt
(shell option) 设置 shell 的可选参数
$ shopt [-psu] [optname...]
-s 开启选项
-u 关闭选项
-p 列出所有可设置选项
extglob
:shell 启用模式匹配
$ shopt extglob
$ shopt -s extglob
$ shopt -u extglob
删除除了 logs 意外的目录:
$ echo rm -rf ./!(logs)
自动输入
echo y | command
yes | command
printf '%s\n' y n n y y n | command
command << 'EOF'
y
n
n
y
y
n
EOF
string 拼接/切割
-
拼接
#!/bin/bash message=$@ echo '{"subject": "udw warn", "content": "'"${message}"'", "message_type": 1}' \ | curl -X POST -d @- http://172.18.176.244:22003/message/inner/245 --header "Content-Type:application/json"
-
切割
line="host1,name1" # 通过 cut 切割 host=$(echo $line | cut -d',' -f1) name=$(echo $line | cut -d',' -f2) echo $host echo $name # 通过数组切割 # declare -a info="(${line/,/ })" declare -a info="(${line//,/ })" echo ${info[0]} echo ${info[1]}
从路径获取目录名与文件名
$ VAR=/home/me/mydir/file.c
$ DIR=$(dirname "${VAR}")
$ echo "${DIR}"
/home/me/mydir
$ basename "${VAR}"
file.c
获取函数名
- Executed script: ${FUNCNAME[0]} is main
- Sourced script: ${FUNCNAME[0]} is source
- Shell function: ${FUNCNAME[0]} is the function’s name
#!/bin/bash
function test_func()
{
echo "Current $FUNCNAME, \$FUNCNAME => (${FUNCNAME[@]})"
another_func
echo "Current $FUNCNAME, \$FUNCNAME => (${FUNCNAME[@]})"
}
function another_func()
{
echo "Current $FUNCNAME, \$FUNCNAME => (${FUNCNAME[@]})"
}
echo "Out of function, \$FUNCNAME => (${FUNCNAME[@]})"
test_func
echo "Out of function, \$FUNCNAME => (${FUNCNAME[@]})"
获取当前目录
# source 执行时 $0 为 -bash,不是脚本名,因此不推荐使用
# cur_dir=$(cd `dirname $0`; pwd)
# 适用于 source 和 直接执行
cur_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
获取数字序列
seq LAST
seq FIRST LAST
seq FIRST INCREMENT LAST
for i in $(seq 5)
do
echo "Welcome $i times"
done
#!/bin/bash
for ((a=1; a <= 5 ; a++))
do
echo "Welcome $a times."
done
for i in {1..10}; do
echo $i
done
set
修改 shell 环境的运行参数
set -u
/set -o nounset
:脚本遇到不存在的变量报错,并停止执行set -x
/set -o xtrace
:在运行结果之前,先输出执行的那一行命令set -e
/set -o errexit
:脚本只要发生错误(返回非 0),就终止执行- 使用
set +e
关闭-e
选项 - 使用
command || true
使得这一语句总是执行成功
- 使用
set -o pipefail
:只要一个子命令失败,整个管道命令就失败
http://www.ruanyifeng.com/blog/2017/11/bash-set.html
判断脚本是通过 source 执行还是直接执行
- 通过
FUNCNAME
判断if [[ ${FUNCNAME[0]} == "main" ]]; then # 直接执行脚本 fi if [[ ${FUNCNAME[0]} == "source" ]]; then # source 脚本 fi
- 通过
BASH_SOURCE
与$0
判断if [[ "$BASH_SOURCE" == "$0" ]]; then # 直接执行脚本,2 个变量都是脚本名 else # source 脚本,`$BASH_SOURCE` 还是脚本名,`$0` 一般为 `-bash` fi
shift 的作用(处理参数列表)
shift
is a bash built-in which kind of removes arguments from the beginning of the argument list. Given that the 3 arguments provided to the script are available in $1
, $2
, $3
, then a call to shift will make $2
the new $1
. A shift 2 will shift by two making new $1
the old $3
. For more information, see here:
- http://ss64.com/bash/shift.html
- http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_07.html
# -t=aaa -f fff -b=bbb -l lll
for arg in "$@"; do
case $arg in
-t=*|--topic=*)
TOPIC="${arg#*=}"
shift
;;
-b=*|--batch=*)
BATCH="${arg#*=}"
shift
;;
-f|--file)
FILE=$2
shift
shift
;;
-l|--log)
LOG=$2
shift
shift
;;
--default)
DEFAULT=YES
shift
;;
*)
# echo "unknown option: $arg"
;;
esac
done
echo $TOPIC
echo $BATCH
echo $FILE
echo $LOG
echo $DEFAULT
获取随机字符
random="$(dd if=/dev/urandom bs=3 count=1)"
# 2 byte unsigned decimal integers
od -vAn -N2 -tu2 < /dev/urandom
# 1 byte signed decimal integer,
od -vAn -N1 -td1 < /dev/urandom
# 4 byte unsigned decimal integers,
od -vAn -N4 -tu4 < /dev/urandom
- older
- Newer