shell 学习(1)

作者: UncleYee | 来源:发表于2014-05-23 09:42 被阅读176次

    花了大概一天半的时间比较全面的接触了一下shell脚本,对于这种语言我对自己的要求很简单,不求深度精通,能写工具类脚本,能阅读别人的脚本,能在在相应场合无障碍沟通就好。这里整理一下这个阶段的知识结构,准备再花一到两天时间接触一下一些高级玩法,到时候Shell就算完篇了。


    Shell 也是一门编程语言,学习编程语言我觉得需要关注这么一些点:

    • 数据类型

    • 语法/ 结构控制

    • 内置函数和变量

    只要相应的关注整理一下,入门还是很快的,我相信读完这篇文章以后,你基本上可以认为自己能独立读写一些简单的Shell脚本,可以有底气的说自己是一个初级的Sheller了。

    变量声明

    首先要了解一下Shell的变量声明,引用等语法规则。有两个command(declare,local)与变量的声明有关:

    declare  a=1  #声明一个全局变量
    local  b="hello~~"   #声明一个局部变量,一般在函数体中使用
    c=1     #实际情况是,这两个command可用可不用,不使用同样可以声明变量
    

    变量的声明特别要注意赋值等号(=)两边不能有空格。

    a = 2  #这个声明就会失败!!!
    

    变量声明了以后还有一个变量读取,Shell读取变量的语法如下:

    ${var} 或者 $var #作为一个资深Coder,当然知道前者更专业,可以避免在字符串中解析出错 
    

    数据类型

    shell 不像那些严谨的编程语言有完整的数据体系,我现在接触到的就这么几种:

    1. 字符串
    2. 数值(整形/浮点)
    3. 数组(简单数组/关联数组)

    字符串

    这个是最常用也是最简单的数据类型了,首先来感受一下

    echo "hello,shell~~" #输出字符串到控制台
    echo "hello, shell " > a.txt  #将字符串输入到文件a.txt中,输入之前会清空文件
    echo "hello,shell" >> a.txt #将字符串输入到文件a.txt中,输入之前不清空文件
    

    和其他的编程语言一样,字符串也有相应的一些操作,整理下来基本上就是这么一些:

    取字符串长度 :  ${#string}
    
    子串截取:  
    ${string:position:length}  从起始位置截取指定长度
    ${string:position}  从起始位置截取到末尾
    
    子串替换:
    ${string/substring/replacement} 替换第一个匹配的子串
    ${string//substring/replacement} 替换所有子串
    
    子串删除
    ${string%substring} 从变量$string的结尾, 删除最短匹配$substring的子串
    ${string%%substring} 从变量$string的结尾, 删除最长匹配$substring的子串
    .....
    

    数值

    数值分为整形和浮点型,他们的操作完全不一样,所以得分开说。
    数值类型的话关注点就在运算了,Shell对于整形的运算有三种方式,如下:
    ** let **

    a=1;
    b=2;
    let c=a+b;
    echo $c;
    

    ** [ ] 中括号**

    a=1;
    b=2;
    c=$[a+b];
    echo $c;
    

    ** (( )) 双小括号 **

    a=1;
    b=2;
    c=$(( $a+$b));
    echo $c;
    

    ***这里总结一下, 这三种方式还是有一些细微的差别的,let 方式最为简单。[ ]需要注意在外面要用$对返回值进行引用。 (( ))最为复杂容易出错,首先在括号内变量的必须要加$引用,其次左括号必须和变量间有空格。 ***

    bash 不支持浮点运算,如果需要进行浮点运算,需要借助bc,awk 处理。

    c=$(echo "5.01-4*2.0"|bc)
    c=$(awk 'BEGIN{print 7.01*5-4.01 }')
    

    bc,awk我暂时也还没有接触到,先放到这吧。todo

    数组

    数组的定义(初始化)比较简单:

    arr=(0 1 2 3 4 5 6 7 8 9);
    或者
    arr[0]=0;
    arr[1]=1;
    arr[2]=2;
    

    下面就是关于数组的一些操作了

    #循环打印数组元素
    for  i  in ${arr[@]} ; do
         echo $arr[i]
    done;
    #这里牵涉到了两个知识点:
    #(1)${arr[@]}  表示数组中的所有元素
    #(1)$arr[i]  获取数组元素的形式,其中i为索引
    
    #顺便说了for结构,对于for结构也可以这样:
    for i in 1 2 3 4 5 ;do
        echo $i
    done;
    
    # 还可以这样:
    for((i=0;i<=10;i++));do
         echo $i;
    done;
    
    #回到数组,获取数组的长度:
    ${#arr[@]}
    

    数组的基本操作就是这些了,这里所说的数组都是简单数组,它的索引只能是数值,关联数组的不同之处在于索引可以是其他类型如字符串。需要bash4.0以上才支持

    declare -A phone
    phone=([jim]=135 [tom]=136 [lucy]=158)
    

    数据类型基本上就是这些了,接下来看看一些常见的结构控制及语法。我们都知道程序的结构有三种:

    • 顺序结构

    • 选择结构

    • 循环结构

    就从这个结构来入手语法吧。

    语法

    顺序结构就没什么好说的了,重点是后面两种。

    选择结构

    选择结构说白了就是会存在逻辑判断,Shell里面也提供两种大结构来支持判断。

    # ------数字比较--------
    if test 100 -gt 50 ;
    then
          ehco " 100 大于50";
    else
         ehco "100 小于50"
    fi
    # ------字符串比较--------
    if test a \> b ;
    then
          ehco " a 大于b";
    else
         ehco "a 小于b"
    fi
    
    #------case 子句----------
    case "$Keypress" in  
      [[:lower:]]   ) echo "Lowercase letter";;  
      [[:upper:]]   ) echo "Uppercase letter";;  
      [0-9]         ) echo "Digit";;  
      *             ) echo "Punctuation, whitespace, or other";;  
    esac  
    
    

    上面只是简单的示例,现在来说说具体的语法。对于if结构,后面的比较操作有三种写法:

    # 1. test
    if test 100 -gt 50 ;
    then
          ehco " 100 大于50";
    else
         ehco "100 小于50"
    fi
    
    # 2. [ ]中括号
    if [ a \> a  ]
        then
        echo  "a>a"
    else
        echo "a<a"
    fi;
    
    # 3. [[ ]]  双中括号
    if [[ a == a && 2 > 3 ]] 
        then
            echo " all true";
        else
            echo " not all true";
    fi;
    

    总结一下,这三种写法中 test, [ ] 最为复杂,对于<,>等操作符必须要要转义\>, \<,而且不支持多条件判断,所以一般情况下就用双中括号[[ ]]来得简单。

    shell中提供的支持判断的操作符比较别扭,分别正对字符串,数值比较各有一套:
    字符串比较

    > , >=  : 大于,大于等于
    <, <=  : 小于,小于等于
    ==  :  等于
    != : 不等于
    -z  : 字符串为空
    -n  : 字符串非空
    

    数值比较

    -eq   等于
    -ne   不等于
    -lt     小于
    -le    小于等于
    -gt    大于
    -ge    大于或等于
    

    文件比较

    -e  文件是否存在      
    -f  是一个标准文件          
    -d  是一个目录
    

    个人感觉这个定义好别扭,字符串和数值完全没有必要弄两套比较体系,而且数值比较很直观的东西变得很复杂。接下来,最蛋痛的事情到了,上面说到if后面的写法有三种:test , [] , [[ ]]。其中test, [] 是严格按照比较操作符的定义来的,如果是字符串比较,只能用符号比较>,<等,如果是数值比较,只能用文字比较符 -gt ,-lt等,但是对于[[ ]] 无论是文字比较还是数值比较,两种比较符都可以任意使用。所以一般建议只用[[]],你以为这就完了?不,还有一种情况,三目运算:

    echo $(( 3 > 2?100:999));
    echo $(( a > b?101:991));
    

    三目运算表达式只能用符号比较符 > ,< 等!!!
    在这一段的学习过程中我是感觉非常混乱,绕了好久才把这些点给理清楚,现在统一总结一下:

    1. test , [] , [[ ]]三种结构中,test ,[] 需要对<,>等转义。而且两种比较符各司其职,不能跨界操作。而 [[]]不需要转义,比较符号也可以跨界操作,非常方便。所以一般就只用[[]].
    2. 三目运算表达式中的比较只能用符号比较符 >, <.

    Case子句的写法就没这么纠结了,当然在我看来,语法还是很蛋痛的。来记录一下它的语法规则:

    case "$Keypress" in  
      [[:lower:]]   ) echo "Lowercase letter";;  
      [[:upper:]]   ) echo "Uppercase letter";;  
      [0-9]         ) echo "Digit";;  
      *             ) echo "Punctuation, whitespace, or other";;  
    esac
    
    1. 条件判断分支必须用右括号 )结束。
    2. 条件子句必须用两个分号;;结束。

    循环结构

    Shell的循环结构比较简单,就是for, while。都来看看吧

    #1. for 指定数组
    for loop in 1 2 3 4 5 6
    do
      echo $loop
    done
    
    #2. for 变量自增长
    for i in `seq 100`
    do
        echo $i;
    done
    
    
    #3. for 循环已定义数组
    arr=(1 2 3  4 5 6 7 8 9 0)
    for i in ${arr[@]} ;do
        echo $i;
    done;
    
    #4.  指定范围
    for i in {1..10} ;do
        echo $i;
    done;
    
    #45. while (( ))
    COUNTER=0
    while(($COUNTER<=5))
    do
            echo $COUNTER
            let "COUNTER++";
    done
    
    #5. while [[ ]]
    while [[ $num != 4 ]]
    do
        echo $num;
        let "num++";
    done;
    

    循环结构远没有选择结构那么复杂,基本上看看这些示例就OK了。

    函数

    语法部分还有一个比较重要的方面就是函数了。函数的定义和其他语言一样:

    function myfun(){
        echo "共传入了:$# 个参数,分别是:" 
        for i in $*; do 
            echo $i
        done
    }
    # 1.   $# 参数个数
    # 2.  $*  所有参数的数值
    #3.  function 可写可不写
    

    Shell似乎不支持带参函数,所以如何在函数内获取参数呢?

    function myfun(){
         echo ${1};
             echo ${2};
             echo ${3};
    }
    #按照索引来获取参数就好了。注意,索引从1开始,${0}获取的是脚本名。
    

    函数的执行也和其它程序不一样,来看一个例子:

    sum=0
    function myadd(){
        for i in $*; do 
            let sum+=$i
        done
    }
    myadd 1 2 3 4  # 执行函数
    echo $sum;
    

    如果函数有返回值,则可以直接赋值:

    function myadd(){
        for i in $*; do 
            let sum+=$i
        done
         return $sum;
    }
    $(myadd 1 2 3 4)  # 执行函数
    num=$?  #获取返回值
    

    到此为止,基本上语法就都走了一遍,接下来要了解一些shell内置的一些变量,命令等,这个是真正能玩转起来的重要因素,看看有哪些东西是需要关注的吧。

    内置变量及命令

    首先来了解一下一些特殊的内置变量

    $0  脚 本名字
    $1- $9  位置参数 #1 - #9
    ${10}   位置参数 #10
    $#     位置参数的个数
    "$*"    所有的位置参数(作为单个字符串) *
    "$@"    所有的位置参数(每个都作为独立的字符串)
    ${#*}   传递到脚本中的命令行参数的个数
    ${#@}   传递到脚本中的命令行参数的个数
    $?  返回值
    $$  脚本的进程ID(PID)
    $-  传递到脚本中的标志(使用set)
    $_  之前命令的最后一个参数
    $!  运行在后台的最后一个作业的进程ID(PID)
    

    对于初次接触者,可能这么几个要重点关注下

    # 1. 位置参数,在上面的示例中已经用过,函数内部使用传递参数的写法
    $1 - $9 位置参数 #1 - #9
    ${10}   位置参数 #10
    
    # 2.   参数的数量 
    $#     位置参数的个数
    
    #3.  参数的集合,在一些不关心具体参数值的函数中特别有用,例如 add
    $@  所有的位置参数(每个都作为独立的字符串)
    
    #4.  函数的返回值, 不能直接将函数执行后复制给变量,必须通过这个返回值变量
    $?  返回值
    
    

    命令的话太多了,这里就不纠结这个话题,通篇下来,shell的初步接触应该比较完备了,后续再研究下高端玩法。

    相关文章

      网友评论

        本文标题:shell 学习(1)

        本文链接:https://www.haomeiwen.com/subject/xdldtttx.html