这是我第一次尝试使用双拼输入法翻译一篇日本的Liunx基础教材上的一章。共花费一周左右。
shell的使用
前言
通过shell输入的命令,可以通过shell script逐次顺序执行。可以灵活利用这个,实现输入命令的自动化。以下学习一下必要的技能。
9.1 shell和shell脚本
9.2 编程
9.2.1 程序例
9.2.2 程序的要素
9.3 shell脚本做成
9.3.1 shell脚本做成
9.3.2 变量
9.3.3 echo命令
9.3.4 read命令
9.3.5 shell变量
9.3.6 环境变量
9.3.7 注释
9.3.8 引用符
9.3.9 引数
9.3.10 shift
9.3.11 转义字符
9.3.12 代码注释
9.4 分支语句
9.4.1 if语句
9.4.2 晚间属性比较
9.4.3 多条件比较
9.4.4 一对多分支
9.5 循环
9.5.1 for语句
9.5.2 while/until 语句
9.5.3 select语句
9.5.4 循环控制
9.6 方法/函数
9.6.1 函数
9.6.2 return语句
9.7 真实的shell脚本
9.7.1 启动脚本
9.7.2 函数脚本
9.8 debug
9.8.1 sh命令
9.1 shell和shell脚本
在开始学习shell之前,先了解一下shell和shell 脚本的区别。
shell
内核是操作系统的基本组成部分,担当着控制硬件等等一系列的机能。
shell直接翻译是【壳】。 当操作系统内核提供的功能的时候,有必要跟操作系统进行对话。
将OS的内核部分包裹起来的机能叫做shell,提供对话功能。 shell接收命令的输入然后执行,返回给输入者处理结果。
shell脚本
假设一个场景,/etc和/home路径压缩后,拷贝到外部服务器上。
实际实行的命令如下:
# tar cvzf 120626-etc.tar.gz /etc
# tar cvzf 120626-home.tar.gz /home
# scp 120626-etc.tar.gz [email protected]:~/backup
# scp 120626-home.tar.gz [email protected]:~/backup
每个命令的执行,都那么麻烦。还有就是,本来所有命令都需要手工从端末输入,一个命令没执行完毕的话,下一个命令就不能执行--这样效率不怎么好。类似这种循环处理的自动化手段,写个shell脚本就能搞定。
上面例子的一连串操作,我们创建个system-backup.sh名称的文件,执行ystem-backup.sh来运行写入的命令。
**shell脚本内容 **
#!/bin/bash
tar cvzf 120626-etc.tar.gz /etc
tar cvzf 120626-home.tar.gz /home
scp 120626-etc.tar.gz [email protected]:~/backup
scp 120626-home.tar.gz [email protected]:~/backup
9.2 编程
对于计算机来说,按照指示顺序执行的技能,被叫做程序。编写程序的过程叫做编程。将学习过的命令,加上条件分支和循环处理之后成为可执行的内容,这个叫做shell脚本。
9.2.1 程序例
一提起程序,最初想到的可能是运动会或者演唱会。或者公开的电视节目表。按照顺序进行的项目,可以称为程序。实际上什么样的例子比较好呢?简单的例子,电视节目录像预约(好像不常用。。。)。需要指定开始时间,结束时间,录像画质。稍微复杂的例子的话,就是空调控制。需要设定【多少度制冷】【到多少度停止制冷】,这就是一种程序。
9.2.2 程序的要素
编程,不论什么样的程序,都有以下4种重要要素:
• 顺序执行
• 条件分支
• 循环
• 子函数
这些需要好好掌握。
9.3 shell脚本做成
接下来尝试实际做成shell脚本。开始介绍shell脚本做成到执行的过程
9.3.1 shell脚本做成
shell脚本用纯文本编辑。请使用vi一类的文本编辑工具。
先尝试创建名为lsdate.sh的shell脚本,里面执行ls和date命令。
$ vi lsdate.sh
(做成lsdate.sh)
在vi中编写以下内容,保存终了退出。
#!/bin/bash
ls
date
shell指定
第一行的内容【#!/bin/bash】,表明利用的shell种类。shell有很多种类,我们的说明中使用的是bash。
在第一行指定shell,从第二行开始逐行输入需要执行的命令。
permission(权限)变更
做成的shell脚本,需要更改权限,追加可执行权限。
下面给例子的脚本追加可执行权限。
首先,使用ls命令查看文件权限。
$ ls -l lsdate.sh
使用chmod命令追加可执行权限。
$ chmod u+x lsdate.sh
$ ls -l lsdate.sh
这样执行后,就已经赋予了可执行权限。我们赶紧执行一下吧。
$ ./lsdate.sh
【./】是指定路径。执行当前路径下可执行脚本的意思。
与之比较的话,执行ls,cp命令的时候是路径已经指定(通过系统的PATH),所以不指定当前路径也可以运行。
上面的例子,是为了在当前路径下执行特殊脚本(非PATH定义过),所以指明了路径。
这样的话,在lsdate.sh记述的ls和date命令就顺序被执行了。
9.3.2 echo命令
echo是给定文字列的参数在标准输出中输出的命令。如果参数的位置是带有$的变量,也可以输出。
命令格式:
echo [option] 字符串
option:
-n 禁止换行
例子:
$ echo Message test
Message test
9.3.3 变量
编程时,非常重要的考虑点就是变量。
变量简单说的话,就是一个箱子。里面存有数值啊,文字列啊等等。类似于中学时的X,Y,Z。
shell脚本编程时,将数值,文字列代入变量中,就可以使用了。
代入变量使用=(等号)。
练习:
给shell变量abc赋值,并使用echo确认内容。
$ abc=123
$ echo $abc
123
给变量abc赋值了123。
bash中也可以使用数组。要素是中括号[]把内容扩起来。
数组的内容表示的时候,$后面用大括号{}把数组变量扩起来。
$ abc[0]=123
$ abc[1]=456
$ echo ${abc[0]}
123
$ index=1
$ echo ${abc[$index]}
456
shell变量和环境变量
shell有shell变量,环境变量两种。
shell变量只在正在执行的shell内部有效。环境变量对执行过的shell也是有效的。
从shell变量可以变成环境变量。
练习:环境变量做成
使用export命令,做成环境变量
$ export abc
shell变量abc变成环境变量
$ export xyz=234
声明环境变量
第一行的命令是做成abc环境变量,abc中没有赋值。
第二行的命令是做成xyz环境变量,xyz被赋值234。
我们用下面的两个脚本 BBB.sh 和 CCC.sh来了解一下shell变量和环境变量的区别。
例子:
$ cat BBB.sh
#!/bin/bash
xxx=123
export yyy=234
./CCC.sh
$ cat CCC.sh
#!/bin/bash
echo xxx=$xxx
echo yyy=$yyy
(值表示)
$ ./BBB.sh
xxx=
yyy=234
(BBB.sh中执行CCC.sh,xxx没有被继承了, yyy被继承了)
这个shell脚本执行时,CCC.sh中的xxx值没有表示。是因为shell变量没有被继承。
另外,因为yyy是环境变量,所以在CCC.sh中被继承了,值也就被表示了。
9.3.4 read命令
read命令是从标准输入读取数据。如果变量中已经有数据了,新读入的数据会覆盖上。
格式:
read 变量名
练习:
使用read命令来变更shell变量内容。
$ echo $abc
123
$ read abc
aaabbbccc
(输入内容)
$ echo $abc
aaabbbccc
(shell变量内容被修改)
9.3.5 shell变量
如果想表示shell变量的一览,请使用set命令。
如果想删除shell变量,使用unset命令。
练习: shell变量的表示和删除
$ set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
abc=aaabbbccc
(此时可以看到abc这个shell变量)
$ unset abc
(删除abc shell变量)
9.3.6 环境变量
如果想表示环境变量的一览,请使用env命令。
如果想删除已经做成的环境变量,使用unset命令。
练习: 环境变量的表示和删除
$ env
HOSTNAME=host1.alpha.jp
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
abc=aaabbbcc
(同上)
$ unset abc
(同上)
9.3.7 注释
注释是程序上记录的用于提示的内容。
shell是用#开始,在程序执行时,注释内容是被无视的。
注释大多是程序员用于记述程序是如何处理的,也有时临时无效化一部分代码(comment out)。
格式:
#!/bin/bash
# assign xyz to variable abc and output it
(这一行在程序执行时被无视)
abc=xyz
echo $abc
练习:执行有注释的代码
首先写一个有注释的脚本,然后执行。
1:脚本做成
# vi comment.sh
2:记入内容
#!/bin/bash
abc=xyz
#abc=123
echo $abc
3:执行脚本
# chmod u+x comment.sh
# ./comment.sh
xyz
9.3.8 引用符
命令执行的时候,变量用引用符括起来。引用符有’(单引号)、"(双引号)、‘(反引号)。
如果用’(单引号)括起来的话,括号内的字符串就是字符串,即使括号内是变量,也是作为字符串输出。
如果用"(双引号)括起来的话,括号内是变量的话,变量执行后输出。
如果用‘(反引号)括起来的话,括号内是命令的话,命令执行后输出。
练习:引用符的动作确认
这几个引用符的区别,让我们试验一下。
$ abc=xyz
$ echo 'value abc is $abc'
value abc is $abc
($abc做为字符串输出)
$ echo "value abc is $abc"
value abc is xyz
($abc被识别成变量了,输出变量内容)
$ abc=`date`;
$ echo $abc
Thu Mar 20 06:08:14 JST 2012
($abc中有date命令,输出date的执行结果)
9.3.9 参数
shell脚本执行时,可以把option作为参数使用。参数的格式$1, $2…,使用时在$后指定编号就可以。
练习:参数输出确认
像下面一样作成args.sh脚本,实验一下调用参数。
$ cat args.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
echo '$0:' $0;
echo '$#:' $#;
$ ./args.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$0: ./args.sh
$#: 3
($1-$3 是参数,$0是 执行的命令名,$#是参数个数)
9.3.10 shift
shift是将参数移位的命令。比如 $2 移动到$1 ,$3 移动到$2。
练习:shift命令的实验
先作成脚本,然后进行shift动作确认。
$ cat argsshift.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
shift
echo '$1:' $1;
echo '$2:' $2;
传参数
$ ./argsshift.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$1: bbb
($1变成了bbb)
$2: ccc
($2变成了ccc)
9.3.11 转义字符(Escape Sequence)
在程序中有一些特别的文字。以echo为例,输出"(双引号)时,像下面这样
echo """ (用双引号括上双引号)
但是,有3个双引号,无法判断对应关系。如果执行的话会报错。所以,为了回避这个报错,需要声明一下『第二个双引号不是区分符号,而是字符串』这个时候,使用的符号叫转义字符。在shell脚本中使用(\、日文系统使用半角的¥)
$ echo ""
"
(成功输出双引号)
这个例子是输出了第二个双引号。
还有,转义字符也可以作为换行符。\在一行的末尾,命令的字符串可以中途换行。
适当的换行的话,命令的可读性会提高很多。
$ echo "I am Cat.My name is nothing."
I am Cat.My name is nothing.
$ echo "I am Cat.Y
> My name is nothing."
I am Cat.My name is nothing.
(虽然命令的字符串中途加入\,但是结果中被无视了。)
\的换行,在shell脚本中也可以使用。
例子:
$ vi escape.sh
#!/bin/bash
echo "I am Cat.Y
My name is nothing."
$ ./escape.sh
I am Cat.My name is nothing
9.3.12 source命令
source是bash内部命令。作用是读取指定的文件后,设定到shell环境中。
文件内容会作为shell命令,被解释执行。
一般用途是,当shell环境变量文件.bashrc"和".bash_profile"修改后,不用重新登录直接使设定在现在的shell环境上有效化。
做个例子:作成set.sh,给$abc变量定义xyz,然后使用source命令读入。
1:作成set.sh
$ cat set.sh
(set.sh 的内容确认)
#!/bin/sh
abc=xyz
echo $abc
2:使用echo输出$abc
$ echo $abc
(什么结果都没有)
3:运行set.sh
$ ./set.sh
xyz
($abc变量里存入了xyz,所以有输出结果)
4:再次使用echo输出$abc
$ echo $abc
($abc还是没有输出)
(到这一步说明了$abc还没有全局化,只是set.sh内有效)
5:使用source读取set.sh
$ source set.sh
xyz
($abc变量里存入了xyz,所以有输出结果)
6:再次使用echo输出$abc
$ echo $abc
xyz
(有结果输出,证明5中source有效了)
9.4 条件分支
根据条件切换动作的条件分支,在程序中经常出现。条件分支几乎是所有语言都存在的功能。
当然,shell脚本里面也可以使用。
9.4.1 if语句
根据比较结果来进行分支处理的时候使用if。格式如下:
if 比较 1 then ... elif 比较2 ... else ... fi
elif...和else...的部分可以省略掉。
elif在第二个比较开始的时候使用。
else在之前的比较条件全都不符合的时候使用。
if语句的结尾fi(表面看是if逆序,实际上是finish的简写)
比较格式如下:
表 9.1 字符串比较
比较公式 | 比较内容 |
---|---|
a == b | 等于判断 |
a != b | a 不等于判断 |
表 9.2 数字比较
比较公式 | 比较内容 |
---|---|
a -eq b | 相等的话 真 |
a -ne b | 不相等的话 真 |
a -ge b | a >= b 真 |
a -le b | a = |
a -gt b | a > b 真 |
a -lt b | a |
9.4.2 文件属性确认
使用以下方式。
格式:
if test -d 文件路径 ; then.....
-d的部分是文件属性的计算命令参数,用于判定后面的内容是不是路径。
所以这句话的全体是,文件路径 如果是路径的话,返回真。
文件属性的确认方法像以下格式一样。也可以使用test [ ] 的方式。
格式:
if [ 条件判断 ]; then ...
if test 条件判断 ; then ....
表 9.3 文件属性判断
演算子 | 内容 |
---|---|
-f 文件名 | 普通文件的话 真 |
-d 文件名 | 路径的话 真 |
-e 文件名 | 文件存在的话 真 |
-L 文件名 | 符号链接的话 真 |
-r 文件名 | 可读文件的话 真 |
-w 文件名 | 可写入文件的话 真 |
-x 文件名 | 文件存在且可以执行的话 真 |
-s 文件名 | 文件大小不为0的话 真 |
跟文件相关的比较方法,除了属性还有别的。详细参考man test。
命令:
man test
9.4.3 多条件判断
分支判断中可以使用多条件判断。条件 A和条件 B 同时成立时,使用AND来表达。
同理,条件 A 或者 条件 B 成立时,使用OR来表达。
shell中存在AND和OR两种写法。
AND
-a 或者&&
格式:
[条件 A -a 条件 B -a 条件 C ] ....
[条件 A] && [条件 B] && [条件 C] ...
-a 和&&は、放在[ ]内侧和外侧效果不一样,需要注意。
OR
-o 或者 ||
格式:
[条件 A -o 条件 B -o 条件 C ] ....
[条件 A] || [条件 B] || [条件 C] ...
9.4.4 一对多分支语句
我们来考虑一下一对多情况下的分支语句。
如果是用if语句,格式如下。
shell脚本里面还有case语句可以实现一对多的分支。
if格式:
if 条件 then
:
elif 条件 then
:
elif 条件 then
:
:
fi
case格式:
case 变量 in
值A)
处理 1;;
值B)
处理 2;;
esac
变量是A的时候,进行 处理 1,最后用esac(case的逆序)结束处理。值可以使用(|)来间隔复数个值。
例子:
$ cat case.sh
#!/bin/bash
case $1 in
a|A)
echo "参数是a或者A";;
b|B)
echo "参数是b或者B";;
esac
执行的话,得到下面的结果
$ ./case.sh a
参数是a或者A
$ ./case.sh B
参数是b或者B
另外一种情况。哪一个分支都不匹配条件的时候,使用(*)来接收。
例子:
$ cat defaultcase.sh
(做成下面内容的sh脚本)
#!/bin/bash
case $1 in
1)
echo "参数是1";;
2)
echo "参数是2";;
*)
echo "参数是1,2以外";;
esac
执行结果如下
$ ./defaultcase.sh 1
参数是1
$ ./defaultcase.sh 2
参数是2
$ ./defaultcase.sh 0
参数是1,2以外
9.5 循环处理
在程序中有跟条件分支一样重要的机能,就是循环处理。
循环处理是,同样的处理循环执行,直到某些条件成立之后才结束。shell脚本可以使用的有三种形式。
9.5.1 for语句
for 语句是列举某个值,将这个值作为对象循环处理。
格式:
for 变量 in 值列表
do
处理内容
done
值列表是文字字符串的话也可以执行。
比如:
$ for i in a b c d
> do
> echo $i
> done
a
b
c
d
’a b c d’作为值列表,当 i=a执行的时候,执行echo $i,接下来i=b的时候执行echo
$i...这么一个循环处理的结果。
把命令的执行结果作为值列表,也可以执行循环。
for i in `ls`
※反引号可以执行引号内的命令。(可以参考9.3.8 引用符)
9.5.2 while/until语句
while语句是在条件成立期间一直循环处理。
until语句是条件不成立期间一直循环处理。
格式:
while 条件
do 处理
done
until 条件
do 处理
done
为了实现C语言中的for语句一样的循环,可以使用expr命令递增或者递减来进行计数,处理while/until。参照下面内容做成loop.sh,尝试执行一下。
例子:
$ cat loop.sh
#!/bin/bash
count=1
while [ $count -le 10 ]
do
echo "这个处理执行$count回了"
count=`expr $count + 1`
done
count=1 意思是初始化count为1。
while [ $count -le 10 ] 的意思是,当count小于10的时候循环处理。
得到以下结果
$ ./loop.sh
这个处理执行1回了
这个处理执行2回了
...
这个处理执行10回了
9.5.3 select语句
select语句用于提取用户输入内容,然后跟值列表进行比较后输出匹配结果。
格式:
select 变量 in 值列表
do 处理
done
例子:
select name in "apple" "banana" "orange"
do
echo "You selected $name";
done
实际执行一下
1) apple
2) banana
3) orange
$? 1
You selected apple
(「Ctrl」 C 中止)
输出1到3,会执行do--done之间的内容。
9.5.4 循环的控制
break和continue可以用来控制循环。
break循环中止,continue当前处理停止,继续下一次循环。
例子:
$ cat ./sample.sh
while true
do
echo "Continue? (y/n)"
read input
case $input in
n) break
;;
y) continue
;;
*) echo "Please input y or n."
;;
esac
done
执行结果如下:
$ ./sample.sh
Continue? (y/n)
y
Continue? (y/n)
a
(输入y,n以外)
Please input y or n.
Continue? (y/n)
n
$
(循环结束)
9.6 方法/函数
程序中把可以再利用的部分整理成一块,叫做方法/函数。每个语言中都有各自的叫法,shell中称呼为方法/函数。
格式
function 方法名
{
处理
}
或者
方法名 ()
{
处理
}
两种写法实际上是一样的。参数都是记载在方法名之后。在方法内都可以使用$1, $2来读取。
9.6.2 return语句
方法内当返回结果时,使用return
格式
return 变量名
当执行return的后,方法内处理也就结束了。把结果返回调用这个函数的地方。
9.7 真实的shell脚本
接下来我们看一下真实的shell脚本。(例子使用的是CentOS 6.3的启动脚本,其他发型版或者CentOS其他版本会多少有些不一样)
9.7.1 启动脚本
我们参考一下/etc/rc。/etc/rc这个shell脚本,在启动流的实际执行脚本中记述着启动必要的处理。
第28行
. /etc/init.d/functions
读入/etc/init.d/functions 。
/etc/路径下有很多各种各种的脚本,包含一些共通用的便利函数。为了使之有效,所以需要执行28行。
/etc/init.d/functions は、/etc/の中に
第50 行
[ -d /etc/rc$runlevel.d ] || exit 0
-d 用于判断路径是否存在。
$runlevel 是个变量,用于取得当前运行阶段。我们用3举例。
|| 是 判断执行。假如||前面是true,就处理结束。如果||前面是false,就开始执行||后面的内容。
exit 退出命令
0 正常退出,默认设定。如果使用变量的?话,格式是 $?
总结下来就是:判断/etc/rc3.d是否存在,如果存在这条命令执行完成,不存在的话就退出。
第60 行
for i in /etc/rc$runlevel.d/K* ; do
继续以$runlevel是3举例。
/etc/rc3.d/路径下,以K开头的脚本循环记入$i,然后执行$i里面的脚本。
在第69行的$i stop用来停止脚本执行。
K开头的脚边的实体存放在/etc/init.d/。
文件名中K后面的两位数字,实际上对应停止的顺序。
9.7.2 方法/函数的脚本
/etc/init.d/functions 就是上面/etc/rc の 28 行说明的一样,是一个便利方法/函数的集合脚本。
我们挑一个方法/函数来说明一下。
578 行
is_ignored_file() {
case "$1" in
*~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave)
return 0
;;
esac
return 1
}
is_ignored_file()这个方法/函数的作用是过滤掉 /etc/rc 下面符合in后面那一些文件名格式的文件。
如果变量$1的值(文件名) 在*~, *.bak, *.orig, *.rpmnew, *.rpmorig, *.rprmsave里面,
那么就返回0(false),否则的话返回1(true)。
当然,除了在/etc/rc 下使用,别的地方也可以调用。
9.8 debug
当做成的代码没有按照自己想定的运行时,有必要调查一下哪里出现问题了。这就是所谓的debug。
一般有两种方式。第一种是在代码中写入很多打印变量的代码,或者写入很多记录程序运行轨迹的代码。
这个方式比较麻烦费事。第二种方式是很多语言都提供的debug的工具。这个比第一种简单省事好多。
shell脚本里同样提供了可以进入debug模式的命令。
9.8.1 sh命令
sh命令本身是用来执行脚本的。如果加上 -x 参数后指定执行的脚本,shell里的命令和变量一边表示一边执行。我们就用有循环的sample.sh脚本,使用sh -x实验一下。
例子:
$ sh -x ./sample.sh
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
y
(输入y)
+ case $input in
+ continue
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
n
(输入n)
+ case $input in
+ break
以上就是shell脚本的基础内容的全部。