Shell脚本总结

一、简介

#!/bin/sh

# 脚本中的第一行的“#!”为Shebang字符串。通常出现在类Unix系统的脚本中第一行,作为前两个字符。在直接调用脚本时,系统的程序载入器会分析 Shebang 后的内容,将这些内容作为解释器指令,并调用该指令,将载有 Shebang 的文件路径作为该解释器的参数,执行脚本,从而使得脚本文件的调用方式与普通的可执行文件类似。例如,以指令#!/bin/sh开头的文件,在执行时会实际调用 /bin/sh 程序(通常是 Bourne shell 或兼容的 shell,例如 bash、dash 等)来执行。
# 如果脚本文件中没有#!这一行,那么执行时会默认采用当前Shell去解释这个脚本(即:SHELL环境变量)。
# 如果#!之后的解释程序是一个可执行文件,那么执行这个脚本时,它就会把文件名及其参数一起作为参数传给那个解释程序去执行。
# 如果#!指定的解释程序没有可执行权限,则会报错 bad interpreter: Permission denied。如果#!指定的解释程序不是一个可执行文件,那么指定的解释程序会被忽略,# 转而交给当前的SHELL去执行这个脚本。
# 如果#!指定的解释程序不存在,那么会报错 bad interpreter: No such file or directory。注意:#!之后的解释程序,需要写其绝对路径(如:#!/bin/bash),它是不会自动到环境变量PATH中寻找解释器的。要用绝对路径是因为它会调用系统调用execve,这可以用strace工具来查看。
# 脚本文件必须拥有可执行权限。可通过chmod +x [filename] 来添加可执行权限。
# 当然,如果你使用类似于 bash test.sh ,python train.py这样的命令来执行脚本,那么#!这一行将会被忽略掉,解释器当然是用命令行中显式指定的解释器。

二、变量

1、Shell内置变量

内置变量 描述
$? 上一条命令执行状态 0 代表执行成功,1代表执行失败
$0~$9 位置参数1-9
${10} 位置参数10
$# 位置参数个数
$$ 脚本进程的PID
$- 传递到脚本中的标识
$! 运行在后台的最后一个作业的进程ID(PID)
$_ 之前命令的最后一个参数
$@ 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同
$* 传递给脚本或函数的所有参数
$0 脚本的文件名
${@: -1} 传递给脚本或函数的最后一个参数
${@:1:$#-1} 传递给脚本或函数除最后一个参数以外的所有参数

$* 和 $@ 的区别

$*$@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 …\$n"的形式输出所有参数;"$@" 会将各个参数分开,以”\$1” "\$2" … "$n" 的形式输出所有参数。

2、变量的定义、赋值

①将命令输出赋值变量

var=`shell命令`  # `是反引号
var=$(shell命令) 
var='
line 1
line 2
line 3
'

②读取标准输入赋值给变量

read -p "请输入一个字符: " key
echo $key

3、变量的引用

①基础引用

$var
${var}
${var:defaultvalue}

②变量的引用默认值

表达式 含义
${var_DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值
${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值
${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值
${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值
${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串
${var:+OTHER} 如 果var被设置了, 那么其值就是$OTHER, 否则就为null字符串
${var?ERR_MSG} 如果var没 被声明, 那么就打印$ERR_MSG
${var:?ERR_MSG} 如果var没 被设置, 那么就打印$ERR_MSG
${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量
${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量

③用变量值作为新变量名

$ name=test
$ test_p=123
$ echo `eval echo '$'"$name""_p"`
123

或者

$ var="world"
$ declare "hello_$var=value"
$ echo $hello_world
value

或者( bash 4.3+)

$ hello_world="value"
$ var="world"
$ declare -n ref=hello_$var
$ echo $ref
value

或者

$ hello_world="value"
$ var="world"
$ ref="hello_$var"
$ echo ${!ref}
value

参考:https://github.com/dylanaraps/pure-bash-bible#variables

name_1=aa
name_2=bbb
for i in ${!name_@} ;do  #  ${!name_@}仅限在sh、 bash中使用
    echo "\$i为当前变量名:" $i
    echo "\${!i}当前变量名的值:" ${!i}
    echo "\${i/name/name_var}可替换当前变量名中的name为name_var: " ${i/name/name_var}
done

4、变量的数值运算

①加减乘除

#样本数据
a=120
b=110

((c=$a+$b))    #结果:230
((d=$a-$b))    #结果:10
((e=$a*$b))    #结果:13200
((f=$a/$b))    #结果:1

c=$((a+b))     #结果:220
d=$((a-b))     #结果:20
e=$((a*b))     #结果:12000
f=$((a/b))     #结果:1

c=`expr a+b`        #结果:220
d=`expr $a - $b`    #结果:20
e=`expr $a \* $b`   #结果:12000
f=`expr $a / $b`    #结果:1

②自增

a=1

#第一种整型变量自增方式
a=$(($a+1))
echo $a

#第二种整型变量自增方式
a=$[$a+1]
echo $a

#第三种整型变量自增方式
a=`expr $a + 1`
echo $a

#第四种整型变量自增方式
let a++
echo $a

#第五种整型变量自增方式
let a+=1
echo $a

#第六种整型变量自增方式
((a++))
echo $a

5、数值变量的判断

-gt    大于,如[ $a -gt $b ]
-lt    小于,如[ $a -lt $b ]
-eq    等于,如[ $a -eq $b ]
-ne    不等于,如[ $a -ne $b ]
-ge    大于等于,如[ $a -ge $b ]
-le    小于等于 ,如 [ $a -le $b ]
<     小于(需要双括号),如:(($a < $b))
<=    小于等于(需要双括号),如:(($a <= $b))
>     大于(需要双括号),如:(($a > $b))
>=    大于等于(需要双括号),如:(($a >= $b))

6、变量的处理

①变量输出多行变一行并追加字符

$ echo $a

1

2

3

$ echo $a | tr '\n' ',’

1,2,3,

②位数截取

a=1110418197875

# 截去后三位,要求只取"1110418197875"

# 方式1: 数值运算
b=$((a/1000))

# 方式2:字符截取(将数值变量当成字符串来处理)
c=${a:0:-3}

7、数组

①定义赋值

tests=('a1a' 'b2b' 'c3c')

②切割字符串为数组

test="a,b-,d"
# bash中的 read 读取输入为数组的参数为 -a
IFS='-' read -a tests <<< "$test"
# zsh中的 read 读取输入为数组的参数为 -A
IFS='-' read -A tests <<< "$test"

# 数组索引,bash是从0开始 ,zsh中索引是从1开始。
echo "数组第一个元素: ${tests[0]} 数组第二个元素: ${tests[1]}" 

# 输出: 数组第一个元素: a,b 数组第二个元素: ,d

③输出

# 数组索引,bash是从0开始 ,zsh中索引是从1开始。
tests=('a1a' 'b2b' 'c3c')
echo ${tests[1]}  # bash输出为"b2b", zsh则输出"a1a"

# 输出,bash只显示第一个"a1a",zsh显示所有元素
echo $tests

④遍历循环

# 切割其他变量出为数组变量
a=$(echo "a,b-,d")
IFS='-' read -A -r tests <<< "$a"  # 以逗号为分割符,切割成"a,b"和",d"作为数组变量元素

# 通用(bash/sh/zsh)的遍历循环方法。。区别是zsh中输出在一行中,sh和bash中分行输出
tests=('a1a' 'b2b' 'c3c')
for test in ${tests[@]} ; do
  echo "test: $test" 
done

8、多行文本变量

①定义赋值、引用、输出

tests='a1a
b2b
c3c
'

echo $tests 
# a1a b2b c3c

echo "$tests"
# a1a
# b2b
# c3c

# 总共是有四行的输出,最后一个是空行

②遍历循环

# zsh中的方法
for test in ${(f)tests}; do
  echo "测试: "$test
done

# bash中的方法
for test in ${tests}; do
  echo "测试: $test"
done

三、文件目录的判断

[ -a FILE ] 如果 FILE 存在则为真。
[ -b FILE ] 如果 FILE 存在且是一个块文件则返回为真。
[ -c FILE ] 如果 FILE 存在且是一个字符文件则返回为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则返回为真。
[ -e FILE ] 如果 指定的文件或目录存在时返回为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则返回为真。
[ -g FILE ] 如果 FILE 存在且设置了SGID则返回为真。
[ -h FILE ] 如果 FILE 存在且是一个符号符号链接文件则返回为真。(该选项在一些老系统上无效)
[ -k FILE ] 如果 FILE 存在且已经设置了冒险位则返回为真。
[ -p FILE ] 如果 FILE 存并且是命令管道时返回为真。
[ -r FILE ] 如果 FILE 存在且是可读的则返回为真。
[ -s FILE ] 如果 FILE 存在且大小非0时为真则返回为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID位时返回为真。
[ -w FILE ] 如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
[ -x FILE ] 如果 FILE 存在且是可执行的则返回为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则返回为真。
[ -G FILE ] 如果 FILE 存在且默认组为当前组则返回为真。(只检查系统默认组)
[ -L FILE ] 如果 FILE 存在且是一个符号连接则返回为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则返回为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则返回为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 比 FILE2 新, 或者 FILE1 存在但是 FILE2 不存在则返回为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 老, 或者 FILE2 存在但是 FILE1 不存在则返回为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则返回为真。

四、字符串的处理

1、截取

表达式 含义
${#string} $string的字符个数
${string:position} 在$string中, 从位置$position开始提取子串
${string:position:length} 在$string中, 从位置$position开始提取长度为$length的子串
${string#substring} 从 变量$string的开头, 删除最短匹配$substring的子串
${string##substring} 从 变量$string的开头, 删除最长匹配$substring的子串
${string%substring} 从 变量$string的结尾, 删除最短匹配$substring的子串
${string%%substring} 从 变量$string的结尾, 删除最长匹配$substring的子串
${string/substring/replacement} 使用$replacement, 来代替第一个匹配的$substring
${string//substring/replacement} 使用$replacement, 代替所有匹配的$substring
${string/#substring/replacement} 如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
${string/%substring/replacement} 如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
expr match "$string" '$substring' 匹配$string开头的$substring* 的长度
expr "$string" : '$substring' 匹 配$string开头的$substring* 的长度
expr index "$string" $substring 在$string中匹配到的$substring的第一个字符出现的位置
expr substr $string $position $length 在$string中 从位置$position开始提取长度为$length的子串
expr match "$string" '\($substring\)' 从$string的 开头位置提取$substring*
expr "$string" : '\($substring\)' 从$string的 开头位置提取$substring*
expr match "$string" '.*\($substring\)' 从$string的 结尾提取$substring*
expr "$string" : '.*\($substring\)' 从$string的 结尾提取$substring*

①#号从左边开始,删除第一次匹配到条件的左边字符,保留右边字符

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"
b=${a#*/};echo $b
# 结果:openshift/origin-metrics-cassandra:v3.9

②##号从左边开始,删除最后一次匹配到条件的左边字符,保留右边字符

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"
b=${a##*/};echo $b
# 结果:origin-metrics-cassandra:v3.9

③%号从右边开始,删除第一次匹配到条件的右边内容,保留左边字符(不保留匹配条件)

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a%/*};echo $b
# 结果:docker.io/openshift

④ %%号从右边开始,删除最后一次匹配到条件的右边内容,保留左边字符(不保留匹配条件)

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a%%/*};echo $b
# 结果:docker.io

⑤从左边第几个字符开始,及字符的个数

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a:0:5};echo $b
# 结果:docke

⑥从左边第几个字符开始,一直到结束

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a:7};echo $b
# 结果:io/openshift/origin-metrics-cassandra:v3.9

⑦从右边第几个字符开始,向右截取 length 个字符。

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a:0-8:5};echo $b
# 结果:dra:v

⑧从右边第几个字符开始,一直到结束

# 样本: a="docker.io/openshift/origin-metrics-cassandra:v3.9"   
b=${a:0-8};echo $b
# 结果:dra:v3.9

⑨截取字符串中的ip

# 样本: a="当前 IP:123.456.789.172  来自于:中国 上海 上海  联通"
b=${a//[!0-9.]/};echo $b

或者
echo $a | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]*"
# 结果:123.456.789.172

⑩提取字符串中的数字

echo "test-v1.1.0" | tr -cd '[0-9.]'    # 输出1.1.0
aa="test-v1.1.0" | echo ${aa//[!0-9.]/} # 输出1.1.0

2、包含判断

样本数据

a="test"
b="curiouser"
c="test hahah devops"

①通过grep来判断

if `echo $c |grep -q $a` ;then
    echo "$c" " ----包含--- " "$a"
else
    echo "$c" " ----不包含--- " "$a"
fi

②字符串运算符

if [[ $c =~ $a ]] ;then
    echo "$c" " ----包含--- " "$a"
else
    echo "$c" " ----不包含--- " "$a"
fi

③用通配符*号

用通配符*号代替str1中非str2的部分,如果结果相等说明包含,反之不包含

if [[ $c == *$a* ]] ;then
    echo "$c" " ----包含--- " "$a"
else
    echo "$c" " ----不包含--- " "$a"
fi

④利用替换

if [[ ${c/$a//} == $c ]] ;then
    echo "$c" " ----不包含--- " "$a"
else
    echo "$c" " ----包含--- " "$a"
fi

五、语句控制

1、IF 判断

if [ command ]; then
    符合该条件执行的语句
fi
if [ command ]; then
    command执行返回状态为0要执行的语句
else
    command执行返回状态为1要执行的语句
fi
if [ command1 ]; then
    command1执行返回状态为0要执行的语句
elif [ command2 ]; then
    command2执行返回状态为0要执行的语句
else
    command1和command2执行返回状态都为1要执行的语句
fi

PS: [ command ],command前后要有空格

2、for循环

①数字性循环

#!/bin/bash
for((i=1;i<=10;i++));
do
    echo $(expr $i \* 3 + 1);
done
#!/bin/bash
for i in $(seq 1 10)
do
    echo $(expr $i \* 3 + 1);
done
#!/bin/bash
for i in {1..10}
do
    echo $(expr $i \* 3 + 1);
done
#!/bin/bash
awk 'BEGIN{for(i=1; i<=10; i++) print i}'

②字符性循环

#!/bin/bash
for i in `ls`;
do
    echo $i is file name\! ;
done
#!/bin/bash
for i in $* ;
do
    echo $i is input chart\! ;
done
#!/bin/bash
for i in f1 f2 f3 ;
do
    echo $i is appoint ;
done
#!/bin/bash
list="rootfs usr data data2"

for i in $list;
do
    echo $i is appoint ;
done

③路径查找

#!/bin/bash
for file in /proc/*;
do
    echo $file is file path \! ;
done
#!/bin/bash
for file in $(ls *.sh)
do
    echo $file is file path \! ;
done

3、While循环

while condition ; do
    statements ...
done

Note: 和if一样,condition可以有一系列的statements组成,值是最后的statment的exit status

4、Util循环

until [condition-is-true] ; do 
  statements ... 
done

Note: 执行statements,直至command正确运行。在循环的顶部判断条件,并且如果条件一直为false那就一直循环下去

六、自定义函数

格式

[ function ] 函数名 [()]
{
    action;
    [return int;]
}

# 1.函数在被调用前先声明好
# 2.function关键字可有无

调用

$ 函数名()
$ 函数名(参数1,参数2)
$ 函数名 参数1 参数2
$ (函数名 参数1 参数2)

七、并发处理

注意

  • 在某些情况下,并发执行(使用&符号)可能会因为系统资源的限制而导致并发执行的效果不明显,甚至比串行执行更慢。
  • 适合并发处理的场景
    • 逻辑复杂,耗时比较长的
    • 程序需要对外交互的,耗时比较长的。例如连接数据库,请求访问外部系统的

示例

例如要同时测试多个服务器 IP的端口是否开起,响应时间多少。

#!/bin/bash
# 功能函数,测试服务器端口是否打开和响应时间。tcping可以测试端口的响应时间

# 函数,测试服务器端口和响应时间
pingMultServer(){
    # 使用tcping测试端口响应时间
    tcp_ping=`tcping -c 2 $1 $2 2>/dev/null | grep "Average" | awk -F"[='ms'.]" '{print $14}'`   
    if [ "$tcp_ping" ];then
        return $tcp_ping
    else
        # 如果tcping不可用,使用常规ping
        o_ping=`ping -W 2 -c 2 $1 2>/dev/null | grep "round-trip" | awk -F '[=|/ |.]' '{print $10}'`
        [ "$o_ping" ] && return "$o_ping" || return 0
    fi
}
# 原始服务器信息
orign_servers='192.168.1.1:8001,192.168.1.1:8002,192.168.1.1:8443,192.168.1.1:9443'

# 存储ping结果的数组
ping=()
# 将原始服务器信息解析成服务器数组
IFS=',' read -a servers <<< "$orign_servers"
# servers=('192.168.1.1:8080' '192.168.1.1:8081' '192.168.1.1:8082' '192.168.1.1:8083')
for server in ${servers[@]}; do
    # 解析每个服务器的子部分
    IFS=':' read -a serverinfo <<< "$server"
    # 并发地调用pingMultServer函数
    pingMultServer ${serverinfo[0]} ${serverinfo[1]}  &
    pids+=($!)
done

# 等待并发进程完成,创建空数组以接收并发进程的返回值
for pid in "${pids[@]}"; do
    wait "$pid"
    ping+=("$?")
done
# 构建ping结果字符串
pingstr=""
for i in "${!ping[@]}"; do
    pingstr+="\"${ping[i]}\","
done

echo "[ ${pingstr%,} ]"
类型 user System total
并发执行 1.01s 0.46s 5.17s
串行执行 0.97s 0.39s 38.97s

send_email $1 $client $client_random_password /etc/openvpn/client/$client.ovpn

send_email $1 $client $clientcnname $client_random_password /etc/openvpn/client/$client.ovpn

Copyright Curiouser all right reserved,powered by Gitbook该文件最后修改时间: 2024-06-21 14:19:25

results matching ""

    No results matching ""