您的位置:首页 > 运维架构 > Shell

《Linux Shell脚本攻略》读书笔记第一章 基本知识

2013-12-12 11:02 330 查看
1、终端打印echo[root@stone ~]# echo hello world !hello world ![root@stone ~]# echo 'hello world !'hello world ![root@stone ~]# echo "hello world !"echo "hello world "hello world [root@stone ~]# echo -n hello world !hello world ![root@stone ~]# #echo 默认最后有一个换行符,加上‘-n’选项后,去掉换行符[root@stone ~]# echo -e "hello\nworld" helloworld[root@stone ~]# echo -e 'hello\nworld'helloworld[root@stone ~]# echo -e hello\nworldhellonworld#‘-e’选项可对以下字符进行特殊处理: \a 发出警告声;
\b 删除前一个字符;
\c 最后不加上换行符号;
\f 换行但光标仍旧停留在原来的位置;
\n 换行且光标移至行首;
\r 光标移至行首,但不换行;
\t 插入tab;
\v 与\f相同;
\\ 插入\字符;
\nnn 插入nnn(八进制)所代表的ASCII字符;#使用echo时参数最好使用引号。
printf[root@stone bin]# cat printf.sh#!/bin/bash#printf.shprintf "%-5s %-10s %-4s\n" No. Name Markprintf "%-5s %-10s %-4.2f\n" 1 Sarath 80.3456printf "%-5s %-10s %-4.2f\n" 2 James 80.3456printf "%-5s %-10s %-4.2f\n" 3 Jeff 80.3456[root@stone bin]# printf.shNo. Name Mark1 Sarath 80.352 James 80.353 Jeff 80.35
2、变量与环境变量查看进程的环境变量[root@stone bin]# pgrep bash7297[root@stone bin]# cat /proc/7297/environ USER=rootLOGNAME=rootHOME=/rootPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/binMAIL=/var/mail/rootSHELL=/bin/bashSSH_CLIENT=172.16.3.151 3686 22SSH_CONNECTION=172.16.3.151 3686 172.16.3.54 22SSH_TTY=/dev/pts/1TERM=linux[root@stone bin]# cat /proc/7297/environ | tr '\0' '\n'USER=rootLOGNAME=rootHOME=/rootPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/binMAIL=/var/mail/rootSHELL=/bin/bashSSH_CLIENT=172.16.3.151 3686 22SSH_CONNECTION=172.16.3.151 3686 172.16.3.54 22SSH_TTY=/dev/pts/1TERM=linux[root@stone ~]# echo -e "\0" | od -a0000000nul nl0000002

变量赋值:var=value变量引用:$var;${var}单引号内的变量不会被解释,双引号内的变量才会被解释设置环境变量:export var[root@stone ~]# echo $PATH/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin[root@stone ~]# PATH="$PATH:/home/stone/bin"[root@stone ~]# export PATH[root@stone ~]# echo $PATH /usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/home/stone/bin
获取字符串长度:${#str}[root@stone ~]# str1=123456789[root@stone ~]# echo ${#str1}9
识别当前的shell版本[root@stone ~]# echo $SHELL/bin/bash[root@stone ~]# echo $0-bash
检查用户是否为超级用户[root@stone ~]# echo $UID0
3、算术运算整数运算:let;(());[][root@stone ~]# num1=4;num2=5[root@stone ~]# let result=num1+num2#使用let时,变量前无需使用$[root@stone ~]# echo $result9[root@stone ~]# let num1++;let num2--[root@stone ~]# echo $num1 $num25 4[root@stone ~]# let num1+=5;let num2+=6[root@stone ~]# echo $num1 $num210 10[root@stone ~]# result=$[ num1 + num2 ][root@stone ~]# echo $result20[root@stone ~]# result=$[ $num1 + $num2 ][root@stone ~]# echo $result 20[root@stone ~]# result=$(( num1 + num2 ))[root@stone ~]# echo $result 20[root@stone ~]# result=$(( $num1 + $num2 )) [root@stone ~]# echo $result 20
浮点运算:bc[root@stone ~]# echo "2*3.14" | bc6.28[root@stone ~]# echo "scale=2;2/3" | bc .66#scale设定小数点后的位数[root@stone ~]# num=100;echo "obase=2;$num" | bc1100100#将十进制转化为二进制[root@stone ~]# num=1100100;echo "obase=10;ibase=2;$num" | bc 100#将二进制转化为十进制[root@stone ~]# echo "sqrt(100)" | bc10#开方[root@stone ~]# echo "10^2" | bc 100#指数
4、文件描述符与重定向文件描述符0——stdin(标准输入)文件描述符1——stdout(标准输出)文件描述符2——stderr(标准错误)tee:从标准输入中读取数据然后标准输出并保存到文件中,用在既想把输出保存到文件中,又想在屏幕上看到输出内容。格式:tee只输出到标准输出,因为没有指定文件嘛。格式:tee file输出到标准输出的同时,保存到文件file中。如果文件不存在,则创建;如果已经存在,则覆盖之。(If a file being written to does not already exist, it is created. If a file being written to already exists, the data it previously
contained is overwritten unless the `-a' option is used.)格式:tee -a file输出到标准输出的同时,追加到文件file中。如果文件不存在,则创建;如果已经存在,就在末尾追加内容,而不是覆盖。格式:tee -输出到标准输出两次。(A FILE of `-' causes `tee' to send another copy of input to standard output, but this is typically not that useful as the copies are interleaved.)格式:tee file1 file2 -输出到标准输出两次,同时保存到file1和file2中。

tee命令与重定向的对比

[root@stone ~]# cat num12[root@stone ~]# cat num > num1[root@stone ~]# cat num | tee num212[root@stone ~]# cat num112[root@stone ~]# cat num212[root@stone ~]# cat num >> num1[root@stone ~]# cat num | tee -a num212[root@stone ~]# cat num11212[root@stone ~]# cat num21212

使用tee命令重复输出字符串

[root@stone ~]# echo 1234 | tee1234[root@stone ~]# echo 1234 | tee -12341234[root@stone ~]# echo 1234 | tee - -123412341234[root@stone ~]# echo 1234 | tee - - -1234123412341234[root@stone ~]# echo -n 1234 | tee - - -1234123412341234[root@stone ~]#

使用tee命令把标准错误输出也保存到文件

[root@stone ~]# ls aaals: aaa: No such file or directory[root@stone ~]# ls aaa | tee -ls: aaa: No such file or directory[root@stone ~]# ls aaa | tee ls.txtls: aaa: No such file or directory[root@stone ~]# cat ls.txt#默认仅保存标准输入[root@stone ~]# ls aaa 2>&1 | tee ls.txtls: aaa: No such file or directory[root@stone ~]# cat ls.txtls: aaa: No such file or directory#以上例子参考:http://codingstandards.iteye.com/blog/833695
5、数组#定义数组[root@stone ~]# array_var=(1 2 3 4)#打印特定数组元素[root@stone ~]# echo ${array_var[0]}1[root@stone ~]# index=3[root@stone ~]# echo ${array_var[$index]}4#打印所有数组元素[root@stone ~]# echo ${array_var[*]} 1 2 3 4[root@stone ~]# echo ${array_var[@]}1 2 3 4#打印数组长度(数组中元素个数)[root@stone ~]# echo ${#array_var[*]}4[root@stone ~]# echo ${#array_var[@]}4#显示目前有值的数组序号echo ${!FILE[@]}0 1 2
6、日期、时间与延时日期时间格式字符串列表
日期时间格式字符串
%y(例如:13) %Y(例如:2013)
%b(例如:Nov)%B(例如:November) %m(例如:10)
%d(例如:31)
%H(例如:14)
%M(例如:30)
%S(例如:10)
mm/dd/yy%D(例如:04/28/13)
星期%a(例如:Sat) %A(例如:saturday)
UNIX时间(单位:秒)%s
[root@stone ~]# date +%s1367831238[root@stone ~]# date "+%d %B %Y"06 May 2013
延时:sleep[root@stone ~]# sleep 10[root@stone ~]# cat bin/sleep.sh #!/bin/bash#filename:sleep.sh
echo -n count:tput sc #存储终端光标位置count=0
while truedoif [ $count -lt 40 ]; thenlet count++sleep 1tput rc #恢复光标位置tput ed #删除当前光标位置到行尾的所有内容echo -n $countelse exit 0fidone
7、脚本调试http://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/index.htmlhttp://hi.baidu.com/mecss/item/3d1785d387ace4e2b2f77703shell的调试选项如下:-n 只读取shell脚本,但不实际执行
-x 进入跟踪方式,显示所执行的每一条命令
-c "string" 从strings中读取命令

shell可用于调试的内置环境变量:$LINENO
代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__
$FUNCNAME
函数的名字,类似于C语言中的内置宏__func__,但宏__func__只能代表当前所在的函数名,而$FUNCNAME的功能更强大,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,故变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字,余者可以依此类推。
$PS4
主提示符变量$PS1和第二级提示符变量$PS2比较常见,但很少有人注意到第四级提示符变量$PS4的作用。我们知道使用“-x”执行选项将会显示shell脚本中每一条实际执行过的命令,而$PS4的值将被显示在“-x”选项输出的每一条命令的前面。在Bash Shell中,缺省的$PS4的值是"+"号。(现在知道为什么使用"-x"选项时,输出的命令前面有一个"+"号了吧?)。利用$PS4这一特性,通过使用一些内置变量来重定义$PS4的值,我们就可以增强"-x"选项的输出信息。例如先执行export PS4='+{$LINENO:${FUNCNAME[0]}} ', 然后再使用“-x”选项来执行脚本,就能在每一条实际执行的命令前面显示其行号以及所属的函数名。
#首先使用-n选项检查语法错误
[root@stone ~]# sh -n bin/sleep.sh
#使用-x选项追踪脚本执行
[root@stone ~]# sh -x bin/sleep.sh
+ echo -n count:
count:+ tput sc
+ tput ed
+ echo -n 40
40+ true
+ '[' 40 -lt 40 ']'
+ exit 0
#打印出程序的行号[root@stone ~]# cat -n bin/sleep.sh 1 #!/bin/bash 2 #filename:sleep.sh 3 4 echo -n count: 5 tput sc 6 count=0 7 8 while true 9 do 10 if [ $count -lt 40 ]; then 11 let count++ 12 sleep 1 13 tput rc 14 tput ed 15 echo -n $count 16 else 17 exit 0 18 fi 19 done#重新定义PS4变量[root@stone ~]# export PS4='+{$LINENO}'[root@stone ~]# sh -x bin/sleep.sh+{4}echo -n count:count:+{5}tput sc+{14}tput ed+{15}echo -n 4040+{8}true+{10}'[' 40 -lt 40 ']'+{17}exit 0#上面调试的结果增加了脚本的行号调试shell脚本的过程:
首先使用“-n”选项检查语法错误,然后使用“-x”选项跟踪脚本的执行,使用“-x”选项之前,别忘了先定制PS4变量的值来增强“-x”选项的输出信息,至少应该令其输出行号信息(先执行export PS4='+[$LINENO]',更一劳永逸的办法是将这条语句加到您用户主目录的.bash_profile文件中去),这将使你的调试之旅更轻松。也可以利用trap,调试钩子等手段输出关键调试信息,快速缩小排查错误的范围,并在脚本中使用“set -x”及“set +x”对某些代码块进行重点跟踪。这样多种手段齐下,相信您已经可以比较轻松地抓出您的shell脚本中的臭虫了。如果您的脚本足够复杂,还需要更强的调试能力,可以使用shell调试器bashdb,这是一个类似于GDB的调试工具,可以完成对shell脚本的断点设置,单步执行,变量观察等许多功能,使用bashdb对阅读和理解复杂的shell脚本也会大有裨益。
8、函数和参数定义函数:function fname(){ statements;
}或者:fname(){ statements;
}
调用函数:fname;fname arg1 arg2;
函数参数:
fnamearg1arg2arg3
$0$1$2$3
$# :代表后接的参数『个数』,以上表为例这里显示为『 3 』;

$@ :代表『 "$1" "$2" "$3" 』之意,每个变量是独立的(用双引号括起来);

$* :代表『 "$1c$2c$3" 』,其中 c 为分隔字节,默认为空白键, 所以本例中代表『 "$1 $2 $3" 』之意。(不常用)

导出函数:export -f fname函数返回值:函数返回值保存在变量?,命令成功退出,返回0,否则返回非0[root@stone ~]# echo $?0
9、引用 命令的输出方式一:子shell方式[root@stone ~]# cmd_output=$(ls | cat -n | head -5)[root@stone ~]# echo $cmd_output1 acltest1 2 anaconda-ks.cfg 3 a.out 4 awk.txt 5 awstats-7.1.1方式二:反引用方式[root@stone ~]# cmd_output=`ls | cat -n | head -3`[root@stone ~]# echo $cmd_output 1 acltest1 2 anaconda-ks.cfg 3 a.out上面两种方式均去掉了换行符(\n),参考下面的IFS,echo输出时加上双引号可保留空格和换行符。[root@stone ~]# cmd=`ls | cat -n | head -5`[root@stone ~]# echo "$cmd" 1 acltest1 2 anaconda-ks.cfg 3 a.out 4 awk.txt 5 awstats-7.1.1
10、读取键盘输入:read常用选项:
选项含义
-n限定输入字符长度
-t限定输入字符等待时间
-d定义输入结束符
-s不显示输入字符
-p增加提示信息
#限定输入字符长度为5[root@stone ~]# read -n 5 var12345[root@stone ~]# echo $var12345#限定输入等待时间为5s[root@stone ~]# read -t 5 var123456[root@stone ~]# #定义输入结束符为”:“[root@stone ~]# read -d ":" var123:[root@stone ~]# echo $var123#不显示输入字符,可用于密码输入[root@stone ~]# read -s var[root@stone ~]# echo $var123456#增加提示信息[root@stone ~]# read -p "enter input:" varenter input:12345[root@stone ~]# echo $var12345
11、内部域分隔符:IFS(Internal Field Seprator)http://blog.csdn.net/whuslei/article/details/7187639http://smilejay.com/2011/12/bash_ifs/#comment-51 Shell 的环境变量分为 set, env 两种,其中 set 变量可以通过 export 工具导入到 env 变量中。其中,set 是显示设置shell变量,仅在本 shell 中有效;env 是显示设置用户环境变量 ,仅在当前会话中有效。换句话说,set 变量里包含了 env 变量,但 set 变量不一定都是 env 变量。这两种变量不同之处在于变量的作用域不同。显然,env 变量的作用域要大些,它可以在 subshell 中使用。
而 IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。
[root@stone ~]# set | grep IFSIFS=$' \t\n'[root@stone ~]# echo -n "$IFS"#直接查看IFS变量默认值为空白[root@stone ~]# echo -n "$IFS" | od -a0000000 sp ht nl0000003#对比ascII码表可知IFS的默认值,sp为空格符,ht为横向制表符,nl为换行符IFS的作用是把变量中对应存在的IFS值转换成空格
如果IFS就是空格,那么类似于" [space][space]a[space]b[space][space]c "会合并重复的部分,且去头空格,去尾空格,那么最终输出会变成类似 a[space]b[space]c ,所以,如果IFS是默认值,那么处理的结果就很好算出来,直接合并、忽略多余空格即可!

[root@stone ~]# IFS=:[root@stone ~]# echo "$IFS":[root@stone ~]# set x y z#设置x y z三个参数[root@stone ~]# echo $*x y z#进行IFS替换[root@stone ~]# echo "$*"x:y:z#不进行IFS替换,故前面echo -n "$IFS"这条语句中的$IFS需要加上双引号,才能看到起内容。[root@stone ~]# echo $@x y z#$@ :代表『 "$1" "$2" "$3" 』之意,每个变量是独立的(用双引号括起来);[root@stone ~]# echo "$@"x y z
[root@stone ~]# set "x" "y z"#设置x 和”y z“两个参数[root@stone ~]# echo $*x y z[root@stone ~]# echo "$*"x:y z[root@stone ~]# echo $@x y z[root@stone ~]# echo "$@"x y z[root@stone ~]# echo $* | od -a0000000 x sp y sp z nl0000006#进行IFS替换[root@stone ~]# echo "$*" | od -a0000000 x : y sp z nl0000006#不进行IFS替换小结:$* 会根据 IFS 的不同来组合值,而 $@ 则会将值用" "(空格)来组合值!
[root@stone ~]# var=" : a:b:c::"[root@stone ~]# echo -n $var | od -a0000000 sp sp sp a sp b sp c sp0000011#进行IFS替换,最后两个”::“,由于后面没有其他字符,替换为一个sp[root@stone ~]# echo -n "$var" | od -a0000000 sp : sp a : b : c : :0000012#不进行IFS替换
[root@stone ~]# for x in $var ;do echo -n $x |od -a ;done0000000 sp00000010000000 sp a00000020000000 b00000010000000 c0000001#以”:“作为变量分割
[root@stone ~]# vim bin/ifstest
1 #!/bin/bash 2 output_args_ifs(){ 3 echo "=$*" 4 echo "="$* 5 for m in $* ;do 6 echo "[$m]" 7 done 8 } 9 10 IFS=':' 11 var='::a:b::c:::::' 12 output_args_ifs $var[root@stone ~]# ifstest =::a:b::c::::= a b c [][][a][][c][][][]
[root@stone ~]# read var aaa bbb[root@stone ~]# echo $varaaa bbb[root@stone ~]# echo "$var"aaa bbb[root@stone ~]# read aaa bbb [root@stone ~]# echo "$REPLY" aaa bbb
总结:IFS的作用是把变量中对应存在的IFS值转换成空格IFS值默认为空白,包括sp为空格符,ht为横向制表符,nl为换行符如果变量引用时加上双引号,则不替换$* 会根据 IFS 的不同来组合值,而 $@ 则会将值用" "(空格)来组合值在函数中,由于$@中不包含IFS,故建议使用$@不建议对IFS变量进行更改,特殊情况可使用$REPLY输出空格
[b]12、判断与条件语句
使用test判断
测试的标志代表意义
1. 关於某个档名的『文件类型』判断,如 test -e filename 表示存在否
-e该『档名』是否存在?(常用)
-f该『档名』是否存在且为文件(file)?(常用)
-d该『档名』是否存在且为目录(directory)?(常用)
-b该『档名』是否存在且为一个 block device 装置?
-c该『档名』是否存在且为一个 character device 装置?
-S该『档名』是否存在且为一个 Socket 文件?
-p该『档名』是否存在且为一个 FIFO (pipe) 文件?
-L该『档名』是否存在且为一个连结档?
2. 关於文件的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外)
-r侦测该档名是否存在且具有『可读』的权限?
-w侦测该档名是否存在且具有『可写』的权限?
-x侦测该档名是否存在且具有『可运行』的权限?
-u侦测该档名是否存在且具有『SUID』的属性?
-g侦测该档名是否存在且具有『SGID』的属性?
-k侦测该档名是否存在且具有『Sticky bit』的属性?
-s侦测该档名是否存在且为『非空白文件』?
3. 两个文件之间的比较,如: test file1 -nt file2
-nt(newer than)判断 file1 是否比 file2 新
-ot(older than)判断 file1 是否比 file2 旧
-ef判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是否均指向同一个 inode 哩!
4. 关於两个整数之间的判定,例如 test n1 -eq n2
-eq两数值相等 (equal)
-ne两数值不等 (not equal)
-gtn1 大於 n2 (greater than)
-ltn1 小於 n2 (less than)
-gen1 大於等於 n2 (greater than or equal)
-len1 小於等於 n2 (less than or equal)
5. 判定字串的数据
test -z string判定字串是否为 0 ?若 string 为空字串,则为 true
test -n string判定字串是否非为 0 ?若 string 为空字串,则为 false。
注: -n 亦可省略
test str1 = str2判定 str1 是否等於 str2 ,若相等,则回传 true
test str1 != str2判定 str1 是否不等於 str2 ,若相等,则回传 false
6. 多重条件判定,例如: test -r filename -a -x filename
-a(and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。
-o(or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。
!反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true
使用[]进行判断
条件判断:if条件if [ condition ];then
fi
if...else条件if [ condition ];then
else
fi
if...elseif...else条件if [ condition ];then
elseif [ condition ];then
else
fi
13、循环语句while循环while [condition]do
done
until循环until [condition]do
done
for循环for var in listdo
done
14、bash中的通配符与特殊符号在 bash 的操作环境中还有一个非常有用的功能,那就是通配符 (wildcard) ! 我们利用 bash 处理数据就更方便了!底下我们列出一些常用的通配符喔:
符号意义
*代表『 0 个到无穷多个』任意字符
?代表『一定有一个』任意字符
[ ]同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符, 可能是 a, b, c, d 这四个任何一个』
[ - ]若有减号在中括号内时,代表『在编码顺序内的所有字符』。例如 [0-9] 代表 0 到 9 之间的所有数字,因为数字的语系编码是连续的!
[^ ]若中括号内的第一个字符为指数符号 (^) ,那表示『反向选择』,例如 [^abc] 代表 一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。
接下来让我们利用通配符来玩些东西吧!首先,利用通配符配合 ls 找檔名看看:
[root@www ~]# LANG=C            <==由于与编码有关,先配置语系一下范例一:找出 /etc/ 底下以 cron 为开头的档名
[root@www ~]# ll -d /etc/cron*  <==加上 -d 是为了仅显示目录而已范例二:找出 /etc/ 底下文件名『刚好是五个字母』的文件名
[root@www ~]# ll -d /etc/?????  <==由于 ? 一定有一个,所以五个 ? 就对了范例三:找出 /etc/ 底下文件名含有数字的文件名
[root@www ~]# ll -d /etc/*[0-9]*<==记得中括号左右两边均需 *范例四:找出 /etc/ 底下,档名开头非为小写字母的文件名:
[root@www ~]# ll -d /etc/[^a-z]*<==注意中括号左边没有 *范例五:将范例四找到的文件复制到 /tmp 中
[root@www ~]# cp -a /etc/[^a-z]* /tmp
除了通配符之外,bash 环境中的特殊符号有哪些呢?底下我们先汇整一下:
符号内容
#批注符号:这个最常被使用在 script 当中,视为说明!在后的数据均不运行
\跳脱符号:将『特殊字符或通配符』还原成一般字符
|管线 (pipe):分隔两个管线命令的界定(后两节介绍);
;连续命令下达分隔符:连续性命令的界定 (注意!与管线命令并不相同)
~用户的家目录
$取用变量前导符:亦即是变量之前需要加的变量取代值
&工作控制 (job control):将命令变成背景下工作
!逻辑运算意义上的『非』 not 的意思!
/目录符号:路径分隔的符号
>, >>数据流重导向:输出导向,分别是『取代』与『累加』
<, <<数据流重导向:输入导向 (这两个留待下节介绍)
' '单引号,不具有变量置换的功能
" "具有变量置换的功能!
` `两个『 ` 』中间为可以先运行的命令,亦可使用 $( )
( )在中间为子 shell 的起始与结束
{ }在中间为命令区块的组合!
以上为 bash 环境中常见的特殊符号汇整!理论上,你的『档名』尽量不要使用到上述的字符啦!摘自《鸟哥私房菜》
本文出自 “石头记” 博客,请务必保留此出处http://stonebox.blog.51cto.com/5409313/1339672
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: