zoukankan      html  css  js  c++  java
  • shell 命令行参数(基本)

    命令行参数

    $0 表示程序名。
    $1 至 $9则是位置参数。
    $# 表示参数的个数。
    $* 将所有参数当做一个整体来引用
    $@ 把每个参数作为一个字符串返回,可以使用for循环来遍历
    $? 最近一个执行的命令的退出状态。0表示执行成功
    $_ 上一个命令的最后一个参数。使用快捷键 ESC+. 也是这个效果

    位置参数

    位置参数不止9个,更多的参数也是一样支持的。只是要使用${10}这样的形式引用。
    $1 和 ${1}的效果是一样的。
    不用花括号的话,$10 会被认为是 $1 和一个字符 0。

    带空格的参数值
    每个参数都是用空格分隔的。要在参数值中包含空格,必须要使用引号(单引号或双引号都可)。

    将文本字符串作为参数传递时,引号并非数据的一部分。它们只是表明数据的起止位置。

    获取脚本名

    $0 表示脚本名,但是不同的调用方法返回的结果也是不同的。下面的脚本就是简单的打印$0的值:

    $ cat filename.sh 
    #!/bin/bash
    echo $0
    $ ./ filename.sh
    -bash: ./: 是一个目录
    $ cat filename.sh 
    #!/bin/bash
    echo $0
    $ ./filename.sh
    ./filename.sh
    $ bash filename.sh 
    filename.sh
    $ bash /root/filename.sh 
    /root/filename.sh
    $ 

    使用 basename 命令
    如果要使用脚本名称来进行判断,可以先用命令 basename 把路径的信息给过滤掉。命令的效果如下:

    $ basename /var/log/messages 
    messages
    $ 

    所以上面的脚本可以修改成这样:

    $ cat filename.sh 
    #!/bin/bash
    echo $(basename $0)
    $ ./filename.sh
    filename.sh
    $ bash filename.sh 
    filename.sh
    $ bash /root/filename.sh 
    filename.sh
    $ 

    测试参数

    在脚本中使用参数要确保参数存在,否则运行时有可能会报错:

    $ cat add.sh 
    #!/bin/bash
    echo $1 + $2 = $[ $1 + $2 ]
    $ ./add.sh 1 2
    1 + 2 = 3
    $ ./add.sh 1
    ./add.sh:行2: 1 +  : 语法错误: 期待操作数 (错误符号是 "+  ")
    $ 

    如果只是当做字符串引用,也不会报错。没有传参的参数默认都是空:

    $ cat hello.sh 
    #!/bin/bash
    echo Hello $1 $2.
    $ ./hello.sh Tom Jerry
    Hello Tom Jerry.
    $ ./hello.sh Jerry
    Hello Jerry .
    $ ./hello.sh
    Hello .
    $ 

    判断参数是否存在
    在 shell 中利用 -n 来判定字符串非空,-z 则正好相反,空即是真。上面已经测试过了,未定义的参数默认是空:

    $ cat hello.sh 
    #!/bin/bash
    if [ -n "$1" ]
    then
        echo Hello $1.
    else
        echo Hello Nobody.
    fi
    $ ./hello.sh Tom
    Hello Tom.
    $ ./hello.sh
    Hello Nobody.
    $ 

    这里的判断的 $1 要加上双引号,否则会被认为是字符串。一个字符串当然非空,所以结果会永远为真。

    判断参数的个数
    上面的例子的脚本也可以通过判断参数数量是否大于0来实现:

    $ cat hello.sh 
    #!/bin/bash
    echo 参数数量: $#
    if [ $# -gt 0 ]
    then
        echo Hello $1.
    else
        echo Hello Nobody.
    fi
    $ ./hello.sh
    参数数量: 0
    Hello Nobody.
    $ ./hello.sh Tom
    参数数量: 1
    Hello Tom.
    $ ./hello.sh Tom Jerry
    参数数量: 2
    Hello Tom.
    $ 

    这里 -gt 比较的是前后两个数字(INT),所以$#是不加引号的。
    用这种方法也能判断参数是否存在。两种方法,效果一样,不同的书上都看到有人使用。

    这里是一样加法的例子,必须要传入2个参数:

    $ cat add.sh 
    #!/bin/bash
    if [ $# -eq 2 ]
    then
        echo $1 + $2 = $[ $1 + $2 ]
    else
        echo 需要参数: 2, 实际参数: $#.
    fi
    $ ./add.sh 1 2
    1 + 2 = 3
    $ ./add.sh 1 2 3
    需要参数: 2, 实际参数: 3.
    $ ./add.sh 1
    需要参数: 2, 实际参数: 1.
    $ 

    如果要表示不相等,就是 if [ $# -ne 2 ]

    获取最后一个参数
    这是一个使用 $# 的小技巧。使用${$#}似乎就是参数的最后一个变量了。
    但是其实不然,花括号里不能这样用$,这里要把里面的换成感叹号:

    $ cat hello.sh 
    #!/bin/bash
    if [ $# -gt 0 ]
    then
        echo Hello ${!#}.
    else
        echo Hello Nobody.
    fi
    $ ./hello.sh Tom Jerry
    Hello Jerry.
    $ 

    如果没有任何命令行参数,那么就是返回$0,也就是脚本名。

    上面感叹号的问题,效果是引用变量的值而不是变量自身。类似于指针的取值。把#号换成一个有名字的变量来说明比较直观:

    $ cat parameter.sh 
    #!/bin/bash
    paramater=key
    key=value
    echo "${paramater}"
    echo "${!paramater}"
    echo "${key}"
    $ ./parameter.sh 
    key
    value
    value
    $ 

    不加感叹号,就是直接去该变量的值。加上感叹号,就是取变量值所对应的变量名的那个变量的值。和php中$$有点类似

    获取所有参数

    $* 和 $@ 都是表示所有的字符串,但是在遍历的时候会有区别:

    $ cat all.sh 
    #!/bin/bash
    echo '$* 的效果:'
    count=1
    for i in "$*"
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    echo '$@ 的效果:'
    count=1
    for i in "$@"
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    $ ./all.sh Oliver Barry Kara Sara Kane
    $* 的效果:
    1: Oliver Barry Kara Sara Kane
    $@ 的效果:
    1: Oliver
    2: Barry
    3: Kara
    4: Sara
    5: Kane
    $ 

    $*就一个整体的值,无法遍历。要遍历每一个变量要使用$@。这里的双引号很重要。

    不加引号的话,就是把 $* 和 $@ 的内容(变量解析后就是多个词)传递给for循环遍历,这样两个参数的效果是一样的。和直接传不加引号的字符串的效果一样。
    加上引号,引号里的内容就是一个整体。如果是$*,这个整体里的所有内容还是一个词,不会拆。如果是$@,这个整体里会按空格拆分成多个词。
    下面是演示的效果:

    $ cat all2.sh 
    #!/bin/bash
    echo '$* 不加引号的效果:'
    count=1
    for i in $*
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    echo '$@ 不加引号的效果:'
    count=1
    for i in $@
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    echo '直接遍历不加引号的字符的效果:'
    count=1
    for i in Oliver Barry Kara Sara Kane
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    echo '加引号遍历的效果:'
    count=1
    for i in "Oliver Barry Kara Sara Kane"
    do
        echo $count: $i
        count=$[ $count + 1 ]
    done
    $ ./all2.sh Oliver Barry Kara Sara Kane
    $* 不加引号的效果:
    1: Oliver
    2: Barry
    3: Kara
    4: Sara
    5: Kane
    $@ 不加引号的效果:
    1: Oliver
    2: Barry
    3: Kara
    4: Sara
    5: Kane
    直接遍历不加引号的字符的效果:
    1: Oliver
    2: Barry
    3: Kara
    4: Sara
    5: Kane
    加引号遍历的效果:
    1: Oliver Barry Kara Sara Kane
    $ 

    强调:特殊参数$@一定要用在双引号内,效果是每个参数都扩展为分隔的单词。在使用for循环遍历的时候会体现出效果。

    移动变量 shift

    shift 命令能够用来操作命令行参数。默认情况下将每个参数向左移动一个位置。被移出的参数就被丢弃了,无法恢复。
    先掌握这个命令的使用,使用这个命令可以方便地解析命令行参数。

    使用示例

    下面是一个简单的示例:

    $ cat pop.sh 
    #!/bin/bash
    count=1
    while [ -n "$1" ]
    # while [ $# -ne 0 ]
    do
        echo "$count: $1"
        count=$[ $count + 1 ]
        shift
    done
    $ ./pop.sh Oliver Barry Kara Sara Kane
    1: Oliver
    2: Barry
    3: Kara
    4: Sara
    5: Kane
    $ 

    这里有2中判断方法来判断是否还有参数,效果是一样的。

    移动多个位置

    带参数执行shift,指明要移动几个位置就可以了:

    $ cat pop.sh 
    #!/bin/bash
    count=1
    # while [ -n "$1" ]
    while [ $# -ne 0 ]
    do
        if [ -n "$2" ]
        then
            echo "$count: $1, $2"
            shift 2
        else
            echo "$count: $1"
            shift
        fi
        count=$[ $count + 1 ]
    done
    $ ./pop.sh Oliver Barry Kara Sara Kane
    1: Oliver, Barry
    2: Kara, Sara
    3: Kane
    $ 

    简单修改下上面的脚本,一次输出2个参数,然后移动2个位置。

    处理选项

    当shell脚本需要多个命令行参数时,在调用脚本的时候就必须将所有参数按固定的顺序。
    或者还可以使用选项来指定参数的值。

    case 配合 shift

    这个例子里有带值的选项也有不带值的选项:

    $ cat format.sh 
    #!/bin/bash
    prefix=""    # 前缀
    base="test"  # 默认字符串
    suffix=""    # 后缀
    upper=off    # 是否大写
    # 解析命令行参数
    while [ -n "$1" ]
    do
        case "$1" in
            -a) suffix="$2"
                shift ;;
            -b) prefix="$2"
                shift ;;
            -s) base="$2"
                shift ;;
            -u) upper=on ;;
             *) echo "$1 is not an option"
                exit 1 ;;  # 发现未知参数,直接退出
        esac
        shift
    done
    # 添加前缀和后缀
    output="${prefix:+${prefix}_}${base}${suffix:+_${suffix}}"
    # 判断是否要全大写输出
    if [ $upper = on ]
    then
        output=${output^^}
    fi
    # 输出结果
    echo "$output"
    $ ./format.sh -a after
    test_after
    $ ./format.sh -s hello -b befor
    befor_hello
    $ ./format.sh -s hello -u -a after -b befor
    BEFOR_HELLO_AFTER
    $ ./format.sh -s hello -u -a after -b befor -l
    -l is not an option
    $ 

    case语句找到一个选项就处理一个选项。如果还需要在命令行提供其他参数,可以在通用情况的处理部分中处理。而这里因为不需要提供任何参数,凡是解析不正确的就报告错误并退出(exit 1)。

    能解析参数的版本
    这个版本匹配所有的参数进行格式化输出:

    $ cat format.sh 
    #!/bin/bash
    prefix=""    # 前缀
    base="test"  # 默认字符串
    suffix=""    # 后缀
    upper=off    # 是否大写
    # 显示声明一下这是个数组变量,其实没有必要
    declare -a names  # 需要格式化输出的所有原始字符串
    # 解析命令行参数
    while [ -n "$1" ]
    do
        case "$1" in
            -a) suffix="$2"
                shift ;;
            -b) prefix="$2"
                shift ;;
            -s) base="$2"
                shift ;;
            -u) upper=on ;;
             *) names=("${names[@]}" "$1") ;;
        esac
        shift
    done
    names[0]=${names[0]:-$base}
    for name in "${names[@]}"
    do
        # 添加前缀和后缀
        output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}"
        # 判断是否要全大写输出
        if [ $upper = on ]
        then
            output=${output^^}
        fi
        # 输出结果
        echo "$output"
    done
    $ 
    $ ./format.sh -a after -b befor -u value1 value2 value3
    BEFOR_VALUE1_AFTER
    BEFOR_VALUE2_AFTER
    BEFOR_VALUE3_AFTER
    $ ./format.sh -a after after1 -b befor befor1 -u value1 value2 value3
    BEFOR_AFTER1_AFTER
    BEFOR_BEFOR1_AFTER
    BEFOR_VALUE1_AFTER
    BEFOR_VALUE2_AFTER
    BEFOR_VALUE3_AFTER
    $ ./format.sh -a after after1 -b befor befor1 -u -v value1 value2 value3
    BEFOR_AFTER1_AFTER
    BEFOR_BEFOR1_AFTER
    BEFOR_-V_AFTER
    BEFOR_VALUE1_AFTER
    BEFOR_VALUE2_AFTER
    BEFOR_VALUE3_AFTER
    $ 

    看最后的两项的结果,提供的命令行参数有问题,但是程序无法发现。
    倒数第二项可以认为提供的参数是对的,但是选项和参数交替出现。
    而最后一项提供了一个错误的选项,但是无法识别出来。
    解决这个问题,需要更加规范的方法来分离参数和选项。下一小节的内容。

    数组带空格的问题
    数组添加元素有很多方法,这里是一种重新创建数组的做法:

    array_name=("${array_name[@]}" value1 ... valueN)

    可以一次添加多个元素,如果字符串包含空格,就要加上引号。

    和命令行参数的$@与$*一样,数组所有的元素也有这两个类似的符号。最严谨的方法是使用 "${names[@]}" 使用带双引号的@。
    添加元素和取出元素的时候都要注意,否则存在带空格的元素的时候就会破坏数组原本的元素分隔。
    添加元素这里使用:

    names=("${names[@]}" "$1")

    不单是数组里的元素,被添加的元素也要加上双引号,否则如果有空格,就会按多个元素被添加进数组。

    遍历元素使用:

    for name in "${names[@]}"

    只有添加的时候正确了,才能正确的遍历。然后遍历的时候也要保证正确。
    验证效果:

    $ ./format.sh -a after -b befor -u value1 "value2 value3" value4
    BEFOR_VALUE1_AFTER
    BEFOR_VALUE2 VALUE3_AFTER
    BEFOR_VALUE4_AFTER
    $ 

    完美。

    分离参数和选项

    这里的参数就是命令行参数中除了定义的选项之外,其他额外的参数。要同时处理参数和选项,就要用特殊字符(双破折线--)将二者分开。双破折线表明选项列表结束,双破折线后面的都是参数。基于这个逻辑,只要在case语句中加一项判断就行了。
    把上面的脚本做一些修改:

    $ cat format.sh 
    #!/bin/bash
    prefix=""    # 前缀
    base="test"  # 默认字符串
    suffix=""    # 后缀
    upper=off    # 是否大写
    # 显示声明一下这是个数组变量,其实没有必要
    declare -a names  # 需要格式化输出的所有原始字符串
    # 解析选项
    while [ -n "$1" ]
    do
        case "$1" in
            -a) suffix="$2"
                shift ;;
            -b) prefix="$2"
                shift ;;
            -s) base="$2"
                shift ;;
            -u) upper=on ;;
            --) shift
                break ;;
             *) echo "$1 is not an option"
                exit 1 ;;  # 发现未知参数,直接退出
        esac
        shift
    done
    # 解析参数
    while [ -n "$1" ]
    do
        names=("${names[@]}" "$1")
        shift
    done
    names[0]=${names[0]:-$base}
    for name in "${names[@]}"
    do
        # 添加前缀和后缀
        output="${prefix:+${prefix}_}${name}${suffix:+_${suffix}}"
        # 判断是否要全大写输出
        if [ $upper = on ]
        then
            output=${output^^}
        fi
        # 输出结果
        echo "$output"
    done
    $ 

    基于这个版本,在使用的时候,需要先输入选项,然后使用双破折线隔开,再输入参数。当脚本遇到双破折线时,它会停止处理选项,并将剩下的参数都当作参数:

    $ ./format.sh -a after -b befor -u value1 value2 value3
    value1 is not an option
    $ ./format.sh -a after -b befor -u -- value1 value2 value3
    BEFOR_VALUE1_AFTER
    BEFOR_VALUE2_AFTER
    BEFOR_VALUE3_AFTER
    $ ./format.sh -a after -b befor -v -u -- value1 value2 value3
    -v is not an option
    $ 

    第一次没有使用双破折线,所以报错。
    第二次正确的用双破折号分隔了参数和选项。
    第三次在选项部分出现了未定义的选项,也能发现错误。

    小结
    这一小节的内容也是为下面的getopt命令做铺垫。getopt就是可以帮我们完成命令行参数的解析,返回一个用双破折线隔开选项和参数的规整的参数列表。
    另外这里还不支持选项合并:

    $ ls -al

    这些问题,用getopt都能解决,而且还支持长选项。

    转自https://blog.51cto.com/steed/2443313

  • 相关阅读:
    模块在insmod之后无法rmmod问题
    FL2440驱动添加(2): RTC(Real time clock)
    虚拟机安装CentOS6.3两个问题
    内核移植和文件系统制作(3)Ramdisk简介和常见问题
    FL2440驱动添加(1):hello world 驱动模块添加
    内核移植和文件系统制作(2):linux内核最小系统和initramfs文件系统
    内核移植和文件系统制作(1):根文件系统制作总结
    mysql 5.7.16多源复制
    mysql 5.7安装脚本
    二进制方式快速安装MySQL数据库命令集合
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/11942567.html
Copyright © 2011-2022 走看看