第二十四章:编写第一个shell脚本
shell脚本是一个包含一系列的命令的文件,shell读取这个文件,然后执行这些命令。三个步骤:
- 编写脚本
- 使脚本可执行
- 将脚本放置于shell能够发现的地方
脚本的格式,最简单的一个脚本:
#!/bin/bash
# This is our first script
echo 'Hello World!'
将上述脚本保存为hello_world。
可执行权限:
chmod 755 hello_world //rwxr-xr-x
执行脚本:
./hello_world //good
hello_world //无法执行
为什么直接写文件名称无法运行,这是因为脚本的位置,查看Path的内容:
echo $PATH //执行后可以看到一系列目录
脚本如果在这一系列目录中任意一个,那么就可以直接执行。
~/bin
目录是存放一个脚本的理想位置。
更多格式的诀窍:
1. 使用长选项,提高可读性
ls -ad
ls --all --directory
- 缩进
一般的长命令:
find playground \( -type -f -not -perm 0600 -exec chomd 0600 '{}' ';'\) -or \( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)
使用缩进后:
find playground \
\( \
-type -f \
-not -perm 0600 \
-exec chmod 0600 '{}' ';' \
\) \
-or \
\( \
-type d \
-not -perm 0700 \
-exec chmod 0700 '{}' ';' \
\) \
每行的末尾的\
(行连接符)都代表换行,行首的四个\
代表转义字符。
第二十五章:启动一个项目
HTML文档:
#!/bin/bash
# Program to output a system information page
echo "<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
<H1>System Information Report </H1>
Page body.
</BODY>
</HTML>"
常量和变量:
变量是由字母、数字和下划线组成,名称的首字母必须是字母或者下划线,且名称中不能有空格和标点。
常量和变量其实没有区别,只是为了编程方便。
通常使用全大写字母表示常量,小写字母表示变量。例如
foo="yes"
FOO="no"
echo $foo //yes
echo $foo1 //空值
为常量和变量赋值:注意在赋值时,变量(常量)和等号,等号和值之间不能有空格
a=z //字符z赋值给变量a
b="a string" //空格必须用引号括起来
c="a string and $b" //使用其它扩展
d=$(ls -l foo.txt) //命令的结果
e=$((5*7)) //算术扩展
f="\t\ta string\n" //转义序列
a=5 b="a string" //一行为多个变量赋值
在扩展区间,变量名称用花括号括起来:
filename="myfile"
touch $filename
mv $filename ${filename}1 //将myfile文件重命名为myfile1
除了echo可以输出文本,“here脚本”也可以用来输出。
command << token
text
token
command是命令名,token是用来指示嵌入文本结尾的字符串。例如:
#!/bin/bash
# Program to output a system information page
cat << _EOF_
<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
<H1>System Information Report </H1>
Page body.
</BODY>
</HTML>
_EOF_
不再使用echo,而是使用cat和“here文档”,特别要注意:在“here文档”中,单引号和双引号都将失去它们在shell中的特殊含义。所以在“here文档”中可以随意嵌入引号。
另外,重定向符<<
如果改成<<-
,shell就会忽略here文档中的Tab字符,这样就能缩进here文档,从而提高可读性。
#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n <<- _EOF_
open $FTP_SERVER
user anonymous me@linuxbox
cd $FTP_PATH
hash
get $REMOTE_FILE
bye
_EOF_
ls -l $REMOTE_FILE
第二十六章:自顶向下设计
1. shell函数:
function name {
commands
return
}
或者
name () {
commands
return
}
举个例子:
#!/bin/bash
# shell function
function func {
echo "step2"
return
}
# Main
echo "step1"
func
echo "step3"
执行该脚本时,会忽略注释行和函数定义行,而且函数调用必须在函数定义之前。
函数命名规则和变量相同。一个函数必须至少包含一条命令,return(可选)可以满足该要求。
2. 局部变量
定义在shell函数内的变量,用local修饰,一旦shellh函数终止,该变量就不再存在。
#!/bin/bash
# local variables
foo=0 # global variable
func_1 () {
local foo
foo=1
echo "func_1: foo = $foo"
}
func_2 () {
local foo
foo=2
echo "func_2: foo = $foo"
}
echo "global foo = $foo"
func_1
func_2
输出的结果显然为:
global foo = 0
func_1: foo = 1
func_2: foo = 2
3. 保持脚本的运行
第二十七章:流控制:IF分支语句
1. IF语句的格式如下:
if commands; then
commands
[elif commands; then
commands...]
[else
commands...]
fi
比如:
x=5
if [ $x = 5 ]; then
echo "x equals 5"
else
echo "x does not equal 5"
fi
后面会解释中括号的内容。
2. 退出状态
命令执行完之后,会向操作系统发送一个值,称之为退出状态,0~255,0表示成功,其它表示失败。
在执行完一个命令后,输入echo $?
,即表示输出上一次命令的退出状态。
另外:linux还内置的两个命令:true
表示命令总是执行成功,false
表示总是失败。
3. 使用test
命令
和if
连用的命令是:
test expression
if [ expression ] //注意有空格
常见的文件表达式:
file1 -ef file2 //两个文件通过硬链接指向同一个文件
file1 -nt file2 //file1比file2新
file1 -ot file2 //file1比file2旧
-e file //文件存在
-d file //文件存在且为目录
-f file //文件存在且为普通文件
-L file //文件存在且为符号链接
-r file //文件存在且可读
-S file //文件存在且是一个网络套接字
常见的字符串表达式:
string //string不为空
-n string //string长度大于0
-z string //string的长度为0
string1=string2
string1==string2 //相等
string1!=string2 //不相等
string1<string2 //排序小于
注意:在使用test命令时,<和>必须使用引号括起来,或者是使用反斜杠转义,否则会被shell解释为重定向。
整数表达式:
int1 -eq int2 //equal
int1 -ne int2 //not equal
int1 -le int2 //less and equal
int1 -lt int2 //less than
int1 -ge int2 //greater and equal
int1 -gt int2 //great than
举个例子:
#!/bin/bash
echo -n "Please enter a number: "
read INT
if [ -z "$INT" ]; then
echo "INT is empty." >&2
exit 1
fi
if [ $INT -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0 ]; then
echo "INT is negetive."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
上述脚本用来判断整数INT是正负数和奇偶数。
4. 更加现代的test命令版本
增强的命令:
[[ expression ]]
该命令增加了新的字符串表达式以及==
操作符:
string=~regex
例如:
INT=-5 FILE=foo.bar
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
...
fi
if [[ $FILE == foo.* ]]; then
...
fi
其中^-?[0-9]+$
是一个正则表达式,表示以负号开头,且以0~9中的某一个数字结尾。foo.*
即模式匹配,匹配以foo.
开头的任意字符串。
5. (())
——为整数设计
bash还提供了(())
复合命令,专门为整数设计(可以嵌套使用),例如:
INT=-5
if ((INT == 0)); then //等价于[ INT -eq 0 ]
if (( ((INT % 2)) == 0 )) //嵌套使用
该命令能够通过变量名称识别变量,因此不需要扩展操作。
6. 组合表达式
表达式之间常常使用逻辑运算符连接:
Operator | test | [[]]和(()) |
---|---|---|
AND | -a | && |
OR | -o | ` |
NOT | ! | ! |
例如:
MIN_VAL=1 MAX_VAL=100 INT=50
if [[ !(INT -ge MIN_VAL) && INT -le MAX_VAL ]];
//等价于[ !\(INT -ge MIN_VAL\) -a INT -le MAX_VAL ]
特别注意:在test命令中,不像[[ ]]
和(())
,在bash中有特殊含义的字符,例如:<, >, (, )
必须进行转义或者用引号括起来,所以上述等价中的括号进行了转义。
7. 控制运算符:另外一种方式的分支
bash还提供了两种可以执行的分支的控制运算符:&&
和||
,与复合命令中的逻辑运算符类似:
command1 && command2
command1 || command2
例如:
mkdir temp && cd temp
第二十八章:读取键盘输入
shell程序如何与用户交互呢?
1. read——从标准输入读取输入值
read [-options] [variables...]
常见的read选项:
-a array //将输入值从索引为0的位置开始赋给array
-s //保密模式,不在屏幕显示输入的字符
-n num //读取num个字符,不是一整行
-e //使用Readline处理输入
-p prompt //使用prompt字符提示用户进行输入
例子:
#!/bin/bash
# read-multipe
echo -n "Please enter one or more values: "
read var1 var2 var3 var4 var5
echo "var1 = '$var1'"
echo "var2 = '$var2'"
echo "var3 = '$var3'"
echo "var4 = '$var4'"
echo "var5 = '$var5'"
此脚本为五个变量赋值,当输入少于5个时,剩下的为空,输入大于5个时,多余的都被赋值到最后一个变量中了。如果read后没有任何一个变量,则会为所有的输入分配一个shell变量:REPLY
。
使用read选项。
使用IFS间隔输入字段:
IFS的默认值包含空格、制表符和换行符,每一种都能把字符彼此间分隔开。例如:
#!/bin/bash
# read-ifs
FILE=/etc/psswd
read -p "Please enter a username >" user_name
file_info=$(grep "^$user_name:" $FILE)
if [ -n "$file_info" ]; then
IFS=":" read user pw uid gid name home shell <<< "$file_info"
echo "User = '$user'"
echo "UID = '$uid'"
echo "GID = '$gid'"
echo "Full Name = '$name'"
echo "Home Dir = '$home'"
echo "Shell = '$shell'"
else
echo "No such user '$user_name'" >&2
exit 1
fi
2. 验证输入
3. 菜单
菜单驱动是一种常见的交互方式,例如:
echo "
Please Select:
1. xxx
2. xxxx
3. xxxxx
"
read -p "Enter selection [0-3]: "
if [[ $REPLY =~ ^[0-3]$ ]]; then
...
if [[ $REPLY == 1 ]]; then
...
elif [[ $REPLY == 2 ]]; then
...
else
...
fi
else
...
fi
第二十九章:流控制,WHILE和UNTIL循环
1. while循环:
while commands; do
commands
done
例如:输出1~5这5个数字
#!/bin/bash
# while-count
count=1
while [ $count -le 5]; do
echo $count
count=$((count + 1))
done
echo "finished."
2. 跳出循环
使用continue
和break
。
3. until
until
和while
的流程是一样的,只是判断条件恰好相反:
until commands; do
commands
done
4. 使用循环读取文件