【变量】
在shell里,使用变量之前无需事先做出声明,只是通过使用来创建它们。在默认情况下,所有变量都被看做字符串并以字符串来存储(即使是数值)。shell和一些工具程序会在需要时把数值型字符转换为对应的数值以进行操作,并且变量名区分大小写。
在shell中,通过在变量名前加一个$符号来访问。当为变量赋值时,只需使用变量名,变量会根据需要被自动创建。一种检查变量内容的简单方式:在变量名前加一个$符号,再用echo命令将其内容输出在终端上。
在命令行上,通过设置和检查变量的不同值来实际查看变量的使用:
$ salutation=Hello
$ echo $salutatino
Hello
$ salutation=”Yes Dear”
$ echo $salutation
Yes Dear
$ salutation=7+5
$ echo $salutation
7+5
如果字符串里包含空格,就必须用引号括起来,此外,等号两边不能有空格。
可以使用read命令将用户的输入赋值给一个变量,这个命令需要一个参数,即准备读入用户输入数据的变量名,然后read会等待用户输入数据。通常情况下,用户按下回车键时,read命令结束。当从终端上读取一个变量时,一般不需要使用引号:
$ read salutation
Wie geht’s?
$ echo $salutaion
Wie geht’s?
如果把一个$变量表达式放在双引号中,程序执行到这一行时会把变量替换为它的值;如果放在单引号中,就不会发生替换现象。还可以通过在$字符前面加上一个\字符以取消它的特殊含义。
字符串通常都被放在双引号中,以防止变量被空白字符分开,同时又允许$扩展。
环境变量 | 说明 |
$HOME | 当前用户的家目录 |
$PATH | 以冒号分隔的用来搜索命令的目录列表 |
$PS1 | 命令提示符,通常是$字符,但是在bash中,可以使用更复杂的值。例如,字符串[\u@\h\W]$是一个流行的默认值,它给出用户名、机器名和当前目录名,也包括一个$提示符 |
$PS2 | 二级提示符,用来提示后续的输入,通常是>字符 |
$IFS | 输入域分隔符。当shell读取输入时,给出用来分隔单词的一组字符,通常是空格、制表符和换行符 |
$0 | shell脚本名 |
$# | 传递给脚本的参数个数 |
$$ | shell脚本的进程号,脚本程序通常会用来生成一个唯一的临时文件,如tmp/tmpfile_$$ |
执行env <command>命令可以查看程序在不同环境下是如何工作的,使用export命令在子shell中可以设置环境变量。
参数变量 | 说明 |
$1, $2, … | 脚本程序的参数 |
$* | 在一个变量中列出所有的参数,各个参数之间用环境变量IFS中的第一个字符分隔开,如果ISF被修改了,那么$*将命令行分割为参数的方式就将随着改变 |
$@ | $*的一种精巧变体,不使用IFS环境变量 |
$@和$*的区别:
$ IFS=’’
$ set foo bar bam
$ echo “$@”
foo bar bam
$ echo “$*”
foobarbam
$ unset IFS
$ echo “$*”
foo bar bam
【条件】
一个shell脚本能够对任何可以从命令行上调用的命令的退出码进行测试,其中也包括自己编写的脚本程序,这也就是为什么要在所有自己编写的脚本程序的结尾包括一条返回值的exit命令的重要原因。
要想查看系统中是否有一个指定名称的外部命令,可以使用which test这样的命令来检查执行的是哪一个test命令,或者使用./test这种执行方式以确保执行的是当前目录下的脚本程序。
检查一个文件是否存在:
if test –f fred.c
then
#...
fi
或者:
if [ -f fred.c ]
then
#...
fi
test命令的退出码(表明条件是否被满足)决定是否需要执行后面的条件代码,注意必须在[符号和被检查的条件之间留出空格。
如果要把then和if放在同一行上,就必须要用一个分号把test语句和then分隔开:
if [ -f fred.c ]; then
#...
fi
字符串比较 | 结果 |
string1 = string2 | 字符串相同则为真 |
string1 != string2 | 字符串不同则为真 |
-n string | 字符串不为空则为真 |
-z string | 字符串为null(一个空串)则为真 |
算术比较 | 结果 |
expression1 –eq expression2 | 表达式相等则为真 |
expression1 –ne expression2 | 表达式不等则为真 |
expression1 –gt expression2 | 1大于2则为真 |
expression1 –ge expressino2 | 1大于等于2则为真 |
expression1 –lt expression2 | 1小于2则为真 |
expression1 –le expression2 | 1小于等于2则为真 |
! expression | 表达式为假则结果为真,反之亦然 |
文件条件测试 | 结果 |
-d file | 如果文件是一个目录则结果为真 |
-e file | 如果文件存在则结果为真。(历史上-e选项不可移植,通常使用的是-f选项) |
-f file | 如果文件是一个普通文件则结果为真 |
-g file | 如果文件的set-group-id位被设置则结果为真 |
-u file | 如果文件的set-user-id位被设置则结果为真 |
-r file | 如果文件可读则结果为真 |
-w file | 如果文件可写则结果为真 |
-x file | 如果文件可执行则结果为真 |
-s file | 如果文件大小不为0则结果为真 |
set-group-id和set-user-id也叫做set-gid和set-uid,set-uid位授予了程序其拥有者的访问权限而不是其使用者的访问权限,而set-gid位授予了程序其所在组的访问权限。这两个特殊位是通过chmod命令的选项s和g设置的,set-gid和set-uid标志对shell脚本程序不起作用,它们只对可执行的二进制文件有用。
测试/bin/bash文件状态:
#! /bin/bash
if [ -f /bin/bash ]
then
echo ‘file /bin/bash exists’
fi
if [ -d /bin/bash ]
then
echo ‘/bin/bash is a directory’
else
echo ‘/bin/bash is not a directory’
fi
【控制结构】
1、if语句
对某个命令的执行结果进行测试,然后根据测试结果有条件地执行一组语句:
if condition
then
statements
else
statements
fi
例子:
#!/bin/bash
echo ‘Is it morning? Please answer yes orno’
read timeofday
if [ $timeofday = ‘yes’ ]
then
echo ‘Good morning’
else
echo ‘Good afternoon’
fi
exit 0
shell会忽略多余的用来缩进if结构内部的空白符。
2、elif语句
#!/bin/bash
echo ‘Is it morning? Please answer yes orno’
read timeofday
if [ $timeofday = ‘yes’ ]
then
echo ‘Good morning’
elif [ $timeofday = ‘no’ ]
then
echo ‘Good afternoon’
else
echo ‘Sorry, $timeofday not recognized.Enter yes or no’
exit 1
fi
exit 0
如果两次测试结果都不成功,就打印一条出错信息并以1为退出码结束脚本程序,调用者可以在调用程序中利用这个退出码来检查脚本程序是否执行成功。
3、变量相关问题
为了避免出现空字符串的问题,必须给变量加上引号:
if [ “$timeofday” = ‘yes’ ]
从而使if [ = ‘yes’ ]变成if [ ‘’ = ‘yes’ ]。
如果想让echo命令去掉每一行后面的换行符,可移植性最好的办法是使用printf命令而不是echo命令。有的shell用echo –e命令来完成这一任务,但并不是所有的系统都支持该命令,bash使用echo –n命令来去除换行符,但必须确信自己的脚本程序只运行在bash上。
4、for语句
for结构用来循环处理一组值,这组值可以是任意字符串的集合。
for variable in values
do
statements
done
使用固定字符串的for循环:
#!/bin/bash
for foo in bar fud 43
do
echo $foo
done
exit 0
for循环经常与shell的文件名扩展一起使用,这意味着在字符串的值中使用一个通配符,并由shell在程序执行时填写出所有的值。
打印当前目录中所有以字母f开头、以.sh结尾的脚本文件:
#!/bin/bash
for file in $(ls f*.sh)
do
lpr $file
done
exit 0
其中,for命令的参数表来自括在$()中的命令的输出结果,shell扩展f*.sh给出所有匹配此模式的文件的名字。
shell脚本程序中所有的变量扩展都是在脚本程序被执行时而不是在编写它时完成的。所以,变量声明中的语法错误只有在执行时才会被发现。
5、while语句
如果需要重复执行一个命令序列,但事先不知道命令序列应该执行的次数,通常会使用一个while循环。
while condition
do
statements
done
简陋的密码检查程序:
#!/bin/bash
echo ‘Enter password’
read trythis
while [ “$trythis” != ‘secret’ ]
do
echo ‘Sorry, try again’
read trythis
done
exit 0
do和done之间的语句反复执行,直到条件不再为真。
6、until语句
until condition
do
statements
done
与while循环很相似,只是把条件测试反过来了,换句话说,循环将反复执行直到条件为真,而不是在条件为真时反复执行。
一般来说,如果需要循环至少执行一次,那么就使用while循环;如果可能根本都不需要执行,就使用until循环。
当某个特定用户登录时就启动的警报:
#!/bin/bash
until who | grep “$1” > /dev/null
do
sleep 60
done
# now ring the bell and announce theexpected user.
echo –e ‘\a’
echo “$1 has just logged in”
exit 0
7、case语句
case variable in
pattern [ | pattern ] …) statements;;
pattern [ | pattern ] …) statemetns;;
…
esac
case结构允许通过一种比较复杂的方式将变量的内容和模式进行匹配,然后再根据匹配的模式去执行不同的代码。
每个模式行都以双分号(;;)结尾,因为可以在前后模式之间放置多条语句,所以需要使用一个双分号来标记前一个语句的结束和后一个模式的开始。
在case结构的模式中,使用如*这样的通配符时要小心,因为case将使用第一个匹配的模式,即使后续的模式有更加精确的匹配也是如此。
用户输入:
#!/bin/bash
echo ‘Is it morning? Please answer yes orno’
read timeofday
case “$timeofday” in
yes) echo ‘Good morning’;;
no ) echo ‘Good Afternoon’;;
y ) echo ‘Good Morning’;;
n ) echo ‘Good Afternoon’;;
* ) echo ‘Sorry, answer not recognized’;;
esac
exit 0
一旦某个字符串与输入匹配成功,case命令就会执行紧随右括号)后面的代码,然后就结束。
case命令会对用来做比较的字符串进行正常的通配符扩展,因此可以指定字符串的一部分并在其后加上一个*通配符。只使用一个单独的*表示匹配任何可能的字符串,所以总是在其他匹配字符串之后再加上一个*以确保如果没有字符串得到匹配,case语句也会执行某个默认动作。之所以能够这样做是因为case语句是按顺序比较每一个字符串,它不会去查找最佳匹配,而仅仅是查找第一个匹配。因为默认条件通常是“最不可能出现”的条件,所以使用*对脚本程序的调试很有帮助。
合并匹配模式:
!/bin/bash
echo ‘Is it morning? Please answer yes orno’
read timeofday
case “$timeofday” in
yes | y | Yes | YES) echo ‘Good Morning’;;
n* | N* ) echo ‘Good Afternoon’;;
* ) echo ‘Sorry, answer not recognized’;;
esac
exit 0
需要注意*通配符扩展在引号中不起作用。
执行多条语句:
#!/bin/bash
echo ‘Is it morning? Please answer yes orno’
read timeofday
case “$timeofday” in
yes | y | Yes | YES )
echo ‘Good Morning’
echo ‘Up bright and early this morning’
;;
[nN]* )
echo ‘Good Afternoon’
;;
* )
echo ‘Sorry, answer not recognized’
echo ‘Please answer yes or no’
exit 1
;;
esac
exit 0
必须把最精确的匹配放在最开始,而把最一般化的匹配放在最后,因为case将执行它找到的第一个匹配而不是最佳匹配。
esac前面的双分号是可选的,因为在shell程序设计中,如果最后一个case模式是默认模式。
为了让case的匹配功能更强大,可以使用如下模式:[Yy] | [Yy][Ee][Ss]
8、命令列表
AND列表结构允许按照特定方式执行一系列命令:只有在前面所有的命令都执行成功的情况下才执行后一条命令。
statement1 && statement2 &&statement3 &&……
从左开始顺序执行每条命令,如果一条命令返回的是true,它右边的下一条命令才能够执行,如此持续直到有一条命令返回false,或者列表中的所有命令都执行完毕。&&的作用是检查前一条命令的返回值,每条语句都是独立执行。AND列表作为一个整体,只有在列表中的所有命令都执行成功时,才算执行成功,否则就算失败。
用AND列表检查每个文件是否存在并给出提示:
#!/bin/bash
touch file_one # 检查文件是否存在,如果不存在就创建它
rm –f file_two
if [ -f file_one ] && echo ‘hello’&& [ -f file_two ] && echo ‘ there’
then
echo ‘in if’
else
echo ‘in else’
fi
exit 0
结果:
hello
in else
echo命令总是返回true,因为命令列表中的一条命令失败了,所以&&列表总的执行结果是false,if语句将执行它的else部分。
OR列表结构允许持续执行一系列命令直到有一条命令成功为止,其后的命令不再被执行。
statement1 || statement2 || statement3 ||……
从左开始顺序执行每条命令,如果一条命令返回的是false,它右边的下一条命令才能够被执行,如此持续直到有一条命令返回true,或者列表中的所有命令都执行完毕。
沿用上一个例子:
#!/bin/bash
rm –f file_one
if [ -f file_one ] || echo ‘hello’ || echo ‘there’
then
echo ‘in if’
else
echo ‘in else’
fi
exit 0
结果:
hello
in if
AND和OR这两种结构的返回结果都等于最后一条执行语句的返回结果。这些列表类型结构只需执行最少的语句就可以确定其返回结果,不影响返回结果的语句不会被执行——短路径求值(short circuit evaluation)。
如果测试成功就会执行第一条命令,否则执行第二条命令:
[ -f file_one ] && command for true|| command for false
9、语句块
如果想在某些只允许使用单个语句的地方(比如AND或OR列表中)使用多条语句,可以把它们括在{}中来构造一个语句块。
【函数】
在一个脚本程序中执行另外一个脚本程序要比执行一个函数慢得多,返回执行结果变得更加困难,而且可能存在非常多的小脚本。
应该考虑脚本程序中是否有可以明显的单独存在的最小部分,并将其作为是否应该将一个大型脚本程序分解为一组小脚本的衡量程度。
function_name ()
{
statements
}
一个简单的函数:
#!/bin/bash
foo ()
{
echo ‘Function foo is executing’
}
echo ‘script starting’
foo
echo ‘script ended’
exit 0
必须在调用一个函数之前对其进行定义,只要把所有函数定义都放在任何一个函数调用之前,就可以保证所有的函数在被调用之前就被定义了。
当一个函数被调用时,脚本程序的位置参数($*、$@、$#、$1、$2等)会被替换为函数的参数,这是读取传递给函数的参数的办法。当函数执行完毕后,这些参数会恢复为它们先前的值(老版本shell可能不会)。
可以通过return命令让函数返回数字值,让函数返回字符串值的常用方法是让函数将字符串保存在一个变量中,该变量然后可以在函数结束之后被使用。
echo一个字符串并捕获其结果:
foo() {echo JAY;}
result=”$(foo)”
可以使用local关键字在shell函数中声明局部变量,局部变量仅在函数的作用范围内有效,此外,函数可以访问全局作用范围内的其他shell变量。
#!/bin/bash
sample_text=’global variable’
foo()
{
local sample_text=’local variable’
echo ‘Function foo is executing’
echo $sample_text
}
echo ‘script starting’
echo $sample_text
foo
echo ‘script ended’
echo $sample_text
exit 0
如果函数里没有使用return命令指定一个返回值,函数返回的就是执行的最后一条命令的退出码。
#!/bin/bash
yes_or_no()
{
echo “Is your name $* ?”
while true
do
echo –n ‘Enter yes or no: ‘
read x
case “$x” in
y | yes ) return 0;;
n | no ) return 1;;
* ) echo ‘Answer yes or no’;;
esac
done
}
echo “Original parameters are $*”
if yes_or_no “$1”
then
echo “Hi $1, nice name”
else
echo ‘Never mind’
fi
exit 0
执行:
$ ./my_name Rick Neil
输出:
Original parameters are Rick Neil
Is your name Rick ?
Enter yes or no: yes
Hi Rick, nice name