linux shell(下)

参数输入

传递参数

向 shell 脚本传递数据的最基本方法是使用命令行参数

$ ./addem 10 30

bash shell 会将所有的命令行参数都指派给称作位置参数(positional parameter)的特殊变量。位置变量的名称都是标准数字:$0 对应脚本名,$1 对应第一个命令行参数,$2 对应第二个命令行参数,以此类推,直到$9。

#!/bin/bash
# Using one command-line parameter
#
factorial=1
for (( number = 1; number <= $1; number++ ))
do
	factorial=$[ $factorial * $number ]
done
	echo The factorial of $1 is $factorial
exit
$ ./positional1.sh 5

参数之间是以空格分隔的,要想在参数值中加入空格,必须使用引号(单引号或双引号均可)。

可以使用位置变量$0 获取在命令行中运行的 shell 脚本名,这里有一个潜在的问题。如果使用另一个命令来运行 shell 脚本,则命令名会和脚本名混在一起;如果运行脚本时使用的是绝对路径,那么位置变量$0 就会包含整个路径。

basename 命令可以返回不包含路径的脚本名

#!/bin/bash
# Using basename with the $0 command-line parameter
#
name=$(basename $0)
#
echo This script name is $name.
exit
特殊参数

特殊变量$#含有脚本运行时携带的命令行参数的个数。你可以在脚本中的任何地方使用这个特殊变量,就跟普通变量一样:

#!/bin/bash
# Counting command-line parameters
# $# 表示参数个数(不包含$0)
if [ $# -eq 1 ]
then
	fragment="parameter was"
else
	fragment="parameters were"
fi
echo $# $fragment supplied.
exit

$*变量和$@变量可以轻松访问所有参数,它们各自包含了所有的命令行参数。

$*变量会将所有的命令行参数视为一个单词。这个单词含有命令行中出现的每一个参数。基本上,$*变量会将这些参数视为一个整体,而不是一系列个体。

$@变量会将所有的命令行参数视为同一字符串中的多个独立的单词,以便你能遍历并处理全部参数。这通常使用 for 命令完成。

#!/bin/bash
# Testing different methods for grabbing all the parameters
#
echo
echo "Using the \$* method: $*"
echo
echo "Using the \$@ method: $@"
echo
exit

shift命令会根据命令行参数的相对位置进行移动。在使用shift命令时,默认情况下会将每个位置的变量值都向左移动一个位置。因此,变量$3的值会移入$2,变量$2的值会移入$1,而变量$1的值则会被删除。

这是遍历命令行参数的另一种好方法,尤其是在不知道到底有多少参数的时候。

#!/bin/bash
## Shifting through the parameters
echo
echo "Using the shift method:"
count=1
while [ -n "$1" ] # 检查参数长度是否大于0
do
       echo "Parameter #$count = $1"
       count=$[ $count + 1 ]
       shift
done
echo
处理选项

处理简单的选项,可以用shift实现

#!/bin/bash
# Extract command-line options
#
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

分离参数和选项:在 Linux 中,处理这个问题的标准做法是使用特殊字符将两者分开,该字符会告诉脚本选项何时结束,普通参数何时开始,这个特殊字符是双连字符(–)。

#!/bin/bash
# Extract command-line options and parameters
#
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

在遇到双连字符时,脚本会用 break 命令跳出 while 循环。由于提前结束了循环,因此需要再加入另一个 shift 命令来将双连字符移出位置变量。

$ ./extractoptionsparams.sh -a -b -c – test1 test2 test3

使用getopt命令

getopt 命令在处理命令行选项和参数时非常方便。它能够识别命令行参数,简化解析过程,格式如下:

getopt optstring parameters

optstring 是这个过程的关键所在。它定义了有效的命令行选项字母,还定义了哪些选项字母需要参数值。

首先,在 optstring 中列出要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后面加一个冒号。getopt 命令会基于你定义的 optstring 解析提供的参数。

$ getopt ab:cd -a -b BValue -cd test1 test2
# (out)-a -b BValue -c -d -- test1 test2
$

optstring 定义了 4 个有效选项字母:a、b、c 和 d。冒号(:)被放在了字母 b 后面,因为 b 选项需要一个参数值。

注意,它会自动将-cd 分成两个单独的选项,并插入双连字符来分隔命令行中额外的参数。

可以在脚本中使用 getopt 命令来格式化脚本所携带的任何命令行选项或参数,getopt 命令生成的格式化版本替换已有的命令行选项和参数,这得求助于set 命令。

set 命令有一个选项是双连字符(–),可以将位置变量的值替换成 set 命令所指定的值。

具体做法是将脚本的命令行参数传给 getopt 命令,然后再将 getopt 命令的输出传给 set命令,用 getopt 格式化后的命令行参数来替换原始的命令行参数,如下所示:

set -- $(getopt -q ab:cd "$@") # getopt 的-q 选项可以忽略错误消息
#!/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

$ ./extractwithgetopt.sh -ac

$ ./extractwithgetopt.sh -c -d -b BValue -a test1 test2

使用getopts命令

getopt 与 getopts 的不同之处在于,前者在将命令行中选项和参数处理后只生成一个输出,而后者能够和已有的 shell 位置变量配合默契。

getopts 每次只处理一个检测到的命令行参数。在处理完所有的参数后,getopts 会退出并返回一个大于 0 的退出状态码。这使其非常适合用在解析命令行参数的循环中。getopts 命令的格式如下:

getopts optstring variable

optstring 值与 getopt 命令中使用的值类似。有效的选项字母会在 optstring 中列出,如果选项字母要求有参数值,就在其后加一个冒号。不想显示错误消息的话,可以在 optstring之前加一个冒号。getopts 命令会将当前参数保存在命令行中定义的 variable 中。

#!/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

$ ./extractwithgetopts.sh -ab BValue -c

while 语句定义了 getopts 命令,指定要查找哪些命令行选项,以及每次迭代时存储它们的变量名(opt)。getopts 命令会移除起始的连字符,所以在 case 语句中不用连字符。

选项标准化

在 Linux 中,有些选项字母在某种程度上已经有了标准含义。如果能在 shell 脚本中支持这些选项,则你的脚本会对用户更友好。

选项描述
-a显示所有对象
-c生成计数
-d指定目录
-e扩展对象
-f指定读入数据的文件
-h显示命令的帮助信息
-i忽略文本大小写
-l产生长格式输出
-n使用非交互模式(批处理)
-o将所有输出重定向至指定的文件
-q以静默模式运行
-r递归处理目录和文件
-s以静默模式运行
-v生成详细输出
-x排除某个对象
-y对所有问题回答 yes

用户输入

read命令从标准输入(键盘)或另一个文件描述符中接受输入。获取输入后,read命令会将数据存入变量。下面是该命令最简单的用法:

#!/bin/bash
## Using the read command
echo -n "Enter your name: "
read name
echo "Hello $name, welcome to my script."
exit

read 命令也提供了-p 选项,允许直接指定提示符:

#!/bin/bash
# Using the read command with the -p option
#
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That means you are over $days days old!"
exit

-t 选项会指定 read 命令等待输入的秒数。如果计时器超时,则 read 命令会返回非 0 退出状态码:

#!/bin/bash
# Using the read command with a timer
#
if read -t 5 -p "Enter your name: " name
then
	echo "Hello $name, welcome to my script."
else
	echo
	echo "Sorry, no longer waiting for name."
fi
exit	
#!/bin/bash
# Using the read command for one character
#
read -n 1 -p "Do you want to continue [Y/N]? " answer
#
case $answer in
Y | y) echo
	echo "Okay. Continue on...";;
N | n) 
	echo
	echo "Okay. Goodbye"
	exit;;
esac
echo "This is the end of the script."
exit

本例中使用了-n 选项和数值 1,告诉 read 命令在接收到单个字符后退出。只要按下单个字符进行应答,read 命令就会接受输入并将其传给变量,无须按 Enter 键。

无显示读取

有时需要从脚本用户处得到输入,但又不想在屏幕上显示输入信息(比如密码)。

-s 选项可以避免在 read 命令中输入的数据出现在屏幕上(其实数据还是会被显示,只不过 read 命令将文本颜色设成了跟背景色一样)

#!/bin/bash
# Hiding input data
#
read -s -p "Enter your password: " pass
echo
echo "Your password is $pass"
exit
从文件中读取

可以使用 read 命令读取文件。每次调用 read 命令都会从指定文件中读取一行文本。需要借助cat命令:

#!/bin/bash
# Using the read command to read a file
#
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

脚本控制

处理信号

Linux 利用信号与系统中的进程进行通信。Linux 系统和应用程序可以产生超过 30 个信号。下面表格列出了在 shell 脚本编程时会遇到的最常见的 Linux 系统信号。

信号描述
1SIGHUP挂起(hang up)进程
2SIGINT中断(interupt)进程
3SIGQUIT停止(stop)进程
9SIGKILL无条件终止(terminate)进程
15SIGTERM尽可能终止进程
18SIGCONT继续运行停止的进程
19SIGSTOP无条件停止,但不终止进程
20SIGTSTP停止或暂停(pause),但不终止进程

在默认情况下,bash shell 会忽略收到的任何 SIGQUIT(3)信号和 SIGTERM(15)信号(因此交互式 shell 才不会被意外终止)。但是,bash shell 会处理收到的所有 SIGHUP(1)信号和SIGINT(2)信号。

bash shell 允许使用键盘上的组合键来生成两种基本的 Linux 信号。这个特性在需要停止或暂停失控脚本时非常方便。

  • 中断进程 :Ctrl+C 组合键会生成 SIGINT 信号,并将其发送给当前在 shell 中运行的所有进程。
  • 暂停进程:Ctrl+Z 组合键会生成 SIGTSTP 信号,停止 shell 中运行的任何进程。停止(stopping)进程跟终止(terminating)进程不同,前者让程序继续驻留在内存中,还能从上次停止的位置继续运行。

trap 命令可以指定 shell脚本需要侦测并拦截的 Linux 信号。如果脚本收到了 trap 命令中列出的信号,则该信号不再由shell 处理,而是由本地处理。

trap 命令的格式如下:

trap commands signals

在 trap 命令中,需要在 commands 部分列出想要 shell 执行的命令,在 signals 部分列出想要捕获的信号(多个信号之间以空格分隔)。指定信号的时候,可以使用信号的值或信号名。

#!/bin/bash
#Testing signal trapping
#
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
#
echo This is a test script.
#
count=1
while [ $count -le 5 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done

要想在脚本中的不同位置进行不同的信号捕获处理,只需重新使用带有新选项的 trap 命令即可:

#!/bin/bash
#Modifying a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
#
trap "echo ' I have modified the trap!'" SIGINT  # 修改捕获信号
#
count=1
while [ $count -le 3 ]
do
    echo "Second Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
#
exit

也可以移除已设置好的信号捕获。在 trap 命令与希望恢复默认行为的信号列表之间加上两个连字符即可

#!/bin/bash
#Removing a set trap
#
trap "echo ' Sorry...Ctrl-C is trapped.'" SIGINT
#
count=1
while [ $count -le 3 ]
do
    echo "Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
#
trap -- SIGINT # 恢复默认的信号捕获
echo "The trap is now removed."
#
count=1
while [ $count -le 3 ]
do
    echo "Second Loop #$count"
    sleep 1
    count=$[ $count + 1 ]
done
#
exit

也可以在 trap 命令后使用单连字符来恢复信号的默认行为。单连字符和双连字符的效果一样。

后台运行脚本

以后台模式运行 shell 脚本非常简单,只需在脚本名后面加上&即可:

$ ./backgroundscript.sh &

在非控制台下运行脚本

有时候,即便退出了终端会话,也想在终端会话中启动 shell 脚本,让脚本一直以后台模式运行到结束。这可以用 nohup 命令来实现。

nohup 命令能阻断发给特定进程的 SIGHUP 信号。当退出终端会话时,这可以避免进程退出。

nohup ./testAscript.sh &

作业控制

作业控制包括启动、停止、“杀死”以及恢复作业。通过作业控制,能完全控制 shell 环境中所有进程的运行方式。

jobs 是作业控制中的关键命令,该命令允许用户查看 shell 当前正在处理的作业。

#!/bin/bash
#Testing job control
#
echo "Script Process ID: $$" # $$变量来显示 Linux 系统分配给该脚本的 PID
#
count=1
while [ $count -le 5 ]
do
    echo "Loop #$count"
    sleep 10
    count=$[ $count + 1 ]
done
#
echo "End of script..."
exit

$ jobs # 可以查看 当前运行的脚本

选项描述
-l列出进程的 PID 以及作业号
-n只列出上次 shell 发出通知后状态发生改变的作业
-p只列出作业的 PID
-r只列出运行中的作业
-s只列出已停止的作业
定时运行作业

at 命令允许指定 Linux 系统何时运行脚本。该命令会将作业提交到队列中,指定 shell 何时运行该作业。at 的守护进程 atd 在后台运行,在作业队列中检查待运行的作业。

atd 守护进程会检查系统的一个特殊目录(通常位于/var/spool/at 或/var/spool/cron/atjobs),从中获取 at 命令提交的作业。在默认情况下,atd 守护进程每隔 60 秒检查一次这个目录。如果其中有作业,那么 atd 守护进程就会查看此作业的运行时间。如果时间跟当前时间一致,就运行此作业。

at 命令的基本格式非常简单:

at [-f filename] time

在默认情况下,at 命令会将 STDIN 的输入放入队列。你可以用-f 选项指定用于从中读取命令(脚本文件)的文件名。

time 选项指定了你希望何时运行该作业。如果指定的时间已经过去,那么 at 命令会在第二天的同一时刻运行指定的作业。指定时间的方式非常灵活。at 命令能识别多种时间格式。

  • 标准的小时和分钟,比如 10:15。
  • AM/PM 指示符,比如 10:15 PM。
  • 特定的时间名称,比如 now、noon、midnight 或者 teatime(4:00 p.m.)。
  • 标准日期,比如 MMDDYY、MM/DD/YY 或 DD.MM.YY。
  • 文本日期,比如 Jul 4 或 Dec 25,加不加年份均可。
  • 时间增量:
    • Now + 25 minutes
    • 10:15 PM tomorrow
    • 10:15 + 7 days

atq 命令可以查看系统中有哪些作业在等待

调度需要定期运行的脚本

Linux 系统使用 cron 程序调度需要定期执行的作业。cron 在后台运行,并会检查一个特殊的表(cron 时间表),从中获知已安排执行的作业。

cron 时间表通过一种特别的格式指定作业何时运行,其格式如下:

minutepasthour hourofday dayofmonth month dayofweek command

cron 时间表允许使用特定值、取值范围(比如 1~5)或者通配符(星号)来指定各个字段。如果想在每天的 10:15 运行一个命令,可以使用如下 cron 时间表字段:

15 10 * * * /home/christine/backup.sh > backup.out

每个用户(包括root用户)都可以使用自己的cron时间表运行已安排好的任务。Linux提供了crontab命令来处理cron时间表。要列出已有的cron时间表,可以用-l选项:

$ crontab -l

在默认情况下,用户的cron时间表文件并不存在。可以使用-e选项向cron时间表添加字段。在添加字段时crontab命令会启动一个文本编辑器。

如果创建的脚本对于执行时间的精确性要求不高,则用预配置的cron脚本目录会更方便。预配置的基础目录共有4个:hourly、daily、monthly和weekly。

$ ls /etc/cron.*ly

anacron 程序

cron 程序唯一的问题是它假定 Linux 系统是 7×24 小时运行的。除非你的 Linux 运行在服务器环境,否则这种假设未必成立。

如果某个作业在 cron 时间表中设置的运行时间已到,但这时候 Linux 系统处于关闭状态,那么该作业就不会运行。当再次启动系统时,cron 程序不会再去运行那些错过的作业。为了解决这个问题,许多 Linux 发行版提供了 anacron 程序。

anacron 程序只处理位于 cron 目录的程序,比如/etc/cron.monthly。它通过时间戳来判断作业是否在正确的计划间隔内运行了。每个 cron 目录都有一个时间戳文件,该文件位于/var/spool/anacron:

$ ls /var/spool/anacron

anacron 时间表的基本格式和 cron 时间表略有不同:

period delay identifier command

period 字段定义了作业的运行频率(以天为单位)。anacron 程序用该字段检查作业的时间戳文件。delay 字段指定了在系统启动后,anacron 程序需要等待多少分钟再开始运行错过的脚本

$ cat /etc/anacrontab

# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
HOME=/root
LOGNAME=root
# These replace cron's entries
1 5 cron.daily run-parts --report /etc/cron.daily
7 10 cron.weekly run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly run-parts --report /etc/cron.monthly
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值