赞
踩
getopt是用来解析,整理传入shell的命令行参数的命令
参考地址:B站传送门
shift
的作用就是从头部删除参数,它可以在后面各跟一个数字
参数表示删除几个参数示例:
shift 2
表示删除两个参数
常见参数使用,可以使用
$@
输入所有参数
[root@105 dongxx]# cat a.sh
#!/bin/bash
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c
a b c
使用
shift
命令之后会发现第一个参数a
没了
[root@105 dongxx]# cat a.sh
#!/bin/bash
shift
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c
b c
使用
shift 2
删除两个参数
[root@105 dongxx]# cat a.sh
#!/bin/bash
shift 2
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c d
c d
shift
参数还可以多次执行,可以多次执行删除多个参数
[root@105 dongxx]# cat a.sh
#!/bin/bash
shift 2
shift
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c d e
d e
需求:处理参数
-a -name zhangsan -age 18
,需要分开输出为-a
-name zhangsan
-age 18
[root@105 dongxx]# cat a.sh #!/bin/bash # 参数:-a -name zhangsan -age 18 # -a 为第一个参数,可以直接使用 $1 echo $1 # 输出 $1 之后,使用 shift 删除第一个参数,那么后面的参数就是从 $1 开始了,所以这里的 $1 和 $2 就是 -name 和 zhangsan shift echo $1 $2 # 因为 -name 选项和它的参数 zhangsan 占了两个位置,那么就需要 shift 两次了,同理,这里的 $1 就是 -age 选项 $2 就是 18 了 shift 2 echo $1 $2 [root@105 dongxx]# sh a.sh -a -name zhangsan -age 18 -a -name zhangsan -age 18
上述案例中没有考虑参数顺序问题,如果参数顺序有不一样输出结果也就乱了,所以在脚本中不仅要能挨个拿到选项和参数,还要根据选项是有参还是无参来控制
shift
的数量,而且也并不是每个选项参数一定要用,也就是说选项参数数量是不固定的。这样我们可以使用循环来处理。示例:
[root@105 dongxx]# cat a.sh #!/bin/bash # 参数:-a -name zhangsan -age 18 while true; do case "$1" in -a) echo "-a 选项" shift;; -name) echo "-name 选项,参数为 $2" shift 2;; -age) echo "-age 选项,参数为 $2" shift 2;; *) echo "非法参数" exit 1 esac done [root@105 dongxx]# sh a.sh -a -name zhangsan -age 18 -a 选项 -name 选项,参数为 zhangsan -age 选项,参数为 18 非法参数
上述脚本中有个问题,在没有参数时会提示
非法参数
,且参数为-a -name zhangsan -age 18
时,仍提示非法参数
[root@105 dongxx]# sh a.sh 非法参数
- 1
- 2
问题解析:
#!/bin/bash # 参数:-a -name zhangsan -age 18 while true; do case "$1" in -a) echo "-a 选项" shift;; -name) echo "-name 选项,参数为 $2" shift 2;; -age) echo "-age 选项,参数为 $2" shift 2;; *) echo "非法参数" exit 1 esac done # 当前脚本在匹配完成会后删除了所有参数,最后 $1 匹配为空会走到最后的 * 匹配,所以就会输出“非法参数”
问题处理:
#!/bin/bash # 参数:-a -name zhangsan -age 18 # 这里我们可以使用 set 命令来处理,set 命令可以用来指定一个结束标记 set -- "$@" -- # 参数说明: # set -- 为删除所有参数,在 -- 后面可以设置自定义参数,最后在添加一个 -- 作为结束标记 # 输出查看 echo "\$@: "$@ while true; do case "$1" in -a) echo "-a 选项" shift;; -name) echo "-name 选项,参数为 $2" shift 2;; -age) echo "-age 选项,参数为 $2" shift 2;; --) break;; *) echo "非法参数" exit 1 esac done
测试
[root@105 dongxx]# cat a.sh #!/bin/bash # 参数:-a -name zhangsan -age 18 # 这里我们可以使用 set 命令来处理,set 命令可以用来指定一个结束标记 set -- "$@" -- # 参数说明: # set -- 为删除所有参数,在 -- 后面可以设置自定义参数,最后在添加一个 -- 作为结束标记 # 输出查看 echo "\$@: "$@ while true; do case "$1" in -a) echo "-a 选项" shift;; -name) echo "-name 选项,参数为 $2" shift 2;; -age) echo "-age 选项,参数为 $2" shift 2;; --) break;; *) echo "非法参数" exit 1 esac done [root@105 dongxx]# sh a.sh $@: -- [root@105 dongxx]# sh a.sh -a -name zhangsan -age 18 $@: -a -name zhangsan -age 18 -- -a 选项 -name 选项,参数为 zhangsan -age 选项,参数为 18
上述测试脚本参数都是约定好的,但是在实际使用过程中可能会出现没有安装约定的方式传参的问题
示例:
-a aaa -name zhangsan -age 18
,那这时候参数的顺序位置就会有问题。这时我们就可以使用getopt
来处理了
常用命令参数
参数 说明 -o 指定解析段格式选项 -l 指定要解析的长格式选项 – 分割真正需要解析的参数 示例:
[root@105 dongxx]# getopt -o a -l name:,age: -- -a --name zhangsan --age 18
-a --name 'zhangsan' --age '18' --
# 参数说明:
-o 后跟上短格式选项
-l 后跟上长格式选择,对于有参数的选项需要再参数后加个冒号,多个长格式选项用逗号隔开
-- 后跟上真正需要解析的参数,而且 getopt 要求长格式选项需要使用 -- ,所以需要在选项前加上 --
解析成功后会在参数最后默认加上 -- 的结束标记
测试给段格式选项
-a
增加参数aaa
,结果是没有参数输出,因为getopt
知道-a
选项是无参选项,所以它将跟在a
后面的参数移动到了结束标记之后,在之前的脚步中是需要--
则跳出循环,所以结束标记之后的参数是不会被处理的
[root@105 dongxx]# getopt -o a -l name:,age: -- -a aaa --name zhangsan --age 18
-a --name 'zhangsan' --age '18' -- 'aaa'
使用
getopt
处理一下就能解决参数位置错误导致的参数解析错乱的问题
[root@105 dongxx]# cat a.sh #!/bin/bash # 参数:-a -name zhangsan -age 18 # 使用 $(getopt -o a -l name:,age: -- "$@") 获取 getopt 处理后的参数信息,在使用 set -- 把这个结果设置后脚本的参数,由于 getopt 本身就有 -- 的结束标记,所以下面的 set -- "$@" -- 就不需要了 set -- $(getopt -o a -l name:,age: -- "$@") # 这里我们可以使用 set 命令来处理,set 命令可以用来指定一个结束标记 # set -- "$@" -- # 参数说明: # set -- 为删除所有参数,在 -- 后面可以设置自定义参数,最后在添加一个 -- 作为结束标记 # 输出查看 echo "\$@: "$@ while true; do case "$1" in -a) echo "-a 选项" shift;; # 为了兼容 getopt 长格式的设置,这里需要改成 --name --name) echo "-name 选项,参数为 $2" shift 2;; --age) echo "-age 选项,参数为 $2" shift 2;; --) break;; *) echo "非法参数" exit 1 esac done [root@105 dongxx]# sh a.sh -a --name zhangsan --age 18 $@: -a --name 'zhangsan' --age '18' -- -a 选项 -name 选项,参数为 'zhangsan' -age 选项,参数为 '18' [root@105 dongxx]# sh a.sh -a aa --name zhangsan --age 18 $@: -a --name 'zhangsan' --age '18' -- 'aa' -a 选项 -name 选项,参数为 'zhangsan' -age 选项,参数为 '18'
执行测试
当我们正常传参时,示例:
sh a.sh -a --name zhangsan --age 18
没有问题。如果我们将有参的选项的参数去掉sh a.sh -a --name zhangsan --age
那么就会报错
[root@105 dongxx]# sh a.sh -a --name zhangsan --age
getopt: option '--age' requires an argument
$@: -a --name 'zhangsan' --
-a 选项
-name 选项,参数为 'zhangsan'
问题:
上述脚本中也有个问题,就是报错之后仍然会继续执行。
解决:
修改脚本,在开头设置一个
set -e
让他需要非 0 状态吗自动退出。如果单纯只增加set -e
命令,脚本同样会继续往下执行,因为在上述脚本中getopt
是在set --
中执行的,getopt
报错,但是set --
是正常执行的,所以结果就不是一个非 0 状态。所以需要将getopt
提取出来单独处理。
[root@105 dongxx]# cat a.sh #!/bin/bash # 参数:-a -name zhangsan -age 18 set -e # 将 getopt 提取出来赋值变量,那么校验失败后,set -e 就会检测到非 0 状态从而退出脚本 args=$(getopt -o a -l name:,age: -- "$@") set -- $args # 使用 $(getopt -o a -l name:,age: -- "$@") 获取 getopt 处理后的参数信息,在使用 set -- 把这个结果设置后脚本的参数,由于 getopt 本身就有 -- 的结束标记,所以下面的 set -- "$@" -- 就不需要了 # set -- $(getopt -o a -l name:,age: -- "$@") # 这里我们可以使用 set 命令来处理,set 命令可以用来指定一个结束标记 # set -- "$@" -- # 参数说明: # set -- 为删除所有参数,在 -- 后面可以设置自定义参数,最后在添加一个 -- 作为结束标记 # 输出查看 echo "\$@: "$@ while true; do case "$1" in -a) echo "-a 选项" shift;; # 为了兼容 getopt 长格式的设置,这里需要改成 --name --name) echo "-name 选项,参数为 $2" shift 2;; --age) echo "-age 选项,参数为 $2" shift 2;; --) break;; *) echo "非法参数" exit 1 esac done [root@105 dongxx]# sh a.sh -a --name zhangsan --age getopt: option '--age' requires an argument
命令展示示例:
[root@105 dongxx]# getopt -o a:bc: -l name:,age:,man -- -a 1 -b -c 2 --name zhangsan --age 18 --man
-a '1' -b -c '2' --name 'zhangsan' --age '18' --man --
脚本展示示例:
#!/bin/bash # 问题:在没有 -o 参数时会报错,这是为什么 # args=$(getopt -l name:,age:,address:,user:,passwd: -- "$@") args=$(getopt -o -a: -l name:,age:,address:,user:,passwd: -- "$@") if [[ $? != 0 ]]; then echo "请输出正确参数" exit 1 fi echo "args: "$args # 问题:这里为什么需要使用 eval ,暂时还不知道 eval set -- "$args" # set -- "$args" while true ;do echo "\$1: "$1 case "$1" in --name) if [[ -z "$NAME" ]]; then NAME=$2 fi shift 2;; --age) if [[ -z "$AGE" ]]; then AGE=$2 fi shift 2;; --address) if [[ -z "$ADDRESS" ]]; then ADDRESS=$2 fi shift 2;; --user) if [[ -z "$USER" ]]; then USER=$2 fi shift 2;; --passwd) if [[ -z "$PASSWD" ]]; then PASSWD=$2 fi shift 2;; --) break;; *) echo "参数错误,请检查" exit 1 ;; esac done echo "name: " $NAME ", age: " $AGE ", address: "$ADDRESS ", user: "$USER ", passwd: "$PASSWD
eval内置命令:
功能:当Shell程序执行到eval语句的时候,Shell读入参数args,并将它们组合成一个新的命令,然后执行。也就是重新运算求出参数的内容。eval可以读取一连串的参数,然后依据参数本身的特性来执行。参数不限数目,彼此之间用分号分开。 eval会对后面的命令进行两遍的扫描,如果第一遍扫描后,命令是普通命令,则执行此命令;如果命令中含有变量的间接引用,则保证间接引用的语义。也就是说,eval语句将会首先扫描命令行进行所有的置换,然后再进行该命令。因此,eval命令适合用于那些一次扫描无法实现其功能的变量。
eval执行分两个步骤:
第一步:执行变量的替换。
第二步:执行替换后的命令
[root@105 1]# cat a.sh #!/bin/bash echo "111 "\$$# echo -e "\n" echo "==============" echo -e "\n" eval "echo 2222 \$$#" [root@105 1]# sh a.sh aa bb 111 $2 ============== 2222 bb
脚本说明:
\$$#
:$#
是表示传参个数,\$
表示转义,显示为普通字符$
所以第一次输出\$$#
只进行了第一步的变量替换, 结果为$2
使用
eval
之后则进行了两次扫描,第一次是$#
变量的替换,结果为$2
,然后再执行替换后的命令$2
,则结果显示$2
的值bb
如果我们知道参数的个数,输入两个参数 aa
bb
,我们可以使用 $2
来查看最后一个参数 bb
。但是如果我们不知道参数个数,还想查看最后一个参数怎么办呢?我们想到 $#
,传给Shell脚本的个数,echo $#
显示的其实是参数个数,而使用 eval echo "$$#"
才显示最后一个参数。和上述示例一样。
[root@105 1]# cat test
Hello World
[root@105 1]# aa="cat test"
[root@105 1]# echo $aa
cat test
[root@105 1]# eval $aa
Hello World
脚本说明:
eval
命令对后面的命令进行了两次扫描,第一次将$aa
替换为cat test
,第二次执行cat test
。- 这些需要进行两次扫描的变量有时也称为复杂变量。不过这些变量并不复杂。
在
file
文件中,有两列数据,第一列对应KEY
,第二列对应VALUE
,使用eval
命令将KEY
和VALUE
的值对应起来,从文件中读取。
[root@105 1]# cat file NAME chang AGE 28 SEX nan [root@105 1]# cat a.sh #!/bin/bash while read KEY VALUE do eval "${KEY}=${VALUE}" done < file echo "NAME: "$NAME", AGE: "$AGE", SEX:" $SEX [root@105 1]# sh a.sh NAME: chang, AGE: 28, SEX: nan [root@105 1]# [root@105 1]# sh -x a.sh + read KEY VALUE + eval NAME=chang ++ NAME=chang + read KEY VALUE + eval AGE=28 ++ AGE=28 + read KEY VALUE + eval SEX=nan ++ SEX=nan + read KEY VALUE + echo 'NAME: chang, AGE: 28, SEX:' nan NAME: chang, AGE: 28, SEX: nan
脚本说明:
eval "${KEY}=${VALUE}"
中eval
第一次扫描获取变量${KEY}=${VALUE}
的值,第二次进行赋值操作
[root@105 1]# cat >a.sh<<EOF"" > #!/bin/bash > x=100 > y=x > eval echo \$$y > eval $y=50 > echo $x > eval echo \$$y > EOF [root@105 1]# cat a.sh #!/bin/bash x=100 y=x eval echo \$$y eval $y=50 echo $x eval echo \$$y [root@105 1]# [root@105 1]# sh a.sh 100 50 50
上面例子中的
eval echo \$$y
首先被读取,然后被执行:在读取的过程中,$y
会被替换成x
,所以读取的结果是echo $x
;执行echo $x
的输出就是打印了变量x
的值。同理,eval $y=50
会被解析成x=50
,然后执行x=50
的结果就是为变量x
赋值。
执行复杂的字符串形式的命令
[root@105 1]# cat a.sh #!/bin/bash dirpath=/root/1 simple_cmd="ls -l $dirpath" complex_cmd="ls -l $dirpath | awk -F ' ' '{print \$9}'" echo '==========================' echo '========Simple Cmd=========' echo '==========================' eval $simple_cmd echo '-----------------------------------' $simple_cmd echo '===========================' echo '========Complex Cmd=========' echo '===========================' eval $complex_cmd echo '-----------------------------------' $complex_cmd [root@105 1]# [root@105 1]# sh a.sh ========================== ========Simple Cmd========= ========================== total 8 -rw-r--r-- 1 root root 467 Jul 11 21:28 a.sh -rw-r--r-- 1 root root 5 Jul 11 21:28 test ----------------------------------- total 8 -rw-r--r-- 1 root root 467 Jul 11 21:28 a.sh -rw-r--r-- 1 root root 5 Jul 11 21:28 test =========================== ========Complex Cmd========= =========================== a.sh test ----------------------------------- ls: cannot access |: No such file or directory ls: cannot access awk: No such file or directory ls: cannot access ': No such file or directory ls: cannot access ': No such file or directory ls: cannot access '{print: No such file or directory ls: cannot access $9}': No such file or directory /root/1: total 8 -rw-r--r-- 1 root root 467 Jul 11 21:28 a.sh -rw-r--r-- 1 root root 5 Jul 11 21:28 test
可以看到,在执行
$simple_cmd
时,是否使用eval
的效果是相同的。但是当我们执行一个稍微复杂一点(比如包含管道(Pipe)
)的字符串形式的命令时,如果不使用eval
,执行会报错!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。