Linux-Shell交互

背景

此前讲述的一直都是Shell脚本中的内容,并没有涉及到脚本与人之间的交互,本文主要对Shell交互部分的内容做了一些笔记,内容参考于《Linux命令行与Shell编程大全》

内容

命令行传参

在shell中,如果需要向脚本中传递参数,有两种方式,一种是通过脚本运行期间的交互进行输入,还有一种是直接在启动脚本时就将对应的参数就传递给脚本,先对第二种方案进行描述,其传递的格式如下:

1
2
# var表示传递的参数
./run.sh var1 var2 var3 ...

此时,对于run.sh脚本而言,它会接受到命令行传递的参数,并且将其依次记录在变量:$1 ~ $9中,当然,如果传递的参数超过了9个,则可以通过${10}的方式再次获取传递的其他参数,譬如:

1
2
total=$[ $1 * ${11} ]
echo "the result is $total"

不过,如果传递的参数内容中有空格,则需要用单引号/双引号对其进行包裹,否则shell会将其解读为多个参数。比如:

1
./run.sh "shuai qi de boy"

你可以会有疑问,$0呢?$0其实表示脚本本身,它记录的是脚本的路径+名称

1
echo "the file is $0"

然后你会得到这样的结果:

1
the file is /Users/wuxiang/Desktop/run.sh

很显然不是我们想要的结果,我们只是想要脚本的名称,此时可以借助basename这个命令:

1
2
name=$( basename $0 )
echo "the file is $0"

这个时候的结果就正常了:

1
the file is run.sh

但是这里也有一点需要说明:当脚本中使用了参数变量,但是运行的时候并没有传递参数,则脚本报错,因此需要注意.

对于脚本而言,有时候需要记录脚本运行的时候一共传递了多少参数,则可以通过参数$#来访问:

1
2
3
4
5
# 无参数则为0
echo "the variable amount is $#"

# 还有个方式也能统计传递的参数量:${!#},当没有参数的时候,则返回脚本的名称+路径
echo "the variable amount is ${!#}"

此外就要讲到两个特殊的变量:$*、$@,它们都能直接访问脚本运行时传递的所有参数:

1
2
3
4
5
6
7
8
# $*:将所有的参数当做一个字符串记录
echo "all the var is $*"

# $@:将所有的参数当做列表来记录
for param in "$@"
do
echo "the var is $param"
done

通过$*、$@遍历的方式固然比较友好,但是它对于-var value格式的参数就没法很好的适应了,此时可以使用shift命令,它可以根据相对位置获取对应的参数或值

1
2
3
4
5
6
7
8
9
10
# 每执行一次shift,输入参数就会去掉最左边的一个参数值,并且去除的参数无法恢复
while [ -n "$1" ]
do
case $1 in
-a | -b ) echo "now is $1";;
-c ) echo "the param now is $1";;
* ) echo "no param";
esac
shift
done

同样,你也可以直接指定左移指定数目的参数值:shift num。比如:

1
2
3
4
5
6
7
# 每执行一次shift,输入参数就会去掉最左边的一个参数值,并且去除的参数无法恢复
# 但如果最后剩余的位数不够shift偏移的位数,则shift出错,导致死循环
while [ -n "$1" ]
do
echo $1
shift 2
done

如果需要获取参数对应的参数值,则可以

1
2
3
4
5
6
7
8
9
10
11
12
while [ -n "$1" ]
do
case $1 in
# 一旦确定$1是-a后,就可以通过$2直接获取-a对应的参数值
-a ) param="$2"
echo "the var $1 value is $2"
# 由于占用了两个参数位,所以需要再shift一次
shift;;
* ) echo "pass"
esac
shift
done

但是上面的方式在遇到-ac这样的情况的时候,就没法好好的工作了,解决它的办法可以使用:getopt命令,它的格式如下:

1
2
3
4
5
6
7
8
9
10
# optstring:是对应的参数简写,如果某个参数需要带值,则在其后追加:
# parmeters:是传递给getopt的参数
getopt optstring parameters

# 比如:
getopt ab:cd -acd -b shuai test1 test2

# 得到的结果:
# --:是参数部分结束的标志
-a -b shuai -c -d -- test1 test2

当getopt遇到未定义的参数名时,则其会出现报错,如果想忽视这样的报错,可以使用-q参数,比如。

1
getopt -q ab:cd -acd -b shuai test1 test2

但是如果我们想将 getopt 的输出结果直接用在shell脚本中,则还需要使用set进行一遍转化,转化方式如下:

1
2
3
# --:是必须的,是set的参数
# ab:cd部分根据自己的需要进行变化
set -- $( getopt ab:cd "$@" )

转化后,shell脚本的其他内容均无需再变,可以直接使用,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 注意:这里有个坑,就是getopt会将参数值外部包裹‘,导致取值的时候带有引号,
set -- $( getopt ab:cd "$@" )

while [ -n "$1" ]
do
case $1 in
-a | -b ) echo "now is $1";;
-c ) param="$2"
echo "the param now is $1, value is $2"
shift;;
-- ) shift
break;;
* ) echo "no param";
esac
shift
done

不过getopt方法并不适用于处理带空格的参数,因为其会直接将空格当成参数的间隔符号,即便是给这样的参数值加上双引号/单引号也不起作用。所以就引出了更高级一些的命令:getopts,它的格式如下:

1
getopts optstring variable

如果optstring中某个参数需要值,则在参数后追加:,比如:

1
getopts ab:c opt

getopts对于未定义的参数名,也会出现报错,如果想忽略这样的报错则在optstring前追加:

1
getopts :ab:c opt

getopts与getopt看似类似,但是实现逻辑不一样,大抵如下:

  • getopt本质只是将命令行的参数进行规范化,但如果需要提取,本质还是需要对每个变量进行遍历判断。而getopts则不然,它每次只取一个命令行参数,并且对非optstring的内容直接跳过,减少了无用的内容判断。
  • getopt中参数和值的提取,需要通过变量$1、$2,而在getopts中则直接通过访问变量$variable获取参数名,通过$OPTARG获取参数值
  • getopt需要set命令进行转化,而getopts直接可以循环遍历

举个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#getopts循环结束时,它会返回一个大于0的状态码
while getopts ab:c opt
do
case $opt in
a | c ) echo "now is $opt";;
b ) echo "the param now is $opt, value is $OPTARG";;
* ) echo "the file is $opt";;
esac
done

# 执行的结果如下:发现过滤了非参数内容部分test1 wudashuai
bash-3.2$ ./run.sh -ac -b shuai test1 "wu da shuai"
now is a
now is c
the param now is b, value is shuai

仔细观察会发现,在getopts循环中,是不需要shift就可以安然的遍历所有的参数和值的,并且它支持空格间隔的参数值了。

除此之外,getopts在每次遍历的时候还会记录两个特殊的变量:$OPTARG$OPTIND,如果对应的参数有参数值,则直接使用$OPTARG便可以获取,每次循环时,getopts会记录当前的位置$OPTIND(数字)。

1
2
3
4
while getopts ab:c opt
do
echo -n "postion is $OPTIND; key is $opt; value is $OPTIND"
done

输入性传参

还有一种传入参数的方式就是在shell运行过程中传递的参数,这种就需要借助命令:read,read最简单的方式就是:

1
2
3
4
5
# 它会等待用户的输入,然后将输入的结果赋值给variable
read variable

# 比如
read name

如果想要给输入的内容前加上一句友好的提示,则可以使用参数:-p

1
read -p "please inter your name: " name

通过上述的方式,则shell终端会一直等待用户的输入,如果希望加个超时时间,则可以使用参数:-t

1
read -p "please inter your name: " -t 10 name

同样,如果希望用户输入指定长度的文字后就直接自动退出,并将输入的内容传递给变量,则可使用参数:-n

1
read -p "please inter your name: " -t 10 -n 10 name

也可以针对输入的加密内容不显示,可以使用参数:-s

1
read -p "please inter your name: " -s name

然后就是通过read读取文件,但是需要借助cat命令,或者输入重定向。它一次性只读取一行内容,因此要获取全部内容的话,则可以通过循环,

1
2
3
4
cat "file.txt" | while read line
do
echo $line
done