一、客户端输入
脚本执行的过程是对数据变量进行处理的过程,之前在脚本中处理的数据都是静态数据,而不是和客户端交互的动态数据。在大多数情况下脚本执行过程需要和客户端进行交互,用来获得脚本处理的数据,这些数据包括参数和命令行选项等等。下面的内容是采用不同的方式从客户端获取数据。
12.1命令行参数
向脚本传递参数的基本方式是命令行参数,命令行参数是在运行命令时同时传递的参数,如下方式:bash67.sh 60 2
上面的命令将60和2两个参数传递个脚本bash67.sh,在脚本运行过程中通Shell内置的变量可以取得这些命令行参数值,下面通过例子说明。
12.1.1读取命令行参数
在脚本中有一些特殊的内置变量叫做位置参数,当脚本执行时,会将这些特殊的变量分配给从命令行输入的所有参数,这些参数包括脚本的名称,变量的名称从0开始,每个变量前使用$符号,如:$0变量代表脚本的名称,$1变量代表命令行第一个参数,以此类推,$2变量代表第二个参数,$9代表第九个参数,从第十个参数开始采用大括号的方式,${10}代表第十个参数。下面通过例子说明。
例子:#!/bin/bash
# 将命令行参数作为循环的次数
for (( i = 1 ; i <= $1 ; i++ ))
do
echo "Loop $i"
done
控制台显示:$ bash66.sh 5
Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
上面的脚本简单的使用的命令行参数,参数决定了脚本循环的次数,因为就一个参数,对参数的引用使用$1,Shell脚本会将命令行参数自动分配给$1。
在脚本中可以像普通变量一样使用位置变量$1、$2和$0。Shell脚本会自动将命令行参数的值分配给位置变量,如果命令行参数有多个,需要使用空格将参数分开。
例子:#!/bin/bash
# 第一个位置参数,代表执行的脚本,包括目录
echo "\$0: $0"
# 位于脚本后面的第一个参数
echo "\$1: $1"
# 位于脚本后面的第二个参数
echo "\$2: $2"
# 成绩
var=$[ $1 * $2 ]
echo "$1 and $2 product is $var"
控制台显示:$ bash67.sh 60 2
$0: ./bash66.sh
$1: 60
$2: 2
60 and 2 product is 120
命令行参数也可以是文本字符串。
例:bash68.sh:#!/bin/bash
# 判断命令行参数值是否是yarn
if [ "$1" = "yarn" ]
then
echo "User is yarn!"
# 判断命令行参数值是否是root
elif [ "$1" = "root" ]
then
echo "User is root!"
else
echo "User is other!"
fi
控制台显示:$ bash68.sh root
User is root!
$ bash68.sh yarn
User is yarn!
$ bash68.sh hadoop
User is other!
如果命令行参数是多个文本字符串,单个参数中间存在空格,Shell会将这个参数分配成两个变量,如下例子。
例子:#!/bin/bash
# 如果命令行文本参数中存在空格,Shell会分配参数值到多个变量
echo "$1"
echo "$2"
echo "$3"
控制台显示:$ bash69.sh Hello Bash Shell
Hello
Bash
Shell
解决的办法是对存在空格的参数使用双引号或单引号,如下:
控制台显示:$ bash69.sh "Hello Bash Shell"
Hello Bash Shell
$ bash69.sh 'Hello Bash Shell'
Hello Bash Shell
如果命令行参数超过九个,从第十个参数开始引用的方式是使用大括号,如第十一各参数采用${11},第十二个参数${12},以此类推,下面通过例子说明。
例子:#!/bin/bash
# 从1到9采用的方式
echo "\$1: $1"
echo "\$2: $2"
echo "\$3: $3"
echo "\$4: $4"
echo "\$5: $5"
echo "\$6: $6"
echo "\$7: $7"
echo "\$8: $8"
echo "\$9: $9"
# 从10开始采用大括号的方式
echo "\${10}: ${10}"
echo "\${11}: ${11}"
echo "\${12}: ${12}"
控制台显示:$ bash70.sh 1 2 3 4 5 6 7 8 9 10 11 12
$1: 1
$2: 2
$3: 3
$4: 4
$5: 5
$6: 6
$7: 7
$8: 8
$9: 9
${10}: 10
${11}: 11
${12}: 12
12.1.2从命令行参数读取脚本名称
Shell会将命令行的参数自动分配给特殊的位置变量,从$1开始。但是脚本的名称会自动分配给$0
例子:#!/bin/bash
# 将脚本名称保存到变量
filename=$0
# 打印
echo "Shell script name is $filename"
控制台显示:$ bash71.sh
Shell script name is ./bash71.sh
$ /home/yarn/bash01/bash71.sh
Shell script name is /home/yarn/bash01/bash71.sh
如果在脚本所在的目录执行脚本,显示的脚本名称为:./bash71.sh,如果采用完整的脚本名称执行脚本,显示的脚本名称:/home/yarn/bash01/bash71.sh。变量$0保存的脚本名称包括目录名称。在有些情况下我们只需要知道脚本名称而不需要包括目录名。可以通过命令basename来获取脚本名称而不包括路径,通过一个例子说明。
例子:#!/bin/bash
# 通过basename命令取得不带路径的脚本名称
scriptname=`basename $0`
echo "Bash Shell script name is $scriptname"
控制台显示:$ bash72.sh
Bash Shell script name is bash72.sh
$ /home/yarn/bash01/bash72.sh
Bash Shell script name is bash72.sh
另外几个常用的命令:which、dirname和pwd命令,which命令返回完整的脚本名称,包括脚本名称和路径名。dirname命令返回执行脚本的位置或目录。pwd命令返回当前目录完整名称,下面通过例子说明。
例子:#!/bin/bash
# 返回完整的脚本名称
filename=`which $0`
# 返回脚本文件所在的目录
filedir=`dirname $0`
# 返回当前目录名称
dir=`cd $filedir ; pwd`
echo "filename: $filename"
echo "filedir: $filedir"
echo "dir: $dir"
在脚本所在的目录执行脚本。
控制台显示:$ bash73.sh
filename: /home/yarn/bash01/bash73.sh
filedir:.
dir: /home/yarn/bash01
在其他的目录执行脚本。
控制台显示:$ /home/yarn/bash01/bash73.sh
filename: /home/yarn/bash01/bash73.sh
filedir: /home/yarn/bash01
dir: /home/yarn/bash01
12.1.3在脚本中测试命令行参数
在编写带命令行参数的脚本时要注意,如果运行脚本的用户没有提供参数,脚本在执行过程中会提示错误,这样的脚本是存在问题的。好的编程方式是在需要使用命令行参数的位置首先判断参数是否存在或是否为空,如果参数不存在要给出提示信息并终止程序的执行。下面通过例子说明。
例子:#!/bin/bash
# 判断命令行参数是否存在
if [ ! -z "$1" ] && [ ! -z "$2" ]
then
# 如果存在,相乘
product=$[ $1 * $2 ]
else
# 提示信息
echo "Missing parameter!"
# 终止脚本执行
exit 1
fi
# 如果执行了exit命令,后面的命令不会执行
echo "The product of $1 and $2 is $product"
控制台显示:$ bash74.sh 3 4
The product of 3 and 4 is 12
$ bash74.sh
Missing parameter!
12.2系统参数变量
Shell提供了一些特殊的参数变量,他们会保存命令行参数相关的信息,在脚本中会用到这些变量。下面通过例子说明每个变量的使用方式。
12.2.1参数计算
如果命令行参数个数较少,可以采用上面例子的方式对每一个参数进行测试,如果命令行参数个数有多个,那么逐个测试参数就比较繁琐了。Shell提供了另外一个内置变量$#,用来保存命令行参数的个数,注意:$0参数不包含在内。可以通过这个变量判断参数的个数是否符合要求。$#变量可以向普通变量一样在脚本中引用。下面通过一个简单的例子说明$#变量的使用方式。
例子:#!/bin/bash
# 将参数个数赋值给变量
sum=$#
echo "Parameter number is $sum"
控制台显示:$ bash75.sh a b c d e f g
Parameter number is 7
$ bash75.sh 1 2 3
Parameter number is 3
$ bash75.sh
Parameter number is 0
修改上面的例子,通过判断参数的个数来决定脚本是否执行
例子:#!/bin/bash
# 判断命令行参数个数是否等于2
if [ $# -eq 2 ]
then
product=$[ $1 * $2 ]
# 判断命令行参数是否小于2
elif [ $# -lt 2 ]
then
echo "Missing parameter!"
# 终止脚本执行
exit 1
# 判断命令行参数个数是否大于2
elif [ $# -gt 2 ]
then
echo "Parameter number error!"
# 终止脚本执行
exit 1
fi
echo "The product of $1 and $2 is $product"
控制台显示:$ bash76.sh 12 3 22
Parameter number error!
$ bash76.sh
Missing parameter!
$ bash76.sh 12 3
The product of 12 and 3 is 36
另外,变量$#还有一个特殊的用途,因为$#变量保存的是参数的个数,理论上可以通过${$#}来访问最后一个变量,而不需要知道参数的数量。但是Bash Shell脚本中的大括号内不能使用$符,所以使用!号代替大括号内的$符。下面通过例子说明。
例:bash77.sh:#!/bin/bash
# 大括号中不能使用$符,所以输出的结果错误
echo "${$#}"
# 使用!号替代$符
echo "${!#}"
控制台显示:$ bash77.sh a b c d
5897
d
$ bash77.sh 23
5898
23
$ bash77.sh
5900
./bash77.sh
可以看出${$#}的输出不正确,而${!#}的输出正确,输出的是最后一个参数值。但是需要注意的是当脚本没有命令行参数时,${!#}输出的是带目录的脚本名称。
12.2.2取得所有的参数
对于命令行参数还可以采用其他的方式访问,一次性全部读取然后循环遍历。Shell提供了两个特殊变量$*和$@对命令行参数快速访问。Shell会将所有的命令行参数分配给这两个变量,通过这两个变量都可以循环访问命令行所有的参数。那么这两个变量有什么区别呢!$*变量将命令行提供的所有参数当成一个完整的单词保存,这个完整的单词包含所有的命令行参数,$*变量会将这个完整的单词当做一个对象或一个参数而不是多个对象或单词。不同的是,$@变量会将命令行提供的所有参数当成同一个字符串中的多个独立的单词,允许使用for循环对每个参数进行遍历。下面通过几个例子说明变量$*和$@区别是使用方式。
第一个例子只是简单的将两个变量的值输出到控制台,通过例子可以看到两个变量都将命令行的所有参数都保存到了变量中。
例子:#!/bin/bash
# 输出变量$*到控制台
echo "\$*: $*"
# 输出变量$@到控制台
echo "\$@: $@"
控制台显示:$ bash78.sh a b c d e f g
$*: a b c d e f g
$@: a b c d e f g
从这个例子中好像看不到两个参数的区别,都将命令行所有的参数都保存到了变量中。下面的例子采用for循环遍历两个变量的参数,注意:在for循环中没有使用双引号,注意有什么区别。
例:bash79.sh:#!/bin/bash
# 计数器
count=1
# 循环遍历变量$*
for var1 in $*
do
echo "\$* $count: $var1"
# 计数器加一
count=$[$count + 1 ]
done
# 计数器复位
count=1
# 循环遍历变量$@
for var2 in $@
do
echo "\$@ $count: $var2"
# 计数器加一
count=$[ $count + 1 ]
done
控制台显示:$ bash79.sh a b c d e f g
$* 1: a
$* 2: b
$* 3: c
$* 4: d
$* 5: e
$* 6: f
$* 7: g
$@ 1: a
$@ 2: b
$@ 3: c
$@ 4: d
$@ 5: e
$@ 6: f
$@ 7: g
从上面的例子可以看出,如果两个变量在for循环中没有加双引号,从输出可以看出两个变量没有区别。下面的例子中在for循环中将两个变量加上双引号,注意输出的变化。
例子:#!/bin/bash
# 计数器
count=1
# 循环遍历变量$*
for var1 in "$*"
do
echo "\$* $count: $var1"
# 计数器加一
count=$[$count + 1 ]
done
# 计数器复位
count=1
# 循环遍历变量$@
for var2 in "$@"
do
echo "\$@ $count: $var2"
# 计数器加一
count=$[ $count + 1 ]
done
控制台显示:$ bash80.sh a b c d e f g
$* 1: a b c d e f g
$@ 1: a
$@ 2: b
$@ 3: c
$@ 4: d
$@ 5: e
$@ 6: f
$@ 7: g
从输出可以看出,$*变量只循环了一次,将所有的参数当成一个完整的单词,而$@变量循环多次,将所有的命令行参数逐个输出到控制台。
通过上面的例子可以看出,$*变量将所有的命令行参数当成一个参数,而$@变量会单独处理每一个参数,通过for循环可以变量命令行参数。
12.2.3命令行参数的移动
Shell提供了一个命令shift,这个命令可以将命令行参数向左移动,将左面的参数覆盖。这种方式处理命令行参数较为常用,不需要循环遍历命令行参数。
使用shift命令时,命令行参数的个数会自动减一,参数$4的值会移动到$3,$3参数的值会移动到$2,参数$2的值移动到$1,而$1参数的值被删除,注意:变量$0的值不会改变。这个命令的好处是在代码中不需要循环遍历命令行参数,并且在代码中只需要访问$1,每执行一次shift命令,后面的参数会填充$1变量。下面通过一个简单的例子说明shift命令的基本用法。
例子:#!/bin/bash
# 保存参数的个数
count=$#
# 循环
for (( i = 1 ; i <= $count ; i++ ))
do
# 输出参数个数和变量$1的值
echo "\$#: $# \$1: $1"
# 左移一位参数,变量$2的值填充变量$1,第一个参数被清除
# 命令行参数的个数减一
shift
done
# 输出$0变量的值,$0变量的值不会改变
echo "\$0: $0"
控制台显示:$ bash81.sh a b c d e f g
$#: 7 $1: a
$#: 6 $1: b
$#: 5 $1: c
$#: 4 $1: d
$#: 3 $1: e
$#: 2 $1: f
$#: 1 $1: g
$0: ./bash81.sh
命令shift执行后,参数个数减一并且变量$1的值会被后面的值依次填充。
下面的例子说明shift命令在脚本中的常用方式。
例子:#!/bin/bash
# 计数器
count=1
# 循环,判断变量$1是否非空
while [ -n "$1" ]
do
echo "count: $count \$1: $1"
# 计数器加一
count=$[ $count + 1 ]
# 左移一位
shift
done
控制台显示:$ bash82.sh a b c d e f g
count: 1 $1: a
count: 2 $1: b
count: 3 $1: c
count: 4 $1: d
count: 5 $1: e
count: 6 $1: f
count: 7 $1: g
注意:引用变量时最好加上双引号。
命令shift还可以使用参数,如:shift 2。
例子:#!/bin/bash
count=1
while [ -n "$1" ]
do
echo "count: $count \$1: $1"
count=$[ $count + 1 ]
# 左移两位,$3的值移动到$1
shift 2
done
控制台显示:$ bash83.sh 1 2 3 4 5 6 7 8
count: 1 $1: 1
count: 2 $1: 3
count: 3 $1: 5
count: 4 $1: 7
shift命令后面的参数是一次移动几位,上面的例子的参数是2,$3移动到$1,后面的参数向前移动两位,以此类推,变量$1和变量$2的值清除。需要注意的是被清除的参数不可恢复。
12.3读取用户输入
命令行参数是和用户端交互的一种方式,但有些情况下,脚本执行过程中需要和客户端进行交互,如需要客户端输入选项,Bash Shell中提供了read命令,可以从控制台读取客户端输入。
12.3.1从控制台读取输入
read命令接受标准输入(键盘)的输入,在接受输入后read命令会将数据放入一个变量,下面通过一个例子说明read命令的基本用法。
例子:#!/bin/bash
# 输出提示
echo -n "Please input your name: "
# 读取输入并保存到变量name中
read name
echo -n "Please input your age: "
read age
echo -n "Please input your address: "
read address
echo -n "Please input your sex: "
read sex
echo -n "Please input your class: "
read class
echo "-------------------"
echo "Your name is $name"
echo "Your sex is $sex"
echo "Your age is $age"
echo "Your address is $address"
echo "Your class is $class"
echo "-------------------"
控制台显示:$ bash84.sh
Please input your name: zhangsan
Please input your age: 23
Please input your address: taiyuan
Please input your sex: nan
Please input your class: 5
-------------------
Your name is zhangsan
Your sex is nan
Your age is 23
Your address is taiyuan
Your class is 5
-------------------
echo命令的参数-n移除字符串后面的换行符。另外,read命令会将提示符后面的输入保存为一个变量,如下例子。
例子:#!/bin/bash
# 提示输入
echo -n "Please input parameter: "
# 将所有的输入保存到一个变量
read parameter
echo "-------------------"
echo "Parameter value is $parameter"
echo "-------------------"
控制台显示:$ bash85.sh
Please input parameter: Hello Bash Shell
-------------------
Parameter value is Hello Bash Shell
-------------------
read命令的另一种用法。
例:bash86.sh:#!/bin/bash
# 使用-p参数的read命令方式,变量在提示信息的后面
read -p "Please input your name: " name
read -p "Please input your age: " age
read -p "Please input your address: " address
read -p "Please input your sex: " sex
read -p "Please input your class: " class
echo "-------------------"
echo "Your name is $name"
echo "Your sex is $sex"
echo "Your age is $age"
echo "Your address is $address"
echo "Your class is $class"
echo "-------------------"
控制台显示:$ bash86.sh
Please input your name: zhangsan
Please input your age: 22
Please input your address: taiyuan
Please input your sex: nan
Please input your class: 5
-------------------
Your name is zhangsna
Your sex is nan
Your age is 22
Your address is taiyuan
Your class is 5
-------------------
指定多个变量的read命令,在使用read命令时可以指定多个变量,将用空格分隔的值存入多个变量中,如果输入的值的数量超过变量的数量,会将多余的值分配给最后一个变量,通过下面的例子说明。
例:bash87.sh:#!/bin/bash
# 所有的变量在read命令提示信息的后面,使用空格分开
read -p "Please input your info -name -sex -age -address -class: " name sex age address class
echo "-------------------"
echo "Your name is $name"
echo "Your sex is $sex"
echo "Your age is $age"
echo "Your address is $address"
echo "Your class is $class"
echo "-------------------"
控制台显示:$ bash87.sh
Please input your info -name -sex -age -address -class: zhangsan nan 23 taiyuan 5 others
-------------------
Your name is zhangsan
Your sex is nan
Your age is 23
Your address is taiyuan
Your class is 5 others
-------------------
每个输入的值对应一个变量,当输入的值的数量多于变量的数量,多余的值会保存到最后一个变量中。变量和值都是以空格分隔的。
当read命令没有指定变量时,会将输入的值保存到变量REPLY中,注意:如果再有输入会将变量REPLY之前的值覆盖,所以如果有多个输入时可以将变量REPLY的值保存到一个用户变量中。
例:bash88.sh:#!/bin/bash
# 提示输入
echo -n "Please input your name: "
# 如果read命令没有指定变量名,会默认保存到REPLY变量中
read
name=$REPLY
echo -n "Please input your age: "
read
age=$REPLY
echo -n "Please input your address: "
read
address=$REPLY
echo -n "Please input your sex: "
read
sex=$REPLY
echo -n "Please input your class: "
read
class=$REPLY
echo "-------------------"
echo "Your name is $name"
echo "Your sex is $sex"
echo "Your age is $age"
echo "Your address is $address"
echo "Your class is $class"
echo "-------------------"
控制台显示:$ bash88.sh
Please input your name: zhangsan
Please input your age: 23
Please input your address: taiyuan
Please input your sex: nan
Please input your class: 6
-------------------
Your name is zhangsan
Your sex is nan
Your age is 23
Your address is taiyuan
Your class is 6
-------------------
环境变量REPLY会保存一次输入的所有数据,如果存在其他的输入,将覆盖之前保存的值。
read命令会中断脚本执行并等待用户的输入,如果用户长时间没有输入,脚本将一直等待下去。read命令提供了参数-t,可以指定超时时间,单位是秒。如果超过了设定的时间,read命令将返回一个非0的退出码,如果输入没有超时,read命令返回的退出码为0。可以使用if语句判断read语句是否正确执行并给出提示信息,如下代码。
例:bash89.sh:#!/bin/bash
# 使用if语句,如果read命令正确执行,返回的退出码为0,执行then后面的命令
# 如果超时,read命令返回非0的退出码,执行else语句
if read -t 5 -p "Please input your username and password: " username password
then
echo "Login success : $username"
else
# 换行
echo
echo "Login timeout..."
fi
控制台显示:$ bash89.sh
Please input your username and password: yarn 123
Login success : yarn
$ bash89.sh
Please input your username and password:
Login timeout...
read命令还可以指定输入字符的个数,用来控制用户的输入,-n 3参数告诉read命令接受三个字符并保存到变量中后自动退出,不需要按回车键,下面通过例子说明。
例:bash90.sh:#!/bin/bash
# 指定输入字符上限为三个
read -n 3 -p "Please input ON/OFF: " action
# 当输入on或NO时执行then语句
if [ $action = "on" ] || [ $action = "ON" ]
then
echo "The system is start-up!"
# 当输入off或OFF时执行then语句
elif [ $action = "off" ] || [ $action = "OFF" ]
then
# 输出换行
echo
echo "The system is closed!"
# 输入其他的字符,提示输入错误
else
echo
echo "Input error!"
fi
控制台显示:$ bash90.sh
Please input ON/OFF: on
The system is start-up!
$ bash90.sh
Please input ON/OFF: off
The system is closed!
$ bash90.sh
Please input ON/OFF: ote
Input error!
使用case语句实现上面脚本的功能。
例:bash91.sh:#!/bin/bash
read -n 3 -p "Please input ON/OFF: " action
case $action in
on | ON)
echo "The system is start-up!"
;;
off | OFF)
echo
echo "The system is closed!"
;;
*)
echo
echo "Input error!"
;;
esac
read命令的-s参数可以使用户的输入隐藏,通常和用户交互时需要用户输入密码时会用到,用户的输入在控制台不显示但会保存到变量中,引用变量可以取得输入的值。通过下面的例子说明。
例:bash92.sh:#!/bin/bash
read -p "Please input login username: " username
# 参数-s可以使输入隐藏,通常输入密码时用到
read -s -p "Please input login password: " password
# 判断用户名和密码是否非空
if [ -n "$username" ] && [ -n "$password" ]
then
# 判断用户名和密码是否正确
if [ $username = "yarn" ] && [ $password = "123" ]
then
echo
echo "User: $username Login success!"
else
echo
echo "Sorry login failed!"
fi
else
echo
echo "Please input your username and password!"
fi
使用read命令可以从Linux系统上的文件中读取数据,使用cat命令将输出通过管道传递给read命令并通过while循环将文件中的数据读取,循环一次读取文件中的一行数据。当文件中没有数据时,read命令会返回一个非零的退出码。下面通过例子说明。
例:bash93.sh:#!/bin/bash
# 保存字段分隔符默认值
IFS_bak=$IFS
# 设置字段分隔符为制表符
IFS=$'\t'
# 使用cat命令输出文件内容到read命令
cat file08 | while read line
do
echo "One line of data: $line"
# 循环显示一行的数据
for var in $line
do
echo "One column of data: $var"
done
done
# 还原字段分隔符默认值
IFS=$IFS_bak
控制台显示:…
One line of data: 1501010005 444.25 1624680 1 04/05/15 11:40:51 1005 User009 108 1 308
One column of data: 1501010005
One column of data: 444.25
One column of data: 1624680
One column of data: 1
One column of data: 04/05/15 11:40:51
One column of data: 1005
One column of data: User009
One column of data: 108
One column of data: 1
One column of data: 308
通常情况下使用read命令可以读取配置文件中的配置信息。