如来神掌第二式第一招----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编辑  收藏  举报

导航