参考http://c.biancheng.net/cpp/view/6994.html
Shell简介
Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。
可以说,shell使用的熟练程度反映了用户对Unix/Linux使用的熟练程度。
注意:单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Linux 基础。
Shell有两种执行命令的方式:
"#!" 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。echo命令用于向窗口输出文本。
运行Shell脚本有两种方法。
将上面的代码保存为test.sh,并 cd 到相应目录:
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
注意,一定要写成./test.sh,而不是test.sh。运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。
通过这种方式运行bash脚本,第一行一定要写对,好让系统查找到正确的解释器。
这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh作为解释器的脚本是不是可以省去第一行呢?是的。
这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:
/bin/sh test.sh
/bin/php test.php
|
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
再看一个例子。下面的脚本使用 read 命令从 stdin 获取输入并赋值给 PERSON 变量,最后在 stdout 上输出:
# Copyright (c) http://see.xidian.edu.cn/cpp/linux/
echo "What is your name?"
|
运行脚本:
chmod +x ./test.sh
$./test.sh
What is your name?
mozhiyan
Hello, mozhiyan
$
|
以"#"开头的行就是注释,会被解释器忽略。
sh里没有多行注释,只能每一行加一个#号。只能像这样:
#--------------------------------------------
# 这是一个自动打ipa的脚本,基于webfrogs的ipa-build书写:
# https://github.com/webfrogs/xcode_shell/blob/master/ipa-build
# 功能:自动为etao ios app打包,产出物为14个渠道的ipa包
# 特色:全自动打包,不需要输入任何参数
#--------------------------------------------
##### 用户配置区 开始 #####
#
#
# 项目根目录,推荐将此脚本放在项目的根目录,这里就不用改了
# 应用名,确保和Xcode里Product下的target_name.app名字一致
#
##### 用户配置区 结束 #####
|
如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?每一行加个#符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
Shell支持自定义变量。
定义变量时,变量名不加美元符号($),如:
注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:
myUrl="http://see.xidian.edu.cn/cpp/linux/"
|
使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:
for skill in Ada Coffe Action Java
echo "I am good at ${skill}Script"
|
如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
推荐给所有变量加上花括号,这是个好的编程习惯。
已定义的变量,可以被重新定义,如:
myUrl="http://see.xidian.edu.cn/cpp/linux/"
myUrl="http://see.xidian.edu.cn/cpp/shell/"
|
这样写是合法的,但注意,第二次赋值的时候不能写 $myUrl="http://see.xidian.edu.cn/cpp/shell/",使用变量的时候才加美元符($)。
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:
myUrl="http://see.xidian.edu.cn/cpp/shell/"
myUrl="http://see.xidian.edu.cn/cpp/danpianji/"
|
运行脚本,结果如下:
/bin/sh: NAME: This variable is read only. |
使用 unset 命令可以删除变量。语法:
变量被删除后不能再次使用;unset 命令不能删除只读变量。
举个例子:
myUrl="http://see.xidian.edu.cn/cpp/u/xitong/"
|
上面的脚本没有任何输出。
运行shell时,会同时存在三种变量:
局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值
可以使用的变量替换形式:
形式 | 说明 |
${var} | 变量本来的值 |
${var:-word} | 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。 |
${var:=word} | 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。 |
${var:?message} | 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。 若此替换出现在Shell脚本中,那么脚本将停止运行。 |
${var:+word} | 如果变量 var 被定义,那么返回 word,但不改变 var 的值。 |
请看下面的例子:
#!/bin/bash
echo ${var:-"Variable is not set"}
echo "1 - Value of var is ${var}"
echo ${var:="Variable is not set"}
echo "2 - Value of var is ${var}"
unset var
echo ${var:+"This is default value"}
echo "3 - Value of var is $var"
var="Prefix"
echo ${var:+"This is default value"}
echo "4 - Value of var is $var"
echo ${var:?"Print this message"}
echo "5 - Value of var is ${var}"
|
运行结果:
2 - Value of var is Variable is not set
4 - Value of var is Prefix
5 - Value of var is Prefix
|
有许多变量是系统自动设定的。
$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。
但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。
如果您需要处理数学表达式,那么您需要使用诸如expr等程序(见下面)。
除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。
字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。
单引号字符串的限制:
str="Hello, I know your are "$your_name"!
"
|
双引号的优点:
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting
$greeting_1
|
string="alibaba is a great company"
echo ${string:1:4}
#输出liba
|
string="alibaba is a great company"
echo `expr index "$string" is`
|
bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。
在Shell中,用括号来表示数组,数组元素用"空格"符号分割开。定义数组的一般形式为:
array_name=(value1 ... valuen)
例如:
array_name=(value0 value1 value2 value3)
|
或者
还可以单独定义数组的各个分量:
可以不使用连续的下标,而且下标的范围没有限制。
读取数组元素值的一般格式是:
${array_name[index]}
例如:
举个例子:
echo "First Index: ${NAME[0]}"
echo "Second Index: ${NAME[1]}"
|
运行脚本,输出:
$./test.sh
First Index: Zara
Second Index: Qadir
|
使用@ 或 * 可以获取数组中的所有元素,例如:
举个例子:
echo "First Method: ${NAME[*]}"
echo "Second Method: ${NAME[@]}"
|
运行脚本,输出:
$./test.sh
First Method: Zara Qadir Mahnaz Ayan Daisy
Second Method: Zara Qadir Mahnaz Ayan Daisy
|
获取数组长度的方法与获取字符串长度的方法相同,例如:
lengthn=${#array_name[n]}
|
Bash 支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加:
echo "Total value : $val"
|
运行脚本输出:
两点注意:
echo "a is not equal to b"
|
运行结果:
a + b : 30
a - b : -10
a * b : 200
b / a : 2
b % a : 0
a is not equal to b
|
注意:
echo "$a -eq $b : a is equal to b"
echo "$a -eq $b: a is not equal to b"
echo "$a -ne $b: a is not equal to b"
echo "$a -ne $b : a is equal to b"
echo "$a -gt $b: a is greater than b"
echo "$a -gt $b: a is not greater than b"
echo "$a -lt $b: a is less than b"
echo "$a -lt $b: a is not less than b"
echo "$a -ge $b: a is greater or equal to b"
echo "$a -ge $b: a is not greater or equal to b"
echo "$a -le $b: a is less or equal to b"
echo "$a -le $b: a is not less or equal to b"
|
运行结果:
10 -eq 20: a is not equal to b
10 -ne 20: a is not equal to b
10 -gt 20: a is not greater than b
10 -lt 20: a is less than b
10 -ge 20: a is not greater or equal to b
10 -le 20: a is less or equal to b
|
关系运算符列表 |
运算符 | 说明 | 举例 |
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 true。 |
-ne | 检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge | 检测左边的数是否大等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
先来看一个布尔运算符的例子:
echo "$a != $b : a is not equal to b"
echo "$a != $b: a is equal to b"
if
[
$a -lt 100 -a $b -gt 15
]
echo "$a -lt 100 -a $b -gt 15 : returns true"
echo "$a -lt 100 -a $b -gt 15 : returns false"
if
[
$a -lt 100 -o $b -gt 100
]
echo "$a -lt 100 -o $b -gt 100 : returns true"
echo "$a -lt 100 -o $b -gt 100 : returns false"
if
[
$a -lt 5 -o $b -gt 100
]
echo "$a -lt 100 -o $b -gt 100 : returns true"
echo "$a -lt 100 -o $b -gt 100 : returns false"
|
运行结果:
10 != 20 : a is not equal to b
10 -lt 100 -a 20 -gt 15 : returns true
10 -lt 100 -o 20 -gt 100 : returns true
10 -lt 5 -o 20 -gt 100 : returns false
|
布尔运算符列表 |
运算符 | 说明 | 举例 |
! | 非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。 |
-o | 或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
-a | 与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
先来看一个例子:
echo "$a = $b : a is equal to b"
echo "$a = $b: a is not equal to b"
echo "$a != $b : a is not equal to b"
echo "$a != $b: a is equal to b"
echo "-z $a : string length is zero"
echo "-z $a : string length is not zero"
echo "-n $a : string length is not zero"
echo "-n $a : string length is zero"
echo "$a : string is not empty"
echo "$a : string is empty"
|
运行结果:
abc = efg: a is not equal to b
abc != efg : a is not equal to b
-z abc : string length is not zero
-n abc : string length is not zero
abc : string is not empty
|
字符串运算符列表 |
运算符 | 说明 | 举例 |
= | 检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。 |
!= | 检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。 |
-z | 检测字符串长度是否为0,为0返回 true。 | [ -z $a ] 返回 false。 |
-n | 检测字符串长度是否为0,不为0返回 true。 | [ -z $a ] 返回 true。 |
str | 检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
文件测试运算符用于检测 Unix 文件的各种属性。
例如,变量 file 表示文件"/var/www/tutorialspoint/unix/test.sh",它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:
file="/var/www/tutorialspoint/unix/test.sh"
echo "File has read access"
echo "File does not have read access"
echo "File has write permission"
echo "File does not have write permission"
echo "File has execute permission"
echo "File does not have execute permission"
echo "File is an ordinary file"
echo "This is sepcial file"
echo "File is a directory"
echo "This is not a directory"
echo "File size is not zero"
echo "File does not exist"
|
运行结果:
File has read access
File has write permission
File has execute permission
File is an ordinary file
This is not a directory
File size is zero
File exists
|
文件测试运算符列表 |
操作符 | 说明 | 举例 |
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
-p file | 检测文件是否是具名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
-s file | 检测文件是否为空(文件大小是否大于0),不为空返回 true。 | [ -s $file ] 返回 true。 |
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
在shell脚本中可以使用三类命令:
虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。
常用命令语法及功能
echo "some text": 将文字内容打印在屏幕上
ls: 文件列表
wc
–l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
cp sourcefile destfile: 文件拷贝
mv oldname newname : 重命名文件或移动文件
rm file: 删除文件
grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt
cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令
cat file.txt: 输出文件内容到标准输出设备(屏幕)上
file somefile: 得到文件类型
read var: 提示用户输入,并将输入赋值给变量
sort file.txt: 对file.txt文件中的行进行排序
uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq
expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3
find: 搜索文件比如:根据文件名搜索find . -name filename -print
tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
head file: 打印文本文件开头几行
tail file : 打印文本文件末尾几行
sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file
awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt | awk -F, '{print $1 "," $3 }'这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA
echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串。命令格式:
您可以使用echo实现更复杂的输出格式控制。
结果将是:
双引号也可以省略。
echo "$name It is a test"
|
结果将是:
同样双引号也可以省略。
如果变量与其它字符相连的话,需要使用大括号({ }):
结果将是:
输出:
输出:
echo "It is a test"
> myfile
|
若需要原样输出字符串(不进行转义),请使用单引号。例如:
结果将显示当前日期
从上面可看出,双引号可有可无,单引号主要用在原样输出中。
如果表达式中包含特殊字符,Shell 将会进行替换。例如,在双引号中使用变量就是一种替换,转义字符也是一种替换。
举个例子:
#!/bin/bash
a=10
echo -e "Value of a is $a
"
|
运行结果:
这里 -e 表示对转义字符进行替换。如果不使用 -e 选项,将会原样输出:
下面的转义字符都可以用在 echo 中:
转义字符 | 含义 |
\ | 反斜杠 |
a | 警报,响铃 |
| 退格(删除键) |
f | 换页(FF),将当前位置移到下页开头 |
| 换行 |
| 回车 |
| 水平制表符(tab键) |
v | 垂直制表符 |
可以使用 echo 命令的 -E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。
printf 命令用于格式化输出, 是echo命令的增强版。它是C语言printf()库函数的一个有限的变形,并且在语法上有些不同。
注意:printf 由 POSIX 标准所定义,移植性要比 echo 好。
如同 echo 命令,printf 命令也可以输出简单的字符串:
$printf
"Hello, Shell
"
Hello, Shell
$
|
printf 不像 echo 那样会自动换行,必须显式添加换行符(
)。
printf 命令的语法:
printf format-string [arguments...]
format-string 为格式控制字符串,arguments 为参数列表。
printf()在C语言入门教程中已经讲到,功能和用法与 printf 命令类似,请查看:C语言格式输出函数printf()详解
这里仅说明与C语言printf()函数的不同:
# format-string为双引号
$ printf
"%d %s
"
1
"abc"
1 abc
# 单引号与双引号效果一样
$ printf
'%d %s
'
1
"abc"
1 abc
# 没有引号也可以输出
$ printf
%s abcdef
abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
$ printf
%s abc def
abcdef
$ printf
"%s
" abc def
abc
def
$ printf
"%s %s %s
" a b c d e f g h i j
a b c
d e f
g h i
j
# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
$ printf
"%s and %d
"
and 0
# 如果以 %d 的格式来显示字符串,那么会有警告,提示无效的数字,此时默认置为 0
$ printf
"The first program always prints'%s,%d
'" Hello Shell
-bash:
printf: Shell: invalid number
The first program always prints 'Hello,0'
$
|
注意,根据POSIX标准,浮点格式%e、%E、%f、%g与%G是"不需要被支持"。这是因为awk支持浮点预算,且有它自己的printf语句。这样Shell程序中需要将浮点数值进行格式化的打印时,可使用小型的awk程序实现。然而,内建于bash、ksh93和zsh中的printf命令都支持浮点格式。
这些不是系统命令,但是他们真的很重要。
管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含有"hello"的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令替换的语法:
注意是反引号,不是单引号,这个键位于 Esc 键下方。
下面的例子中,将命令执行结果保存在变量中:
echo "Logged in user are $USERS"
|
运行结果:
Date is Thu Jul 2 03:59:57 MST 2009
Logged in user are 1
Uptime is Thu Jul 2 03:59:57 MST 2009
03:59:57 up 20 days, 14:03, 1 user, load avg: 0.13, 0.07, 0.15
|
命令的输出不仅可以是显示器,还可以很容易的转移向到文件,这被称为输出重定向。
命令输出重定向的语法为:
这样,输出到显示器的内容就可以被重定向到文件。
例如,下面的命令在显示器上不会看到任何输出:
打开 users 文件,可以看到下面的内容:
$ cat users
oko tty01 Sep 12 07:30
ai tty15 Sep 12 13:32
ruth tty21 Sep 12 10:10
pat tty24 Sep 12 13:07
steve tty25 Sep 12 13:03
$
|
输出重定向会覆盖文件内容,请看下面的例子:
$ echo line 1 > users
$ cat users
line 1
$
|
如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:
$ echo line 2 >> users
$ cat users
line 1
line 2
$
|
和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:
这样,本来需要从键盘获取输入的命令会转移到文件读取内容。
注意:输出重定向是大于号(>),输入重定向是小于号(<)。
例如,计算 users 文件中的行数,可以使用下面的命令:
也可以将输入重定向到 users 文件:
注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
如果希望 stderr 追加到 file 文件末尾,可以这样写:
2 表示标准错误文件(stderr)。
如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
或
如果希望对 stdin 和 stdout 都重定向,可以这样写:
command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。
全部可用的重定向命令列表 |
命令 | 说明 |
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
Here Document 目前没有统一的翻译,这里暂译为"嵌入文档"。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本的形式如下:
它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
注意:
This is a simple lookup program
for good (and bad) restaurants
|
运行结果:
This is a simple lookup program
for good (and bad) restaurants
in Cape Town.
|
下面的脚本通过 vi 编辑器将 document 保存到 test.txt 文件:
vi $filename
<<EndOfCommands
This file was created automatically from
|
运行脚本:
$ sh test.sh
Vim: Warning: Input is not from a terminal
$
|
打开 test.txt,可以看到下面的内容:
$ cat test.txt
This file was created automatically from
a shell script
$
|
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
$ command
> /dev/null 2>&1
|
if 语句通过关系运算符判断表达式的真假来决定执行哪个分支。Shell 有三种 if ... else 语句:
echo "a is not equal to b"
|
运行结果:
if ... else ... fi 语句的语法:
if [ expression ]
then
Statement(s) to be executed if expression is true
else
Statement(s) to be executed if expression is not true
fi
如果 expression 返回 true,那么 then 后边的语句将会被执行;否则,执行 else 后边的语句。
举个例子:
echo "a is not equal to b"
|
执行结果:
a is not equal to b
if ... elif ... fi 语句可以对多个条件进行判断,语法为:
if [ expression 1 ]
then
Statement(s) to be executed if expression 1 is true
elif [ expression 2 ]
then
Statement(s) to be executed if expression 2 is true
elif [ expression 3 ]
then
Statement(s) to be executed if expression 3 is true
else
Statement(s) to be executed if no expression is true
fi
哪一个 expression 的值为 true,就执行哪个 expression 后面的语句;如果都为 false,那么不执行任何语句。
举个例子:
echo "a is greater than b"
echo "None of the condition met"
|
运行结果:
a is less than b
if ... else 语句也可以写成一行,以命令的方式来运行,像这样:
if
test
$[2*3] -eq $[1+5];
then echo 'The two numbers are equal!';
fi;
|
if ... else 语句也经常与 test 命令结合使用,如下所示:
if
test
$[num1] -eq $[num2]
echo 'The two numbers are equal!'
echo 'The two numbers are not equal!'
|
输出:
The two numbers are equal!
test 命令用于检查某个条件是否成立,与方括号([ ])类似。
case ... esac 与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构。
case 语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:
case 值 in
模式1)
command1
command2
command3
;;
模式2)
command1
command2
command3
;;
*)
command1
command2
command3
;;
esac
case工作方式如上所示。取值后面必须为关键字 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
下面的脚本提示输入1到4,与每一种模式进行匹配:
echo 'Input a number between 1 to 4'
echo 'Your number is:c'
read aNum
case
$aNum
in
1) echo 'You select 1'
;;
2) echo 'You select 2'
;;
3) echo 'You select 3'
;;
4) echo 'You select 4'
;;
*) echo 'You do not select a number between 1 to 4'
;;
esac
|
输入不同的内容,会有不同的结果,例如:
Input a number between 1 to 4
Your number is:3
You select 3
再举一个例子:
#!/bin/bash
option="${1}"
case
${option}
in
-f)
FILE="${2}"
echo "File name is $FILE"
;;
-d)
DIR="${2}"
echo "Dir name is $DIR"
;;
*)
echo "`basename ${0}`:usage: [-f file] | [-d directory]"
exit
1
# Command to come out of the program with status 1
;;
esac
|
运行结果:
$./test.sh
test.sh: usage: [ -f filename ] | [ -d directory ]
$ ./test.sh -f index.htm
$ vi test.sh
$ ./test.sh -f index.htm
File name is index.htm
$ ./test.sh -d unix
Dir name is unix
$
for循环一般格式为:
for 变量 in 列表
do
command1
command2
...
commandN
done
列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。
in 列表是可选的,如果不用它,for 循环使用命令行的位置参数。
例如,顺序输出当前列表中的数字:
for loop in
1
2
3
4
5
do
echo "The value is: $loop"
done
|
运行结果:
The value is: 1
The value is: 2
The value is: 3
The value is: 4
The value is: 5
顺序输出字符串中的字符:
for str in
'This is a string'
do
echo $str
done
|
运行结果:
This is a string
显示主目录下以 .bash 开头的文件:
#!/bin/bash
for FILE in
$HOME/.bash*
do
echo $FILE
done
|
运行结果:
/root/.bash_history
/root/.bash_logout
/root/.bash_profile
/root/.bashrc
while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:
while command
do
Statement(s) to be executed if command is true
done
命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。
以下是一个基本的while循环,测试条件是:如果COUNTER小于5,那么返回 true。COUNTER从0开始,每次循环处理时,COUNTER加1。运行上述脚本,返回数字1到5,然后终止。
COUNTER=0
while
[
$COUNTER -lt 5
]
do
COUNTER='expr $COUNTER+1'
echo $COUNTER
done
|
运行脚本,输出:
1
2
3
4
5
while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环。
echo 'type <CTRL-D> to terminate'
echo -n 'enter your most liked film: '
while
read FILM
do
echo "Yeah! great film the $FILM"
done
|
运行脚本,输出类似下面:
type <CTRL-D> to terminate
enter your most liked film: Sound of Music
Yeah! great film the Sound of Music
until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。
until 循环格式为:
until command
do
Statement(s) to be executed until command is true
done
command 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。
例如,使用 until 命令输出 0 ~ 9 的数字:
#!/bin/bash
a=0
until
[
!
$a -lt 10
]
do
echo $a
a=`expr $a
+
1`
done
|
运行结果:
0
1
2
3
4
5
6
7
8
9
在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,像大多数编程语言一样,Shell也使用 break 和 continue 来跳出循环。
break命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,就要使用break命令。
echo -n "Input a number between 1 to 5: "
1|2|3|4|5) echo "Your number is $aNum!"
*) echo "You do not select a number between 1 to 5, game is over!"
|
在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。例如:
表示跳出第 n 层循环。
下面是一个嵌套循环的例子,如果 var1 等于 2,并且 var2 等于 0,就跳出循环:
if
[
$var1 -eq 2 -a $var2 -eq 0
]
|
如上,break 2 表示直接跳出外层循环。运行结果:
1 0
1 5
continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
对上面的例子进行修改:
echo -n "Input a number between 1 to 5: "
1|2|3|4|5) echo "Your number is $aNum!"
*) echo "You do not select a number between 1 to 5!"
|
运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句
永远不会被执行。
同样,continue 后面也可以跟一个数字,表示跳出第几层循环。
再看一个 continue 的例子:
echo "Number is an even number!!"
|
运行结果:
Found odd number
Number is an even number!!
Found odd number
Number is an even number!!
Found odd number
Number is an even number!!
Found odd number
函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell 也支持函数。Shell 函数必须先定义后使用。
Shell 函数的定义格式如下:
function_name () {
list of commands
[ return value ]
} |
如果你愿意,也可以在函数名前加上关键字 function:
function function_name () {
list of commands
[ return value ]
} |
函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。
Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:"numeric argument required"。
如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。
先来看一个例子:
#!/bin/bash
funWithReturn(){
echo "The function is to get the sum of two numbers..."
echo -n "Input first number: "
read aNum
echo -n "Input another number: "
read anotherNum
echo "The two numbers are $aNum and $anotherNum !"
return
$(($aNum+$anotherNum))
}
funWithReturn
# Capture value returnd by last command
ret=$?
echo "The sum of two numbers is $ret !"
|
运行结果:
The function is to get the sum of two numbers...
Input first number: 25
Input another number: 50
The two numbers are 25 and 50 !
The sum of two numbers is 75 !
|
函数返回值在调用该函数后通过 $? 来获得。
再来看一个函数嵌套的例子:
#!/bin/bash
# Calling one function from another
number_one () {
echo "Url_1 is http://see.xidian.edu.cn/cpp/shell/"
number_two
}
number_two () {
echo "Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/"
}
number_one
|
运行结果:
Url_1 is http://see.xidian.edu.cn/cpp/shell/
Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/
|
像删除变量一样,删除函数也可以使用 unset 命令,不过要加上 .f 选项,如下所示:
如果你希望直接从终端调用函数,可以将函数定义在主目录下的 .profile 文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用。
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...
带参数的函数示例:
#!/bin/bash
funWithParam(){
echo "The value of the first parameter is $1 !"
echo "The value of the second parameter is $2 !"
echo "The value of the tenth parameter is $10 !"
echo "The value of the tenth parameter is ${10} !"
echo "The value of the eleventh parameter is ${11} !"
echo "The amount of the parameters is $# !"
# 参数个数
echo "The string of the parameters is $* !"
# 传递给函数的所有参数
}
funWithParam 1
2
3
4
5
6
7
8
9
34
73
|
运行脚本:
The value of the first parameter is 1 !
The value of the second parameter is 2 !
The value of the tenth parameter is 10 !
The value of the tenth parameter is 34 !
The value of the eleventh parameter is 73 !
The amount of the parameters is 12 !
The string of the parameters is 1 2 3 4 5 6 7 8 9 34 73 !"
注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。
另外,还有几个特殊变量用来处理参数,前面已经提到:
特殊变量 | 说明 |
$# | 传递给函数的参数个数。 |
$* | 显示所有传递给函数的参数。 |
$@ | 与$*相同,但是略有区别,请查看Shell特殊变量。 |
$? | 函数的返回值。 |
像其他语言一样,Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本。
Shell 中包含脚本可以使用:
或
两种方式的效果相同,简单起见,一般使用点号(.),但是注意点号(.)和文件名中间有一空格。
例如,创建两个脚本,一个是被调用脚本 subscript.sh,内容如下:
url="http://see.xidian.edu.cn/cpp/view/2738.html"
|
一个是主文件 main.sh,内容如下:
#!/bin/bash
.
./subscript.sh
echo $url
|
执行脚本:
$chomd +x main.sh
./main.sh
http://see.xidian.edu.cn/cpp/view/2738.html
$
注意:被包含脚本不需要有执行权限。