1 处理用户输入
1.1传递参数
向 shell 脚本传递数据的最基本方法是使用命令行参数。命令行参数允许运行脚本时在命令行中添加数据:
./addem 10 30
将文本字符串作为参数传递时,引号并不是数据的一部分,仅用于表明数据的起止位置。
./addem "a b"
1.2 读取参数
bash shell 会将所有的命令行参数都指派给称作位置参数(positional parameter)的特殊变量。
这也包括shell脚本名称。位置变量的名称都是标准数字:$0对应脚本名,$1对应第一个命令行参数,$2对应第二个命令行参数,以此类推,直到$9。
1.3 读取脚本名
可以使用位置变量$0获取在命令行中运行的shell脚本名。
#!/bin/bash
echo This script name is $0.
exit
这里有一个潜在的问题。如果使用另一个命令来运行 shell脚本,则命令名会和脚本名混
在一起,出现在位置变量$0中:
./positional0.sh
This script name is ./positional0.sh.
如果运行脚本时使用的是绝对路径,那么位置变量$0 就会包含整个路径
$HOME/scripts/positional0.sh
This script name is /home/christine/scripts/positional0.sh.
basename命令可以返回不包含路径的脚本名
name=$(basename $0)
2 特殊参数变量
2.1 参数统计
特殊变量$#含有脚本运行时携带的命令行参数的个数
变量${!#}获取最后一个位置参数
当命令行中没有任何参数时,$#的值即为0,但${!#}会返回命令行中的脚本名
2.2 获取所有的数据
$*变量和$@变量可以轻松访问所有参数,它们各自包含了所有的命令行参数。
$*变量会将所有的命令行参数视为一个单词。这个单词含有命令行中出现的每一个参数。
基本上,$*变量会将这些参数视为一个整体,而不是一系列个体。
$@变量会将所有的命令行参数视为同一字符串中的多个独立的单词,以便你能遍历并处理全部参数。这通常使用for命令完成。
#!/bin/bash
echo "Using the \$* method: $*"
count=1
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done
echo
echo "Using the \$@ method: $@"
count=1
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done
echo
exit
3 移动参数
bash shell 工具箱中的另一件工具是 shift 命令,该命令可用于操作命令行参数。跟字面上的意思一样,shift命令会根据命令行参数的相对位置进行移动。
在使用 shift 命令时,默认情况下会将每个位置的变量值都向左移动一个位置。因此,变量$3的值会移入s2,变量s2的值会移入s1,而变量s1的值则会被删除(注意,变量s0的值也就是脚本名,不会改变)。
这是遍历命令行参数的另一种好方法,尤其是在不知道到底有多少参数的时候。你可以只操作第一个位置变量,移动参数,然后继续处理该变量
#!/bin/bash#
echo "Using the shift method:"
count=1
while [ -n "$1" ]
do
echo "Parameter #$count=$1"
count=$[ $count +1 ]
shift
done
echo
exit
使用shift命令时要小心。如果某个参数被移出,那么它的值就被丢弃了,无法再恢复。
另外,也可以一次性移动多个位置。只需给shift 命令提供一个参数,指明要移动的位置数即可
#!/bin/bash
echo "The original parameters: $*"
echo "Now shifting 2..."
shift 2
echo "Here's the new first parameter: $1"
exit
4 处理选项
4.1 处理简单选项
在提取单个参数时,使用case语句来判断某个参数是否为选项
#!/bin/bash
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
*) echo "$1 is not an option" ;;
esac
shift
done
echo
exit
./extractoptions.sh -a -b -c -d
case 语句会检查每个参数,确认是否为有效的选项。找到一个,处理一个。
无论选项在命令行中以何种顺序出现,这种方法都能应对
4.2 分离参数和选项
在Linux中,这个特殊字符是双连字符(--)。shell会用双连字符表明选项部分结束。在双连字符之后,脚本就可以放心地将剩下的部分作为参数处理了
要检查双连字符,只需在case语句中加一项即可
#!/bin/bash
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) echo "Found the -b option" ;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
echo
exit
4.3 处理含值的选项
#!/bin/bash
# Extract command-line options and values
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param=$2
echo "Found the -b option with parameter value $param"
shift;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
在这个例子中,case语句定义了3个要处理的选项。-b选项还需要一个额外的参数值。由于要处理的选项位于$1,因此额外的参数值就应该位于$2(因为所有的参数在处理完之后都会
被移出)。只要将参数值从$2变量中提取出来就可以了。当然,因为这个选项占用了两个位置,所以还需要使用shift命令多移动一次。
4.4 合并多个选项
getopt
#!/bin/bash
# Extract command-line options and values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param=$2
echo "Found the -b option with parameter value $param"
shift;;
-c) echo "Found the -c option" ;;
--) shift
break;;
*) echo "$1 is not an option" ;;
esac
shift
done
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
getopt 命令并不擅长处理带空格和引号的参数值。
getopts
#!/bin/bash
# Extract command-line options and values with getopts
#
echo
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option with parameter value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
exit
5 获取用户输入
5.1 基本的读取
read 命令从标准输入(键盘)或另一个文件描述符中接受输人。获取输入后,read 命令会将数据存人变量。
#!/bin/bash
echo -n"Enter your name:
read name
echo "Hello Sname,welcome tomy script."
exit
非常简单。注意,用于生成提示的echo命令使用了-n选项。该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据。这让脚本看起来更像表单。
实际上,read命令也提供了-p选项,允许直接指定提示符
#!/bin/bash
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That means you are over $days days old!"
exit
也可以在read命令中不指定任何变量,这样read命令便会将接收到的所有数据都放进特殊环境变量REPLY中
#!/bin/bash
read -p "Enter your name: "
echo
echo "Hello $REPLY, welcome to my script."
exit
5.2 超时
用-t选项来指定一个计时器。-t选项会指定read命令等待输入的秒数。如果计时器超时,则read命令会返回非0退出状态码
#!/bin/bash
if read -t 5 -p "Enter your name: " name
then
echo "Hello $name, welcome to my script."
else
fi
exit
5.3 无显示读取
-s 选项可以避免在read 命令中输入的数据出现在屏幕上(其实数据还是会被显示,只不过read命令将文本颜色设成了跟背景色一样)
#!/bin/bash
read -s -p "Enter your password: " pass
echo
echo "Your password is $pass"
exit
5.4 从文件中读取
#!/bin/bash
count=1
cat $HOME/scripts/test.txt | while read line
do
echo "Line $count: $line"
count=$[ $count + 1 ]
done
echo "Finished processing the file."
exit
6 呈现数据
6.1 理解输入和输出
Linux 系统会将每个对象当作文件来处理,这包括输入和输出。Linux 用文件描述符来标识每个文件对象。文件描述符是一个非负整数,唯一会标识的是会话中打开的文件。
每个进程一次最多可以打开9个①文件描述符。出于特殊目的,bash shell保留了前3个文件描述符(0、1和2)
文件描述符 缩 写 描 述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误
6.2 只重定向错误
ls -al badfile 2> test4
6.3 重定向错误消息和正常输出
ls -al test test2 test3 badtest 2> test6 1> test7
6.4 特殊的重定向
将STDERR和STDOUT的输出重定向到同一个文件。为此,bash shell提供了特殊的重定向符&>
ls -al test test2 test3 badtest &> test7
等价于
ls -al test test2 test3 > test7 2>&1
将错误输出重定向到标准输出
6.5 临时重定向
#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"
./test8 2> test9
6.6 永久重定向
如果脚本中有大量数据需要重定向,那么逐条重定向所有的echo语句会很烦琐。这时可以用exec命令,它会告诉shell在脚本执行期间重定向某个特定文件描述符
#!/bin/bash
# redirecting all output to a file
exec 1>testout
echo "This is a test of redirecting all output"
echo "from a script to another file."
echo "without having to redirect every individual line"
exec 命令会启动一个新 shell 并将 STDOUT 文件描述符重定向到指定文件。脚本中送往STDOUT 的所有输出都会被重定向。
6.7 创建自己的重定向
在脚本中重定向输入和输出时,并不局限于这3个默认的文件描述符。前文提到过,在shell
中最多可以打开9个文件描述符。替代性文件描述符从3到8共6个,均可用作输入或输出重定向。
这些文件描述符中的任意一个都可以分配给文件并用在脚本中。
创建输出文件描述符
可以用exec命令分配用于输出的文件描述符。和标准的文件描述符一样,一旦将替代性文件描述符指向文件,此重定向就会一直有效,直至重新分配。
#!/bin/bash
# using an alternative file descriptor
exec 3>test13out
echo "This should display on the monitor"
echo "and this should be stored in the file" >&3
echo "Then this should be back on the monitor"
也可以不创建新文件,而是使用exec命令将数据追加到现有文件
exec 3>>test13out
6.8 重定向文件描述符
#!/bin/bash
# storing STDOUT, then coming back to it
exec 3>&1
exec 1>test14out
echo "This should store in the output file"
echo "along with this line."
exec 1>&3
echo "Now things should be back to normal"
第一个exec命令将文件描述符3重定向到了文件描述符1(STDOUT)的当前位置,也就是显示器。这意味着任何送往文件描述符3 的输出都会出现在屏幕上。
第二个exec命令将STDOUT重定向到了文件,shell现在会将发送给STDOUT的输出直接送往该文件。但是,文件描述符3仍然指向STDOUT原先的位置(显示器)。如果此时将输出数据发送给文件描述符3,则它仍然会出现在显示器上,即使STDOUT已经被重定向了。
向STDOUT(现在指向一个文件)发送一些输出之后,第三个exec命令将STDOUT重定向到了文件描述符3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又恢复如初了,即指向其原先的位置——显示器。
6.9 创建读/写文件描述符
#!/bin/bash
# testing input/output file descriptor
exec 3<> testfile
read line <&3
echo "Read:$line"
echo "This is atest line" >&3
在这个例子中,exec命令将文件描述符3用于文件testfile的读和写。接下来,使用分配好的文件描述符,通过read命令读取文件中的第一行,然后将其显示在STDOUT中。最后,使用echo 语句将一行数据写入由同一个文件描述符打开的文件中。
在运行脚本时,一开始还算正常。输出内容表明脚本读取了 testfile 文件的第一行。但如果在脚本运行完毕后查看testfile文件内容,则会发现写入文件中的数据覆盖了已有数据。
当脚本向文件中写入数据时,会从文件指针指向的位置开始。read命令读取了第一行数据,这使得文件指针指向了第二行数据的第一个字符。当echo语句将数据输出到文件时,会将数据写入文件指针的当前位置,覆盖该位置上的已有数据。
6.10 关闭文件描述符
如果创建了新的输入文件描述符或输出文件描述符,那么 shell会在脚本退出时自动将其关闭。然而在一些情况下,需要在脚本结束前手动关闭文件描述符。
要关闭文件描述符,只需将其重定向到特殊符号&-即可。
exec 3>&-
#!/bin/bash
# testing closing file descriptors
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3
一旦关闭了文件描述符,就不能在脚本中向其写入任何数据,否则shell会发出错误消息。
在关闭文件描述符时还要注意另一件事。如果随后你在脚本中打开了同一个输出文件,那么shell 就会用一个新文件来替换已有文件。这意味着如果你输出数据,它就会覆盖已有文件。
#!/bin/bash
# testing closing file descriptors
exec 3> test17file
echo "This is a test line of data" >&3
exec 3>&-
cat test17file
exec 3> test17file
echo "This'll be bad" >&3
在向test17file 文件发送了字符串并关闭该文件描述符之后,脚本会使用cat 命令显示文件
内容。到这一步的时候,一切都还好。接下来,脚本重新打开了该输出文件并向它发送了另一个
字符串。在显示该文件的内容时,你能看到的就只有第二个字符串。shell覆盖了原来的输出文件。
6.11 抑制命令输出
ls -al > /dev/null
cat /dev/null > testfile
文件 testfile 仍然还在,但现在是一个空文件。这是清除日志文件的常用方法
6.12 记录消息
有时候,也确实需要将输出同时送往显示器和文件。与其对输出进行两次重定向,不如改用特殊的tee命令
tee 命令就像是连接管道的 T型接头,它能将来自 STDIN 的数据同时送往两处。一处STDOUT,另一处是tee命令行所指定的文件名。
#!/bin/bash
# using the tee command for logging
tempfile=test22file
echo "This is the start of the test" | tee $tempfile
echo "This is the second line of the test" | tee -a $tempfile
echo "This is the end of the test" | tee -a $tempfile