shell 命令行参数(基本)
命令行参数
$0 表示程序名。 位置参数位置参数不止9个,更多的参数也是一样支持的。只是要使用${10}这样的形式引用。 带空格的参数值
获取脚本名$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 /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 . $ 判断参数是否存在 $ 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 要加上双引号,否则会被认为是字符串。一个字符串当然非空,所以结果会永远为真。 判断参数的个数 $ 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. $ 如果要表示不相等,就是 获取最后一个参数 $ 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 $ 不加感叹号,就是直接去该变量的值。加上感叹号,就是去变量值所对应的变量名的那个变量的值。 获取所有参数$* 和 [email?protected] 都是表示所有的字符串,但是在遍历的时候会有区别: $ cat all.sh #!/bin/bash echo ‘$* 的效果:‘ count=1 for i in "$*" do echo $count: $i count=$[ $count + 1 ] done echo ‘[email?protected] 的效果:‘ count=1 for i in "[email?protected]" do echo $count: $i count=$[ $count + 1 ] done $ ./all.sh Oliver Barry Kara Sara Kane $* 的效果: 1: Oliver Barry Kara Sara Kane [email?protected] 的效果: 1: Oliver 2: Barry 3: Kara 4: Sara 5: Kane $ $*就一个整体的值,无法遍历。要遍历每一个变量要使用[email?protected]。这里的双引号很重要。 不加引号的话,就是把 $* 和 [email?protected] 的内容(变量解析后就是多个词)传递给for循环遍历,这样两个参数的效果是一样的。和直接传不加引号的字符串的效果一样。 $ cat all2.sh #!/bin/bash echo ‘$* 不加引号的效果:‘ count=1 for i in $* do echo $count: $i count=$[ $count + 1 ] done echo ‘[email?protected] 不加引号的效果:‘ count=1 for i in [email?protected] 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 [email?protected] 不加引号的效果: 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 $ 强调:特殊参数[email?protected]一定要用在双引号内,效果是每个参数都扩展为分隔的单词。在使用for循环遍历的时候会体现出效果。 移动变量 shiftshift 命令能够用来操作命令行参数。默认情况下将每个参数向左移动一个位置。被移出的参数就被丢弃了,无法恢复。 使用示例下面是一个简单的示例: $ 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) 可以一次添加多个元素,如果字符串包含空格,就要加上引号。 和命令行参数的[email?protected]与$*一样,数组所有的元素也有这两个类似的符号。最严谨的方法是使用 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 $ 第一次没有使用双破折线,所以报错。 小结 $ ls -al 这些问题,用getopt都能解决,而且还支持长选项。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |