tongsiying

阅读|运动|自律

0%

shell-draft

1
2
3
4
break(循环控制)
continue(循环控制)
exit(退出脚本)
return(退出函数)

小数和整数比较

1
2
3
4
5
6
7
https://www.cnblogs.com/shixun/p/6179642.html
shell脚本小数和整数比较
1.错误:
if [ $mya -le 4 ]; then echo "ok";else echo "fail"; fi
-bash: [: 5.7: integer expression expecte
2.正确:
if [ $(echo "$mya <= 4"|bc) = 1 ]; then echo "ok";else echo "fail";fi

变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
https://www.jianshu.com/p/f7234405eb79

假设我们定义了一个变量为:
file=/dir1/dir2/dir3/my.file.txt

#是左,$是右

可以用${ }分别替换得到不同的值:
${file#*/}:删掉第一个/ 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删掉最后一个/ 及其左边的字符串:my.file.txt
${file#*.}:删掉第一个. 及其左边的字符串:file.txt
${file##*.}:删掉最后一个. 及其左边的字符串:txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个/ 及其右边的字符串:(空值)
${file%.*}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删掉第一个. 及其右边的字符串:/dir1/dir2/dir3/my

记忆的方法为:
# 是 去掉左边(键盘上#在 $ 的左边)
%是去掉右边(键盘上% 在$ 的右边)
单一符号是最小匹配;两个符号是最大匹配

${file:0:5}:提取最左边的5 个字节:/dir1
${file:5:5}:提取第5 个字节右边的连续5个字节:/dir2

也可以对变量值里的字符串作替换:
${file/dir/path}:将第一个dir 替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir 替换为path:/path1/path2/path3/my.file.txt

利用${ } 还可针对不同的变数状态赋值(沒设定、空值、非空值):
${file-my.file.txt} :假如$file 沒有设定,則使用my.file.txt 作传回值。(空值及非空值時不作处理)
${file:-my.file.txt} :假如$file 沒有設定或為空值,則使用my.file.txt 作傳回值。(非空值時不作处理)
${file+my.file.txt} :假如$file 設為空值或非空值,均使用my.file.txt 作傳回值。(沒設定時不作处理)
${file:+my.file.txt} :若$file 為非空值,則使用my.file.txt 作傳回值。(沒設定及空值時不作处理)
${file=my.file.txt} :若$file 沒設定,則使用my.file.txt 作傳回值,同時將$file 賦值為my.file.txt 。(空值及非空值時不作处理)
${file:=my.file.txt} :若$file 沒設定或為空值,則使用my.file.txt 作傳回值,同時將$file 賦值為my.file.txt 。(非空值時不作处理)
${file?my.file.txt} :若$file 沒設定,則將my.file.txt 輸出至STDERR。(空值及非空值時不作处理)
${file:?my.file.txt} :若$file 没设定或为空值,则将my.file.txt 输出至STDERR。(非空值時不作处理)
${#var} 可计算出变量值的长度:
${#file} 可得到27 ,因为/dir1/dir2/dir3/my.file.txt 是27个字节

read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
情景
我们知道,read命令可以读取文件内容,并把内容赋值给变量。
以如下的数据文件为例。
$ cat data.txt
1 201623210021 wangzhiguo 25
2 201623210022 yangjiangbo 26
3 201623210023 yangzhen 24
4 201623210024 wangdong 23
5 201623210025 songdong 25

以上文件的四列分别为序号(index)、学号(number)、姓名(name)、年龄(age)。用shell脚本读取该文件并输出每行的值:
$ cat read_data.sh
#!/bin/bash
cat data.txt | while read index number name age
do
echo "index:${index}"
echo "number:${number}"
echo "name:${name}"
echo "age:${age}"
echo " "
done
执行脚本,查看结果:
$ sh read_data.sh
index:1
number:201623210021
name:wangzhiguo
age:25

index:2
number:201623210022
name:yangjiangbo
age:26

index:3
number:201623210023
name:yangzhen
age:24

index:4
number:201623210024
name:wangdong
age:23

index:5
number:201623210025
name:songdong
age:25

不知你发现没有,这样的实现方式有着明显的弊端:
列名(read index number name age)显式地在代码中指定,如果只是想弄清楚数据文件的每列含义,则需要阅读脚本;
该脚本中指明了每列的名称,如果希望修改各字段的英文名称(比如序号的英文名称希望改为NUMBER)则需要修改脚本,且修改多处;
该脚本按一定顺序读取数据文件,因此,如果数据文件中的列顺序发生了变化,则依然需要修改脚本;
如果有其他数据文件需要按此方式读取,则需要根据数据文件的实际情况再重写一个新脚本;
上述实现方式虽然看起来简单,但基于上述的弊端,我们还应对其进行优化。

方案
解决的根本应该是写尽可能通用的脚本,不依赖数据文件本身的列数、列顺序、列名称(含义)等。

可以将数据文件的各字段名称存于该数据文件的首行。当读取数据文件时,首先读取数据文件的首行,以获取各字段名称的列表;读取其它行时,将首行的值与非首行的值进行一一对应即可。

数据文件:
$ cat new_data.txt
index number name age
1 201623210021 wangzhiguo 25
2 201623210022 yangjiangbo 26
3 201623210023 yangzhen 24
4 201623210024 wangdong 23
5 201623210025 songdong 25
脚本

$ cat new_read_data.sh
#!/bin/bash

# 读取文件头行,存于一个数组中
tablehead=(`head -n 1 new_data.txt`)

# 从文件第二行开始读取,按上述数组顺序读取各字段
tail -n +2 new_data.txt | while read ${tablehead[*]}
do
# 遍历数组的下标,获取tablehead数组的对应值,以及以该值命名的变量的值
for i in `seq 0 $((${#tablehead[@]}-1))`
do
temp=${tablehead[$i]}
echo "${temp}:${!temp}"
done
echo ""
done
结果

$ sh new_read_data.sh
index:1
number:201623210021
name:wangzhiguo
age:25

index:2
number:201623210022
name:yangjiangbo
age:26

index:3
number:201623210023
name:yangzhen
age:24

index:4
number:201623210024
name:wangdong
age:23

index:5
number:201623210025
name:songdong
age:25
要写出更通用的脚本,还可以做一些判断和处理,比如:数据文件作为参数传入、检查数据文件的行数、检查数据文件的列数,等等。

EOT、EOF

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
EOT:EOT是“end of file”,表示文本结束符。即可避免使用多行echo命令的方式,并实现多行输出的结果。
function usage ()
{
cat <<- EOT
Usage : $0 [options] -p package -s script file1 file2 file3 ..

Options:
-h|help Display this message
-p|package The output package name
-j|jar file The jar file
EOT
}

usage

#EOT只是标识,不是固定的,可以写成EOF、HHH等


其它用法:
在某些场合,可能我们需要在脚本中生成一个临时文件,然后把该文件作为最终文件放入目录中。(可参考ntop.spec文件)这样有几个好处,其中之一就是临时文件不是唯一的,可以通过变量赋值,也可根据不同的判断生成不同的最终文件等等。

一、cat和EOF
cat命令是linux下的一个文本输出命令,通常是用于观看某个文件的内容的;
EOF是“end of file”,表示文本结束符。
结合这两个标识,即可避免使用多行echo命令的方式,并实现多行输出的结果。

二、使用
看例子是最快的熟悉方法:
# cat << EOF > test.sh
> #!/bin/bash
> #you Shell script writes here.
> EOF

结果:
引用
cat test.sh
#!/bin/bash
#you Shell script writes here.。

三、其他写法
1、追加文件
# cat << EOF >> test.sh
2、换一种写法
# cat > test.sh << EOF
3、非脚本中
如果不是在脚本中,我们可以用Ctrl-D输出EOF的标识
# cat > iii.txt
skldjfklj
sdkfjkl
kljkljklj
kljlk
Ctrl-D

结果:
引用
# cat iii.txt
skldjfklj
sdkfjkl
kljkljklj
kljlk

exec

1
2
3
4
5
6
懒的整理:http://xstarcd.github.io/wiki/shell/exec_redirect.html
简明说说exec使用,不深究:
1.作为日志打印重定向使用
SCRTIP_NAME="cp_image.sh" #脚本名
exec 5>>/tmp/${SCRTIP_NAME/.sh/}.log #带>&5都输出到这个日志
echolog "Error, Delete the image file failed!" >&5

$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$0: 脚本本身文件名称
$1: 命令行第一个参数,$2为第二个,以此类推
$*: 所有参数列表
$@: 所有参数列表
$#: 参数个数
$$: 脚本运行时的PID
$?: 脚本退出码

*与@的区别

当命令行为test.sh 1 2 3
"$*“表示"1 2 3
"$@“表示"1” “2” “3
二者没有被引号括起来时是一样的都为"1 2 3",只有当被引号括起来后才表现出差异

printf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
printf
https://www.cnblogs.com/simadongyang/p/8228098.html

上一章节我们学习了Shell的echo命令,本章节我们来学习Shell的另一个输出命令printf.
printf命令模仿C程序库(library)里的printf()程序。
printf由POSIX标准多定义,因此使用printf 的脚本比使用echo 移值性好。
printf 使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以制定字符串的宽度 左右对齐方式等。
默认printf不会向echo 自动添加换行符, 我们可以手动添加 \n.

printf 命令的语法:
printf format-string [arguments ...]
参数说明:
format-string:为格式控制字符串
arguments:为参数列表。
实例如下:
$ echo "Hello,Shell"
Hello,Shell
$ prinrf "Hello,Shell\n"
Hello,Shell
$
接下来,我来用一个脚本来体现printf的强大功能:
#!/binbash
# author:菜鸟教程
# url:www.runoob.com
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
执行脚本,输出结果如下所示:
姓名 性别 体重kg
郭靖 男 66.12

%s %c %d %都是格式替代符
%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4 2f 指格式化小数,其中,.2指保留2为小数。
更多实例:
#!/bin/bash
# author:菜鸟教程
# url:www.runoob.com
# fotmat-sting为双引号
printf "%d %s\n" 1 "abc"
# 单引号与双引号效果一样
printf '%d %s\n' 1 "abc"
# 没有引号也可以输出
printf %s abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出, format-string 被重用
printf %s abc def
printf "%s\n" abc def
printf "%s %s %s\n" a b c e f g h
# 如果没有 arguments,那么 %s 用HULL 代替, %d 用0 代替
printf "%s and %d \n"
执行脚本 ,输出结果如下所示:
printf的转义序列
序列 说明
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且, 任何留在参数里的字符,
任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f 换页(formfeed)
\n 换行
\r 回车(Carriage return)
\t 水平制表符
\v 垂直制表符
\\ 一个字面上的反斜杠字符
\ddd 表示13位数八进制的字符,仅在格式字符串中有效
\0ddd 表示13位的八进制值字符
笔记
%d %s %c %f 格式替代符详解
d:Decimal 十进制整数 对应位置参数必须是十进制整数,否则报错!
s:String 字符串 对应位置参数必须是字符串或者字符型 否则报错
c:Char 字符 对应位置参数必须是字符串或者字符型 否则报错
f: Float 浮点 对应位置参数必须是数字型 否则报错
如: 其中最后一个参数是 "def" %c 自动截取字符串的第一个字符作为结果输出。

printf:001

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@host102442549 install.b]# printf "%03d\n" {1..20}
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020

shift:

1
2
3
4
5
6
7
参数左移
shell脚本shfit
shift其实很简单的,就是左移参数列表,shift一次就将最左边的参数$1移出去了,然后
原来的$2现在就变成了$1

shift后面还可以带上一个数字,指明要移出多少个参数(默认只移出一个),比如说
shift 3 就是移出3个参数,之后原来的$4就变成了现在的$1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本)。

示例1:依次读取输入的参数并打印参数个数:
run.sh:
#!/bin/bash
while [ $# != 0 ];do
echo "第一个参数为:$1,参数个数为:$#"
shift
done
输入如下命令运行:run.sh a b c d e f
结果显示如下:
第一个参数为:a,参数个数为:6
第一个参数为:b,参数个数为:5
第一个参数为:c,参数个数为:4
第一个参数为:d,参数个数为:3
第一个参数为:e,参数个数为:2
第一个参数为:f,参数个数为:1
从上可知 shift(shift 1) 命令每执行一次,变量的个数($#)减一(之前的$1变量被销毁,之后的$2就变成了$1),而变量值提前一位。

同理,shift n后,前n位参数都会被销毁,比如:
输入5个参数: abcde
那么$1=a,$2=b,$3=c,$4=d,$5=e,执行shift 3操作后,前3个参数a、b、c被销毁,就剩下了2个参数:d,e(这时d=$1,e=$2,其中d由$4—>$1,e由$5—>$2),参考示例如下:
示例2:
#!/bin/bash
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done

shift 3

echo "执行shift 3操作后:"
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
#通过eval把i变量的值($i)作为变量j的名字
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done

输出结果为:
参数个数为:5,其中:
第1个参数($1):a
第2个参数($2):b
第3个参数($3):c
第4个参数($4):d
第5个参数($5):e
执行shift 3操作后:
参数个数为:2,其中:
第1个参数($1):d
第2个参数($2):e

readlink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
全路径文件名/dirname:文件所在的目录
dirname:文件所在的目录
dirname
直接从dirname返回的未必是绝对路径,取决于提供给dirname的参数是否是绝对路径。
例子1
[root@ceph4 home]# dirname /home/1.sh
/home

例子2
SHELL_FOLDER=$(dirname "$0")
echo $SHELL_FOLDER
返回.

例子3
SHELL_FOLDER1=$(cd "$(dirname "$0")";pwd)
echo $SHELL_FOLDER1
返回:/home

SHELL_FOLDER=$(dirname "$0")

readlink :全路径文件名
[hadoopet2-centos6 codemgrs]$ readlink --help
用法: readlink [选项]...文件
输出符号链接值或权威文件名.
-f, --canonicalize 递归跟随给出文件名的所有符号链接以标准化,除最后一个外所有组件必须存在
-e, --canonicalize-existing 递归跟随给出文件名的所有符号链接以标准化,所有组件都必须存在
-m, --canonicalize-missing 递归跟随给出文件名的所有符号链接以标准化,但不对组件存在性作出要求
-n, --no-newline 不输出尾随的新行
-q, --quiet,
-S, --silent 缩减大多数的错误消息
-v, --verbose 报告所有错误消息
--help 显示此帮助信息并退出
--version 显示版本信息并退出
所以用readlink命令我们可以直接获取$0参数的全路径文件名,然后再用dirname获取其所在的绝对路径:
举例1
readlink -f "$0"
/home/6.sh

举例2
SHELL_FOLDER=$(dirname $(readlink -f "$0"))
echo $SHELL_FOLDER
/home

random:

1
2
3
4
5
6
7
8
9
10
11
12
从数组中随机抽取元素
#!/bin/bash
student=(1 DPL YPD LT ZZM HY CQW LSJ ybr)
a=$[RANDOM%4+3]
c=$[RANDOM%2+1]
echo -----------------------------------
echo $a${student[$a]}
echo $(($a+$c))号 ${student[$(($a+$c))]}
echo $(($a-$c))号 ${student[$(($a-$c))]}
echo -----------------------------------

#done

getopt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
getopt(一个外部工具)
https://www.cnblogs.com/lidabo/p/5382617.html
具体用用法可以 man getopt
char*optstring = “ab:c::”;
#单个字符a 表示选项a没有参数 格式:-a即可,不加参数
#单字符加冒号b: 表示选项b有且必须加参数 格式:-b 100或-b100,但-b=100错
#单字符加2冒号c:: 表示选项c可以有,也可以无 格式:-c200,其它格式错误
#getopt处理以'-’开头的命令行参数,如optstring="ab:c::d::",命令行为getopt.exe -a -b host -ckeke -d haha
#在这个命令行参数中,-a是选项元素。host是b的参数,keke是c的参数。但haha并不是d的参数,因为它们中间有空格隔开。
#还要注意的是默认情况下getopt会重新排列命令行参数的顺序,所以到最后所有不包含选项的命令行参数都排到最后。
#-o表示短选项
#--long表示长选项
#"$@"在上面解释过
# -n:出错时的信息
# -- :举一个例子比较好理解:
我们要创建一个名字为 "-f"的目录你会怎么办?
mkdir -f #不成功,因为-f会被mkdir当作选项来解析,这时就可以使用
mkdir -- -f 这样-f就不会被作为选项。
#下面的例子都是使用的$2
举例:
!/bin/bash
TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \
-n 'example.bash' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

#set 会重新排列参数的顺序,也就是改变$1,$2...$n的值,这些值在getopt中重新排列过了
eval set -- "$TEMP"

#经过getopt的处理,下面处理具体选项。
while true ; do
case "$1" in
-a|--a-long) echo "Option a" ; shift ;;
-b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
-c|--c-long)
case "$2" in
"") echo "Option c, no argument"; shift 2 ;;
*) echo "Option c, argument \`$2'" ; shift 2 ;;
esac ;;
--) shift ; break ;;
*) echo "Internal error!" ; exit 1 ;;
esac
done
echo "Remaining arguments:"
for arg do
echo '--> '"\`$arg'" ;
done
运行命令:
./getopt.sh -b abc -a -c33 remain
Option b, argument `abc'
Option a
Option c, argument `33'
Remaining arguments:
--> `remain'

再举一个例子:(安装脚本里面的)
function parse()
{"
RET=`getopt -o ht:e: \
--long help,type:,env: \
-n "ERROR" -- "$@"`
if [ $? -ne 0 ]
then
usage
exit 0
fi
eval set -- "$RET"
while true
do
case "$1" in
-h|--help)
usage
exit 0
;;
-t|--type)
TYPE=$2
shift 2
;;
-e|--env)
ENV=$2
shift 2
;;
--)
shift
break
;;
*)
usage
exit 0
;;
esac
done
}

eval

1
2
3
4
5
6
https://www.cnblogs.com/f-ck-need-u/p/7426371.html
使用示例来说明:
[root@xuexi ~]# a=24;name='long$a' # 注意,使用的是单引号,禁止$a被扩展
如果直接执行 echo $name ,则结果为"long$a",但如果执行 eval echo $name ,结果将是"long24"
[root@xuexi ~]# eval echo $name
long24

exit

1
2
exit0):正常运行程序并退出程序;
exit1):非正常运行导致退出程序;

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
https://www.cnblogs.com/sco1234/p/8906527.html
1、数组定义
1.1 declare定义
使用declare -a 命令定义数组(数组的索引是从0开始计数的),接下来就可以通过[]操作符为不同索引位置的元素赋值。
declare -a names
names[0]=tom
names[1]=jack
注意:shell变量是弱类型的,比要求数组中元素类型相同
在定义数组的同时,可以同时赋值,用()表示,各元素之间用空格隔开
#定义的同时直接赋值
declare -a names=(tom jack)
#增加元素
names[2]=sue
1.2直接定义
创建数组最简单的方法是使用()直接定义数组,括号中元素用空格隔开;在括号中也可以声明下标。实例:
#使用()直接数组
days1=(one two three four five)
#或者在()中声明下标,默认从0开始
days2=([0]=’one’ [1]=’two’ [2]=’three’ [3]=’four’)
#下标可以不连续
days3=([0]=’one’ [2]=’three’)
1.3带下标定义
使用[]操作符,为每一个指定下标赋值,下标也可以不连续
header[0]=’user’
header[1]=’pid’
header[2]=’%CPU’
header[3]=’%MEM’
1.4从文件读取数组
days=(`cat days.txt`)
2、数组操作
2.1赋值
指定下标,使用[]操作符为元素赋值,当下标不存在时,相当于为数组增加元素
declare -a names
names[0]=tom
2.2取值
指定下标,使用[]操作符从数组中对应元素,然后使用$取值,格式:${数组名[索引]}
days=(one two three four five)
#取出第1个元素

echo ${days[0]}
#取出第3个元素

echo ${days[2]}
获得数组中所有值:${数组名[@]}${数组名[*]}
${数组名[@]}得到是以空格隔开的元素,可以用在数组遍历中;${数组名[*]}得到的是一整个字符串。
注意:${数组名}并不会获得所有值,它只会获得到第一个元素的值。即${数组名}等价于${数组名[0]}
示例:

#!/bin/bash
#数组取值

days=(one two three four)

echo ${days}

echo ${days[0]}

echo ${days[1]}

echo ${days[@]}

echo ${days[*]}



echo "**********"
names=()
names[1]=tom
names[2]=jack

echo ${names}

echo ${names[1]}

echo ${names[@]}

结果:
 
2.3长度
利用”@”或“*”字符,将数组扩展成列表,然后使用”#”来获取数组元素的个数。
示例:
#!/bin/bash
#数组个数



days=(one two three four)

echo "数组days的元素个数为:${#days[@]}"

echo "**********"

names=()

names[1]=tom

names[2]=jack

echo "数组names的元素个数为:${#names[*]}"
结果:

注意:如果某个元素是字符串,可以通过制定索引的方式获得该元素的长度。
2.4遍历
存在以下三种方式:
(1)${数组名[@]}${数组名[@]}均可以获得所有元素(不管是元素列表,还是一整个字符串),使用for循环遍历即可
(2)带数组下标的遍历,当需要使用到数组的下标时,可以使用${!数组名[@]}
(3)while循环:根据元素的个数遍历,但对于稀疏数组,可能会丢失数据
示例:
#!/bin/bash
#数组遍历

days=(one two three four)
#for循环遍历

for day in ${days[*]} #或${days[@]}
do
echo $day
done
echo "***************"
#fou循环带下标遍历

for i in ${!days[@]}
do
echo ${days[$i]}
done

echo "***************"
#while循环
names=() #数组names是一个稀疏数组
names[1]=tom
names[2]=jack
i=0
while [ $i -lt ${#names[*]} ]
do
echo ${names[$i]}
let i++
done
结果:

2.5删除
删除一个数组或数组中元素用unset命令。
unset 数组名[索引]      #删除索引下的元素
unset 数组名         #删除整个数组

2.6连接
用()将多个数组连接在一起,()中各个数组用空格隔开。
days=(one two three four)
names=(tom jack)
days=(${days[@]} ${names[@]})

注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1. 单行注释
# echo "ni"

2. 多行注释:
法一:
: << !
语句
!

例如:
linux101:/home/wsj # more dian
#!/bin/ksh
:<<!
echo "ni"
echo "ni"
!

法二:
: '
语句
'

例如:
linux101:/home/wsj # more dian
#!/bin/ksh
: '
echo "ni"
echo "ni"
'

ssh到另一台设备执行命令

1
ssh root@ceph2 "cd /home;mkdir 123"

运算符

1
2
3
4
5
6
7
8
9
10
11
12
https://www.runoob.com/linux/linux-shell-basic-operators.html

%:整除
#!/bin/bash
sum=0
for a in `seq 1 300`
do
if [ $(($a%4)) = '0' ];then
let sum=$sum+$a
fi
done
echo $sum

nohup

1
nohup ./start.sh >output 2>&1 &

nohup免enter

1
2
3
4
5
6
#!/bin/bash
str=$"\n"
nohup hadoop jar analysis_v4_test_args.jar -1 8 8 >/dev/null 1>logs&
sstr=$(echo -e $str)
echo $sstr
tail -20 logs

timeout

1
2
如果该命令长时间没有执行结束,则自动停止
timeout 60 ./run.sh

for:循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
for循环:
for((i=1;i<=10;i++));
do
echo $i;
done
for死循环:
#!/bin/bash
set i=0
set j=0
for((i=0;i<10;))
do
let "j=j+1"
echo $j
done

for (( i=1, j=7; i<5 && j >3; i++, j-- ))
do
echo "i $i, j $j"
done

第一类:数字性循环
-----------------------------
for1-1.sh
#!/bin/bash
for((i=1;i<=10;i++));
do
echo $(expr $i \* 3 + 1);
done
-----------------------------
for1-2.sh
#!/bin/bash
for i in $(seq 1 10)
do
echo $(expr $i \* 3 + 1);
done
-----------------------------
for1-3.sh
#!/bin/bash
for i in {1..10}
do
echo $(expr $i \* 3 + 1);
done
-----------------------------
for1-4.sh
#!/bin/bash
awk 'BEGIN{for(i=1; i<=10; i++) print i}'


第二类:字符性循环
-----------------------------
for2-1.sh
#!/bin/bash
list="rootfs usr data data2"
for i in $list;
do
echo $i is appoint ;
done
-----------------------------
for2-2.sh
#!/bin/bash
for i in $* ;
do
echo $i is input chart\! ;
done
-----------------------------
for2-3.sh
#!/bin/bash
for i in f1 f2 f3 ;
do
echo $i is appoint ;
done
-----------------------------
for2-4.sh
#!/bin/bash
list="rootfs usr data data2"
for i in $list;
do
echo $i is appoint ;
done

第三类:路径查找
-----------------------------
for3-1.sh
#!/bin/bash
for file in /proc/*;
do
echo $file is file path \! ;
done
-----------------------------
for3-2.sh
#!/bin/bash
for file in $(ls *.sh)
do
echo $file is file path \! ;
done

if:判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
-gt 等只能判断整数:
小数:
https://blog.csdn.net/david__peng/article/details/82980288
前言
用于数值比较的无非大于、小于、等于、大于等于、小于等于这几个。

比较格式: [ 数值1 比较符 数值2 ] 注意左边的括号与数值1之间有一个空格,同样,数值2和右边的括号之间也有空格。

数值比较运算符对应下面几个:
-eq 判断相等,相等返回true,否则返回false
-ne 判断是否不相等,不相等返回true,否则返回false
-gt 判断左边是否大于右边的值,大于则返回true,否则返回false
-lt 判断左边是否小于右边的值,小于则返回true,否则返回false
-ge 判断左边是否大于或等于右边的值,大于则返回true,否则返回false
-le 判断左边是否小于右边的值,小于则返回true,否则返回false

小数的比较
1.shell中 可以用 bc 对小数进行运算
#echo 5.5+1.1 | bc
6.6

2.使用,awk判断小数点后的位数
num=6.6666;echo $num | awk 'BEGIN{FS="."}{printlength($2)}'

3.直接使用awk比较大小
awk -v num1=6.6 -v num2=5.5'BEGIN{print(num1>num2)?"0":"1"}'
如果num1>num2打印输出0,否则输出1

4.使用expr,返回1表示a> a&gt;a>b, 0表示a&lt; a&lt;a<b
a=6.6 b=5.5;expr $a > $b

5.使用br
echo "6.6>5.5" |br

linux shell 中小数进行比较
if [ `expr $a \> $b` -eq 0 ];then
echo $b is bigger
else
echo $a is bigger
fi

shell脚本报错:"[: =: unary operator expected"解决办法
https://www.jb51.net/article/107710.htm
在匹配字符串相等时,我用了类似这样的语句:
if [ $STATUS == "OK" ]; then
echo "OK"
fi
在运行时出现了 [: =: unary operator expected 的错误,就一直找不到原因,尝试了删除等号两侧的空格和括号里的空格都不管用,最后baidu了一下,才找到原因。把语句改成这样就不会出错了.
if [[ $STATUS = "OK" ]];
then
echo "OK"
fi
字符串比较:
if [ $port -eq 1 ]; then
return 0
else
return 1
fi

#判断字符串是否相等
if [ "$A" = "$B" ];then
echo "[ = ]"
fi

#判断字符串是否相等,与上面的=等价
if [ "$A" == "$B" ];then
echo "[ == ]"
fi

#注意:==的功能在[[]]和[]中的行为是不同的,如下

#如果$A以”a”开头(模式匹配)那么将为true
if [[ "$A" == a* ]];then
echo "[[ ==a* ]]"
fi

#如果$A等于a*(字符匹配),那么结果为true
if [[ "$A" == "a*" ]];then
echo "==/"a*/""
fi

#字符串不相等
if [ "$A" != "$B" ];then
echo "[ != ]"
fi

#字符串不相等
if [[ "$A" != "$B" ]];then
echo "[[ != ]]"
fi

#字符串不为空,长度不为0
if [ -n "$A" ];then
echo "[ -n ]"
fi

#字符串为空.就是长度为0.
if [ -z "$A" ];then
echo "[ -z ]"
fi

#需要转义<,否则认为是一个重定向符号
if [ $A /< $B ];then
echo "[ < ]"
fi

if [[ $A < $B ]];then
echo "[[ < ]]"
fi

#需要转义>,否则认为是一个重定向符号
if [ $A /> $B ];then
echo "[ > ]"
fi

if [[ $A > $B ]];then
echo "[[ > ]]"
fi

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
$ cat test4.sh 
#/bin/bash

all_num=10# 任务总数
thread_num=5# 设置并发的进程数

a=$(date +%H%M%S)

# mkfifo
tempfifo="my_temp_fifo"
mkfifo ${tempfifo}
# 使文件描述符为非阻塞式
exec 6<>${tempfifo}#将fd6指向fifo类型
rm -f ${tempfifo}#删也可以

#根据线程总数量设置令牌个数
for ((i=1;i<=${thread_num};i++))
do
{
echo
}
done >&6

#
for num in `seq 1 ${all_num}`#任务数量
do
{
# 一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,

# fd6中没有回车符的时候,就停在这了,从而实现了线程数量控制
read -u6
{
#可以把具体的需要执行的命令封装成一个函数
sleep 1
echo ${num}
echo "" >&6 #当进程结束以后,再向fd6中加上一个回车符,即补上了read -u6减去的那个
} &
}
done

wait
exec 6>&-#关闭fd6管道
exit 0

b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:
$ sh test4.sh
1
3
2
4
5
6
7
8
9
10
startTime: 195227
endTime: 195229
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@ns_10.2.1.242 test]$ cat p3.sh 
#!bash
# 2014-12-5
# --------------------
# 此例子说明了一种用wait、read命令模拟多线程的一种技巧
# 此技巧往往用于多主机检查,比如ssh登录、ping等等这种单进程比较慢而不耗费cpu的情况
# -------------------------
operation(){
sleep $1
}

tmp_fifofile=/tmp/$$.fifo
#echo $tmp_fifofile

mkfifo $tmp_fifofile # 新建一个fifo的管道文件
exec 6<>$tmp_fifofile # 绑定fd6
rm $tmp_fifofile

# 这里是向管道添加了$thread个空行
THREAD=3 # 线程数,可以改变
for i in $(seq 0 $THREAD);do
echo
done >&6

CONFIG_FILE=config.txt
# 修改这个脚本到生成环境,主要是修改operation和CONFIG_FILE配置
# 每次读取一行数据
while read line
do
# 一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,
# fd6 中没有回车符的时候,就停在这了,从而实现了线程数量控制
read -u6

{
# 操作成功,记录到成功日志,修改echo
# 操作失败,记录到错误日志
operation $line && echo " $line success" || echo "$line error"

# 当进程结束以后,再向fd6中加上一个回车符,即补上了read -u6减去的那个
echo >&6
} & # 后台执行,这里的 &是非常重要的,同时有$THREAD个后台进程

done < ${CONFIG_FILE}


wait # 等待所有的后台子进程结束
exec 6>&- # 关闭df6
exit 0
1
2
3
4
5
6
7
8
9
10
11
12
13
shell 中用管道模拟多线程
这里以两个例子来对比多线程和单进程
单线程的例子
# config.txt在这个例子和多线程的例子中都会用到
[root@ns_10.2.1.242 test]$ cat config.txt
1
2
3
4
1
2
3
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 下面的代码,是从config.txt中读取配置,然后sleep一定时间,然后打印sleep 的时间长度,
# 注意 while从文本读取数据,是逐行读取的
[root@ns_10.2.1.242 test]$ cat while.sh
while read line
do
sleep $line && echo "$line success"
done < config.txt

# 这个脚本,消耗了20秒,结果如下:
[root@ns_10.2.1.242 test]$ time sh while.sh
1 success
2 success
3 success
4 success
1 success
2 success
3 success
4 success

real 0m20.011s
user 0m0.000s
sys 0m0.004s
1
2
3
4
5
6
7
8
9
10
11
多线程的例子:
# config.txt在这个例子和多线程的例子中都会用到
[root@ns_10.2.1.242 test]$ cat config.txt
1
2
3
4
1
2
3
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 多线程的代码,如下
[root@ns_10.2.1.242 test]$ cat p3.sh
#!bash
# 2014-12-5
# --------------------
# 此例子说明了一种用wait、read命令模拟多线程的一种技巧
# 此技巧往往用于多主机检查,比如ssh登录、ping等等这种单进程比较慢而不耗费cpu的情况
# -------------------------
operation(){
sleep $1
}

tmp_fifofile=/tmp/$$.fifo
#echo $tmp_fifofile

mkfifo $tmp_fifofile # 新建一个fifo的管道文件
exec 6<>$tmp_fifofile # 绑定fd6
rm $tmp_fifofile

# 这里是向管道添加了$thread个空行
THREAD=3 # 线程数,可以改变
for i in $(seq 0 $THREAD);do
echo
done >&6

CONFIG_FILE=config.txt
# 修改这个脚本到生成环境,主要是修改operation和CONFIG_FILE配置
# 每次读取一行数据
while read line
do
# 一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,
# fd6 中没有回车符的时候,就停在这了,从而实现了线程数量控制
read -u6

{
# 操作成功,记录到成功日志,修改echo
# 操作失败,记录到错误日志
operation $line && echo " $line success" || echo "$line error"

# 当进程结束以后,再向fd6中加上一个回车符,即补上了read -u6减去的那个
echo >&6
} & # 后台执行,这里的 &是非常重要的,同时有$THREAD个后台进程

done < ${CONFIG_FILE}


wait # 等待所有的后台子进程结束
exec 6>&- # 关闭df6
exit 0

# 脚本的结果,执行了7秒
[root@ns_10.2.1.242 test]$ time sh p3.sh
1 success
2 success
1 success
3 success
4 success
2 success
3 success
4 success

real 0m7.007s
user 0m0.000s
sys 0m0.003s
[root@ns_10.2.1.242 test]$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
另外一个 命名管道基础的文章
多进程脚本中需要注意的知识点,有3
1.while读取文件
while read line
do
echo $line
done < config.txt

2.管道的绑定, exec 6<>$tmp_fifofile 和 向管道写数据 echo >&6
[root@ns_10.2.1.242 test]$ mkfifo testfifo
[root@ns_10.2.1.242 test]$ exec 6<>testfifo
[root@ns_10.2.1.242 test]$ echo 1 >&6
[root@ns_10.2.1.242 test]$ cat testfifo
1
#注意,进程会卡死的

# 没有& 符号,是创建了文件名为6的文件,然后把1 输出问文件名为6的文件
[root@ns_10.2.1.242 test]$ echo 1 >6
[root@ns_10.2.1.242 test]$ cat 6
1
[root@ns_10.2.1.242 test]$ ls -alh 6
-rw-r--r--. 1 root root 2 Jan 22 20:26 6

3.read -u6
read
-u Read input from file descriptor fd(从文件描述符读取输入)

read 读取一行,向管道写n行,就可以读取n次,n+1会堵塞
[root@ns_10.2.1.242 test]$ mkfifo testfifo
[root@ns_10.2.1.242 test]$ exec 6<>testfifo
[root@ns_10.2.1.242 test]$ echo 1 >&6
[root@ns_10.2.1.242 test]$ echo 1 >&6
[root@ns_10.2.1.242 test]$ echo 1 >&6
[root@ns_10.2.1.242 test]$ read -u6
[root@ns_10.2.1.242 test]$ read -u6
[root@ns_10.2.1.242 test]$ read -u6
注意:
多线程脚本应用在真实环境的时候,只需要修改CONFIG_FILE和operation函数
THREAD控制线程数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
情景
shell脚本的执行效率虽高,但当任务量巨大时仍然需要较长的时间,尤其是需要执行一大批的命令时。因为默认情况下,shell脚本中的命令是串行执行的。如果这些命令相互之间是独立的,则可以使用“并发”的方式执行这些命令,这样可以更好地利用系统资源,提升运行效率,缩短脚本执行的时间。如果命令相互之间存在交互,则情况就复杂了,那么不建议使用shell脚本来完成多线程的实现。
为了方便阐述,使用一段测试代码。在这段代码中,通过seq命令输出110,使用for...in语句产生一个执行10次的循环。每一次循环都执行sleep 1,并echo出当前循环对应的数字。
注意:
真实的使用场景下,循环次数不一定等于10,或高或低,具体取决于实际的需求。
真实的使用场景下,循环体内执行的语句往往比较耗费系统资源,或比较耗时等。

请根据真实场景的各种情况理解本文想要表达的内容。
$ cat test1.sh
#/bin/bash
all_num=10
a=$(date +%H%M%S)
for num in `seq 1 ${all_num}`
do
sleep 1
echo ${num}
done
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

通过上述代码可知,为了体现执行的时间,将循环体开始前后的时间打印了出来。
运行结果:
$ sh test1.sh
1
2
3
4
5
6
7
8
9
10
startTime: 193649
endTime: 193659
10次循环,每次sleep 1秒,所以总执行时间10s。

方案1:使用"&"使命令后台运行
在linux中,在命令的末尾加上&符号,则表示该命令将在后台执行,这样后面的命令不用等待前面的命令执行完就可以开始执行了。示例中的循环体内有多条命令,则可以以{}括起来,在大括号后面添加&符号。
$ cat test2.sh
#/bin/bash
all_num=10
a=$(date +%H%M%S)
for num in `seq 1 ${all_num}`
do
{
sleep 1
echo ${num}
} &
done
b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
运行结果:
sh test2.sh
startTime: 194147
endTime: 194147
[j-tester@merger142 ~/bin/multiple_process]$ 1
2
3
4
5
6
7
8
9
10

通过结果可知,程序没有先打印数字,而是直接输出了开始和结束时间,然后显示出了命令提示符[j-tester@merger142 ~/bin/multiple_process]$(出现命令提示符表示脚本已运行完毕),然后才是数字的输出。这是因为循环体内的命令全部进入后台,所以均在sleep了1秒以后输出了数字。开始和结束时间相同,即循环体的执行时间不到1秒钟,这是由于循环体在后台执行,没有占用脚本主进程的时间。

方案2:命令后台运行+wait命令
解决上面的问题,只需要在上述循环体的done语句后面加上wait命令,该命令等待当前脚本进程下的子进程结束,再运行后面的语句。
$ cat test3.sh
#/bin/bash
all_num=10
a=$(date +%H%M%S)
for num in `seq 1 ${all_num}`
do
{
sleep 1
echo ${num}
} &
done

wait

b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
运行结果:

$ sh test3.sh
1
2
3
4
5
6
7
9
8
10
startTime: 194221
endTime: 194222
但这样依然存在一个问题:
因为&使得所有循环体内的命令全部进入后台运行,那么倘若循环的次数很多,会使操作系统在瞬间创建出所有的子进程,这会非常消耗系统的资源。如果循环体内的命令又很消耗系统资源,则结果可想而知。
最好的方法是并发的进程是可配置的。

方案3:使用文件描述符控制并发数
$ cat test4.sh
#/bin/bash

all_num=10
# 设置并发的进程数
thread_num=5

a=$(date +%H%M%S)

# mkfifo
tempfifo="my_temp_fifo"
mkfifo ${tempfifo}
# 使文件描述符为非阻塞式
exec 6<>${tempfifo}
rm -f ${tempfifo}

# 为文件描述符创建占位信息
for ((i=1;i<=${thread_num};i++))
do
{
echo
}
done >&6

#
for num in `seq 1 ${all_num}`
do
{
read -u6
{
sleep 1
echo ${num}
echo "" >&6
} &
}
done

wait

# 关闭fd6管道
exec 6>&-

b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:
$ sh test4.sh
1
3
2
4
5
6
7
8
9
10
startTime: 195227
endTime: 195229


方案4:使用xargs -P控制并发数
xargs命令有一个-P参数,表示支持的最大进程数,默认为1。为0时表示尽可能地大,即方案2的效果。
$ cat test5.sh
#/bin/bash

all_num=10
thread_num=5

a=$(date +%H%M%S)

seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}"

b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
运行结果:

$ sh test5.sh
1
2
3
4
5
6
8
7
9
10
startTime: 195257
endTime: 195259

方案5:使用GNU parallel命令控制并发数
GNU parallel命令是非常强大的并行计算命令,使用-j参数控制其并发数量。
$ cat test6.sh
#/bin/bash

all_num=10
thread_num=6

a=$(date +%H%M%S)

parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10`

b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:
$ sh test6.sh
1
2
3
4
5
6
7
8
9
10
startTime: 195616
endTime: 195618

总结
“多线程”的好处不言而喻,虽然shell中并没有真正的多线程,但上述解决方案可以实现“多线程”的效果,重要的是,在实际编写脚本时应有这样的考虑和实现。
另外:
方案345虽然都可以控制并发数量,但方案3显然写起来太繁琐。
方案45都以非常简洁的形式完成了控制并发数的效果,但由于方案5的parallel命令非常强大,所以十分建议系统学习下。
方案345设置的并发数均为5,实际编写时可以将该值作为一个参数传入。

参考文章
http://blog.csdn.net/qq_34409701/article/details/52488964
https://www.codeword.xyz/2015/09/02/three-ways-to-script-processes-in-parallel/
http://www.gnu.org/software/parallel/

多线程删除文件:oss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
i=0
a=()
a=`find /mnt/sdoss/read_1023a1 -name "*.file"`

for item in ${a[@]}
do
i=`expr $i + 1`
done
rm -rf read_1023a1.txt
echo $i >>read_1023a1.txt
在oss上删除文件,通过删除接口删除
#!/bin/bash
a=()
a=`find /mnt/sdoss/90/ -name "vdb_*.file"|sed 's/\/mnt\/sdoss\///g'`
pid=$$
thread=200
tmp_fifo="/tmp/$$.fifo"
mkfifo $tmp_fifo
exec 8<>$tmp_fifo
rm $tmp_fifo
for i in $(seq 1 $thread)
do
echo
done >&8

echo ">>> start delete file!"
for item in ${a[@]}
do
read -u8
{
curl -v -X DELETE "http://10.244.76.87:8888/ossfuse/testfuse90/${item}" 1>&2 2>/dev/null
echo "" >&8
} &
done

wait
exec 8>&-

多线程举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[root@ceph-mon ~]# cat /opt/test/test-swift.sh 
#!/bin/bash

url=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep "publicURL" |head -n1 |awk -F '"' '{print $4}'`
id=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep -A 12 token |grep -w id |head -n 1 |awk -F '"' '{print $4}'`
pid=$$
thread=10
tmp_fifo="/tmp/$$.fifo"
mkfifo $tmp_fifo
exec 7<>$tmp_fifo
rm $tmp_fifo
for i in $(seq 1 $thread)
do
echo
done >&7

> test-error.log

echo ">>> start upload file!"

for i in $(seq 1 1000)
do
read -u7
{
for j in $(seq 1 1000)
do
ret=`curl -s -w "%{http_code}" -X PUT -T "test.jpg" -H "X-Auth-Token:${id}" ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg |tail -1 |awk -F '[{}]' '{print $NF}'`
if [ "x${ret}" != "x201" ]
then
echo "[`date '+%F %T'`] upload ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg error, code ${ret}!" >> test-error.log
fi
done
echo >&7
} &
done
wait

sleep 5
echo ">>> start download file!"

for i in $(seq 1 1000)
do
read -u7
{
for j in $(seq 1 1000)
do
ret=`curl -s -w "%{http_code}" -X GET -H "X-Auth-Token:${id}" -H "X-Storage-Policy:swiftonfile" -o /dev/null ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg`
if [ "x${ret}" != "x200" ]
then
echo "[`date '+%F %T'`] download ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg error, code ${ret}!" >> test-error.log
fi
done
echo >&7
} &
done
wait

exec 7>&-

多并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
一、普及(网址http://www.cnblogs.com/zhengbin/p/9513762.html#_label1)
Shell 实现多线程(多任务)
阅读目录
1.命令结尾添加:&
2.解决主线程提前退出问题,添加 wait
3.控制后台执行数(线程数),mkfifo
实现方案:
1.命令结尾添加:&
#/bin/bash
all_num=10
a=$(date +%H%M%S)
for num in `seq 1 ${all_num}`
do {
sleep 1
echo ${num}
} &
done
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
在命令的末尾加 & 符号,则命令将在后台执行,这样后面的命令不需要等待该命令执行完再开始执行。

2.解决主线程提前退出问题,添加 wait
#/bin/bash
all_num=10
a=$(date +%H%M%S)
for num in `seq 1 ${all_num}`
do {
sleep 1
echo ${num}
} & # 将命令在后台执行,命令范围用{}包围
done
# 等待当前脚本进程下的子进程结束
wait
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

3.控制后台执行数(线程数),mkfifo
#/bin/bash
all_num=10
# 设置并发的进程数
thread_num=5
a=$(date +%H%M%S)
# mkfifo
tempfifo="my_temp_fifo"
mkfifo ${tempfifo}
# 使文件描述符为非阻塞式
exec 6<>${tempfifo}
rm -f ${tempfifo}
# 为文件描述符创建占位信息
for ((i=1;i<=${thread_num};i++))
do
{
echo
}
done >&6
#
for num in `seq 1 ${all_num}`
do
{
read -u6
{
sleep 1
echo ${num}
echo "" >&6
} &
}
done
wait
# 关闭fd6管道
exec 6>&-
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
二、普及
(参考网址:https://blog.csdn.net/qq_34409701/article/details/52488964)
默认的情况下,Shell脚本中的命令是串行执行的,必须等到前一条命令执行完后才执行接下来的命令,但是如果我有一大批的的命令需要执行,而且互相又没有影响的情况下(有影响的话就比较复杂了),那么就要使用命令的并发执行了。

如下:
#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'"
done >> result.txt

echo "resutl"|mutt -a result.txt -s update-result meta@126.com12345678

对于上面的代码,因为 iplist 中有好多ip,每个”tar zxf”都挺耗时的,所以打算使用并发编程,这样就可以节省大量时间了。

修改如下:
#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'" &
done >> result.txt

echo "resutl"|mutt -a result.txt -s update-result meta@126.com12345678

加上“&” 之后 “tar zxf”就可以并行执行了。 实质是将”tar zxf” 作为后台进程在执行,这样该命令就不会占用当前bash,其他命令也不用等待前面命令执行完再继续了,而且可以放入多个任务到后台,这样就实现了多任务并发。

我本来目的是让”tar zxf”这个循环都执行结束后,再“mutt”前面的结果。如果像上面这样写的话,在”tar zxf”都还没结束时就已经开始执行“mutt”了,得到了错误的结果,因此需要做如下修改:



#!/bin/bash
IPLIST=/home/meta/ipinfo/iplist
for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}')
do
ssh $i "cd ~/update/;tar zxf patch-20160909.tgz -C ~/LMDG/ && echo '/$i ok' || echo '/$i bad'" &
done >> result.txt
wait
echo "resutl"|mutt -a result.txt -s update-result meta@126.com12345678

这里添加了“wait” 之后就可以达到我们预期的效果了,wait的作用就是等待子任务都执行完之后在结束父任务,继而执行下面的任务。

但是,紧接着又有问题了,如果这个iplist中的量巨大,这样一口气都放到后台,系统超出负载后,会有性能变差或者宕机风险,因此我们需要一个控制并发数的机制。
因此我们引入了任务队列的概念,有点类似之前socket举例中的消费者生产者模型,通过消息队列来调节供需的不平衡
修改如下:



#!/bin/bash

IPLIST=/home/meta/ipinfo/iplist #任务(消费者)
THREAD=50 #声明并发线程并发个数,这个是此应用的关键,也就是设置管道的最大任务数
TMPFIFO=/tmp/$$.fifo #声明管道名称,'$$'表示脚本当前运行的进程PID
mkfifo $TMPFIFO #创建管道
exec 5<>${TMPFIFO} #创建文件标示符“5”,这个数字可以为除“0”、“1”、“2”之外的所有未声明过的字符,以读写模式操作管道文件;系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变,换句话说就是在调用进程内部执行一个可执行文件
rm -rf ${TMPFIFO} #清除创建的管道文件

#为并发线程创建同样个数的占位
for((i=1;i<=$THREAD;i++))
do
echo ;
#借用read命令一次读取一行的特性,使用一个echo默认输出一个换行符,来确保每一行只有一个线程占位;这里让人联想到生产者&消费者模型,管道文件充当消息队列,来记录消费者的需求,然后由生产者去领任务,并完成任务,这里运用了异步解耦的思想。
done >&5
#将占位信息写入管道

for i in $(cat ${IPLIST} |grep -viE "^#|备机|ts"|awk '{print $1}') #从任务队列中依次读取任务
do
read -u5
#从文件描述符管道中,获取一个管道的线程占位然后开始执行操作;read中 -u 后面跟fd,表示从文件描述符中读入,该文件描述符可以是exec新开启的。
{
echo $(cat ~/ipinfo/iplist|grep $i|awk '{print $2}');
ssh -oConnectTimeout=10 -oConnectionAttempts=3 $i "cd /home/Log/;grep 'MIL' mission_2016-08-03*.log |awk -F, '{if(\$19==1370) print \$0}'|
awk -F, '{if(\$20==0) print \$0}'>miss_info.txt"
echo "" >&5
#任务执行完后在fd5中写入一个占位符,以保证这个线程执行完后,线程继续保持占位,继而维持管道中永远是50个线程数,&表示该部分命令/任务放入后台不占当前的bash,实现并行处理
} &
done
wait #等待父进程的子进程都执行结束后再结束父进程
exec 5>&- #关闭fd5的管道

exit 0
二、举例:
[root@ceph-mon ~]# cat /opt/test/test-swift.sh
#!/bin/bash

url=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep "publicURL" |head -n1 |awk -F '"' '{print $4}'`
id=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep -A 12 token |grep -w id |head -n 1 |awk -F '"' '{print $4}'`
pid=$$
thread=10
tmp_fifo="/tmp/$$.fifo"
mkfifo $tmp_fifo
exec 7<>$tmp_fifo
rm $tmp_fifo
for i in $(seq 1 $thread)
do
echo
done >&7

> test-error.log

echo ">>> start upload file!"

for i in $(seq 1 1000)
do
read -u7
{
for j in $(seq 1 1000)
do
ret=`curl -s -w "%{http_code}" -X PUT -T "test.jpg" -H "X-Auth-Token:${id}" ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg |tail -1 |awk -F '[{}]' '{print $NF}'`
if [ "x${ret}" != "x201" ]
then
echo "[`date '+%F %T'`] upload ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg error, code ${ret}!" >> test-error.log
fi
done
echo >&7
} &
done
wait

sleep 5
echo ">>> start download file!"

for i in $(seq 1 1000)
do
read -u7
{
for j in $(seq 1 1000)
do
ret=`curl -s -w "%{http_code}" -X GET -H "X-Auth-Token:${id}" -H "X-Storage-Policy:swiftonfile" -o /dev/null ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg`
if [ "x${ret}" != "x200" ]
then
echo "[`date '+%F %T'`] download ${url}/mycontainers1/test_${pid}_${i}_${j}.jpg error, code ${ret}!" >> test-error.log
fi
done
echo >&7
} &
done
wait

exec 7>&-

进程间通信

1
https://www.cnblogs.com/scue/p/4075088.html

脚本打印进度+过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
读下去文件并打印百分比以及当前数和总数的比,以及运行时间:
#!/bin/bash
ProgressBar()
{
local current=$1; local total=$2
local now=$((current*100/total))
local last=$(((current-1)*100/total))
[[ $((last % 2)) -eq 1 ]]&&let last++
local str=$(for i in `seq 1 $((last/2))`; do printf '#'; done)
use_time=`ps -p $$ -o etime|column -t|tail -1`
for ((i=$last;$i<=$now;i+=2));do printf "\r[%-50s]%d%%[%03d/%03d][%-5s]\r" "$str" $i $current $total ${use_time};str+='#';done
}

len=`cat 4.log|wc -l`
num=0
cat 4.log | while read line
do
let num=num+1
ProgressBar $num ${len}
done
echo
效果:
[##################################################]100%[924/924][00:22]
打印过程
echo `date "+%Y-%m-%d %T"` "******安装mysql......"
#第一步:停止并卸载原有的mysql

echo `date "+%Y-%m-%d %T"` "---->>停止彻底卸载已有的mysql......"
echo `date "+%Y-%m-%d %T"` "---->>配置优化、开机自启设置完毕"
echo `date "+%Y-%m-%d %T"` "******mysql安装完毕,登录密码为${mysql_passwd}"

返回如下:
2019-11-28 085414 ******安装mysgl ...
2019-11-28 085414 --->>停止彻底卸载已有的mysql....
2019-11-28 085414 --->>配置优化、开机自启设置完毕
2019-11-28 085414 ******mysq1安装完毕,登录密码为


读取文件并打印百分比以及当前数和总数的比:
#!/bin/bash
ProgressBar()
{
local current=$1; local total=$2
local now=$((current*100/total))
local last=$(((current-1)*100/total))
[[ $((last % 2)) -eq 1 ]]&&let last++
local str=$(for i in `seq 1 $((last/2))`; do printf '#'; done)
for ((i=$last;$i<=$now;i+=2));do printf "\r[%-50s]%d%%[%03d/%03d]\r" "$str" $i $current $total;str+='#';done
}

len=`cat 4.log|wc -l`
num=0
cat 4.log | while read line
do
let num=num+1
ProgressBar $num ${len}
done
echo

效果:
[##################################################]100%[924/924]


读取大于100的文件百分比:
#!/bin/bash
ProgressBar()
{
local current=$1; local total=$2
local now=$((current*100/total))
local last=$(((current-1)*100/total))
[[ $((last % 2)) -eq 1 ]]&&let last++
local str=$(for i in `seq 1 $((last/2))`; do printf '#'; done)
for ((i=$last;$i<=$now;i+=2));do printf "\r[%-50s]%d%%" "$str" $i;sleep 0.02;str+='#';done
}

len=`cat 4.log|wc -l`
num=0
cat 4.log | while read line
do
let num=num+1
ProgressBar $num ${len}
done
echo


Shell 进度条效果的一个实现;https://www.cnblogs.com/tangxin-blog/p/6293488.html
#!/bin/bash
processBar()
{
now=$1
all=$2
percent=`awk BEGIN'{printf "%f", ('$now'/'$all')}'`
len=`awk BEGIN'{printf "%d", (100*'$percent')}'`
bar='>'
for((i=0;i<len-1;i++))
do
bar="="$bar
done
printf "[%-100s][%03d/%03d]\r" $bar $len 100
}

whole=100
process=0
while [ $process -lt $whole ]
do
let process++
processBar $process $whole
sleep 0.1
done
printf "\n"

效果:
[root@host102442549 Master]# sh 2.sh
[==============================================================================>[100/100]

 shell实现进度条(100%)
##方法1:https://www.jianshu.com/p/7ba06b47e0b6
#!/bin/bash
ProgressBar()
{
local current=$1; local total=$2
local now=$((current*100/total))
local last=$(((current-1)*100/total))
[[ $((last % 2)) -eq 1 ]]&&let last++
local str=$(for i in `seq 1 $((last/2))`; do printf '#'; done)
for ((i=$last;$i<=$now;i+=2));do printf "\r[%-50s]%d%%" "$str" $i;sleep 0.02;str+='#';done
}
for n in `seq 1 100`
do
ProgressBar $n 100
done
echo

效果:
[##################################################]100%

##方法2:http://blog.chinaunix.net/uid-9188830-id-2007059.html
#!/bin/sh

b=''
for ((i=0;$i<=100;i+=2))
do
        printf "progress:[%-50s]%d%%\r" $b $i
        sleep 0.1
        b=#$b
done
echo
效果:
progress:[##################################################]100%

读配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
配置文件config内容如下
ID=123
I
IP=192.168.3.154
N
Name=test

方法一,利用eval方法解析
while read line;do
eval "$line"
done < config
echo $ID
echo $IP
echo $Name

方法二,直接将配置信息加载到session的环境变量中
source config
echo $ID

123
echo $IP
192.168.3.154
echo $Name
t
test

脚本执行时间统计

1
2
3
4
5
a=$(date +%H%M%S)
b=$(date +%H%M%S)

echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

getopt:选项参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
https://www.cnblogs.com/f-ck-need-u/p/9758075.html
https://www.cnblogs.com/lidabo/p/5382617.html
"a:b:cd::e",这就是一个选项字符串。对应到命令行就是-a ,-b ,-c ,-d, -e 。冒号又是什么呢? 冒号表示参数,一个冒号就表示这个选项后面必须带有参数(没有带参数会报错哦),但是这个参数可以和选项连在一起写,也可以用空格隔开,比如-a123 和-a   123(中间有空格) 都表示123是-a的参数;两个冒号的就表示这个选项的参数是可选的,即可以有参数,也可以没有参数,但要注意有参数时,参数与选项之间不能有空格(有空格会报错的哦),这一点和一个冒号时是有区别的。

24.getopt(一个外部工具)
https://www.cnblogs.com/lidabo/p/5382617.html
具体用用法可以 man getopt
char*optstring = “ab:c::”;
#单个字符a         表示选项a没有参数            格式:-a即可,不加参数
#单字符加冒号b:     表示选项b有且必须加参数      格式:-b 100或-b100,但-b=100错
#单字符加2冒号c::   表示选项c可以有,也可以无     格式:-c200,其它格式错误
#getopt处理以'-’开头的命令行参数,如optstring="ab:c::d::",命令行为getopt.exe -a -b  host -ckeke -d haha
#在这个命令行参数中,-a是选项元素。host是b的参数,keke是c的参数。但haha并不是d的参数,因为它们中间有空格隔开。
#还要注意的是默认情况下getopt会重新排列命令行参数的顺序,所以到最后所有不包含选项的命令行参数都排到最后。
#-o表示短选项
#--long表示长选项
#"$@"在上面解释过
# -n:出错时的信息
# -- :举一个例子比较好理解:
我们要创建一个名字为 "-f"的目录你会怎么办?
mkdir -f #不成功,因为-f会被mkdir当作选项来解析,这时就可以使用
mkdir -- -f 这样-f就不会被作为选项。
#下面的例子都是使用的$2


举例:
!/bin/bash
TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \
     -n 'example.bash' -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
#set 会重新排列参数的顺序,也就是改变$1,$2...$n的值,这些值在getopt中重新排列过了
eval set -- "$TEMP"
#经过getopt的处理,下面处理具体选项。
while true ; do
        case "$1" in
                -a|--a-long) echo "Option a" ; shift ;;
                -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
                -c|--c-long)
                        case "$2" in
                                "") echo "Option c, no argument"; shift 2 ;;
                                *)  echo "Option c, argument \`$2'" ; shift 2 ;;
                        esac ;;
                --) shift ; break ;;
                *) echo "Internal error!" ; exit 1 ;;
        esac
done
echo "Remaining arguments:"
for arg do
   echo '--> '"\`$arg'" ;
done
运行命令:
./getopt.sh -b abc -a -c33 remain
Option b, argument `abc'
Option a
Option c, argument `33'
Remaining arguments:
--> `remain'
再举一个例子:(安装脚本里面的)
function parse()
{"
    RET=`getopt -o ht:e: \
                --long help,type:,env: \
                -n "ERROR" -- "$@"`
    if [ $? -ne 0 ]
    then
        usage
        exit 0
    fi
    eval set -- "$RET"
    while true
    do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            -t|--type)
                TYPE=$2
                shift 2
                ;;
            -e|--env)
                ENV=$2
                shift 2
                ;;
            --)
                shift
                break
                ;;
            *)
                usage
                exit 0
                ;;
        esac
    done
}

生成随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
1.使用系统的 $RANDOM 变量
fdipzone@ubuntu:~$ echo $RANDOM  
17617 
$RANDOM 的范围是 [0, 32767]
如需要生成超过32767的随机数,可以用以下方法实现。
例:生成400000~500000的随机数
#!/bin/bash
function rand(){
min=$1
max=$(($2-$min+1))
num=$(($RANDOM+1000000000)) #增加一个10位的数再求余
echo $(($num%$max+$min))
}

rnd=$(rand 400000 500000)
echo $rnd
exit 0


2.使用date +%s%N
例:生成1~50的随机数

#!/bin/bash

function rand(){
min=$1
max=$(($2-$min+1))
num=$(date +%s%N)
echo $(($num%$max+$min))
}

rnd=$(rand 1 50)
echo $rnd

exit 0

3.使用/dev/random 和 /dev/urandom
/dev/random 存储着系统当前运行环境的实时数据,是阻塞的随机数发生器,读取有时需要等待。
/dev/urandom 非阻塞随机数发生器,读取操作不会产生阻塞。
例:使用/dev/urandom生成100~500的随机数,使用urandom避免阻塞。

#!/bin/bash

function rand(){
min=$1
max=$(($2-$min+1))
num=$(cat /dev/urandom | head -n 10 | cksum | awk -F ' ' '{print $1}')
echo $(($num%$max+$min))
}

rnd=$(rand 100 500)
echo $rnd

exit 0

4.使用linux uuid
uuid 全称是通用唯一识别码,格式包含32个16进制数字,以'-'连接号分为5段。形式为8-4-4-4-12 的32个字符。
fdipzone@ubuntu:~/shell$ cat /proc/sys/kernel/random/uuid
fd496199-372a-403e-8ec9-bf4c52cbd9cd
例:使用linux uuid 生成100~500随机数
#!/bin/bash

function rand(){
min=$1
max=$(($2-$min+1))
num=$(cat /proc/sys/kernel/random/uuid | cksum | awk -F ' ' '{print $1}')
echo $(($num%$max+$min))
}

rnd=$(rand 100 500)
echo $rnd

exit 0

5.生成随机字符串
例:生成10位随机字符串

#使用date 生成随机字符串
date +%s%N | md5sum | head -c 10

#使用 /dev/urandom 生成随机字符串
cat /dev/urandom | head -n 10 | md5sum | head -c 10

数组脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function swift_delete() {
url=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep "publicURL" |head -n 1 |awk -F '"' '{print $4}'`
id=`curl -s -d '{"auth":{"tenantName": "adminTenant","passwordCredentials":{"username": "admin","password": "admin"}}}' -H "Content-type:application/json" http://10.37.2.15:5000/v2.0/tokens | python -mjson.tool |grep -A 12 token |grep -w id |head -n 1 |awk -F '"' '{print $4}'`
a=()
a=`curl -s -w "%{http_code}" -X GET -H "X-Auth-Token:${id}" ${url}/mycontainers1?format=json|jq .|grep name|sed 's/ "name": "//g'|sed 's/",//g'`
for item in ${a[@]}
do
ret=`curl -s -w "%{http_code}" -X DELETE -H "X-Auth-Token:${id}" ${url}/mycontainers1/${item} |tail -1 |awk -F '[{}]' '{print $NF}'`
done
}

set i=0
set j=0
for((i=0;i<3;))
do
let "i=i+1"
swift_delete
done

shell 中的动态变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vr_0=
avr_1=
avr_2=
avr_3=
avr_4=
avr_5=
avr_6=
avr_7=

# 赋值 avr_x =
for i in $(seq 0 7)
do
eval avr_${i}=${i}
done

# 取值
for i in $(seq 0 7)
do
tmp=$(eval echo '$'avr_${i})
eval echo ${tmp}
done

使用/dev/urandom生成固定位数的随机数

1
2
3
4
5
6
7
8
9
1.纯数字
head /dev/urandom | tr -dc 0-9 | head -c n

2.小写字母+数字
head /dev/urandom | tr -dc a-z0-9 | head -c n

3.大小写字母+数字
head /dev/urandom | tr -dc A-Za-z0-9 | head -c n
最后的n代表要生成的随机数的位数

监控脚本:check.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
check.sh:
function sar_count() {
echo "网络流量:"
#echo "[$(date +'%F %H:%M:%S')]"
sar_count=`sar -n DEV 1 1|sed -n '3p;5p'`
echo "${sar_count}"
}

function MEN_count() {
echo "内存:"
a=`free -m | sed -n '2p'|awk '{print $2}'`
b=`free -m | sed -n '3p'|awk '{print $3}'`
c=`echo "scale=2;$b/$a*100" |bc`
d=`echo "total:"$a"M"`
e=`echo "used:"$b"M"`
f=`echo "%used:${c}%"`
echo "$(date +'%H:%M:%S')" "${d}" "${e}" "${f}"
echo " "
}

function CPU_count() {
echo "CPU:"
date_count=`date +'%H:%M:%S'`
sar -u 1 1|sed -n '3,4p'
#iostat -x 1 1|sed -n '3,4p'|sed 's/avg-cpu/'"$date_count"'/g'
echo " "
}

function IO_count() {
echo "磁盘IO:"
date_count=`date +'%H:%M:%S'`
iostat -x 1 1|sed -n '6,16p'|sed 's/Device:/'"$date_count"'/g'
echo " "
}

for((j=0;j<10;)); do
sar_count
MEN_count
CPU_count
IO_count
echo "================================================================================================================="
sleep 10
done

美女

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// .::::.
// .::::::::.
// :::::::::::
// ..:::::::::::'
// '::::::::::::'
// .::::::::::
// '::::::::::::::..
// ..::::::::::::.
// ``::::::::::::::::
// ::::``:::::::::' .:::.
// ::::' ':::::' .::::::::.
// .::::' :::: .:::::::'::::.
// .:::' ::::: .:::::::::' ':::::.
// .::' :::::.:::::::::' ':::::.
// .::' ::::::::::::::' ``::::.
// ...::: ::::::::::::' ``::.
// ```` ':. ':::::::::' ::::..
// '.:::::' ':'````.

监控

top.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WHERE=${2}
mkdir -p ${1}
rm -rf ${1}*
for i in `cat ${WHERE}/needtomon.conf`
do
mkdir ${1}${i}
done
sleep 1
for i in `cat ${WHERE}/needtomon.conf`
do
ssh ${i} "sar -n DEV 1" >>${1}${i}/sar.txt &
ssh ${i} "iostat -tkx 1" >>${1}${i}/iostat.txt &
#ssh ${i} "ps auxw|head -1;ps auxw|sort -rn -k3|head -5" >>${1}${i}/cpu-top.txt
#ssh ${i} "ps auxw|head -1;ps auxw|sort -rn -k4|head -5" >>${1}${i}/mem-top.txt
ssh ${i} "vmstat -t 1" >>${1}${i}/vmstat.txt &
ssh ${i} free |sed -n '2p'|awk '{print $2}' >>${1}${i}/memtotal.txt
ssh ${i} "cachehit" >> ${1}${i}/CacheHit.txt &
done

log.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
STARTTIME=${1}
ENDTIME=${2}
WHERE=${4}
SPSTARTTIME=`echo ${STARTTIME}|awk '{print $2}'|cut -b 1-5`
SPENDTIME=`echo ${ENDTIME}|awk '{print $2}'|cut -b 1-5`

k=0
for i in `cat ${WHERE}/needtomon.conf`
do
let k=k+1
echo "============="${i}"=============" >>${3}${i}/${k}.txt
enscript -B -p - ${3}${i}/${k}.txt | ps2pdf - |convert -background "rgb(199,237,204)" -extent 600x50 - ${3}${i}/${k}.png >> /dev/null 2>&1;
rm -rf ${3}${i}/${k}.txt
ssh ${i} "sed -n '/${SPSTARTTIME}/,/${SPENDTIME}/'p /mnt/snbslog/snbs/Gateway/gateway.INFO" >>${3}${i}/gateway.log
ssh ${i} "sed -n '/${SPSTARTTIME}/,/${SPENDTIME}/'p /mnt/snbslog/snbs/ChunkServer/chunkserver.INFO" >>${3}${i}/chunkserver.log
ssh ${i} "sed -n '/${SPSTARTTIME}:/,/${SPENDTIME}:/'p /mnt/snbslog/snbs/Speed/gateway.INFO" >>${3}${i}/gateway-speed.log
ssh ${i} "sed -n '/${SPSTARTTIME}:/,/${SPENDTIME}:/'p /mnt/snbslog/snbs/Speed/chunkserver.INFO" >>${3}${i}/chunkserver-speed.log
cat ${3}${i}/gateway-speed.log|sed '/read/d;/rate/d;s/[ ][ ]*/,/g'|awk '$0=NR","$0' >> ${3}${i}/gwspeed.log
cat ${3}${i}/chunkserver-speed.log|sed '/read/d;/rate/d;s/[ ][ ]*/,/g'|awk '$0=NR","$0' >> ${3}${i}/chspeed.log
cat ${3}${i}/gateway.log|grep -A 81 Session|sed 's/ //g'|sed 's/(0,4]ms://g'|sed 's/(4,8]ms://g'|sed 's/(8,12]ms://g'|sed 's/(16,20]ms://g'|sed 's/(20,30]ms://g'|sed 's/(30,...]ms://g'|sed 's/(12,16]ms://g'|sed 's/ /,/g'|sed 's/ //g'|sed 's/,,/,/g'|grep -v Session|grep -v 2020 >> ${3}${i}/${i}.log
a=`cat ${3}${i}/${i}.log|grep -E "write|read|request"|sort -n| uniq|grep -E "gateway_singlechunk|gateway"|grep -v gather`
for j in ${a[@]}
do
cat ${3}${i}/${i}.log|grep -A 2 "${j}$"|grep -v ${j} >> ${3}${i}/${j}.txt
echo "--" >> ${3}${i}/${j}.txt
sed -i "1i--" ${3}${i}/${j}.txt
cat ${3}${i}/${j}.txt|grep -A 1 "\-\-"|grep -v "\-\-"|awk '$0=NR"\t"$0'|sed 's/ /,/g' >> ${3}${i}/delay-${j}.txt
rm -rf ${3}${i}/${j}.txt
done
python ${WHERE}/3.pyc ${3}${i}/ ${i} >> /dev/null 2>&1
python ${WHERE}/speed.py ${3}${i}/ gwspeed.log gwspeed- >> /dev/null 2>&1
python ${WHERE}/speed.py ${3}${i}/ chspeed.log chspeed- >> /dev/null 2>&1
cp -r ${3}${i}/delay.png ${3}delay-${k}.png
ssh ${i} "sh /opt/peace/cacehit.sh"
done
cd ${3}
PNG=`ls delay*|sort -t - -k2 -n`
convert ${PNG} -append gw-delay.png >> /dev/null 2>&1
convert vdbench.png gw-delay.png cpu-mem-all.png iostatall.png sarall.png -append all.png >> /dev/null 2>&1
rm -rf delay* vdbench.png gw-delay.png cpu-mem-all.png iostatall.png sarall.png iostat-all.txt sar-all.txt

#DIR=`echo ${3}|awk -F / '{print $(NF-1)}'`
#cd ../
#tar -zcvf ${DIR}.tar.gz ${DIR}/ >> /dev/null 2>&1
#mv ${DIR}.tar.gz ${DIR}/
#cd - >> /dev/null 2>&1

j=0
for i in `cat ${WHERE}/needtomon.conf`
do
cd ${i}
let j=j+1
rm -rf mem-a.txt vmstat-a.txt vmstat-b.txt vmstat-all.txt ${i}.log delay*.txt
convert gwspeed-io-rate.png gwspeed-read-iops.png gwspeed-read-bytes.png gwspeed-read-resp.png gwspeed-read-maxResp.png gwspeed-write-iops.png gwspeed-write-bytes.png gwspeed-write-resp.png gwspeed-write-maxResp.png gwspeed-MBsec-1024-2.png gwspeed-queue-depth.png -append gwspeed.png >> /dev/null 2>&1
convert chspeed-io-rate.png chspeed-read-iops.png chspeed-read-bytes.png chspeed-read-resp.png chspeed-read-maxResp.png chspeed-write-iops.png chspeed-write-bytes.png chspeed-write-resp.png chspeed-write-maxResp.png chspeed-MBsec-1024-2.png chspeed-queue-depth.png -append chspeed.png >> /dev/null 2>&1
convert ${j}.png gwspeed.png -append ../gwspeed-${j}.png >> /dev/null 2>&1
convert ${j}.png chspeed.png -append ../chspeed-${j}.png >> /dev/null 2>&1
rm -rf gwspeed*.png
rm -rf chspeed*.png
cd - > /dev/null 2>&1
done
PNG1=`ls gwspeed*.png|sort -t - -k2 -n`
convert ${PNG1} -append gw-speed.png >> /dev/null 2>&1
PNG2=`ls chspeed*.png|sort -t - -k2 -n`
convert ${PNG2} -append ch-speed.png >> /dev/null 2>&1
rm -rf ${PNG1} ${PNG2}

DIR=`echo ${3}|awk -F / '{print $(NF-1)}'`
cd ../
tar -zcvf ${DIR}.tar.gz ${DIR}/ >> /dev/null 2>&1
mv ${DIR}.tar.gz ${DIR}/
cd - >> /dev/null 2>&1

order.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
WHERE=${2}
cd ${1}
rm -rf ${1}iostat-all.txt
rm -rf ${1}sar-all.txt
j=0
for i in `cat ${WHERE}/needtomon.conf`
do
let j=j+1
cd $i
rm -rf vmstat-b.txt vmstat-a.txt mem-a.txt vmstat-all.txt
echo "============="${i}"=============" >> ../iostat-all.txt
echo "============="${i}"=============" >> ../sar-all.txt
cat iostat.txt | head -n 299 | tail -n +219 >> ../iostat-all.txt
cat sar.txt | head -n 209 | tail -n +175 >> ../sar-all.txt
cat vmstat.txt |sed '/procs/d'|sed '/swpd/d'|awk '{print 100-$15","$13","$14","$16","$4","$5","$6}' >> vmstat-b.txt
totalmem=`cat memtotal.txt`
sed -i 's/$/,'${totalmem}'/' vmstat-b.txt
cat vmstat-b.txt|awk '$0=NR"\t"$0'|sed 's/ /,/g' >> vmstat-a.txt
cat vmstat-a.txt |awk -F , '{print ($9-$8-$7-$6)*100/$9}' >> mem-a.txt
paste -d, vmstat-a.txt mem-a.txt >> vmstat-all.txt
python ${WHERE}/5.pyc ./ ${i}
convert cpu.png mem.png -append cpu-mem.png >> /dev/null 2>&1
mv cpu-mem.png ../cpu-mem-${j}.png
cd - > /dev/null 2>&1
done
#cd ${1}
for i in iostat sar
do
enscript -B -p - ${i}-all.txt | ps2pdf - |convert -background "rgb(199,237,204)" -extent 600x800 - ${i}.png > /dev/null 2>&1;
PNG=`ls ${i}-*.png|sort -t - -k2 -n`
convert ${PNG} -append ${i}all.png >> /dev/null 2>&1
done
rm -rf iostat-*.png sar-*.png
convert iops.png MB.png read-resp.png write-resp.png resp.png resp-max.png -append vdbench.png
rm -rf iops.png MB.png read-resp.png write-resp.png resp.png resp-max.png resp-max.png

PNG=`ls cpu-mem-*|sort -t - -k2 -n`
convert ${PNG} -append cpu-mem-all.png >> /dev/null 2>&1
convert cpu-mem-1.png cpu-mem-2.png cpu-mem-3.png cpu-mem-4.png cpu-mem-5.png cpu-mem-6.png -append cpu-mem-all.png
rm -rf cpu-mem-1.png cpu-mem-2.png cpu-mem-3.png cpu-mem-4.png cpu-mem-5.png cpu-mem-6.png

ip是否通验证脚本

1
2
3
4
5
6
#!/bin/sh
for i in `cat hostip.txt`
do
#ping -c 4 $i|grep -q 'ttl=' && echo "$i ok" || echo "$i failed"
ping -c 4 $i|grep -q 'ttl=' || echo "$i failed"
done

shell脚本转换成二进制的可执行文件方法–加密

1
2
3
4
5
6
7
8
shell脚本转换成二进制的可执行文件方法--加密

第一种方法(gzexe):
这种加密方式不是非常保险的方法,但是能够满足一般的加密用途,可以隐蔽脚本中的密码等信息。
它是使用系统自带的gzexe程序,它不但加密,同时压缩文件。
使用方法:
gzexe file.sh
它会把原来没有加密的文件备份为 file.sh~ ,同时 file.sh 即被变成加密文件;

echo 在shell及脚本中显示色彩及闪烁警告效果

https://www.cnblogs.com/su-root/p/10743544.html

1
2
语法格式:
echo -e "\033[颜色1:颜色2m 要展示的文字 \033[0m"

${},##, %% , :- ,:+, ?的使用

https://www.jianshu.com/p/f7234405eb79

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
假设我们定义了一个变量为:
file=/dir1/dir2/dir3/my.file.txt

#是左,$是右

可以用${ }分别替换得到不同的值:
${file#*/}:删掉第一个/ 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删掉最后一个/ 及其左边的字符串:my.file.txt
${file#*.}:删掉第一个. 及其左边的字符串:file.txt
${file##*.}:删掉最后一个. 及其左边的字符串:txt
${file%/*}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/*}:删掉第一个/ 及其右边的字符串:(空值)
${file%.*}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删掉第一个. 及其右边的字符串:/dir1/dir2/dir3/my

记忆的方法为:
# 是 去掉左边(键盘上#在 $ 的左边)
%是去掉右边(键盘上% 在$ 的右边)
单一符号是最小匹配;两个符号是最大匹配

${file:0:5}:提取最左边的5 个字节:/dir1
${file:5:5}:提取第5 个字节右边的连续5个字节:/dir2

也可以对变量值里的字符串作替换:
${file/dir/path}:将第一个dir 替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path}:将全部dir 替换为path:/path1/path2/path3/my.file.txt

利用${ } 还可针对不同的变数状态赋值(沒设定、空值、非空值):
${file-my.file.txt} :假如$file 沒有设定,則使用my.file.txt 作传回值。(空值及非空值時不作处理)
${file:-my.file.txt} :假如$file 沒有設定或為空值,則使用my.file.txt 作傳回值。(非空值時不作处理)
${file+my.file.txt} :假如$file 設為空值或非空值,均使用my.file.txt 作傳回值。(沒設定時不作处理)
${file:+my.file.txt} :若$file 為非空值,則使用my.file.txt 作傳回值。(沒設定及空值時不作处理)
${file=my.file.txt} :若$file 沒設定,則使用my.file.txt 作傳回值,同時將$file 賦值為my.file.txt 。(空值及非空值時不作处理)
${file:=my.file.txt} :若$file 沒設定或為空值,則使用my.file.txt 作傳回值,同時將$file 賦值為my.file.txt 。(非空值時不作处理)
${file?my.file.txt} :若$file 沒設定,則將my.file.txt 輸出至STDERR。(空值及非空值時不作处理)
${file:?my.file.txt} :若$file 没设定或为空值,则将my.file.txt 输出至STDERR。(非空值時不作处理)
${#var} 可计算出变量值的长度:
${#file} 可得到27 ,因为/dir1/dir2/dir3/my.file.txt 是27个字节

脚本举例:

SHELL打印两个日期之间的日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@umout shell]# cat date_to_date.sh    
THIS_PATH=$(cd `dirname $0`;)
cd $THIS_PATH
##要求传入的数据格式为yyyyMMdd的两个开始和结束参数,如20170201 20170310
start_input=$1
end_input=$2

##将输入的日期转为的时间戳格式
startDate=`date -d "${start_input}" +%s`
endDate=`date -d "${end_input}" +%s`

##计算两个时间戳的差值除于每天86400s即为天数差
stampDiff=`expr $endDate - $startDate`
dayDiff=`expr $stampDiff / 86400`

##根据天数差循环输出日期
for((i=0;i<$dayDiff;i++))
do
process_date=`date -d "${start_input} $i day" +'%Y-%m-%d'`
echo $process_date
done

shell中AScll值和字符之间的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Shell中ASCII值和字符之间的转换
97-121
1、ASCII值转换为字符
方法一:
i=97
echo $i | awk '{printf("%c", $1)}'
方法二:

t=`printf "%x" 97`
printf "\\x$t"
或者直接
printf \\x`printf %x 97`
注意上面是的字符不是“’”,而是数字键1前面的那个字符“·”。

2、字符转换为ASCII值
方法一:
printf "%d" \'a
或者
printf "%d" "'a"
方法二:
echo "A"| tr -d "\n" | od -An -t dC

切换用户执行脚本:

1
2
3
4
su - browsr << EOF
xxxx;
exit;
EOF

简单的将Shell和一些文件打包成一个单独的“可执行文件”

https://www.cnblogs.com/scue/p/4447911.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
有时候给别人分享一个工具的时候,同时需要提供的文件比较多;

如果分享一个压缩包还得教会对方如何解压、执行哪个脚本,感觉需要传输的内容多了就不方便;


把几个Shell脚本和文件打包成一个“单独的可执行文件”,对方接收到这个文件,只需要执行一下这个文件,就可以实现解压、执行对应脚本了,相对比较方便;


#!/bin/bash -
#===============================================================================
#
# FILE: shell_pack.sh
#
# USAGE: ./shell_pack.sh
#
# DESCRIPTION:
#
# OPTIONS: ---
# REQUIREMENTS: ---
# BUGS: ---
# NOTES: ---
# AUTHOR: lwq (28120), scue@vip.qq.com
# ORGANIZATION:
# CREATED: 04/22/2015 02:38:01 PM CST
# REVISION: ---
#===============================================================================

#=== FUNCTION ================================================================
# NAME: usage
# DESCRIPTION: Display usage information.
#===============================================================================
function usage ()
{
cat <<- EOT

Usage : $0 -p package -s script file1 file2 file3 ..

Options:
-h|help Display this message
-p|package The output package name
-s|script The script will run when unpack package
Other The all files what you want to pack

EOT
} # ---------- end of function usage ----------

#-----------------------------------------------------------------------
# Handle command line arguments
#-----------------------------------------------------------------------

while getopts ":hp:s:" opt
do
case $opt in

h|help ) usage; exit 0 ;;
p|package ) package_name=$OPTARG ;;
s|script ) install_script=$OPTARG ;;
\? ) echo -e "\n Option does not exist : $OPTARG\n"
usage; exit 1 ;;

esac # --- end of case ---
done
shift $(($OPTIND-1))

if [[ -z $package_name ]]; then
echo "package_name can't not be empty"
usage
exit
fi

if [[ -z $package_name ]]; then
echo "install_script can't not be empty"
usage
exit
fi

files=$@

generate_wrapper_script(){
local install_script=$1
local wrapper_script=$2
cat <<-'EOT' >$wrapper_script
#!/bin/sh
echo "begin ..."
unpackdir=/tmp/$(basename $0)_unpack
rm -rf $unpackdir 2>/dev/null
mkdir -p $unpackdir
echo "unpacking ..."
sed '1, /^#__SCRIPTEND__/d' $0 | tar zxf - -C $unpackdir
if [ $? -ne 0 ]; then
echo "unpack package failed."
exit 1
fi
echo ""
echo "installing ..."
cd $unpackdir
EOT
cat <<-EOR >>$wrapper_script
chmod +x $install_script
./$install_script
EOR
cat <<-'EOE' >>$wrapper_script
if [ $? -ne 0 ]; then
echo "install failed."
exit 2
elif [[ -d $unpackdir ]]; then
rm -rf $unpackdir
fi
echo "install ok, enjoy!"
exit 0
#__SCRIPTEND__
EOE
}

tarfile=package_content_$$.tgz
wrapfile=wrap_$$.sh

echo -e "start packing ..\n"
tar zcvf $tarfile $files $install_script
generate_wrapper_script $install_script $wrapfile
cat $wrapfile $tarfile > $package_name
chmod +x $package_name

echo -e "\noutput: $package_name\n"

rm -f $tarfile
rm -f $wrapfile


文件内容保存到 shell_pack.sh,使用方法举例:

  ./shell_pack.sh -p logcat_install -s logcat_install.sh logcat_all.sh logcat_wrapper.sh vmstat2

将产生可执行文件“logcat_install”,执行logcat_install时,会解压自身文件内的tar.gz文件,并执行关键的脚本 logcat_install.sh

Linux高级编程

进程间通信(Interprocess Communication)

ALP Chapter 5 进程间通信(Interprocess Communication)

  • 这一章就是著名的IPC,这个东西实际的作用和它的名字一样普及。例如我们浏览网页,打印文章,等等。
  • IPC总共有五种类型:
    • 共享内存(Shared Memory):最容易理解的一种,就像一个特工把情报放在特定地点(内存),另一个特工再过来取走一样。
    • 内存映射(Mapped Memory):和共享内存几乎相同,除了特工们把地点从内存改成了文件系统。
    • 管道(Pipes):从一个进程到另一个进程的有序通信,用电话来比喻再恰当不过了。
    • FIFOs:和管道和类似,唯一的区别是FIFOs比管道更神通一些,允许没有关系的进程之间的有序通信。
    • 套接字(Sockets):为什么说浏览网页也是IPC?就是因为它。

5.1 共享内存(Shared Memory)

  • 共享内存是最快捷的进程间通信方式。访问共享内存的效率和访问进程自己的非共享内存的效率是相同的,而且这种通信方式不需要任何额外的系统调用。
  • 系统不会自动为共享内存处理同步问题,这个问题必须由用户自己解决。
  • 共享内存的步骤通常是:
    • 一个进程申请一块共享内存,即在它的页表中加入新的一项
    • 所有进程Attach该共享内存,即从申请内存的进程中拷贝对应的页表
    • 使用该内存进行通讯
    • 结束后所有进程detach该共享内存
    • 申请共享内存的进程在确定所有进程都detach后,释放该内存
  • 由于共享内存是通过页表来实现的,我们可以得出一个结论:共享内存的大小是页面大小的整数倍,页面的大小可以通过getpagesize()来得到,通常在Linux下该值是4KB
  • 相关的API函数:
    • 申请共享内存:shmget,返回共享内存segment的id
    • Attach,Detach函数:shmat,shmdt。需要共享内存segment的id
    • 释放申请的内存:shmctl。一定要记得释放!调用exit和exec会自动detach,但不会自动释放。
  • 使用 ipcs -m来观看当前系统存在的共享内存

5.2 进程信号量

  • 信号量(Semaphore)的概念前面已经介绍过了。Linux对用来同步进程的信号量采取了一种特别的实现方式。这些信号量也就被称为进程信号量(Process Semaphore)。(这一节下面所提到的所有信号量默认都是指进程信号量)
  • 相关的API函数:
    • 申请:semget
    • 释放:semctl。需要注意的是信号量不会被自动释放,我们必须显式释放它。
    • Wait和Post:semop
  • 使用ipcs -s来观看当前系统存在的信号量

5.3 内存映射

  • 内存映射使得不同的进程可以通过一个共享文件来互相通信。
  • 相关的API函数:
    • 映射:mmap
    • 同步:msync。用来指定对文件的修改是否被buffer。
    • 释放:munmap。在程序结束的时候会自动unmap
  • mmap的其他用法:
    • 可以替代read和write,有时使用内存映射后的效率比单纯使用I/O操作来的更快
    • 在内存映射文件中构建structure,修改structure再次将文件映射到内存中可以快速的将structure恢复到原来的状态
    • 把/dev/zero文件映射到内存中。该文件可以提供无限的0,并且写到该文件的所有内容将被直接丢弃

5.4 管道(Pipes)

  • 管道是单向的,即一个线程写,另一个线程读,无法互换
  • 如果写的速度太快,造成管道满了,那么写的线程就会被block;如果读的速度太快,造成管道空了,那么读的进程就会被block。因此事实上我们可以说管道自动实现了同步机制
  • 我们可以通过调用pipe函数来生成一对pipe file description。(为什么是一对?因为一个读一个写)。可是,生成的pipe file description无法传送给不相关的进程(因为做为file descriptor即使它拿到了也没法用)。但是我们注意到fork之后父进程所有的file descriptor在子进程中依然有效,因此管道最大的作用是在父子进程之间通信。或者更确切的说,是在有共同祖先的进程之间通信。
  • 典型的创建管道的流程如下:
    • 用pipe生成2个pipe file description(简称fds)。然后调用fork
    • 在父进程关闭fds0,并以只读(或只写)方式打开fds1。在子进程中关闭fds1,并以只写(或只读)方式打开fds0。打开的函数是fdopen。
    • 开始通信。结束后用close函数关闭剩下的fds。
  • 这里有一个技巧:可以利用管道来达成重定向stdin, stdout和stderr。注意到dup2这个API可以把一个file descriptor复制到另一个上。
  • 事实上,我们有一对更为简洁的函数popen/pclose来完成上面的一系列复杂的操作。popen有两个参数:
    • 第一个参数接受一个exec,子进程将执行这个exec
    • 第二个参数为”w”或者”r”,”w”表示父进程写子进程读,”r”则反之
    • 返回值为管道的一端,也就是一个file descriptor
    • pclose用来关闭popen返回的file descriptor
  • FIFO(First In First Out)文件事实上是一个有名字的管道,换句话说,他可以用来让“不相干”的程序互相通信。
    • 我们使用mkfifo函数来创建一个FIFO文件
    • 我们可以使用任何的低级I/O函数(open, write, read, close等)以及C库I/O函数(fopen, fprintf, fscanf, fclose等)来操作FIFO文件。
  • Linux的管道和Windows下的命名管道(Named Pipes)的区别
    • Windows的命名管道更像一个套接字(sockets),它可以通过网络让不同主机上的程序进行通信
    • Linux的管道允许有多个reader和writer,每个reader和writer进行读/写的最大容量为 PIPE_BUF(4KB),如果有多个writer同时写,他们写的东西会被分为一个一个的chunk(每个4KB)并允许交错写。(例如进程A有两个 chunk,A1,A2。进程B也有两个chunk,B1,B2。A和B同时写,则顺序可能为A1,B1,A2,B2) Windows的管道允许在同一个管道上有多个reader/writer对,他们之间读写的数据没有交叉。

5.5 套接字(Sockets)

  • 套接字的特点:
    • 它是双向通信的
    • 它是进程间通信的,包括其他机器上的进程
  • 套接字有三个参数:
    • 通讯类型(communication style)
      • 连接(connection)类型:保证所有的包按发送的顺序到达接受方。(类似于电话)如果包丢失或者抵达顺序错误,会自动重发。
      • datagram类型:所有包单独发送,可能会出现丢失或者晚发早到的现象。(类似于邮寄)
    • 命名空间(namespace):描述套接字的地址是如何表示的,例如本地就是文件名,internet上就是ip地址。
    • 协议(protocol):通讯协议,常用的有TCI/IP,AppleTalk等。
  • 相关的API(套接字也是通过file descriptor来表示的):
    • socket:创建一个socket
    • closes:销毁一个socket
    • connect:在两个socket之间创建一个连接。这个API通常由客户端调用。
    • bind:给服务器的一个套接字绑定一个地址,服务器端调用。
    • listen:让一个套接字开始侦听,准备接受请求,服务器端调用。
    • accept:接受一个连接请求,并且为该连接创建一个新的套接字,服务器端调用。
  • 服务器端的生命流程:
    • 创建一个connection类型的socket
    • 给该socket绑定一个地址
    • 调用listen来enable该socket(listen可以指定最多有多少个请求在等待队列中,如果等待队列满了,又有新的请求到达的时候,则该请求被拒绝)
    • 对于收到的连接请求调用accept来接受
    • 关闭socket
  • 本地socket(local socket)
    • 如果是同一台电脑上的两个进程需要通信的话,可以使用本地socket。这种情况下socket的地址是文件路径。注意进程必须对该路径拥有可写权限,否则无法建立连接
    • 完成之后使用unlink来关闭一个socket
赞赏一下吧~