文章目录
使用getopt处理参数
getopt是用来解析,整理传入shell的命令行参数的命令
参考地址:B站传送门
1. shift 命令
shift
的作用就是从头部删除参数,它可以在后面各跟一个数字
参数表示删除几个参数示例:
shift 2
表示删除两个参数
常见参数使用,可以使用
$@
输入所有参数
[root@105 dongxx]# cat a.sh
#!/bin/bash
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c
a b c
1.1 删除一个参数
使用
shift
命令之后会发现第一个参数a
没了
[root@105 dongxx]# cat a.sh
#!/bin/bash
shift
# 输入所有参数
echo $@
[root@105 dongxx]# sh a.sh a b c
b c
1.2 删除多个参数
使用
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
1.3 多次执行 shift 参数
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
1.4 参数解析示例
需求:处理参数
-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
1.5 优化处理
上述案例中没有考虑参数顺序问题,如果参数顺序有不一样输出结果也就乱了,所以在脚本中不仅要能挨个拿到选项和参数,还要根据选项是有参还是无参来控制
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 非法参数
问题解析:
#!/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
1.6 问题处理
上述测试脚本参数都是约定好的,但是在实际使用过程中可能会出现没有安装约定的方式传参的问题
示例:
-a aaa -name zhangsan -age 18
,那这时候参数的顺序位置就会有问题。这时我们就可以使用getopt
来处理了
2. getopt 命令
2.1 常用参数及示例
常用命令参数
参数 说明 -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'
2.2 脚本参数优化示例
使用
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'
执行测试
2.3 参数校验
当我们正常传参时,示例:
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
3. 示例展示
命令展示示例:
[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
4. eval 命令
eval内置命令:
功能:当Shell程序执行到eval语句的时候,Shell读入参数args,并将它们组合成一个新的命令,然后执行。也就是重新运算求出参数的内容。eval可以读取一连串的参数,然后依据参数本身的特性来执行。参数不限数目,彼此之间用分号分开。 eval会对后面的命令进行两遍的扫描,如果第一遍扫描后,命令是普通命令,则执行此命令;如果命令中含有变量的间接引用,则保证间接引用的语义。也就是说,eval语句将会首先扫描命令行进行所有的置换,然后再进行该命令。因此,eval命令适合用于那些一次扫描无法实现其功能的变量。
eval执行分两个步骤:
第一步:执行变量的替换。
第二步:执行替换后的命令
4.1 示例
示例 1
[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 "$$#"
才显示最后一个参数。和上述示例一样。
示例 2
[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
。- 这些需要进行两次扫描的变量有时也称为复杂变量。不过这些变量并不复杂。
示例 3
在
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}
的值,第二次进行赋值操作
示例 4
[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
赋值。
示例 5
执行复杂的字符串形式的命令
[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
,执行会报错!