目录
[1.1 读取参数](#1.1 读取参数)
[1.2 读取脚本名](#1.2 读取脚本名)
[4.1 查找选项](#4.1 查找选项)
[4.1.1 处理简单选项](#4.1.1 处理简单选项)
[4.1.2 分离参数和选项](#4.1.2 分离参数和选项)
[4.1.3 处理含值的选项](#4.1.3 处理含值的选项)
[5.1 使用 getopt 命令](#5.1 使用 getopt 命令)
[5.1.1 命令格式](#5.1.1 命令格式)
[5.1.2 在脚本中使用getopt](#5.1.2 在脚本中使用getopt)
[5.2 使用getopts命令](#5.2 使用getopts命令)
[6.1 read读取](#6.1 read读取)
[6.2 从文件中读取](#6.2 从文件中读取)
一、传递参数
向 shell 脚本传递数据的最基本方法是使用命令行参数 。bash shell 会将所有的命令行参数都指派给称作位置参数的特殊变量。这也包括shell脚本名称。位置变量的名称都是标准数字,$0 对应脚本名,$1 对应第一个命令行参数,$2 对应第二个命令行参数,以此类推,直到 $9,第9个之后,必须在变量名两侧加上花括号,比如 ${10} 。
1.1 读取参数
如果需要 输入更多的命令行参数,则参数之间必须用空格分开
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat position.sh
#! /bin/bash
value=$[ ${10} * ${11} ]
echo "第10个参数是${10}"
echo "第11个参数是${11}"
echo $value
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ./position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
1.2 读取脚本名
可以使用位置变量 $0 获取在命令行中运行的脚本名。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat position.sh
#! /bin/bash
value=$[ ${10} * ${11} ]
echo "第10个参数是${10}"
echo "第11个参数是${11}"
echo $value
echo "脚本名是$0"
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
脚本名是position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
但是这会有一个问题,如果使用另一个命令运行shell脚本,则命令名会和脚本名混在一起
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat position.sh
#! /bin/bash
value=$[ ${10} * ${11} ]
echo "第10个参数是${10}"
echo "第11个参数是${11}"
echo $value
echo "脚本名是$0"
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
脚本名是position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ./position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
脚本名是./position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
这还不是唯一的问题,如果运行脚本时使用的是绝对路径,那么位置变量 $0 就会包含整个路径
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# pwd
/root
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ~/position.sh
/root/position.sh: line 3: * : syntax error: operand expected (error token is "* ")
第10个参数是
第11个参数是
脚本名是/root/position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
basename 命令可以返回不包含路径的脚本名
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat position.sh
#! /bin/bash
value=$[ ${10} * ${11} ]
echo "第10个参数是${10}"
echo "第11个参数是${11}"
echo $value
name=$(basename $0)
echo "脚本名是$name"
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ./position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
脚本名是position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
利用此技术可以编写一个脚本,生成能标识脚本运行时间的日志信息
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat position.sh
#! /bin/bash
value=$[ ${10} * ${11} ]
echo "第10个参数是${10}"
echo "第11个参数是${11}"
echo $value
name=$(basename $0)
echo "脚本名是$name"
echo "The $name ran at $(date)" >> ~/scripttrack.log
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# date
Fri May 10 15:42:54 CST 2024
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ./position.sh 1 2 3 4 5 6 7 8 9 10 12
第10个参数是10
第11个参数是12
120
脚本名是position.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# ll
total 12
-rwxr--r-- 1 root root 210 May 10 15:42 position.sh
drwxr-xr-x 2 root root 4096 May 9 23:36 script
-rw-r--r-- 1 root root 52 May 10 15:43 scripttrack.log
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat scripttrack.log
The position.sh ran at Fri May 10 15:43:01 CST 2024
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
二、跟踪参数
特殊变量 #** 含有脚本运行时携带的命令行参数的个数,**{!#} 代表最后一个位置变量,\*** 和 **@ 各自包含了所有的命令行参数。\*** 将所有参数视为一个整体,**@ 会将所有命令行参数视为同一字符串中的多个独立的单词,可以用 for 遍历所有参数。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat params.sh
#! /bin/bash
echo
echo "\$*用法:$*"
count=1
for param in "$*"
do
echo "\$* #$count = $param"
count=$[ $count + 1 ]
done
########
echo
echo "\$@用法:$@"
count=1
for param in "$@"
do
echo "\$@ #count = $param"
count=$[ $count +1 ]
done
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash params.sh 1 2 3
$*用法:1 2 3
$* #1 = 1 2 3
$@用法:1 2 3
$@ #count = 1
$@ #count = 2
$@ #count = 3
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
三、移动参数
shift命令会根据命令行参数的相应位置进行移动,默认情况下会将每个位置的变量值都向左移动一个位置。因此,变量 $3 的值会移入 $2 ,变量 $2 的值会移入 $1 ,而变量 $1 的值会被删除(变量 $0 是脚本名,不会变)。这是遍历命令行参数的另一种好方法,尤其在不知道到底有多少参数的时候。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat shift.sh
#! /bin/bash
echo
echo "shift的用法"
count=1
while [ -n "$1" ]
do
echo "#$count = $1"
count=$[ $count + 1 ]
shift
done
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash shift.sh ab cd 12 34 df
shift的用法
#1 = ab
#2 = cd
#3 = 12
#4 = 34
#5 = df
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
四、处理选项
4.1 查找选项
在命令行中,选项紧跟在脚本名之后,就跟其他命令行参数一样 。你可以像处理命令行参数一样处理命令行选项
4.1.1 处理简单选项
在提取单个参数时,使用 case 语句来判断某个参数是否为选项
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# vim option.sh
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat option.sh
#! /bin/bash
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "a是一个选项";;
-b) echo "b是一个选项";;
-c) echo "c是一个选项";;
*) echo "$1不是一个选项";;
esac
shift
done
echo
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -b -a e -f
b是一个选项
a是一个选项
e不是一个选项
-f不是一个选项
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
4.1.2 分离参数和选项
经常会碰到同时使用选项和参数的情况。在Linux系统中,处理这个问题的标准做法是使用特殊字符将两者分隔,该字符告诉脚本选项何时结束,普通字符何时开始。在Linux系统中这个特殊字符是双连字符(--)。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat option.sh
#! /bin/bash
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "-a是一个选项";;
-b) echo "-b是一个选项";;
-c) echo "-c是一个选项";;
--) shift
break;;
*) echo "$1不是一个选项";;
esac
shift
done
#
echo
count=1
for param in "$@"
do
echo "参数#$count = $param"
count=$[ $count + 1 ]
done
echo
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -b -a -d -- 1 21 6
-b是一个选项
-a是一个选项
-d不是一个选项
参数#1 = 1
参数#2 = 21
参数#3 = 6
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
在遇到双连字符时,脚本会用break命令跳出while循环。由于提前结束了循环,因此需要再加入另一个shift命令将双连字符移除位置变量
4.1.3 处理含值的选项
有些选项需要一个额外的参数值,像下面这样
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat option.sh
#! /bin/bash
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "-a是一个选项";;
-b) echo "-b是一个选项"
echo "参数$2"
shift ;;
-c) echo "-c是一个选项";;
--) shift
break;;
*) echo "$1不是一个选项";;
esac
shift
done
#
echo
count=1
for param in "$@"
do
echo "参数#$count = $param"
count=$[ $count + 1 ]
done
echo
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -b test -a -d -- 1 21 6
-b是一个选项
参数test
-a是一个选项
-d不是一个选项
参数#1 = 1
参数#2 = 21
参数#3 = 6
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
在这个例子中,case语句定义了3个要处理的选项。-b选项还需要一个额外的参数。由于要处理的选项位于 $1 ,因此额外的参数值就应该位于 $2 ,因为选项占用了两个位置,所以还需要使用 shift 命令多移动一次。
五、选项标准化
现在shell脚本已经拥有处理命令行选项的基本能力了,但是当你想合并多个选项时,脚本就不管用了。
5.1 使用 getopt 命令
5.1.1 命令格式
getopt 命令可以接受一系列任意形式的命令行选项和参数,并自动将其转换为适当格式。getopt 格式如下:getopt optstring parameters
首先,在 optstring 中列出要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后面加一个冒号。如下:
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# getopt ab:cd -a -b value -cd 1 2
-a -b value -c -d -- 1 2
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
如果 optstring 未包含制定的选项,则会输出一条错误信息,如果想忽略,可以使用 -q 选项。
5.1.2 在脚本中使用getopt
1)可以在脚本中使用 getopt 格式化脚本所携带的任何命令行选项或参数。难点在于使用 getopt 格式化版本替换已有的命令行选项和参数。这得求助于 set 命令;
2)set 命令有个选项是双连字符(--),可以将位置变量的值替换为 set 命令所指定的值;
3)具体做法是将脚本的命令行参数传递给getopt命令,然后将getopt命令的输出传递给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数,如下所示:
bash
set -- $(getopt -q ab:cd "$@")
现在,位置变量原先的值会被getopt命令的输出替换掉,后者已经是格式化好的命令行参数。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat option.sh
#! /bin/bash
set -- $(getopt -q ab:cd "$@")
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "-a是一个选项";;
-b) echo "-b是一个选项"
echo "参数$2"
shift ;;
-c) echo "-c是一个选项";;
--) shift
break;;
*) echo "$1不是一个选项";;
esac
shift
done
#
echo
count=1
for param in "$@"
do
echo "参数#$count = $param"
count=$[ $count + 1 ]
done
echo
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -ab bvalue 12 3
-a是一个选项
-b是一个选项
参数bvalue
参数#1 = 12
参数#2 = 3
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
5.2 使用getopts命令
getopt 命令存在一个问题,如下代码所示:就是不擅长处理带空格和引号的参数值。它会将空格当做参数值的分隔符,而不是根据双引号将二者当做一个参数。这是 getopts就派上用场了。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -ab bvalue 12 3 -c
-a是一个选项
-b是一个选项
参数bvalue
-c是一个选项
参数#1 = 12
参数#2 = 3
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash option.sh -ab bvalue "12 3" -c
-a是一个选项
-b是一个选项
参数bvalue
-c是一个选项
参数#1 = 12
参数#2 = 3
getopts 命令格式如下:
bash
getopts optstring variable
1) optstring 值与 getopt 命令中使用的类似。有效的选项字母会在 optstring 中列出,如果选项要求参数,就在其后面加一个冒号。不想显示错误消息的话,可以在 optstring 之前加一个冒号。getopts 会将当前参数保存在命令行中定义的varibales中。
2)getopts 会用到两个环境变量。如果选项需要加带参数值,那么 OPTARG环境变量保存的就是这个值。OPTIND 环境变量保存着参数列表中 getopts 正在处理的参数位置。这样在处理完当前选项之后就能继续处理其他命令行参数了。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat getopts.sh
#! /bin/bash
echo
while getopts :ab:cd opt
do
case "$opt" in
a) echo "-a是选项";;
b) echo "-b是一个选项"
echo "-b选项的参数是$OPTARG";;
c) echo "-c是一个选项";;
*) echo "未知选项$opt";;
esac
done
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash getopts.sh -ab2
-a是选项
-b是一个选项
-b选项的参数是2
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
1) 在解析命令行选项时,getopts 会移除起始的连字符,所以 case 语句中不用连字符;
2)可以在参数值中加空格;
3)可以将选项字母和参数值写在一起,两者之间不加空格;
4)在处理每个选项时,getopts 会将OPTIND 环境变量的值增1.处理完选项后,可以使用shift命令和OPTIND值来移动参数。
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat getopts.sh
#! /bin/bash
echo
while getopts :ab:cd opt
do
case "$opt" in
a) echo "-a是选项";;
b) echo "-b是一个选项"
echo "-b选项的参数是$OPTARG";;
c) echo "-c是一个选项";;
*) echo "未知选项$opt";;
esac
done
#
shift $[ $OPTIND -1 ]
echo
count=1
for param in "$@"
do
echo "参数#$count = $param"
count=$[ $count + 1 ]
done
exit
六、获取用户输入
尽管命令行选项和参数是从脚本用户处获取输入的一种重要方式,但有时候脚本还需要更多
的交互性。为此,bash shell 提供了 read 命令。
6.1 read读取
bash
read -p "Please enter your age:" age
read -t 5 -p "Please enter your age:" age # 超时
read -s "Enter your password:" password # 无显示输出
6.2 从文件中读取
bash
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# cat read.sh
#! /bin/bash
echo
count=1
cat ~/getopts.sh | while read line
do
echo "#$count = $line"
count=$[ $count + 1 ]
done
echo "处理完文本的所有行"
exit
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]# bash read.sh
#1 = #! /bin/bash
#2 = echo
#3 = while getopts :ab:cd opt
#4 = do
#5 = case "$opt" in
#6 = a) echo "-a是选项";;
#7 = b) echo "-b是一个选项"
#8 = echo "-b选项的参数是$OPTARG";;
#9 = c) echo "-c是一个选项";;
#10 = *) echo "未知选项$opt";;
#11 = esac
#12 = done
#13 = #
#14 = shift $[ $OPTIND -1 ]
#15 = echo
#16 = count=1
#17 = for param in "$@"
#18 = do
#19 = echo "参数#$count = $param"
#20 = count=$[ $count + 1 ]
#21 = done
#22 = exit
处理完文本的所有行
[root@iZbp1ir1vzqwzkdy7mvjthZ ~]#
while 循环会持续通过read命令处理文件中的各行,直到read命令以非0退出状态码退出