通常情况下,为了让Shell脚本更灵活、应用更广泛、具备多种不同行为,会在编写脚本时接收一些命令行参数,通过命令行参数指定脚本中变量的值使得脚本执行不同的操作。
当脚本只接收一个命令行参数,并且根据参数的不同采取不同行为时,通常使用case语句来处理。
编写脚本文件nginx.sh。(前提时源码安装了nginx服务,源码安装nginx服务不提供service启动脚本)
[root@nginx mnt]# cat nginx.sh # chkconfig: - 85 15 # description: nginx is a World Wide Web server. It is used to serve #加载function库,使用function种的status、killproc等函数 . /etc/rc.d/init.d/functions #加载网络配置文件 . /etc/sysconfig/network #检查网络是否启动,如果不能直接退出 [ "$NETWORKING" = "no" ] && exit 0 #定义变量 nginx="/usr/sbin/nginx" prog=$(basename $nginx) NGINX_CONF_FILE="/etc/nginx/nginx.conf" lockfile=/var/lock/subsys/nginx #如果有该文件,加载进来 test -f "/etc/sysconfig/nginx" && . /etc/sysconfig/nginx #测试nginx主配置文件是否有语法错误 configtest() { $nginx -t } #启动函数 start() { #检查关键文件是否存在,二进制脚本和配置文件 [ -x $nginx ] || exit 5 [ -f $NGINX_CONF_FILE ] || exit 6 #创建pid存放目录,如果该目录不存在才创建 mkdir -p /var/run/nginx #使用daemon函数便利nginx主配置文件,并启动 echo -n $"Starting $prog:" daemon $nginx -c $NGINX_CONF_FILE retval=$? echo [ $retval -eq 0 ] && touch $lockfile return $retval } stop() { #停止函数 echo $"Stopping $prog: " killproc $prog -QUIT retval=$? echo [ $retval -eq 0 ] && rm -f $lockfile return $retval } restart() { #重启函数 configtest || return $? stop sleep 3 start } reload() { #不重启情况下,重新加载配置文件 configtest || return $? echo -n $"Reloading $prog: " killproc $nginx -HUP RETVAL=$? echo } #强制重启 force_reload() { restart } case "$1" in start) start ;; stop) stop ;; restart) restart ;; reload) reload ;; status) status $prog [ $? -eq 0 ] && echo -n `configtest` ;; force_reload) restart ;; *) echo "Usage:$prog {start|stop|restart|reload|force_reload|status}" exit 1 ;; esac
注意:
如果要添加脚本service启动,必须包含如下两行
# chkconfig: - 85 15 # description: nginx is a World Wide Web server. It is used to serve
里面使用了linux内置函数status和killproc
添加到service列表中
[root@nginx mnt]# cp nginx.sh /etc/rc.d/init.d/nginx [root@nginx mnt]# chmod +x /etc/rc.d/init.d/nginx [root@nginx mnt]# chkconfig --add nginx
测试
[root@nginx mnt]# service nginx status nginx.service - SYSV: nginx is a World Wide Web server. It is used to serve Loaded: loaded (/etc/rc.d/init.d/nginx) Active: active (running) since Sun 2018-07-08 10:55:29 CST; 6min ago Process: 4781 ExecStop=/etc/rc.d/init.d/nginx stop (code=exited, status=0/SUCCESS) Process: 4803 ExecStart=/etc/rc.d/init.d/nginx start (code=exited, status=0/SUCCESS) CGroup: /system.slice/nginx.service ├─4808 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf └─4810 nginx: worker process Jul 08 10:55:29 nginx systemd[1]: Starting SYSV: nginx is a World Wide Web server. It is...e... Jul 08 10:55:29 nginx nginx[4803]: Starting nginx:[ OK ] Jul 08 10:55:29 nginx systemd[1]: Started SYSV: nginx is a World Wide Web server. It is ...rve. Hint: Some lines were ellipsized, use -l to show in full. nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful [root@nginx mnt]# service nginx restart Restarting nginx (via systemctl): [ OK ] [root@nginx mnt]# service nginx we Usage:nginx {start|stop|restart|reload|force_reload|status}
# 检测一个二进制可运行程序是否运行: # 使用方法: status [-p pidfile] {program} status() { local base pid pid_file= # Test syntax. # 测试调用该函数时的参数格式。 if [ "$#" = 0 ] ; then echo $"Usage: status [-p pidfile] {program}" # 如果调用该函数没有给一个参数的话就显示:该函数的使用方法,然后直 #接退出。退出状态码:1 return 1 fi if [ "$1" = "-p" ]; then pid_file=$2 shift 2 fi base=${1##*/} # 使用$base 记录二进制可运行程序的文件名。 # First try "pidof" __pids_var_run "$1" "$pid_file" # 调用函数:__pids_var_run 来检测二进制程序的运行状态。 RC=$? if [ -z "$pid_file" -a -z "$pid" ]; then # 如果调用 status时,没有传递pidfile文件,并且没有检测到PID号 pid="$(__pids_pidof "$1")" # 因为有些二进制可运行程序,是不会把PID写到 # /var/run/$base.pid文件当中的。就调用函数__pids_pidof来查找PID号 fi #################################################################### if [ -n "$pid" ]; then # 如果查到二进制运行程序的PID号,就代表该该二进制程序是运行的。 echo $"${base} (pid $pid) is running..." return 0 fi case "$RC" in # 根据函数:__pids_var_run返回的状态码,来判断二进制程序的运行状态。 0) echo $"${base} (pid $pid) is running..." return 0 ;; 1) echo $"${base} dead but pid file exists" # 如果函数:__pids_var_run返回的状态码是:1 ,的话.... return 1 ;; esac ##################################################################### # See if /var/lock/subsys/${base} exists # ***锁文件。 if [ -f /var/lock/subsys/${base} ]; then echo $"${base} dead but subsys locked" return 2 fi echo $"${base} is stopped" return 3
# 该函数的作用是关闭进程的. # 用法: # killproc [-p pidfile] [-d delay][-signal] # -p: 指定进程的pid文件,一般在/var/run/xxx.pid # -d: 指定延迟多长时间就强度关闭进程 # -signal: 关闭进程的信号 # A function to stop a program. killproc() { local RC killlevel= base pid pid_file= delay # 定义局部变量,只在函数体内有效。 RC=0; delay=3 # 默认延迟3秒,不能正常关闭就强制关闭 # Test syntax. # 判断用户调用该函数时,给的参数格式。并使用变量记录下来 if [ "$#" -eq 0 ]; then echo $"Usage: killproc [-p pidfile] [ -d delay] {program} [-signal]“ # 如果调用该函数时,不使用任何参数的话将给用户输出使用该函数的使用方法。 return 1 # 退出该函数。 fi if [ "$1" = "-p" ]; then # 如果参数1,是字符 -p 的话,那么用户输入的第二个参数就是: # 程序的pid_file文件。通常是/var/run/XXX.pid pid_file=$2 shift 2 fi if [ "$1" = "-d" ]; then delay=$2 # 记录的参数的作用是用来。如果进程在正常情况下不能关闭, # 延迟多长时间就向该进程发送SIGKILL信号强制关闭。 shift 2 fi # check for second arg to be kill level # 检查用户是否指定函数关闭进程时,所使用的信号。 [ -n "${2:-}" ] && killlevel=$2 # Save basename. # 保存进程的二进制运行文件的。 base=${1##*/} # 参数替换,把程序的二进制文件的路径名***掉。 #################################################################### # 查找可运行二进制程序的进程号PID 部分 # Find pid. __pids_var_run "$1" "$pid_file" #调用 函数:__pids_var_run 来检查用户指定的程序的进程号。 # 该函数会使用一个变量:PID记录程序的进程号的。 if [ -z "$pid_file" -a -z "$pid" ]; then # 如果pidfile文件不存在,并且找不到进程的pid号。 # 就使用该方法查找该程序$base的PID号。 pid="$(__pids_pidof "$1")" fi ##################################################################### # Kill it. # 找到二进制可运行程序的进程号,是如果关闭进程的 if [ -n "$pid" ] ; then # 如果$PID 不为空,证明进程运行的。就执行 then 后的内容 [ "$BOOTUP" = "verbose" -a -z "${LSB:-}" ] && echo -n "$base " if [ -z "$killlevel" ] ; then #如果用户没有指定函数要使用关闭进程的信号的话,就执行 then后的内容 if checkpid $pid 2>&1; then # 调用函数:checkpid 来判断进程号为:$PID的进程是否 #运行。其实 checkpid是判断$PID,如果伪目录/proc #下是否有一个目录名和$PID相同 # TERM first, then KILL if not dead kill -TERM $pid >/dev/null 2>&1 # 给进程$PID发送 -15信号。等待进程关闭资源后, # 再杀死该进程。 usleep 100000 #等待100000 微秒 if checkpid $pid && sleep 1 && #再使用函数checkpid检查,如果进程还运行的话, #再等待1秒, checkpid $pid && sleep $delay && #再使用函数checkpid检查,如果进程还运行的话, #就使用用户给的延迟时间,再等待 checkpid $pid ; then #在用户给的延时时间到了,再次使用函数 #checkpid检查,如果进程还是运行的 # 话,就强制关闭。 kill -KILL $pid >/dev/null 2>&1 # 给进程$PID发送 -9 信号,强制关闭该进程。 usleep 100000 fi fi checkpid $pid # 再次确认下,进程$PID是否是关闭的。 RC=$? # 记录函数checkpid的返回结果。 # 如果是:0 的话就代表关闭进程$PID成功。 [ "$RC" -eq 0 ] && failure $"$base shutdown" || success $"$base shutdown" # 判断函数checkpid的返回值,给用户输出进程是否关闭成功的信 RC=$((! $RC)) # 对变量RC进行,非操作,避免下面使用该变量时,判断有误。 # use specified level only #调用函数:killproc时传递有关闭进程的信号就执行else 后的程序 else # 如果用户给函数killproc 传递了关闭进程所使用的信号, #就执行else后的代码 # 作用想要通知进程重读配置文件时, 可以传递一个 -HUP 信号给进程。 if checkpid $pid; then #使用函数checkpid检查,进程$PID是否运行的, kill $killlevel $pid >/dev/null 2>&1 #使用用户传递关闭进程的信号来关闭进程$PID RC=$? #记录函数checkpid的返回结果。 [ "$RC" eq 0 ] && success $"$base $killlevel" || failure $"$base $killlevel" # 根据$RC判断进程$PID是否关闭成功 elif [ -n "${LSB:-}" ]; then RC=7 # Program is not running fi fi ###########################找不到二进制可运行程序的进程号 else if [ -n "${LSB:-}" -a -n "$killlevel" ]; then RC=7 # Program is not running else failure $"$base shutdown" RC=0 fi fi # Remove pid file if any. if [ -z "$killlevel" ]; then rm -f "${pid_file:-/var/run/$base.pid}" fi return $RC }
如果命令行有多个参数时,可以使用shift命令在一个变量中一个接一个地获取多个命令行参数。
shift时Bash的一个内部命令,用于将传递的参数变量左移。语法如下:
shift [n]
其中,n必须是一个小于等于"#"的非负整数。如果n为0,位置参数将不会改变。如果没有指定n,默认为1.如果n大于$#或小于0,此命令的返回状态码将大于1,否则为0。
比如,使用shift 1命令,位置参数将会如此移动:
$1 <- $2 $2 <- $3 ... $n-1 <- $n
原本的$1将会丢失。因为$1丢失所以特殊变量$#的值也会发生变化。
如:
#如果命令行参数个数不为0,则继续while循环,否则退出循环 while [ $# -ne 0 ] #如果$1的值不为空,则进入while循环 #while test -n "$1" do #打印$1的值,和特殊变量$#的值 echo "Current Parameter \$1: $1,Remaining $#." #然后可以将$1传递到别的函数进行其他操作 #Pass $1 to some bash function ir do whatever #左移一位 shift done
执行脚本结果:
[root@rs1 shift]# bash getCml.sh fsx coco tom kobe Current Parameter $1: fsx,Remaining 4. Current Parameter $1: coco,Remaining 3. Current Parameter $1: tom,Remaining 2. Current Parameter $1: kobe,Remaining 1.
有一种情况:如,当执行的shift 5时,有可能传递的参数个数不能被5整除,如传递7个参数,当左移一次后,到达第六个参数($6),而剩余两个参数($#==2),这样将不会继续执行,但是$1不为空,$#的值不为0,就会使得while进行无限循环下去。
为了解决这个问题,可以使用shift命令的退出状态。通过shift命令后添加变量$?获取其值是否为0可以判断shift命令是否被执行。如果$?不为0,那么就终止执行。
[root@rs1 shift]# cat shift5.sh #如果命令行参数个数不为0,则继续while循环,否则退出循环 while test -n "$1" do #打印$1的值,和特殊变量$#的值 echo "Current Parameter \$1: $1,Remaining $#." #然后可以将$1传递到别的函数进行其他操作 #Pass $1 to some bash function ir do whatever #左移五位 shift 5 #如果上一条命令执行结果不为0,则执行if语句 if [ $? -ne 0 ] then #终止执行 break fi done
执行脚本:
[root@rs1 shift]# bash -x shift5.sh a b c d e f g + test -n a + echo 'Current Parameter $1: a,Remaining 7.' Current Parameter $1: a,Remaining 7. + shift 5 + '[' 0 -ne 0 ']' //此处$?为0,则不进入if语句内部 + test -n f + echo 'Current Parameter $1: f,Remaining 2.' Current Parameter $1: f,Remaining 2. + shift 5 + '[' 1 -ne 0 ']' //此处#?为1,则进入if语句内部,执行break + breakS
当命令行参数个数过多时,除了使用shift命令之外,还可以使用for循环一个接一个的处理所有命令行参数。
如:
[root@rs1 for]# cat listarg.sh #定义变量E_BADARGS E_BADARGS=65 #如果特殊变量$1为空,则打印脚本使用方法,并以退出码65退出脚本 if [ ! -n "$1" ] then #打印脚本的使用方法到标准输出 echo "Usage: `basename $0` argument1 argument2 ..." #退出脚本,状态码为65 exit $E_BADARGS fi #定义变量index index=1 #打印双引号种的内容到标准输出 echo "Listing args wiht \"$*\":" #使用for循环遍历整个特殊变量$*的值 for arg in "$*" do #打印输出变量index和arg的值及相应内容到标准输出 echo "Arg $index = $arg" #将index值加一 let index+=1 done #重新将变量index值设置为1 index=1 #打印双引号内容到标准输出 echo "Listing args with \"$@\":" #使用for循环遍历特殊变量$@的值 for arg in "$@" do #打印输出变量index和arg的值及相应内容到标准输出 echo "Arg $index = $arg" #将变量index值加一 let index+=1 done
执行脚本:
[root@rs1 for]# bash listarg.sh fsx coco kobe Listing args wiht "$*": //如果$*加了"",则其中内容会被当成一个字符串。$*不加""则会由IFS隔开,被认为成多个字符串 Arg 1 = fsx coco kobe Listing args with "$@": Arg 1 = fsx Arg 2 = coco Arg 3 = kobe
注意:"$*"和$*的区别——$*如果加了双引号,即"$",其值将被扩展为包含所有位置参数的值的单个字符串,for循环只迭代一次。
特殊变量$0的值是当前Shell脚本的名称,即可以通过$0读取脚本名。
在编写Shell时,为了使得脚本更加严谨,减少脚本运行时可能产生的异常错误,在脚本开头,一般都需要编写一段与脚本相关的环境或变量进行检查代码。如:检查指定给的脚本的命令行参数个数是否符合脚本定义,如果不符合,则直接打印一条关于脚本的命令行参数使用方法的信息到标准输出,并退出脚本。
如:
[root@rs1 for]# cat check.sh #定义脚本参数个数 ARGS=3 #如果指定给脚本的参数个数不为3,则进入if语句 if test $# -ne "$ARGS" then #打印使用方法 echo "Usage:`basename $0` param1 param2 param3." #退出脚本 exit 2 fi
执行脚本:
[root@rs1 for]# bash check.sh //参数个数不对 Usage:check.sh param1 param2 param3. [root@rs1 for]# bash check.sh 1 2 3 //参数个数正确 [root@rs1 for]# bash check.sh 1 2 3 4 //参数个数不对 Usage:check.sh param1 param2 param3.
为了使脚本更加严谨,防止运行中由于参数错误产生的异常,一般还会检查参数的值,如指定的参数时文件或者目录,还会检查其是否存在、是否可执行等。
如:
[root@rs1 for]# cat test2.sh #定义脚本的参数个数 ARGS=2 if test $# -ne "$ARGS" then echo "Usage: `basename $0` param1 filename" exit 2 fi #将第一个命令行参数赋值给变量varStr varStr=$1 #如果$2存在,则执行if语句,否则执行else语句 if [ -f "$2" ] then echo "$0 I Like You But Just Like You." >> $2 cat "$2" else echo "File \"$2\" does not exist." exit 3 fi
执行脚本:
[root@rs1 for]# bash test2.sh //没有参数,提示usage Usage: test2.sh param1 filename [root@rs1 for]# bash test2.sh 1 //只有一个参数,提示usage Usage: test2.sh param1 filename [root@rs1 for]# bash test2.sh 1 2 //有两个参数,但是第二个文件不存在,提示文件不存在 File "2" does not exist. [root@rs1 for]# touch 2 //创建了一个文件,文件名为2 [root@rs1 for]# bash test2.sh 1 2 //执行没有问题 test2.sh I Like You But Just Like You. [root@rs1 for]# cat 2 test2.sh I Like You But Just Like You.