Linux(四)Shell程序设计

发布于 2024-03-30  124 次阅读


Shell 是 UNIX/Linux 系统中用户与系统交互的接口。它除了作为命令解释器以外,还是一种高级程序设计语言,利用 shell 程序设计语言可以把命令有机地组合在一起,形成功能强大、使用灵活、交互能力强,但代码简单的新命令工具。使用 UNIX/Linux 的用户可以通过编写 shell 程序来设计适合自己的新功能,这样有利于提高用户管理使用 UNIX/Linux 系统的工作效率。

4.1 Shell概述

4.1.1 Shell简介

Shell 的功能

通常人们所理解的 shell 是指 UNIX/Linux 的命令解释器,其主要功能是:接受用户输入的命令进行分析,创建子进程,由子进程实现命令所规定的功能,等子进程终止后发出提示符。而实际上 Shell 有两层含义,其中一个是作为命令解释程序,另一个是作为一种程序设计语言。Shell 程序设计可以简单地理解成DOS/Windows 下的批处理,但它远比批处理要强大,shell 编程有很多 C 语言和其他编程语言的特性,例如都有变量和各种控制语句等,然而又没有其他编程语言那么复杂。

Shell 的主要版本

目前,Shell 是 UNIX/Linux 系统的标准组成部分,正如 UNIX 的版本众多一样,Shell 也产生了很多个版本,经过多年的发展和完善,现在流行的 Shell 主要有 sh(Bourne Shell)、bash(Bourne Shell)、csh(C-Shell)、ksh(Korn Shell)。

  • sh

sh 由 Steve Bourne 开发,是 Bourne Shell 的缩写,sh 是 Unix 标准默认的shell。

  • bash

bash是 Linux 标准默认的 shell。bash 由 Brian Fox 和 Chet Ramey 共同完成,是 BourneAgain Shell 的缩写,内部命令一共有40

个。bash 完全兼容 sh,也就是说,用 sh 写的脚本可以不加修改的在bash中执行。

Linux使用它作为默认的shell是因为它有诸如以下的特色:

  1. 可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。
  2. 自动通过查找匹配的方式给出以某字符串开头的命令。
  3. 包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。
  • ash

ash shell 是由Kenneth Almquist编写的,Linux中占用系统资源最少的一个小shell,它只包含24个内部命令,因而使用起来很不方便。

  • csh

csh 是Linux比较大的内核,它由以William Joy为代表的共计47位作者编成,共有52个内部命令。该shell其实是指向/bin/tcsh这样的一个shell,也就是说,csh其实就是tcsh。

  • ksh

ksh 是Korn shell的缩写,由Eric Gisin编写,共有42条内部命令。该shell最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以

在不用花钱购买商业版本的情况下尝试商业版本的性能了。

本教程所介绍的 shell 即是 Bash 版本的 shell,其他版本的 shell 与之类似,区别不大。

脚本语言与编译型语言的差异

编译型语言: 很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将写好的源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。运行程序时,直接读取目标代码(object code)。由于编译后的目标代码(object code)非常接近计算机底层,因此执行效率很高,这是编译型语言的优点。但是,由于编译型语言多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象,往往实现一个简单的功能需要大量复杂的代码。例如,在C++里,就很难进行“将一个目录里所有的文件复制到另一个目录中”之类的简单操作。

脚本语言: 脚本语言是一种通过解释执行的语言。这类语言通常需要一个特定的解释器来解释执行。执行这类程序时,解释器(interpreter)需要读取我们编写的源代码(source code),并将其转换成目标代码(object code),再由计算机运行。因为每次执行程序都多了编译的过程,因此效率有所下降。使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。脚本语言非常的便捷,可以花很短的时间完成很多复杂的功能。脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。

第一个Shell程序

有一天,我们突然想干这么一件事:先看一下当前的目录位置,再看一下主目录下有没有vim的配置文件,再打印一下日历,再输出一下今天当前登录的用户,最后看一下当前网络的配置情况,那么我们就可以写出下面的脚本文件:

test1.sh

pwd
ls -a ~/.vimrc
cal
who
ifconfig

然后通过下面这行命令运行该脚本文件

sh test1.sh

通过上述例子,我们可以发现,shell可以将所有需要执行的命令按照类似于编程的方法写到一个文件中,当需要使用的时候,只需要运行这个文件即可完成指定的任务,我们局的这个例子知识方便我们理解Shell的本质,它主要用于将一些简单的工具命令进行组合,实现更为复杂的功能。

4.1.2 Shell脚本的建立与执行

Shell脚本的建立

建立脚本文件的方式与前面建立普通文本文件的方式相同,这里不再赘述。

在Shell脚本中,使用#来做程序注释,但是要注意,#!并不是注释的意思,#!/bin/bash用来声明使用/bin目录下的bash解释器来解释执行脚本。

test2.sh

#!/bin/bash
​
echo "Please Enter Your Name:"
read PERSON
echo "Hello, $PERSON"

这里read用于获取用户输入,其实如果有C++、Java等编程语言的学习经理,上述程序理解起来十分容易。

对于 shell 脚本文件的命名没有任何限制,只要符合 UNIX/Linux 下的文件命令规则即可。Shell脚本文件的默认后缀名是 .sh。在 UNIX/Linux 中不以文件的后缀名来区分的文件类型,所以在为 shell 脚本文件命名时不需要添加额外的后缀名。当然,也可以为你的 shell 脚本文件加上 .sh的后缀名,这样一些编辑器会为文件添加语法高亮显示,这样看起来比较方便。

Shell脚本的执行方式

执行Shell脚本的方式一般有下面三种:

1.输入定向的执行方式

这种方式是用输入重定向的方式让 Shell 从给定文件中读入命令行并进行相应的处理,其格式为:

sh < script-name

sh实际上是一个软链接,它代表的是系统默认的 shell 解释器,用 ls -lwhich sh` 命令可以查看,例如在我的系统上 sh 就指向的是dash。既然说到这儿,我就简单说说 bash 与 dash 区别。dash` 是轻量级的 shell 解释器,他比 bash 少了很多功能,但速度比 bash 快,例如变量的很多扩展功能 dash 就不支持。所以在编写脚本时,一定要注意文件头部申明的解释器类型,否则脚本程序可能无法正常运行

2.以脚本名作为Shell参数的执行方式

这种方式是脚本名作为 Shell 命令的参数。利用该方式则可以在执行命令时向脚本传递参数。其格式为:

sh script-name [parameter]
3.改为可执行权限后的直接执行方式

给 shell 脚本添加可执行权限后变可以直接执行。添加可执行权限可以用如下命令:

chmod a+x script-name

在执行脚本时需要加上路径,也就是要让系统找到脚本文件的位置。例如在当前目录下则可用如下方式执行:

./script-name

如果想让编写的 Shell 脚本像 Shell 提供的命令一样为每个用户使用(即在任何位置直接输入命令即可执行),可以在编写好的 shell 脚本上为所有用户添加可执行权限后,将其放在命令搜索路径的目录之下(环境变量 PATH 所包含的路径,可以用 echo $PATH命令查看),这样就等于为系统开发了一个新的命令工具。

Shell 接收用户输入的 shell 命令和脚本名进行分析。如果文件被标记有可执行权限,但不是被编译过的程序,就认为它是一个 shell 脚本。Shell 将读取其中的内容,并加以解释执行。

Shell脚本的执行流程

默认情况下,shell 脚本的执行是通过创建子进程来完成的。首先,交互式 shell(bash) fork/exec 一个子 shell(sh)用于执行脚本,父进程 bash 等待子进程 sh 终止。接着 sh 读取脚本中的内容执行,直到读到文件尾,sh终止。Sh 终止后,bash 继续执行,打印提示符等待用户输入。

如果将命令行下输入的命令用括号括起来,那么也会 fork 出一个子 shell 执行小括号中的命令,一行中可以输入由分号隔开的多个命令,例如:

(cd ..; ls -l)

当执行以上命令后你会发现,虽然执行了 cd ..命令,但是 shell 当前的路径并没改变,这就是因为它是通过创建子进程来执行的原因。这跟执行脚本的效果是一样的。但是如果用 source命令或者 .来执行脚本时,则不会创建子进程,而是直接在交互式 shell 中执行脚本中的命令。如下所示:

source ./script-name

或:

. ./script-name

4.2 Shell变量

与其他高级语言一样,Shell 支持自定义变量。Shell 的变量类型有两种,即 Shell 环境变量(Shell Enviroment Variable)和用户自定义变量(User Define Variable)。

4.2.1 环境变量

Shell 环境变量的作用是定制 Shell 的运行环境,并保证 Shell 命令的正确执行。它又分为可写和只读两大类。

可写的环境变量

可写的环境变量可以对其进行赋值操作,大部分可写的 shell 环境变量都在登录过程中执行 “/etc/profile”文件时进行初始化。该文件在用户登录时,被系统自动执行,为所有用户设置公共的工作环境。用户也可以通过修改自己主目录下的 .profile.bash_profile中的部分变量值来定制自己的运行环境。以下列出部分可写的环境变量:

BASH 展开为调用bash实例时使用的全路径名
CDPATH cd命令的搜索路径。它是以冒号分隔的目录列表,shell通过它来搜索cd命令指定的目标目录。
EDITOR 内置编辑器emacs、gmacs或vi的路径名
ENV 每一个新的bash shell(包括脚本)启动时执行的环境文件。通常赋予这个变量的文件名是.bashrc。
EUID 展开为在shell启动时被初始化的当前用户的有效ID
GROUPS 当前用户所属的组
HISTFIL E 指定保存命令行历史的文件。默认值是~/.bash_history。如果被复位,交互式shell退出时将不保存命令行历史
HISTSIZE 记录在命令行历史文件中的命令数。默认是500
HOME 主目录。未指定目录时,cd命令将转向该目录
IFS 内部字段分隔符,一般是空格符、制表符和换行符,用于由命令替换,循环结构中的表和读取的输入产生的词的字段划分
LANG 用来为没有以LC_开头的变量明确选取的种类确定locale类
OLDPWD 前一个工作目录
PATH 命令搜索路径。一个由冒号分隔的目录列表,shell用它来搜索命令。
LD_LIBRARY_PATH 库文件搜索路径。在执行程序是会自动到该变量设置的路径下搜索库文件。
PPID 父进程的进程ID
PS1 主提示符串,默认值是$
PS2 次提示符串,默认值是>
PS3 与select命令一起使用的选择提示符串,默认值是#?
PS4 当开启追踪时使用的调试提示符串,默认值是+。追踪可以用set –x开启
PWD 当前工作目录。由cd设置
RANDOM 每次引用该变量,就产生一个随机整数。随机数序列可以通过给RANDOM赋值来初始化。如果RANDOM被复位,即使随后再设置,它也将失去特定的属性
REPLY 当没有给read提供参数时设置
SHEL L 当调用shell时,它扫描环境变量以寻找该名字。shell给PATH、PS1、PS2、MAILCHECK和IFS设置默认值。HOME和MAIL由login(1)设置
SHELLOPTS 包含一列开启的shell选项,比如braceexpand、hashall、monitor等
UID 展开为当前用户的用户ID,在shell启动时初始化

环境变量一般都是大写的,系统启动后自动加载,可写的环境变量用户也可以随时进行修改。查看环境变量时,可以在变量名前加上美元符号 $,然后再用 echo 打印, 例如:

echo $PS1

只读的环境变量

只读的 Shell 环境变量意味着用户能使用和读取它们的值,而不能对他们进行修改。只读的 Shell 环境变量有两种:一种是特殊的环境变量,另一种是位置参数。

(1)特殊环境变量

特殊的环境变量是系统预先定义好的,用户不能重新设置,常见的只读环境变量如下所示:

$0:当前脚本的文件名
$num:num为从1开始的数字, $1是第一个参数,$2是第二个参数,
$#:传入脚本的参数的个数
$*:所有的位置参数(作为单个字符串)
$@:所有的位置参数(每个都作为独立的字符串)。
$?:当前shell进程中,上一个命令的返回值,如果上一个命令成功执行则$?的值为0,否则为其他非零值,常用做if语句条件
$$:当前shell进程的pid
$!:后台运行的最后一个进程的pid
$-:显示shell使用的当前选项
$_:之前命令的最后一个参数

(2)位置参数

用于处理命令行参数(Command Lines Argument),出现在命令行上的位置确定的参数称作位置参数(Positon Argument),也就是在命令行传递给 Shell 脚本的参数。

在 Bash 中总共有十个位置参数,其对应的名称一次是 0, 1, 2, 3, 4, 5, 6, 7, 8, 9。其中 0 始终表示命令名或者 Shell 脚本名,对于一个名行,必然有命令名,也就必有 0;而其他位置参数依据实际需求,可有可无。

  • 输出位置参数:

可以用 echo 命令输出位置参数。如下例所示:

#!/bin/bash

# Filename: test2-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $0

执行该脚本:

sh test2-1.sh a b c d e f g

输出:

a b c d e f g test2-1.sh
  • 用 set 命令给位置参数赋值

除了 $0 不能用 set 赋值外,其他位置参数都能用 set 对其手动赋值。示例:

#!/bin/bash

# Filename: test2-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo $1 $2 $3
set m1 m2 m3
echo $1 $2 $3

运行脚本:

sh test2-2.sh a1 a2 a3

输出:

a1 a2 a3 m1 m2 m3

  • 移动位置参数

在 Shell 中规定,位置参数最多不能超过 9 个,即 1 ~ 9。如果实际给定的命令行参数多于 9 个,就需要用 shift 命令移动位置参数。执行一次 shift 命令,就把诶之参数整体向左移一位,即原来 1 的值被移走了,新 1 的值是原来 2 的值,新 2 的值是原来 3 的值,依次类推。Shitf 命令不会将 0 移走,所以经 shitf 左移参数后, 1 不会取代 0 的值。Shift 命令可以带有一个整数作为参数,该参数表示一次左移的位数,若果未带参数,则默认值为1。如下示例所示:

#!/bin/bash

# Filename: test2-3.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $#
shift
echo $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $#
shift 5
echo $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $#

执行脚本:

sh test2-3.sh a b c d f g h i j k

输出结果:

test2-3.sh a b c d f g h i j 10 test2-3.sh b c d f g h i j k 9 test2-3.sh h i j k 4

4.2.2 自定义变量

Shell 编程中允许用户自定义变量,变量拥有临时的存储空间,在程序执行过程中其值可以改变,这些变量可以被设置为只读,也可以传递给定义他们的 Shell 脚本中的命令。Shell 中的变量无需声明和初始化,一个未初始化的 Shell 变量,其默认的初始化值为空字符串。

用户自定义变量的命名只要是合法的标识符即可,且区分大小写,变量名的长度不收限制。定义变量名并赋值的形式有以下几种:

(1)字符串赋值,其格式为:

变量名=字符串

例如: mydir=/home/a,其中,“mydir”是变量名,“=”是赋值符号,字符串“/home/a”是赋予的值。变量的值可以改变,秩序利用赋值语句重新给它赋值即可。 注意: 再赋值语句中,赋值符号的两边没有空格,否则在执行时会引起错误。

在程序中,变量的使用方式是再变量名前加上符号 $。该符号告诉 Shell 要取出其后变量的值。示例:

$ mydir=/home/a

echo mydir

mydir=/home/a

$ echo mydir

mydir

以上演示结果显示,如果不加 $就如法引用变量的值。

(2)如果再赋值给变量的值中含有空格、制表符或者换行符,那么就应该用双引号把这个字符串括起来。例如:myname=”Huoty Kong”,如果没有用引号括起来,则 myname 的值就是 Huoty。

(3)变量值可以作为某个长字符串中的一部分,如果它在长字符串的末尾,就可以利用直接引用形式。变量名出现在一个长字符串的开头或者中间,应该用 "{}"把变量名括起来。示例:

#!/bin/bash

# Filename: test2-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

s1=ing
echo walk$s1 or read$s1 or sleep$s1
dir=/home/huoty/
echo ${dir}m1.c

使用不带参数的 set命令可以显示可以显示所有 Shell 变量(包括环境变量和用户自定义变量)名以及它们的当前值。Set 命令也可以用于改变一些只读 Shell 环境变量的值。对用户自定义变量使用 readonly命令,可以将变量定义为只读变量,只读变量的值不能被改变。定义好的变量也可以使用 unset命令进行删除。示例:

#!/bin/bash

# Filename: test2-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

myEmail="huoty@gmail.com"
echo $myEmail
unset myEmail
echo $myEmail

myUrl="http://kuanghy.github.io/"
echo $myUrl
readonly myUrl
myUrl="http://kuanghy.github.io/kswd"

运行时会出现如下错误:

test2-5.sh: 15: test2-5.sh: myUrl: is read only

4.2.3 变量扩展功能

Shell 中有一些变量扩展的技巧,可以很方便的处理变量。

取子符串

Shell 从变量中取子字符串的规则如下:

${变量名:位置起点}

${变量名:位置起点:长度}

示例:

var="12345678"
echo ${var:5}
echo${var:0:5}

执行脚本输出结果为:

678
12345

计算字符串长度

在 Shell 中可以计算字符串的长度,其规则为:

${#变量名称}

示例:

var="12345678"
echo ${#var}  # 输出:8

对比样式

规则1:${变量#样式}

含义: 表示由变量值的最左边开始与样式进行对比,删除”最短相符合的字符串”

例1:

var="12345678"
echo ${var#*3} #输出:45678

规则2:${变量##样式}

含义: 表示由变量值的最左边开始与样式进行对比,删除”最长相符合的字符串”

例2:

var="12341234"
echo ${var##*3}  # 输出:4

规则3:${变量%样式}

含义: 表示由变量值的最右边或最后边开始与样式进行对比,删除”最短相符的字符串”

例3:

var="12341234"
echo ${var%3*}  # 输出:123412

规则4:${变量%%样式}

含义: 表示由变量值的最右边或最后边开始与样式进行对比,删除”最长相符的字符串”

例4:

var="12341234"
echo ${var%%3*}  # 输出:12

替换或删除部分字符串

规则1:${变量/样式/替换字符串}

含义: 如果变量中有符合样式的字符串,则使用替换字符串替代,只替换第一个符合样式的字符串

例1:

var="12341234"
echo ${var/1234/1111}  # 输出:11111234

规则2:${变量//样式/替换字符串}

含义: 如果变量中有符合样式的字符串,则使用替换字符串替代,替换全部符合样式的字符串

例1:

var="12341234"
echo ${var//123/}  # 输出:44

4.2.4 Shell定义数组

Bash 支持一维数组(不支持多维数组),并且没有限定数组的大小,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。 在 Shell 中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:

array_name=(value0 value1 value2 … valuen)

也可以单独定义数组的各个分量:

array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
...
array_name[n]=valuen

读取数组元素值的一般格式是:

${array_name[index]}

使用@ 或 * 可以获取数组中的所有元素,示例:

#!/bin/sh
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
echo "First Index: ${NAME[0]}"
echo "Second Index: ${NAME[1]}"
echo "First Method: ${NAME[*]}"
echo "Second Method: ${NAME[@]}"

获取数组长度的方法与获取字符串长度的方法相同,例如:

# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}

4.3 Shell中的特殊字符

Shell 中除使用普通字符外,还使用了一些特殊字符,它们有特定的含义,也有着重要的作用,如通配符、单引号、双引号、管导线等。在使用时应该注意它们表示的意义和作业范围。

4.3.1 Shell通配符

通配符用于模式匹配,如果文件名匹配、路径名搜索、字符串查找等。常用的通配符有如下几种:

1. 星号(*)

它匹配任意个字符串,在搜索文件时经常使用。但应该注意的是,星号不能匹配文件名前的圆点 .和路径名中的斜杠 /,所以应用中必须显示。例如,模式 “ file” 不能匹配 “.profile”,而需要用 “. file” 才可匹配。

2. 问号(?)

它匹配任意一个字符。例如,“f?” 可以匹配 f1、fa 等。

3. 一对方括号([ ])

方括号中有一对字符组。其作用是匹配该字符组所限定的任何一个字符。例如,”f[abcd]” 可以匹配 fa、fb、fc、fd,但不能匹配 fab、fabcd 等。方括号中的字符可以由直接给出的字符组成,也可以由表示范围的起始字符、终止字符及中间一个字符(-)组成。例如,“f[a-d]” 与 “f[abcd]” 作用相同。很明显,前者表示方式更便捷。

4. 感叹号(!)

如果它紧跟在一对方括号的左方括号之后,则表示不包括在方括号中所列出的字符。例如,“f[!a-d].c” 表示以 f 打头、第二个字符不是 a 到 d 的 .c 文件名。

在一个表达式中,也可以同时使用上述符号来提高工作效率。这里要特别强调一点, 通配符与正则表达式很相似,使用时应该注意两者的区别,正则表达式比通配符更加强大

4.3.2 Shell引号

在 Shell 中引号分为三种:即双引号、单引号和倒引号。

1. 双引号(" ")

由双引号括起来的字符,除$、倒引号和反斜杠(\)仍然保留其特殊功能外,其余字符通常作为普通字符对待。示例:

#!/bin/bash

# Filename: test3-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo "My current directory is `pwd`"
echo "My home the directory is $HOME \n"

注意: 双引号一定为英文的双引号(”“),而不能为中文的双引号(“”),本节中所讨论的所有特殊字符都是英文符号。

2. 单引号(' ')

由单引号括起来的所有字符或字符串都作为普通字符出现。如果将上例中的双引号改为单引号,则 pwd和 $HOME 就会作为普通字符输出,而失去原有的特殊意义。总结的说就是,被单引号括起来的字符,是什么就输出什么。

3. 倒引号(``)

倒引号括起来的字符串被 Shell 解释为命令行,在执行时,Shell 会先执行该命令行,并以它的标准输出结果取代整个倒引号部分。常用的方式有以下几种:

(1)Shell 解释执行

$ echo current directory is pwd current directory is /home/huoty

由上可见,Shell 在执行此命令时,先执行 pwd 中的命令 pwd,然后用执行结果取代 pwd 部分,最后输出输出替换后的结果。

(2)利用倒引号的 Shell 解释功能可以进行命令替换,即把倒引号中的命令的结果赋给指定变量。示例:

#!/bin/bash

# Filename: test3-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

mypath=`pwd`
echo My directory is $mypath

(3)倒引号的嵌套应用,倒引号嵌套时在内层的一组倒引号必须用反斜杠(\)进行转义。示例:

#!/bin/bash

# Filename: test3-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

mypath=`echo My directory is `pwd``
echo $mypath

4.3.3 Shell命令执行顺序操作符

在之前的示例程序中,我们大都是一行执行一个命令,而事实上,多条命令可以在一行中出现,顺序执行。相邻命令间也可以存在罗辑关系,即 罗辑“与” 和 “或”。

1. 顺序执行

(1)顺序分隔符(;)

多条命令可以在多行中输入,也可以将这些命令在一行中输入,但各命令应以分号(;)隔开,例如在本教程中的第一个例子可以用如下方式实现:

$ pwd; ls -l ~/.vimrc; cal; who; ifconfig

(2)管道线(|)

管道的作用是将前边命令的输出作为后边命令的输入。用管道线 “|” 可以将多个简单的命令集合在一起,用以完成较复杂的功能。管道的执行顺序也是顺序执行。例如:

$ who | wc -l | write username

上例的含义是统计在线人数,并把结果以消息形式发送给 username 用户。

2. 罗辑与(&&)

罗辑与操作符 “&&” 可把两个或两个以上命令联系在一起,格式如下:

command1 && command2 && … && commandN

功能: 先运行 command1,如果结果运行成功,才运行 command2;否则若 command1 运行不成,则不运行 command2。一次类推,只有前 n-1 个命令都正确运行后,第 n 个命令才运行。示例:

$ cp test1 /home/huoty/ && cat /home/huoty/test1

如果成功复制 test1 到要求路径,则查看其内容。 注: 在 Shell 中,命令执行成功返回值为 0, 执行失败返回值为非 0。

3. 罗辑或(||)

罗辑或操作符 “||” 可以把两个或两个以上命令联系起来,其一般格式为:

command1 || command2 || … || commandN

功能: 先运行 command1,如果运行不成功,则运行 command2;否则若 command1 运行成功,则不运行 command2。示例:

cp test1 /home/huoty || ls -l

如果没有成功复制 test1 文件到要求路径,则查看当前路径内容。

4.3.4 Shell注释&反斜杠&后台操作符

1. 注释符(#)

在 UNIX/Linux 中,使用 “#” 来进行 Shell 程序的注释。为了让编写的程序更容易让人理解,应该养成在程序中添加注释的好习惯,注释用于描述一组特定命令的用途。

2. 反斜杠(\)

反斜杠(\)是转义字符,它能把特殊字符编程普通字符。例如:

echo “Filename is No$ * n1”

Filename is No$ * n1

如果想要在字符串中使用反斜杠本身,则必须采用 “\\”的形式,其中第一个反斜杠作为转义字符,从而第二个反斜杠变为普通字符。

另外,反斜杠还作为续行符使用。如果把它放在一行的回车符换行之前,那么表示下一行接续此行被视为同一行,可以用于表示长的输入行。

3. 后台操作符(&)

有些程序的运行会花很长时间,或者要保持一直运行。这样它们就会一直占着 Shell 终端,而我们也无法做其他的事情。我们可以把进程放到后台去执行,后台运行程序的方法是,再命令后加上后台运行操作符 &。利用前后台进程轮流在 CPU 上执行,可以提高工作效率,并且充分地利用了系统资源。例如在编译一个大型 C 程序项目时,如果想要再编译的同时做点别点事情,可以用诸如一下的方式:

gcc file1.c &

在一条命令最后输入 &符号,Shell 就再后台启动该程序,并且马上显示主提示符。用 jobs命令可以查看挂起在后台的进程。用 fg命令可以将后台挂起的进程恢复到前台来运行。用快捷键 Ctrl + Z可以暂时把当前程序挂起到后台,挂起后的进程将不再进行任何操作。

4.4 Shell中的输入输出命令

计算机都能执行输入、处理和输出的基本操作过程。而 UNIX/Linux 下的典型命令则包含执行输入输出的的基本操作。

4.4.1 Shell输入输出的标准文件

Linux/UNIX 中每个命令以进程的方式运行,而每个进程运行时自动打开三个文件,这些文件称为命令的标准文件,分别用于命令读取输入、输出结果以及输出错误信息,即标准输入文件(stdin)、标准输出文件(stdout)、标准错误输出文件(stderr)。这些文件与执行命令的终端关联。更明确的说,键盘是标准输入,显示器是标准输出和标准错误输出。因此,默认情况下,每条命令都是从键盘读取输入,并将输出和错误信息发送到显示屏上。通过使用 Linux/UNIX 中文件重定向命令,可以将命令的输入、输出以及错误信息重定向到其他文件中,这就可以将多个命令组合起来,以完成单个命令不能完成的复杂任务。

4.4.2 Shell输入输出的重定向命令

1. 输入重定向命令

输入重定向是通过使用小于号 “<”来实现的。它的作用是解除键盘作为命令 “command” 的标准输入,并将文件 “input-file” 作为命令的输入源。这样,命令 “command” 读取的输入来自文件 “input-file”, 而不是与命令运行终端相连接的键盘。命令被 Shell 解释执行后,输出到显示器 “monitor” 上。如下图所示:

其使用格式为:

command < input-file

2. 输出重定向命令

输出重定向通过使用大于号 “>”来实现。该语法用于将命令 “command” 的输出重定向到文件 “output-file” 上以取代显示屏。如下图所示:

其使用格式为:

command > output-file

3. 输出附加定向命令

输出附加定向命令通过使用两个大于号 “>>”来实现。它的作用是把命令(或执行程序)的输出附加到指定文件的后面,文件原有内容不被破坏。其格式为:

command >> output-file

4. 标准错误重定向命令

标准错误重定向使用操作符 “2>”,对命令的错误进行重定向,将产生的错误消息发送到文件中,命令的输入也可以是命令行参数所指定的文件。其一般格式为:

command 2> error-file

在 Shell 编程的过程中,可以混合的使用输入输出重定向命令,从而实现一些特定复杂的功能。

4.4.3 Shell的输入输出命令

Shell 的输入输出命令最常用的两个是 readecho。echo 作为向屏幕输出信息的工具应用比较多,read 主要用于读取用户输入。

1. read 命令

read命令是标准的输入命令,可以利用 read 命令从标准输入读取数据,然后赋值给指定的变量。其一般格式为:

read 变量1 [变量2] …

利用 read 命令可以交互地为变量赋值。输入数据时,数据间以空格或制表符作为分隔符。若变量个数与给定数据个数相同,则一次对应赋值。若变量个数少于数据个数,则将剩下的数据全部赋给最后一个变量。若变量个数多于给定的数据个数,则多余的变量就被赋值为空串。示例:

#!/bin/bash

# Filename: test4-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

read n1 n2
# Input: a b <回车>

read na nb
# Input: First Second Third <回车>

read a b c 
# Input: 1 2 <回车>

2. echo 命令

echo命令是将其后的参数在标准输出上输出,个参数间以空格隔开,以换行符终止。如果数据间保留对个空格,则要用单引号或双引号把它们括起来,通常,最好用双引号把所有参数括起来,这样不仅易读并且能使 Shell 对它们进行正确的解释。

echo参数中有一些特殊字符,用于输出控制或者打印出无法显示的字符,如下表所示:

字符含义
\b退格
\c不将光标移动到下一行
\f换页
\n换行(光标移到下一行)
\r回车
\t水平制表符
\垂直制表符
\转义符
\ONASCII码为八进制 N 的字符

下面是一个 read 和 echo 的综合例子:

#!/bin/bash

# Filename: test4-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo "Enter input: \c"
read line
echo "You entered: $line"
echo "Enter another line: \c"
read word1 word2 word3
echo "the first words is: $word1"
echo "the second word is: $word2"
echo "the rest words is: $word3"
exit 0

4.5 Shell流程控制语句

Shell 程序类似其他高级语言,同样具有控制结构,程序的控制结构语句用于决定 Shell 脚本中的语句的执行顺序。脚本的控制结构语句有三种基本类型:两个分支、多路分支以及一个或者多个命令的循环执行。Linux 的 Bash 中的两路分支语句是 if 语句,多路分支是 if 和 case 语句,代码的循环执行语句是 for、while 和 unitl 语句。

4.5.1 if语句

Shell 提供了功能丰富的 if 语句,其一般用于两路分支,但也可以用于多路分支,是最常用的条件控制语句。

1. 两路分支的 if 语句

if 语句的最基本形式一般是用于两路分支,下面是该语句的一般格式:

if 判断条件
then 命令1
else 命令2
fi

其中 if、then、else 和 fi 是关键字,若没有 else 行,则变为“一路分支”单纯的 if 语句。判断条件包括命令语句和测试语句两种方式。

(1)命令语句形式的判断条件

一般以命令的执行成功与否来判断,如果命令成功执行并正常结束,则返回值 0,判断条件为真;如果命令执行不成功,其返回值不等于 0,判断条件为假。如果命令语句形式的判断条件由多条命令组成,那么判断条件以最后一条命令是否执行成功为准。

下面列举一个示例,查找给定用户是否在系统中工作,如果在系统中就发一个问候给他:

#!/bin/bash

# Filename: test5-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo "Type in the user name"
read user

if who | grep $user
then echo "Hello $user" | write $user
else echo "$user has not logged in the system"
fi

(2)测试语句形式的判断条件

测试语句是 Shell 程序最常用的判断条件,它包括字符串测试、文件测试和数值测试,之后将详细介绍这些内容。下面列举一个简单示例,利用位置参数携带一个文件名,判断该文件在当前目录下是否存在且是一个普通文件:

#!/bin/bash

# Filename: test5-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

if test -f "$1"
then echo "$1 is an ordinary file"
else echo "$1 is not an ordinary file"
fi

2. 多路条件判断分支的 if 语句

在 if 语句两路分支中再嵌套一组 if 语句两路分支,则可以变成多路条件分支,它可以简写为以下格式:

if 判断条件1
then 命令1
elif 判断条件2
then 命令2
...
else 命令n
fi

其中 elif 是 else if 的缩写。下面编写一个简单例子,输入 1 ~ 10 之间的一个数,并判断是否小于5。

#!/bin/bash

# Filename: test5-3.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

echo "key in a number(1-10):"
read a
if [ "$a" -lt 1 -o "$a" -gt 10 ]
then echo "Error Number."
elif [ ! "$a" -lt 5 ]
then echo "It's not less 5."
else echo "It's less 5."
fi

4.5.2 测试语句

测试语句是 Shell 的特有功能,它往往是和各种条件语句结合使用,如与 if、case、while 搭配,它在 Shell 编程中起着很重要的作用,使用频率很高。测试语句计算一个表达式的值,并返回“真”或“假”。该语句有两种语法格式,一种是使用关键字 test,而另一种是使用方括号。格式如下:

格式1:

test expression

格式2:

[ expression ]

例如测试位置参数携带的文件名是否在当前目录下已存在并且为普通文件,可写成:

test -f  "$1"  # 格式1写法
[ -f "$1" ]    # 格式2写法

注意: (1)如果在 test 语句中使用 Shell 变量,为表示完整,避免造成歧义,最好用双引号将变量括起来。 (2)在任何一个运算符、圆括号或者方括号等操作符额前后 至少需要留有一个空格,否则可能会出错 。 (3)如果需要下一行继续测试表达式,应该在换行之前加上反斜杠(\),这样 Shell 就会将下一行当做上一行的接续。

测试语句支持很多运算符,它们用于三种形式的测试:文件测试、字符测试和数值测试,也可以在罗辑上将两个或者更多的测试语句连接成更复杂的表达式,大多数 UNIX/Linux 系统上的测试语句都支持运算符的含义。

1. 文件测试

文件测试是判断当前路径下的文件属性即类型,所指的文件一般用变量所代表的文件名表示。文件测试的各个参数及功能如下所示:

命令含义
-b filename当filename 存在并且是块文件时返回真(返回0)
-c filename当filename 存在并且是字符文件时返回真
-d pathname当pathname 存在并且是一个目录时返回真
-e pathname当由pathname 指定的文件或目录存在时返回真
-f filename当filename 存在并且是正规文件时返回真
-g pathname当由pathname 指定的文件或目录存在并且设置了SGID 位时返回真
-h filename当filename 存在并且是符号链接文件时返回真
-k pathname当由pathname 指定的文件或目录存在并且设置了”粘滞”位时返回真
-p filename当filename 存在并且是命名管道时返回真
-r pathname当由pathname 指定的文件或目录存在并且可读时返回真
-s filename当filename 存在并且文件大小大于0 时返回真
-S filename当filename 存在并且是socket 时返回真
-t fd当fd 是与终端设备相关联的文件描述符时返回真
-u pathname当由pathname 指定的文件或目录存在并且设置了SUID 位时返回真
-w pathname当由pathname 指定的文件或目录存在并且可写时返回真
-x pathname当由pathname 指定的文件或目录存在并且可执行时返回真
-O pathname当由pathname 存在并且被当前进程的有效用户id 的用户拥有时返回真(字母O 大写)
-G pathname当由pathname 存在并且属于当前进程的有效用户id 的用户的用户组时返回真
file1 -nt file2file1 比file2 新时返回真 file1 -ot file2 : file1 比file2 旧时返回真

2. 字符串测试

有关字符串的测试参数及功能如下所示:

命令含义
-z string判断字符串string是否为空串
-n string判断字符串string是否为非空串
string1 = string2判断string1string2是否相等
string1 != string2判断string1string2是否不相等
string1 < string2判断在字典序上string1是否在string2之前
string1 > string2判断在字典序上string1是否在string2之后

字符串的测试语句实例:判断两个变量 是s1 和 s2 所代表的字符串是否相等,可以写成:

[ “ s1" = " s2” ] 或 test “ s1" = " s2”

在引用变量及字符串时,要求用双引号括起来,又如在判断变量 s1 是否等于字符串 “yes” 时,可以写成:

[ “ s1" = "yes" ] 或 test " s1” = “yes”

3. 数值测试

有关数值测试参数及功能如下所示:

命令含义
val1 -eq val2判断val1val2的数值是否相等
val1 -ne val2判断val1val2的数值是否不相等
val1 -lt val2判断val1是否小于val2
val1 -le val2判断val1是否小于等于val2
val1 -gt val2判断val1是否大于val2
val1 -ge val2判断val1是否大于等于val2

数值测试语句实例:判断变量 s1 所代表的数值是否大于10,可以写成:

[ “ s1" -gt 10 ] 或 test " s1” -gt 10

4. 用逻辑操作符进行组合的测试语句

判断条件可以在 if 语句或循环语句中单个使用,也可以通过逻辑操作符把它们组合起来使用,形成更复杂的表达式。可以在测试语句中使用的逻辑操作符有:逻辑与、逻辑或、逻辑非。它们的参数和功能如下所示:

命令含义
param1 -a param2逻辑与
param1 -o param2逻辑或
!param逻辑非

-a 逻辑与,操作符两边均为真,结果为真,否则为假。 -o 逻辑或,操作符两边一边为真,结果为真,否则为假。 ! 逻辑否,条件为假,结果为真。即将原来真的表达式变为假,假的变为真。 () 圆括号,用于将表达式分组,优先得到结果。括号前后应该有空格并用转义符转义。

用逻辑操作符进行组合的测试语句实例如下:

#!/bin/bash

# Filename: test5-4.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

s1=1
s2=test5-4.sh
s3=5
a=6

if [ ! "$s1" -le 0 ]
then echo "s1大于0"
else echo "s1小于等于0"
fi

if [ -f "$s2" -a -w "$s2" ]
then echo "$s2 是普通文件并且具有写权限"
else echo "$s2 不是普通文件或者没有写权限"
fi

if [ "$s1" -gt 0 -o "$s3" -lt 10 ]
then echo "s1大于0且s3小于10"
else echo "不在指定范围内"
fi

if [ \( "$a" -gt 0 -a "$a" -lt 10 \) -a "$a" -ne 5 ]
then echo "0 < a < 10 且 a 不等于 5"
else echo "不在指定范围内"
fi

case语句即选择语句,其允许从几种情况中选择一种情况执行,而且 case 语句不但取代了多个 elif 和 then 语句,还可以用变量值对多个模式进行匹配,当某个模式与变量匹配后,其后的一系列命令将被执行。在 Shell 中,可以使用 case 语句比较带有通配符的字符串,这比其他高级语句的功能稍强。Bash 的 case 语句格式如下:

case string1 in
str1)
    commands-list1;;
str2)
    commands-list2;;
...
strn)
    commands-listn;;
esac

作用: 将 string1 和 str1, … , strn 比较,如果 str1 到 strn 中的任何一个和 string1 相符合,则执行其后的命令一直到分号(;;)结束。如果 str1 到 strn 中没有和 string1 相符合的,则其后语句不被执行。其中,str1 到 strn 也称之为正则表达式。示例:

#!/bin/bash

# Filename: test5-5.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

case $1 in
    file) 
        echo "it is a file";;
    dir|path)
        echo "current directory is `pwd`";;
    [Dd]ate)
        echo "the date is `date`";;
    *)
        echo "it is not a filename";;
esac

使用 case 时应注意以下几点: (1)每个正则表达式后面可以有一条或多条命令,其最后一条命令必须以两个分号(;;)结束。 (2)正则表达式中可以使用通配符。 (3)如果一个正则表达式是由多个模式组成,那么各模式之间应以 “|”隔开。表示各模式是“或”的关系,即只要给定字符串与其中一个模式相匹配,就会执行其后的命令表。 (4)各正则表达式是唯一的,不应重复出现。 (5)case 语句以关键字 case 开头,以关键字 esac 结束。 (6)case 的退出(返回)值是整个结构中最后执行的那个命令的退出值。若没有执行任何命令则退出值为零

4.5.3 for 语句

Shell 中有有三种用于循环的语句,它们是 for 语句、while 语句和 until 语句。 for 语句是程序设计中循环语句最常用的一个,其一般格式为:

for variable [in argument-list]
do
    command-list
done

作用: 重复执行 command-list 中命令,执行次数与 in argument-list 中的单词个数相同。其中的 [in argument-list] 部分为可选项,由于它的不同又可以有三种形式:

1. [argument-list]为变量值表

其执行过程是:变量 variable 依次取值表中各字符串,取值表中字符串的个数就决定了 for 循环执行的次数。例如:

for people in Debbie Tom John Kitty Kuhn
do
    echo "$people"
done

2. [argument-list]为文件的表达式

其执行过程是:变量的值依次取当前目录(或指定目录)下与文件表达式相匹配的文件名,每取值一次,就进入循环执行命令表,直到所有匹配的文件取完为止。例如:

for i in *.sh
do
    cat $i | pr
done

其中的 pr命令用于将文本转换成适合打印的文件,基本用途就是将较大的文件分割成多个页面,并为每个页面添加标题。

3. [argument-list]为空

此种形式中,[argument]也可以用 $* 来代替,两者都是等价的。执行过程是变量依次取位置参数的值,然后执行循环体中的命令表,直至所有位置参数取完为止。示例:

#!/bin/bash

# Filename: test5-8.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

dir=$1; shift
if [ -d $dir ]
then cd $dir
     for name # or "for name in $*"
     do
         if [ -f $name ]
         then cat $name
              echo "End of ${dir}/$name"
         else echo "Invalid file name: ${dir}/$name"
         fi
     done
else echo "Bad directory name:$dir"
fi

4.5.4 while 语句

while 语句用于根据一个表达式的条件重复执行代码块,即循环语句。其一般语法格式为:

while expression
do
    command-list
done 

作用: 只要 expression 的值为真,则进入循环体,执行 command -lsit 中的命令,然后再做条件测试,直到测试条件为假时才终止 while 语句的执行。下面列举一个示例,判断各个位置参数是否是一个普通文件,若是则显示其内容,否则显示它不是文件名的信息:

#!/bin/bash

# Filename: test5-9.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

while [ $1 ]
do
    if [ -f $1 ]
    then echo "display: $1"
        cat $1
    else echo "$1 is not filename."
    fi
    shift
done

该例中巧妙的利用了 shift 命令,每读取一个位置参数后便将剩余的位置参数左移。这一编程技巧非常值得借鉴,应为在 Shell 中默认位置参数之后 10 个,如果需要获取更多的位置参数,则需要采用一些特殊的技巧。 while 的条件表达式除了可以为测试语句外,还可以是一组命令,根据命令的退出状态决定是否执行循环体

4.5.5 until 语句

until 语句是另一种循环,它的语法类似与 while 语句,但在语义上有所不同。在 while 语句中,只有表达式的值为真时才执行循环体;而在 until 语句中,只在表达式为假时才执行循环体。该语句的语法格式为:

until expression
do
    command-list
done

作用: 只要 expression 的值为假,则执行 command-list 的命令。示例:

#!/bin/bash

# Filename: test5-10.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

x=1
until [ $x -gt 10 ]
do
    echo $x
    x=`expr $x + 1`
done

上例主要实现循环输出 1 到 10 之间的整数。其中 x 赋值符号右边为倒引号包裹的算术表达式,expr 为数值运算符。

4.5.6 break 和 continue 语句

breakcontinue命令用于中断循环体的顺序执行。其中 break 命令将控制转移到 done 后面的命令,因此循环提前结束。continue 命令将控制转移到 done,接着再次计算条件的值,以决定是否继续循环。

1. break 语句

break 命令可以从循环体中退出。其语法格式为:

break [n]

其中 n 表示跳出几层循环。默认值是 1,表示只跳出一层循环。如果 n 为 3,则表示一次跳出 3 层循环。执行 break 时,是从包含它的那个循环体中向外跳出。示例:

#!/bin/bash

# Filename: test5-11.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

x=1
while true
do
    echo $x
    x=`expr $x + 1`
    if [ "$x" -gt 10 ]
    then break
    fi
done

2. continue 语句

continue命令跳过循环体中在它之后的语句,回到本层循环的开头,进行下一次循环。其语法格式为:

continue [n]

其中 n 表示从 continue 语句的最内层循环向外跳出第 n 层循环,默认值为 1。示例,输出一组数,打印除了值为 3 的所有数:

#!/bin/bash

# Filename: test5-12.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

for i in 1 2 3 4 5 6
do
    if [ "$i" -eq 3 ]
    then continue
    else echo "$i"
    fi
done

4.5.7 算术表达式和退出脚本程序命令

与其他编程语言一样,Shell 也提供了丰富的算术表达式。

1. 算术表达式

Shell 提供五种基本的算术运算:+(加)、-(减)、*(乘)、/(除)和 %(取模)。Shell 只提供整形数的运算。其一般格式为:

expr n1 运算符 n2

示例:

expr 20 - 10    # =10
expr 15 \* 15 # =225
expr 15 % 4 # =3

注意: 在运算符的前后都留有空格,否则 expr 不对表达式进行计算,而直接输出它们。表示“乘”的运算符前应加一个转义符 “*”。

expr除了可以实现算术运算外,还可以用于操作字符串,其操作字符串的格式如下:

match String1 String2    将 String1 的运算得到的字符串与 String2 的运算结果的正则表达式模式进行比较.
length String1    返回 String1 的长度.
index String1 String2    返回 String1 中包含 String2 中任意字符的第一个位置.
substr String1 StartPosition Length    返回一个以 StartPosition 的字符开始的在 String1 中的字符串,并且是 Length 长度的字符串。

一些应用示例:

(1)、计算字串长度

expr length “this is a test”

14

(2)、抓取字串

expr substr “this is a test” 3 5

is is

(3)、抓取第一个字符数字串出现的位置

expr index “sarasara” a

2

2. 退出脚本程序命令

在 Shell 脚本中, exit 命令是立即退出正在运行的 Shell 脚本,并设有退出值。其语法格式为:

exit [n]

其中 n 为设定的退出值,如果未给定 n 的值,则退出状态为最后一个命令的执行状态。

4.6 Shell自定义函数

在 Shell 中可以自定义并使用函数。其定义格式为:

Function()
{
command-list
[ return-value ]
}

函数应先定义,后使用。 调用函数时,直接利用函数名调用 。示例:

#!/bin/bash

# Filename: test6-1.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

hello()
{
echo "Hello, I am huoty(http://kuanghy.github.io/) !"
}

hello

Shell 函数也可以有 返回值 ,但是该返回值只能为整数。我们可以这样来理解函数的返回值,其实函数就相当于一个命令,其返回值用于表示其执行的状态,所以只能为整数。返回值的接受有两种方式:一种是用变量接收,这其实是将标准输出传递给主程序的变量;另一种是用 $?接收。示例:

#!/bin/bash

# Filename: test6-2.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

fun_with_return()
{
echo -n "Your first name: "
read fname
echo -n "Your last name:"
read lname
echo "Your name is $fname $lname"

return 10
}

fun_with_return
ret=$? # or "ret=`fun_with_return`"
echo "Ret: $ret"

Shell 函数可以 嵌套调用 ,示例:

#!/bin/bash

# Filename: test6-3.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

# Calling one function from another
number_one () {
echo "Url_1 is http://kuanghy.github.io/"
number_two
}

number_two () {
echo "Url_2 is http://kuanghy.github.io/about/"
}

number_one

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 n 的形式来获取参数的值,例如, 1表示第一个参数, 2表示第二个参数等等。但需要注意的是, 10 不能获取第十个参数,获取第十个参数需要 {10}。当n>=10时,需要使用 来获取参数。在调用函数传参时,直接在函数名后加上参数即可,各参数间用空格隔开。示例:

#!/bin/bash

# Filename: test6-4.sh
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

testfile()
{
if [ -d "$1" ]
then echo "$1 is a directory."
else echo "$1 is not a directory."
fi
echo "End of the function."
}

testfile /home/huoty/.vimrc

另外,还有几个特殊变量用来处理参数: $#传递给函数的参数个数; $*显示所有传递给函数的参数; $?函数的返回值。

最后强调一个值得注意的点,Shell 的函数体不能为空,不能出现像如下形式的函数声明:

func()
{
}

或者

func()
{}

实际上是 shell 无法识别这样的定义,它不会认为这里的大括号是用于定义函数的。如果你真的需要一个什么都不用做的空函数,那么你可以在函数体中加一个空语句。在 shell 中,可以用 : 来表示空语句,即只消耗 cpu 时间,不做任何实际的工作。那么空函数的实现可以用如下的方式:

func()
{

}

4.7 Shell脚本调试方法和文件包含

4.7.1 Shell 脚本调试方法

在编写shell脚本时,shell提供了一些用于调试的选项:

-n    读一遍脚本的命令但不执行,用于检查脚本中的错误。
-v 一遍执行脚本,一遍将执行过的脚本命令打印到标准错误输出。
-x 提供跟踪执行信息,将执行的每一条命令和结果一次打印出来。

使用这些选项有三种方法: (1)在命令行提供参数:

sh -x /script.sh

(2)在脚本开头提供参数:

#! /bin/bash -x

(3)在脚本中用 set 命令启用或禁用参数

#!/bin/bash 

if [ -z "$1" ]; then
set -x
echo "ERROR: Insufficient Args."
exit 1
set +x
fi

set -x 和 set +x 分别表示启用和禁用 -x 参数,这样可以只对脚本中的某一段进行跟踪调试。

4.7.2 Shell 文件包含

像其他语言一样,Shell 也可以包含外部脚本,即将外部脚本的内容合并到当前脚本。其包含脚本的方法有两种,即 “.”“source”

. filename

source filename

例如先创建一个名为 subscript.sh 的脚本,然后再在其他脚本中包含它:

# filename:subscript.sh

url="http://kuanghy.github.io/"

主脚本如下:

#!/bin/bash

# Filename:
# Author: huoty <sudohuoty@163.com>
# Script starts from here:

. ./subscript.sh
echo $url

被包含脚本不需要有执行权限,只保持主脚本有执行权限即可。其实这种文件包含的内容在之前的内容中我们已经提到过,只不过不是用来解决文件包含的问题。之前我们谈到过,Shell 在执行脚本时是另外开启一个子进程来执行的,所以想要让脚本的内容在当前 shell 中执行,则需要用 “.” 或 “source” 来执行脚本。其实这这两者的含义是一样的,所谓的让脚本内容在当前 shell 执行,也就是将脚本文件的内容包含到当前 shell 来执行,其实质都是文件包含。

4.7.3 Shell 图形界面编程

在 Shell 编程中,有一个工具可以提供简单的图形界面编程实现,即 zenity。Zenity 只能提供一些简单的对话框,其实际是基于GTK+的一个对话框工具。。Zenity 主要对话框的列表包括日历、输入、报错、消息、文件选择、清单、通知、进展、问题、警告、比例和文字信息。