如来神掌第二式第一招----Shell脚本基础
###############################################################################
#
Name : Mahavairocana
#
Author : Mahavairocana
#
QQ : 10353512
#
WeChat : shenlan-qianlan
#
Blog : http://www.cnblogs.com/Mahavairocana/
#
Description : You are welcome to reprint, or hyperlinks to indicate the
#
source of the article, as well as author
information.
###############################################################################
一、术语解释
1、变量:
变量类型:
本地变量: 只对当前shell进程有效的,对当前进程的子进程和其它shell进程无效。
定义:VAR_NAME=VALUE
变量引用:${VAR_NAME}
取消变量:unset VAR_NAME
相当于java中的私有变量(private),只能当前类使用,子类和其他类都无法使用。
环境变量: 自定义的环境变量对当前shell进程及其子shell进程有效,对其它的shell进程无效
定义:export VAR_NAME=VALUE
对所有shell进程都有效需要配置到配置文件中
vi /etc/profile
source /etc/profile
相当于java中的protected修饰符,对当前类,子孙类,以及同一个包下面可以共用。
局部变量: 在函数中调用,函数执行结束,变量就会消失
对shell脚本中某代码片段有效
定义:local VAR_NAME=VALUE
相当于java代码中某一个方法中定义的变量,只对这个方法有效。
变量位置: $1,$2,.....${10}....
test.sh 3 89
$0:脚本自身
$1:脚本的第一个参数
$2:脚本的第二个参数
相当于java中main函数中的args参数,可以获取外部参数。
特俗变量: $?:接收上一条命令的返回状态码
返回状态在0-255之间
$#:参数个数
$*:或者$@:所有的参数
$$:获取当前shell的进程号(PID)(可以实现脚本自杀)(或者使用exit命令直接退出也可以使用exit [num])
变量定义规范
命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
中间不能有空格,可以使用下划线(_)。
不能使用标点符号。
不能使用bash里的关键字(可用help命令查看保留关键字)。
2、函数:方便程序和管理和模块化并减少代码的重复
3、引号区别:
双引号(" "):在双引号中,除了$, '', `和\以外所有的字符都解释成字符本身。 root@gyb-ubuntu:~# echo "$PATH" /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games 单引号(' '):在单引号中所有的字符包括特殊字符($,'',`和\)都将解释成字符本身而成为普通字符。 root@gyb-ubuntu:~# echo '$PATH' $PATH 反引号(` `):在反引号中的字符串将解释成shell命令来执行。 root@gyb-ubuntu:~# echo `ls` 99.sh cloud_curr_design cloud_curr_design.tar.gz exefile for.sh gyb_virsh httpd-2.2.31 qemu_help readfile.sh switch.sh temp temp10.sh temp1.sh temp2.sh temp3.sh temp4.sh temp5.sh temp6.sh temp7.sh temp8.sh temp9.sh te.sh test9.sh ubuntu1204Server.img ubuntu1204Server.xml ubuntuGuest.xml ubuntu-server.img win7.img
文件测试:
文件状态测试 -b filename : 当filename 存在并且是块文件时返回真(返回0) -c filename : 当filename 存在并且是字符文件时返回真 -d pathname : 当pathname 存在并且是一个目录时返回真 -e pathname : 当由pathname 指定的文件或目录存在时返回真 -f filename : 当filename 存在并且是正规文件时返回真 -g pathname : 当由pathname 指定的文件或目录存在并且设置了SGID 位时返回真 -h filename : 当filename 存在并且是符号链接文件时返回真 (或 -L filename) -k pathname : 当由pathname 指定的文件或目录存在并且设置了"粘滞"位时返回真 -p filename : 当filename 存在并且是命名管道时返回真 -r pathname : 当由pathname 指定的文件或目录存在并且可读时返回真 -s filename : 当filename 存在并且文件大小大于0 时返回真 -S filename : 当filename 存在并且是socket 时返回真 -t fd : 当fd 是与终端设备相关联的文件描述符时返回真 -u pathname : 当由pathname 指定的文件或目录存在并且设置了SUID 位时返回真 -w pathname : 当由pathname 指定的文件或目录存在并且可写时返回真 -x pathname : 当由pathname 指定的文件或目录存在并且可执行时返回真 -O pathname : 当由pathname 存在并且被当前进程的有效用户id 的用户拥有时返回真(字母O 大写) -G pathname : 当由pathname 存在并且属于当前进程的有效用户id 的用户的用户组时返回真 file1 -nt file2 : file1 比file2 新时返回真 file1 -ot file2 : file1 比file2 旧时返回真 举例: if [ -b /dev/hda ] ;then echo "yes" ;else echo "no";fi // 将打印 yes test -c /dev/hda ; echo $? // 将打印 1 表示test 命令的返回值为1,/dev/hda 不是字符设备 [ -w /etc/passwd ]; echo $? // 查看对当前用户而言,passwd 文件是否可写 测试时逻辑操作符 -a 逻辑与,操作符两边均为真,结果为真,否则为假。 -o 逻辑或,操作符两边一边为真,结果为真,否则为假。 ! 逻辑否,条件为假,结果为真。 举例: [ -w result.txt -a -w score.txt ] ;echo $? // 测试两个文件是否均可写 常见字符串测试 -z string : 字符串string 为空串(长度为0)时返回真 -n string : 字符串string 为非空串时返回真 str1 = str2 : 字符串str1 和字符串str2 相等时返回真 str1 != str2 : 字符串str1 和字符串str2 不相等时返回真 str1 < str2 : 按字典顺序排序,字符串str1 在字符串str2 之前 str1 > str2 : 按字典顺序排序,字符串str1 在字符串str2 之后 举例: name="zqf"; [ $name = "zqf" ];echo $? // 打印 0 表示变量name 的值和字符串"zqf"相等 常见数值测试 int1 -eq int2 : 如果int1 等于int2,则返回真 int1 -ne int2 : 如果int1 不等于int2,则返回真 int1 -lt int2 : 如果int1 小于int2,则返回真 int1 -le int2 : 如果int1 小于等于int2,则返回真 int1 -gt int2 : 如果int1 大于int2,则返回真 int1 -ge int2 : 如果int1 大于等于int2,则返回真 举例: x=1 ; [ $x -eq 1 ] ; echo $? // 将打印 0 表示变量x 的值等于数字1 x=a ; [ $x -eq "1" ] // shell 打印错误信息 [: a: integer expression expected
二、基础语法
1、for
for i in {1..10} do echo $i done
2、if
if [ "$1" = 1 ];then
echo "number is 1"
elif [ "$1" = 2 ];then
echo "number is 2"
elif [ "$1" = 3 ];then
echo "number is 3"
else
echo "number not is 1 2 3"
fi
注意:$1 不加引号的时候会报错“line 4: [: =: unary operator expected” 报错的原因是:如果变量$1的值为空,那么就if语句就变成了if [ ="yes" ],这不是一个合法的条件。为了避免出现这种情况,我们必须给变量加上引号if [ "$1"="yes" ],这样即使是空变量也提供了合法的测试条件,,if [ " "="yes" ]
3、while 也称为前测试循环语句,重复次数是利用一个条件来控制是否继续重复执行这个语句。为了避免死循环,必须保证循环体中包含循环出口条件即表达式存在退出状态为非0的情况。
#!/bin/bash sum=0 i=1 while(( i <= 10 )) do let "sum+=i" let "i += 2" done echo "sum=$sum" #!/bin/bash cat 1 | while read line do echo $line done
4、until: until命令和while命令类似,while能实现的脚本until同样也可以实现,但区别是until循环的退出状态是不为0,退出状态是为0(与while刚好相反),即whie循环在条件为真时继续执行循环而until则在条件为假时执行循环。
#!/bin/bash i=0 until [[ "$i" -gt 5 ]] do let "num=i*i" echo "$i * $i = $num" let "i++" done
5、select :select结构从技术角度看不能算是循环结构,只是相似而已,它是bash的扩展结构用于交互式菜单显示,功能类似于case结构比case的交互性要好。
#!/bin/bash echo "What is your favourite num? " select num in "1" "2" "3" do break done echo "You have selected $num"
6、case
case $1 in 1 | 2) # arg in pattern or sample echo "number is 1 or 2" ;; 3) # arg in pattern1 echo "number is 3" ;; *) #default echo "number not is 1 2 3";; esac
7、case+select
#!/bin/sh select ch in "start" "stop" "restart" "exit" do case $ch in "start") echo "start" ;; "stop") echo "stop" ;; "restart") echo "restart" ;; "exit") echo "exit" break; ;; *) echo "Ignorant" ;; esac done;
8、函数
#! /bin/bash function list () { echo "Mahavairocana " } function list1 () { echo "Mahavairocana1 " } list list1
9、break: break命令是在处理过程中跳出循环的一种简单方法,可以使用break命令退出任何类型的循环,包括while循环和until循环
1:跳出单循环
2:跳出内循环
使用多循环时break命令自动终止你所在的最里面的循环,注意,当内循环被break命令终止,外循环会继续执行。
3:跳出外循环
可能有时处于内循环但需要停止外循环,break命令后面就需要指定一个参数了
break n
n表明要跳出的循环级别,默认情况下,n是1,代表跳出当前循环,如果将n设置为2,break命令将停止外循环的下一级循环。
#!/bin/bash while [ 1 -eq 1 ] do for ((i=0;i<10;i++)) do if [ $i -eq 2 ] then break ; fi echo $i done echo 'yes' sleep done
10 、continue:continue命令是一种提前停止循环内命令,而不完全终止循环的方法,这就需要在循环内设置shell不执行命令的条件
#!/bin/bash for ((i=0;i<10;i++)) do if [ $i -eq 2 ] then continue fi echo $i done
11、shift:位置参数可以用shift命令左移,shift n表示把第n+1个参数移到第1个参数, 即命令结束后$1的值等于$n+1的值, 而命令执行前的前面n个参数不能被再次引用, 后面$#-n+1到$#的参数被unset, 参数的个数减少为$#-n个.n的值不能为负数, 若n为0或大于参数个数$#则参数不变, 若n没有给定则默认为1. 当n小于0或者大于参数个数$#时shift命令的返回值大于0, 否则返回0.
#!/bin/bash while [ "$#" -gt 0 ] do echo $* shift done 运行结果: [root@nn shell]# ./shift_fun1.sh 9 8 7 6 5 4 3 2 1 9 8 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 7 6 5 4 3 2 1 6 5 4 3 2 1 5 4 3 2 1 4 3 2 1 3 2 1 2 1 1
12、getopts 的设计目标是在循环中运行,每次执行循环,getopts 就检查下一个命令行参数,并判断它是否合法。即检查参数是否以 - 开头,后面跟一个包含在 options 中的字母。如果是,就把匹配的选项字母存在指定的变量 variable 中,并返回退出状态0;如果 - 后面的字母没有包含在 options 中,就在 variable 中存入一个 ?,并返回退出状态0;如果命令行中已经没有参数,或者下一个参数不以 - 开头,就返回不为0的退出状态。
#!/bin/bash while getopts "a:b:cdef" opt; do #“a:b:cdef”第一个冒号代表的含义是:第一个冒号表示忽略错误,即当出现没有的选项是会忽略;字符后面的冒号 表示该选项必须有自己的参数, case $opt in a) echo "this is -a the arg is ! $OPTARG" ;; b) echo "this is -b the arg is ! $OPTARG" ;; c) echo "this is -c the arg is ! $OPTARG" ;; \?) echo "Invalid option: -$OPTARG" ;; esac done
13、sleep指定延迟时间
sleep : 默认以秒为单位。 usleep : 默认以微秒为单位。 1s = 1000ms = 1000000us sleep 不但可以用秒为单位,还可以指定延迟的单位,例如: sleep 1s 表示延迟一秒 sleep 1m 表示延迟一分钟 sleep 1h 表示延迟一小时 sleep 1d 表示延迟一天
三、小技巧
1、注释
1.多行注释: 1. 首先按esc进入命令行模式下,按下Ctrl + v,进入列(也叫区块)模式; 2. 在行首使用上下键选择需要注释的多行; 3. 按下键盘(大写)“I”键,进入插入模式; 4. 然后输入注释符(“//”、“#”等); 5. 最后按下“Esc”键。 注:在按下esc键后,会稍等一会才会出现注释,不要着急~~时间很短的 2.删除多行注释: 1. 首先按esc进入命令行模式下,按下Ctrl + v, 进入列模式; 2. 选定要取消注释的多行; 3. 按下“x”或者“d”. 注意:如果是“//”注释,那需要执行两次该操作,如果是“#”注释,一次即可 3.多行删除 1.首先在命令模式下,输入“:set nu”显示行号; 2.通过行号确定你要删除的行; 3.命令输入“:32,65d”,回车键,32-65行就被删除了,很快捷吧 如果无意中删除错了,可以使用‘u’键恢复(命令模式下)
2、定义自己的专用vim
3、将脚本放置到后台运行
在脚本后面加一个& test.sh & 这样的话虽然可以在后台运行,但是当前会话窗口关闭之后这个脚本也会停止运行 nohup命令 不挂断的运行命令,忽略所有挂断(SIGHUP)信号 使用nohup test.sh & nohup命令将进程和终端分开,所以关闭当前会话窗口不会影响这个进程的执行。 nohup会在当前执行的目录生成一个nohup.out日志文件
4、let命令用法
#!/bin/bash let a=5*4 b=9/3 echo $a $b
5、shell脚本并发
实例1: #!/bin/bash start=`date +%s` #定义脚本运行的开始时间 for ((i=1;i<=10;i++)) do { sleep 1 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替) echo 'success'$i; }& #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行 #一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了 #循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理 #这些任务 done wait #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再 #往下执行。 #在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统 #shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它 #前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续 #往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令 end=`date +%s` #定义脚本运行的结束时间 echo "TIME:`expr $end - $start`" 实例2: #!/bin/bash start_time=`date +%s` #定义脚本运行的开始时间 [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #创建有名管道 exec 3<>/tmp/fd1 #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性 rm -rf /tmp/fd1 #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了 for ((i=1;i<=1;i++)) #i<=1 控制并发数量 do echo >&3 #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌" done for ((i=1;i<=100;i++)) do read -u3 #代表从管道中读取一个令牌 { sleep 2 #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替) echo 'success'$i echo >&3 #代表我这一次命令执行到最后,把令牌放回管道 }& done wait stop_time=`date +%s` #定义脚本运行的结束时间 echo "TIME:`expr $stop_time - $start_time`" exec 3<&- #关闭文件描述符的读 exec 3>&- #关闭文件描述符的写 实例3: #!/bin/bash count=0 Times=`date +%F-%H-%M` rm -rf ./log/* PASSWDPath=./log/passwd.txt >./log/run.log if [ ! -d "./log/host" ]; then mkdir "./log/host" fi for i in `cat config/hosts.txt | grep -v "^#"` ;do if [[ "$count" -ge 20 ]] ;then #控制通知并发执行20次 count=0 sleep 10 fi let count+=1 { export server=`echo $i | awk -F "|" '{print $1}'` export port=`echo $i | awk -F "|" '{print $2}'` export user=`echo $i | awk -F "|" '{print $3}'` export passwd=`echo $i | awk -F "|" '{print $4}'` export rootpasswd=`echo $i | awk -F "|" '{print $5}'` export su=`echo $i | awk -F "|" '{print $6}'` export cmdfile="config/commands.txt" nc -v -w 4 -z $server $port if [ $? -eq 0 ] then ./expect-run.bmc $server $port $user $passwd $rootpasswd $cmdfile if [ $? -ne 0 ] ;then echo "$server-----error" >>$PASSWDPath else echo "$server-----ok">>$PASSWDPath fi else echo "$server-----down">>$PASSWDPath continue fi }& done wait
posted on 2018-01-08 23:30 Mahavairocana 阅读(421) 评论(0) 编辑 收藏 举报