之前一直没有涉及程序接收和处理命令行选项及实参的能力,本节将介绍程序访问命令行内容的shell功能
shell提供了一组名为位置参数的变量,用于存储命令行中的关键字,这些变量分别命名0~9,可以通过以下方式访问这些变量
#!/bin/bash
# posit-param:script to view command line parameters
echo "
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
"
在没有任何命令行实参的情形下执行此脚本,运行结果如下
$0 = a.sh
$1 =
$2 =
$3 =
$4 =
$5 =
$6 =
$7 =
$8 =
$9 =
可以看到,即便没有任何的命令行实参,变量$0总是会存储所执行程序所在的文件名。
!!!使用参数扩展技术,用户实际可以获取多于9个的位置参数,为标明一个大于9的数字,需要将数字用大括号括起来,例如${10}、${56}、${256}
shell还提供了变量 $# 以给出命令行参数的个数。
#!/bin/bash
# posit-param:script to view command line parameters
echo "
Number of arguments:$#
\$0 = $0
\$1 = $1
\$2 = $2
\$3 = $3
\$4 = $4
\$5 = $5
\$6 = $6
\$7 = $7
\$8 = $8
\$9 = $9
\$10 = ${10}
"
($0 :表示执行脚本名称;$# :表示命令行参数个数)
#!/bin/bash
# posit-param2:script to display all arguments
count=1
while [[ $# -gt 0 ]];do
echo "Argument $count: $1"
count=$((count+1))
shift
done
shift提供了一种机制,每次执行shift命令后,所有的参数值均“下移一位”。即变量$2的值会赋给变量$1,而$3的值则会赋给变量$2,以此类推,全部“下移一位”,并且变量$#的值同时减1。
通过shift命令,我们只需要处理一个参数即可,即$1。
以上脚本,创建了一个循环,只要还存在1个实参,循环就不停止。首先输出当前的实参,其次,每次循环迭代一次,就将变量count值加1,代表处理过的实参数目。最后,执行shift命令使得$1载入下一个实参的值。
#!/bin/bash
# file_info:simple file information program
PROGNAME=$(basename $0)
while [[ $# -gt 0 ]];do
if [[ -e $1 ]];then
echo -e "\nFile Type:"
file $1
echo -e "\nFile Status:"
stat $1
else
echo "$PROGNAME: usage:$PROGNAME file" >&2
exit 1
fi
shift
done
列出指令路径下实参(路径下的各类文件)的文件类型(file命令)和文件状态(stat命令)。
如同位置参数可以向脚本传递实参一样,位置参数也可以用于shell函数实参的传递。
file_info(){
# file_info:functiono to display file information
if [[ -e $1 ]];then
echo -e "\nFile Type:"
file $1
echo -e "\nFile Status:"
stat $1
else
echo "$FUNCNAME: usage: $FUNCNAME file" >&2
return 1
fi
}
函数的返回状态用return,如果不是函数则用exit命令。 tips
我们不仅可以在普通脚本中传递位置参数给函数,也可以在.bashrc文件中维护使用位置参数的函数。
需要注意的是,PROGNAME变量被换成了名为FUNCNAME的shell变量。shell会自动更新FUNCNAME以追踪当前执行的shell函数。
$0变量表示的是执行脚本的脚本文件名称;$FUNCNAME变量用于函数中,表示的是所在函数的名称。注意区别。
有时候我们会把所有位置参数当做一个整体来处理。
shell为这项功能提供了两种特殊的参数。两种参数都能扩展为一个完整的位置参数列,但是又有着微妙的区别。
参数 | 描述 |
$* | 可扩展为从1开始的位置参数列。当包括在双引号内时,扩展为双引号引用的由全部位置参数构成的字符串,每个位置参数以IFS shell变量的第一个字符(默认情况下为空格)间隔开 |
$@ | 可扩展为从1开始的位置参数列。当包含在双引号内时,将每个位置参数扩展为双引号引用的单独单词 |
#!/bin/bash
# posit-params3:script to demonstrate $* and $@
print_params(){
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$4 = $4"
}
pass_params(){
echo -e "\n" '$* :';print_params $*
echo -e "\n" '"$*" :';print_params "$*"
echo -e "\n" '$@ :';print_params $@
echo -e "\n" '"$@" :';print_params "$@"
}
pass_params "word" "words with spaces"
执行结果
$* :
$1 = word
$2 = words
$3 = with
$4 = spaces
"$*" :
$1 = word words with spaces
$2 =
$3 =
$4 =
$@ :
$1 = word
$2 = words
$3 = with
$4 = spaces
"$@" :
$1 = word
$2 = words with spaces
$3 =
$4 =
"$*" 和 "$@" 都产生了包含4个单词的结果。"$*"产生的是只包含一条字符串的结果,即word words with spaces。"$@"产生的则是包含了两条字符串的结果,即word和words with spaces。
因为"$@"保持了每个位置参数的完整性,没有破坏位置参数的结构,所以"$@"是大多数情况下的使用格式!
在位置参数的帮助下,用户可以写出功能性更强的脚本。通常,对于重复性的任务,位置参数使得用户可以写出很有帮助的shell函数,并放置在用户的.bashrc文件中。
回到系统信息报告生成器脚本,如下
#!/bin/bash
# sys_info_page: program to output a system information page
PROGNAME=$(basename $0)
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME ,by $USER"
report_uptime () {
cat <<- _EOF_
System Uptime
$(uptime)
_EOF_
return
}
report_disk_space () {
cat <<- _EOF_
Disk Space Utilization
$(df -h)
_EOF_
return
}
report_home_space () {
if [[ $(id -u) -eq 0 ]];then
cat <<- _EOF_
Home Space Utilization(All Users)
$(du -sh /home/*)
_EOF_
else
cat <<- _EOF_
Home Space Utilization ($USER)
$(du -sh $HOME)
_EOF_
fi
return
}
usage () {
echo "$PROGNAME: usage: $PROGNAME [-f file| -i ]"
return
}
write_html_page () {
cat <<- _EOF_
$TITLE
$TITLE
$TIME_STAMP
$(report_uptime)
$(report_disk_space)
$(report_home_space)
_EOF_
return
}
# process command line option
interactive=
filename=
while [[ -n $1 ]];do
case $1 in
-f | --file) shift
filename=$1
;;
-i | --interactive) interactive=1
;;
-h | --help) usage
exit
;;
*) usage >&2
exit 1
;;
esac
shift
done
# interactive mode
if [[ -n $interactive ]];then
while true;do
read -p "Enter name of output file: " filename
if [[ -e $filename ]];then
read -p "'$filename' exists. Overwrite? [y/n/q] > "
case $REPLY in
Y|y) break
;;
Q|q) echo "Program terminated."
exit
;;
*) continue
;;
esac
elif [[ -z $filename ]];then
continue
else
break
fi
done
fi
# output html page
if [[ -n $filename ]];then
if touch $filename && [[ -f $filename ]];then
write_html_page > $filename
else
echo "$PROGNAME:Cannot write file '$filename'" >&2
exit 1
fi
else
write_html_page
fi
需要多看以上脚本,理解与学习其编写脚本的设计思想。