shell 命令行参数(基本)

命令行参数

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

位置参数

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

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

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

获取脚本名

\$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
$

测试参数

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

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

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

$ 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
$

不加感叹号,就是直接去该变量的值。加上感叹号,就是去变量值所对应的变量名的那个变量的值。

获取全部参数

\$* 和 \$@ 都是表示全部的字符串,可是在遍历的时候会有区别:

$ 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都能解决,并且还支持长选项。

相关文章
相关标签/搜索