key words: bash编程
转自
这里另一篇
详解Bash命令行处理bash编程教学实例
bash编程
--------------------------------------------------------------------------------
時間:2004/03/02 來源:不详
Shell Script(bash)简介
众所皆知地,UNIX上以小工具著名,利用许多简单的小工具,来完成原本需要大量软体开发的工作,这一点特色,使得UNIX成为许多人心目中理想的系统平台。
在众多的小工具中,Shell Script算得上是最基本、最强大、运用最广泛的一个。它运用围之广,不但从系统启动、程式编译、定期作业、上网连线,甚至安装整个Linux系统,都可以用它来完成。
因为Shell Script是利用您平日在使用的一些指令,将之组合起来,成为一个"程式"。如果您平日某些序列的指令下得特别频繁,便可以将这些指令组合起来,成为另一个新的指令。这样,不但可以简化并加速操作速度,甚至还可以干脆自动定期执行,大大简化系统管理工作。
*************************
Bash(GNU Bourne-Again SHell)是许多Linux平台的内定Shell,事实上,还有许多传统UNIX上用的Shell,像tcsh、csh、ash、bsh、ksh等等, Shell Script大致都类同,当您学会一种Shell以后,其它的Shell会很快就上手,大多数的时候,一个Shell Script通常可以在很多种Shell上使用。
这里我介绍您bash的使用方法。事实上,当您"man bash"时,就可以看到bash的说明书,不过对许多人来说,这份说明书犹如"无字天书"一样难懂。这份文件,主要资料来源为"man bash",我加上一些实际日常的应用例来说明。希望这样能让那些始终不得其门而入的人们,多多少少能有点概念。
教学例子
"Hello world" Shell Script
照传统程式教学例,这一节介绍Shell Script的"Hello World"如何撰写。
*************************
#!/bin/sh
# Filename : hello
echo "Hello world!"
*************************
大家应该会注意到第一行的"#!/bin/sh"。在UNIX下,所有的可执行Script,不管是那一种语言,其开头都是"#!",例如Perl是 "#!/usr/bin/perl",tcl/tk是"#!/usr/bin/wish",看您要执行的Script程式位置在那里。您也可以用"#! /bin/bash"、"#!/bin/tcsh"等等,来指定使用特定的Shell。
echo是个bash的内建指令。
*************************
接下来,执行hello这个script:
要执行一个Script的方式有很多种。
*************************
第一种 : 将hello这个档案的权限设定为可执行。
[foxman@foxman bash]# chmod 755 hello
执行
[foxman@foxman bash]# ./hello
hello world
*************************
第二种 : 使用bash内建指令"source"或"."。
[foxman@foxman bash]# source hello
hello world
或
[foxman@foxman bash]# . hello
hello world
*************************
第三种 : 直接使用sh/bash/tcsh指令来执行。
[foxman@foxman bash]# sh hello
hello world
或
[foxman@foxman bash]# bash hello
hello world
*************************
Bash执行选项
*************************
-c string : 读取string来当命令。
-i : 互动介面。
-s : 由stdin读取命令。
- : 取消往后选项的读取。
-norc : 不要读~/.bashrc来执行。
-noprofile : 不要读/etc/profile、~/.bash_profile、~/.bash_login、~/.profile等等来执行。
-rcfile filename : 执行filename,而非~/.bashrc
-version : 显示版本。
-quiet : 启动时不要哩唆。
-login : 确保bash是个login shell。
-nobraceexpansion : 不要用curly brace expansion({}符号展开)。
-nolineediting : 不用readline来读取命令列。
-posix : 改采Posix 1003.2标准。
用于自动备份的Shell Script
一个用于自动备份的Shell Script
我们先前提到,可利用Shell Script搭配crond来作定期的工作。要作定期性的工作,在UNIX上,就是与crond的搭配运用。
*************************
首先我们先来研究如何对系统进行备份。
要对系统进行备份,不外乎便是利用一些压缩工具。在许多UNIX系统上,tar及gzip是de facto的资料交换标准。我们经常可以看见一些tar.gz或tgz档,这些档案,被称为tarball。当然了,您也可以用bzip2、zip等等压缩工具来进行压缩,不必限定于gzip。但tar配合gzip是最普遍的,也是最方便的方式。
要将我们想要的资料压缩起来,进行备份,可以结合tar及gzip一起进行。方式有很多种,最常用的指令是以下这一种:
tar -c file/dir ... | gzip -9 > xxxx.tar.gz
您也可以分开来做:
tar -r file/dir ... -f xxxx.tar
gzip -9 xxxx.tar
或
tar -r file/dir ... -f xxxx.tar
gzip -9 < xxxx.tar > xxxx.tar.gz
*************************
在解过Linux下档案备份的基本知识后,我们来写一个将档案备份的Script。
#!/bin/sh
# Filename : backup
DIRS="/etc /var /your_directories_or_files"
BACKUP="/tmp/backup.tgz"
tar -c $DIRS | gzip -9 > $BACKUP
其中DIRS放的是您要备份的档案及目录,BACKUP是您的备份档。可不要将/tmp放进DIRS中,那样做,您是在做备份的备份,可能将您的硬碟塞爆。
*************************
接下来测试
[foxman@foxman bash]# chmod 755 backup
[foxman@foxman bash]# ./backup
执行完成后在/tmp就会有一个backup.tgz,里面储存了您重要的资料。您可用
gzip -dc /tmp/backup.tgz | tar -vt
或
tar vtfz /tmp/backup.tgz
来看看里面的档案列表。
要解开时,可用以下指令来完成复原:
gzip -dc /tmp/backup.tgz | tar -xv
或
tar xvfz /tmp/backup.tgz
备份通常是仅备份系统通常最重要的部份,/etc可说是不可缺少的一部份。另外,看您系统中有那些重要的资料需要备份。通常来说,您没有必要备份 /bin、/sbin、/usr/bin、/usr/sbin、/usr/X11R6/bin等等这些执行档目录。只要备份您重要的档案即可,别把整个硬碟备份,那是蛮呆的动作。
*************************
如果您有许多台机器,可利用其中一台任务较轻的内部网路主机,做为主要备份主机。将所有机器都自动执行备份,然后利用NFS/Coda/Samba等网路档案系统,将备份的资料放到该备份机器中,该机器则定时收取备份资料,然后您再由该机器中进行一次备份。
这里是整个系统备份方案的图示。
在您进行之前,先解一下,系统中那些是要备份的,那些是不需要的。
*************************
新的backup
#!/bin/sh
HOSTNAME=`hostname`
DIRS="/etc /var /your_important_directory"
BACKUP="/tmp/$HOSTNAME.tgz"
NFS="/mnt/nfs"
tar -c $DIRS | gzip -9 > $BACKUP
mv -f $BACKUP $NFS
*************************
备份主机内的Script : collect_backup
#!/bin/sh
NFS="/mnt/nfs"
BACKUP="/backup"
mv -f $NFS/*.tgz $BACKUP
在此,您不能够将所有备份都直接放在/mnt/nfs,这是危险的。万一任一台机器不小心将/mnt/nfs所有内容删除,那么备份就会消失。因此,您需要将/mnt/nfs移到一个只有该备份主机可存取的目录中。
*************************
当这些个别的Script都测试好以后,接下来我们将他们放到crontab里面。找到您的crontab,它的位置可能在/var/spool/cron/crontabs/root、/etc/crontab、/var/cron/tabs/root。
在crontab中选择以下之一加入(看您定期的时间):
Slackware : /var/spool/cron/crontabs/root
01 * * * * /full_backup_script_path/backup 1> /dev/null 2> /dev/null # 每小时(太过火一点)
30 16 * * * /full_backup_script_path/backup 1> /dev/null 2> /dev/null # 每日16:30,下班前备份
30 16 * * 0 /full_backup_script_path/backup 1> /dev/null 2> /dev/null # 每周一16:30
0 5 1 * * /full_backup_script_path/backup 1> /dev/null 2> /dev/null # 每月一号5:0
RedHat/Debian : /etc/crontab
RedHat可直接将backup放入/etc/cron.hourly, /etc/cron.daily, /etc/cron.weekly, /etc/cron.monthly。或采用如上加入/etc/crontab的方式:
有关crontab的用法,可查"man 5 crontab",在此不详述。
备份主机的设定类同。
注意: 所有机器不要同时进行备份,否则网路会大塞车。备份主机收取备份的时间要设为最后,否则会收不到备份资料。您可以在实作后,将时间间隔调整一下。
*************************
看看,两个小小不到三行的Shell Script,配合cron这个定时工具。可以让原本需要耗时多个小时的人工备份工作,简化到不到十分钟。善用您的想像力,多加一点变化,可你让您的生活变得轻松异常,快乐悠哉。
档案系统检查
系统安全一向是大多数电脑用户关心的事,在UNIX系统中,最重视的事,即系统中有没有"木马"(Trojan horse)。不管Trojan horse如何放进来的,有一点始终会不变,即被放置木马的档案,其档案日期一定会被改变,甚至会有其它的状态改变。此外,许多状况下,系统会多出一些不知名的档案。因此,平日检查整个档案系统的状态是否有被改变,将所有状态有改变的档案,以及目前有那些程式正在执行,自动报告给系统管理员,是个避免坐上 "木马"的良方。
*************************
#!/bin/sh
# Filename : whatever_you_name_it
DIRS="/etc /home /bin /sbin /usr/bin /usr/sbin /usr/local /var /your_directory"
ADMIN="email@your.domain.com"
FROM="admin@your.domain.com"
# 写入Sendmail的标头
echo "Subject: $HOSTNAME filesystem check" > /tmp/today.mail
echo "From: $FROM" >> /tmp/today.mail
echo "To: $ADMIN" >> /tmp/today.mail
echo "This is filesystem report comes from $HOSTNAME" >> /tmp/today.mail
# 报告目前正在执行的程式
ps axf >> /tmp/today.mail
# 档案系统检查
echo "File System Check" >> /tmp/today.mail
ls -alR $DIRS | gzip -9 > /tmp/today.gz
zdiff /tmp/today.gz /tmp/yesterday.gz >> /tmp/today.mail
mv -f /tmp/today.gz /tmp/yesterday.gz
# 寄出信件
sendmail -t < /tmp/today.mail
然后把它放到一个不显眼的地方去,让别人找不到。
把它加入crontab中。
30 7 * * * /full_check_script_path/whatever_you_name_it 1> /dev/null 2> /dev/null #上班前检查
有些档案是固定会更动的,像/var/log/messages、/var/log/syslog、/dev/ttyX等等,不要太大惊小怪。
控制圈for
演示了几个简单的Shell Script,相信您应该对Shell Script有点概念了。现在我们开始来仔细研究一些较高等的Shell Script写作。一些进一步的说明,例如"$"、">"、"<"、">>"、"1>"、"2>"符号的使用,会在稍后解释。
*************************
for name [ in word; ] do list ; done
控制圈。
word是一序列的字,for会将word中的个别字展开,然后设定到name上面。list是一序列的工作。如果[in word;]省略掉,那么name将会被设定为Script后面所加的参数。
*************************
例一:
#!/bin/sh
for i in a b c d e f ; do
echo $i
done
它将会显示出a到f。
*************************
例二: 另一种用法,A-Z
#!/bin/sh
WORD="a b c d e f g h i j l m n o p q r s t u v w x y z"
for i in $WORD ; do
echo $i
done
这个Script将会显示a到z。
*************************
例三 : 修改副档名
如果您有许多的.txt档想要改名成.doc档,您不需要一个一个来。
#!/bin/sh
FILES=`ls /txt/*.txt`
for txt in $FILES ; do
doc=`echo $txt | sed "s/.txt/.doc/"`
mv $txt $doc
done
这样可以将*.txt档修改成*.doc档。
*************************
例四 : meow
#!/bin/sh
# Filename : meow
for i ; do
cat $i
done
当您输入"meow file1 file2 ..."时,其作用就跟"cat file1 file2 ..."一样。
*************************
例五 : listbin
#!/bin/sh
# Filename : listbin
for i in /bin/* ; do
echo $i
done
当您输入"listbin"时,其作用就跟"ls /bin/*"一样。
*************************
例六 : /etc/rc.d/rc
拿一个实际的例来说,Red Hat的/etc/rc.d/rc的启动程式中的一个片断。
for i in /etc/rc.d/rc$runlevel.d/S*; do
# Check if the script is there.
[ ! -f $i ] && continue
# Check if the subsystem is already up.
subsys=${i#/etc/rc.d/rc$runlevel.d/S??}
[ -f /var/lock/subsys/$subsys ] || \
[ -f /var/lock/subsys/${subsys}.init ] && continue
# Bring the subsystem up.
$i start
done
这个例中,它找出/etc/rc.d/rcX.d/S*所有档案,检查它是否存在,然后一一执行。
流程控制case
case word in [ pattern [ | pattern ] ... ) list ;; ] ... esac
case/esac的标准用法大致如下:
case $arg in
pattern | sample) # arg in pattern or sample
;;
pattern1) # arg in pattern1
;;
*) #default
;;
esac
arg是您所引入的参数,如果arg内容符合pattern项目的话,那么便会执行pattern以下的程式码,而该段程式码则以两个分号";;"做结尾。
可以注意到"case"及"esac"是对称的,如果记不起来的话,把"case"颠倒过来即可。
*************************
例一 : paranoia
#!/bin/sh
case $1 in
start | begin)
echo "start something"
;;
stop | end)
echo "stop something"
;;
*)
echo "Ignorant"
;;
esac
执行
[foxman@foxman bash]# chmod 755 paranoia
[foxman@foxman bash]# ./paranoia
Ignorant
[foxman@foxman bash]# ./paranoia start
start something
[foxman@foxman bash]# ./paranoia begin
start something
[foxman@foxman bash]# ./paranoia stop
stop something
[foxman@foxman bash]# ./paranoia end
stop something
*************************
例二 : inetpanel
许多的daemon都会附上一个管理用的Shell Script,像BIND就附上ndc,Apache就附上apachectl。这些管理程式都是用shell script来写的,以下示一个管理inetd的shell script。
#!/bin/sh
case $1 in
start | begin | commence)
/usr/sbin/inetd
;;
stop | end | destroy)
killall inetd
;;
restart | again)
killall -HUP inetd
;;
*)
echo "usage: inetpanel [start | begin | commence | stop | end | destory | restart | again]"
;;
esac
*************************
例三 : 判断系统
有时候,您所写的Script可能会跨越好几种平台,如Linux、FreeBSD、Solaris等等,而各平台之间,多多少少都有不同之处,有时候需要判断目前正在那一种平台上执行。此时,我们可以利用uname来找出系统资讯。
#!/bin/sh
SYSTEM=`uname -s`
case $SYSTEM in
Linux)
echo "My system is Linux"
echo "Do Linux stuff here..."
;;
FreeBSD)
echo "My system is FreeBSD"
echo "Do FreeBSD stuff here..."
;;
*)
echo "Unknown system : $SYSTEM"
echo "I don't what to do..."
;;
esac
流程控制select
select name [ in word; ] do list ; done
select顾名思义就是在word中选择一项。与for相同,如果[in word;]省略,将会使用Script后面所加的参数。
例:
#!/bin/sh
WORD="a b c"
select i in $WORD ; do
case $i in
a)
echo "I am A"
;;
b)
echo "I am B"
;;
c)
echo "I am C"
;;
*)
break;
;;
esac
done
执行结果
[foxman@foxman bash]# ./select_demo
1) a
2) b
3) c
#? 1
I am A
1) a
2) b
3) c
#? 2
I am B
1) a
2) b
3) c
#? 3
I am C
1) a
2) b
3) c
#? 4
返回状态Exit
在继续下去之前,我们必须要切入另一个话题,即返回状态值 - Exit Status。因为if/while/until都迁涉到了使用Exit Status来控制程式流程的问题。
*************************
许多人都知道,在许多语言中(C/C++/Perl....),都有一个exit的函数,甚至连Bash自己都有个exit的内建命令。而exit后面所带的数字,便是返回状态值 - Exit Status。
返回状态值可以使得程式与程式之间,利用Shell script来结合的可能性大增,利用小程式,透过Shell script,来完成很杂的工作。
在shell中,返回值为零表示成功(True),非零值为失败(False)。
*************************
举例来说,以下这个两个小程式yes/no分别会返回0/1(成功/失败):
/* yes.c */
void main(void) { exit(0); }
/* no.c */
void main(void) { exit(1); }
那么以下这个"YES"的shell script便会显示"YES"。
#!/bin/sh
# YES
if yes ; then
echo "YES"
fi
而"NO"不会显示任何东西。
#!/bin/sh
# NO
if no ; then
echo "YES"
fi
*************************
test express
[ express ]
在Shell script中,test express/[ express ]这个语法被大量地使用,它是个非常实用的指令。由于它的返回值即Exit Status,经常被运用在if/while/until的场合中。而在后面,我们也会大量运用到,在进入介绍if/while/until之前,有必要先解一下。
其返回值为0(True)或1(False),要看表述(express)的结果为何。
express格式
-b file : 当档案存在并且属性是Block special(通常是/dev/xxx)时,返回True。
-c file : 当档案存在并且属性是character special(通常是/dev/xxx)时,返回True。
-d file : 当档案存在并且属性是目录时,返回True。
-e file : 当档案存在时,返回True。
-f file : 当档案存在并且是正常档案时,返回True。
-g file : 当档案存在并且是set-group-id时,返回True。
-k file : 当档案存在并且有"sticky" bit被设定时,返回True。
-L file : 当档案存在并且是symbolic link时,返回True。
-p file : 当档案存在并且是name pipe时,返回True。
-r file : 当档案存在并且可读取时,返回True。
-s file : 当档案存在并且档案大小大于零时,返回True。
-S file : 当档案存在并且是socket时,返回True。
-t fd : 当fd被开启为terminal时,返回True。
-u file : 当档案存在并且set-user-id bit被设定时,返回True。
-w file : 当档案存在并且可写入时,返回True。
-x file : 当档案存在并且可执行时,返回True。
-O file : 当档案存在并且是被执行的user id所拥有时,返回True。
-G file : 当档案存在并且是被执行的group id所拥有时,返回True。
file1 -nt file2 : 当file1比file2新时(根据修改时间),返回True。
file1 -ot file2 : 当file1比file2旧时(根据修改时间),返回True。
file1 -ef file2 : 当file1与file2有相同的device及inode number时,返回True。
-z string : 当string的长度为零时,返回True。
-n string : 当string的长度不为零时,返回True。
string1 = string2 : string1与string2相等时,返回True。
string1 != string2 : string1与string2不相等时,返回True。
! express : express为False时,返回True。
expr1 -a expr2 : expr1及expr2为True。
expr1 -o expr2 : expr1或expr2其中之一为True。
arg1 OP arg2 : OP是-eq[equal]、-ne[not-equal]、-lt[less-than]、-le[less-than-or-equal]、-gt [greater-than]、-ge[greater-than-or-equal]的其中之一。
*************************
在Bash中,当错误发生在致命信号时,bash会返回128+signal number做为返回值。如果找不到命令,将会返回127。如果命令找到了,但该命令是不可执行的,将返回126。除此以外,Bash本身会返回最后一个指令的返回值。若是执行中发生错误,将会返回一个非零的值。
Fatal Signal : 128 + signo
Can't not find command : 127
Can't not execute : 126
Shell script successfully executed : return the last command exit status
Fatal during execution : return non-zero
流程控制if
if list then list [ elif list then list ] ... [ else list ] fi
几种可能的写法
*************************
第一种
if list then
do something here
fi
当list表述返回值为True(0)时,将会执行"do something here"。
例一 : 当我们要执行一个命令或程式之前,有时候需要检查该命令是否存在,然后才执行。
if [ -x /sbin/quotaon ] ; then
echo "Turning on Quota for root filesystem"
/sbin/quotaon /
fi
例二 : 当我们将某个档案做为设定档时,可先检查是否存在,然后将该档案设定值载入。
# Filename : /etc/ppp/settings
PHONE=1-800-COLLECT
#!/bin/sh
# Filename : phonebill
if [ -f /etc/ppp/settings ] ; then
source /etc/ppp/settings
echo $PHONE
fi
执行
[foxman@foxman ppp]# ./phonebill
1-800-COLLECT
*************************
第二种
if list then
do something here
else
do something else here
fi
例三 : Hostname
#!/bin/sh
if [ -f /etc/HOSTNAME ] ; then
HOSTNAME=`cat /etc/HOSTNAME`
else
HOSTNAME=localhost
fi
*************************
第三种
if list then
do something here
elif list then
do another thing here
fi
例四 : 如果某个设定档允许有好几个位置的话,例如crontab,可利用if then elif fi来找寻。
#!/bin/sh
if [ -f /etc/crontab ] ; then
CRONTAB="/etc/crontab"
elif [ -f /var/spool/cron/crontabs/root ] ; then
CRONTAB="/var/spool/cron/crontabs/root"
elif [ -f /var/cron/tabs/root ] ; then
CRONTAB="/var/cron/tabs/root"
fi
export CRONTAB
*************************
第四种
if list then
do something here
elif list then
do another thing here
else
do something else here
fi
例五 : 我们可利用uname来判断目前系统,并分别做各系统状况不同的事。
#!/bin/sh
SYSTEM=`uname -s`
if [ $SYSTEM = "Linux" ] ; then
echo "Linux"
elif [ $SYSTEM = "FreeBSD" ] ; then
echo "FreeBSD"
elif [ $SYSTEM = "Solaris" ] ; then
echo "Solaris"
else
echo "What?"
fi
控制圈while/until
while list do list done
当list为True时,该圈会不停地执行。
例一 : 无限回圈写法
#!/bin/sh
while : ; do
echo "do something forever here"
sleep 5
done
例二 : 强迫把pppd杀掉。
#!/bin/sh
while [ -f /var/run/ppp0.pid ] ; do
killall pppd
done
*************************
until list do list done
当list为False(non-zero)时,该圈会不停地执行。
例一 : 等待pppd上线。
#!/bin/sh
until [ -f /var/run/ppp0.pid ] ; do
sleep 1
done
参数与变数
在继续下去介绍function之前,我们必须停下来介绍"参数与变数"。
*************************
参数(Parameters)是用来储存"值"的资料型态,有点像是一般语言中的变数。它可以是个名称(name)、数字(number)、或者是以下所列出来一些特殊符号(Special Parameters)。
在shell中,变数是由name形式的参数所构成的。
*************************
在前面的许多例中,我们事实上已经看到许多参数的运用。要设定一个Parameter实际很简单:
name=value
例如说:
MYHOST="foxman"
而要使用它时,则是加个"$"符号。
echo $MYHOST
*************************
位置参数(Positional Parameters)
*************************
所谓的位置参数便是0,1,2,3,4,5,6,7,8,9...。使用时,用$0,$1,$2...。
位置参数是当script被载入时,后面所附加的参数。$0是本身,$1则为第一个参数,$2为第二个,依此类推。而当Positional Parameters被function所使用时,它们会被暂时取代(下一节会介绍function)。
例如以下这个script:
#!/bin/sh
# Filename : position
echo $0
echo $1
执行时:
[foxman@foxman bash]# ./position abc
./position
abc
当位置参数超过两位数时,有特别的方法来展开,称为Expansion。
*************************
特殊参数(Speical Parameters)
这些符号,非常不人性,对新手来说很困扰。但上手后,会觉得方便无比,有些如果您看不懂的话,就--算了,不用浪费太多时间在上面。
*************************
* 星号
将Positional Parameters合成一个参数,其间隔为IFS内定参数的第一个字元(见内建变数一节)。
例:
#!/bin/sh
# starsig
echo $*
执行:
[foxman@foxman bash]# starsig a b c d e f g
a b c d e f g
*************************
@ at符号
与*星号类同。不同之处在于不参照IFS。
例:
#!/bin/sh
# atsig
echo $@
执行:
[foxman@foxman bash]# atsig a b c d e f g
a b c d e f g
*************************
# 井字号
展开Positional parameters的数量。
例:
#!/bin/sh
# poundsig
echo $#
执行
[foxman@foxman bash]# poundsig a b c d e f g
7
*************************
? 问号
最近执行的foreground pipeline的状态。
*************************
- 减号
最近执行的foreground pipeline的选项参数。
*************************
$ 钱钱钱
本身的Process ID。
[foxman@foxman bash]# ps ax | grep bash
1635 p1 S 0:00 /bin/bash
[foxman@foxman bash]# echo $$
1635
*************************
! 惊号
最近执行背景命令的Process ID。
*************************
0 零
在Positional Parameters一部份已经说明过了,是执行的shell script本身。但如果是用"bash -c",则$0被设为第一个参数。
[foxman@foxman bash]# echo $0
/bin/bash
*************************
_ 底线符号
显示出最后一个执行的命令。
[foxman@foxman bash]# echo $_
bash
*************************
内建变数(Shell Variables)
Bash有许多内建变数,像PATH、HOME、ENV......等等。这些内建变数将在另一节中,专门一一说明。
函数function
[ function ] name () { list; }
function的参数是Positional Paraments。
例
#!/bin/sh
function func() {
echo $1
echo $2
return 1
}
func "Hello" "function"
局部变数可用local来宣告。
函数可export,使用下一层的shell可以使用。
函数可递,没有递层数的限制。
Bash内建指令集
以下的命令,大部份都没有使用例,您可能会看不出所以然,摸不著头脑。在我加入例说明前,建议您"man bash",然后自己实际操作一次。
*************************
: [arguments]
不做任何事,除了[arguments]一些参数展开及一些特定重导向的作业外。
永远返回零。它的用法跟true一样。
*************************
. filename [arguments]
source filename [arguments]
由filename中读取命令,并执行。
您会在/etc/rc.d/*中发现很多
. /xxxx
的指令,而xxxx的permission都不是可执行的。事实上,在tcsh中,需要用
source /xxxx
来做同样的指令。
注意到"."的后面是有空格的(比较一下". /"跟"./",不一样)。filename是内含指令的纯文字档即可,无须chmod 755 filename。
例
filename : my_source
DEV=lo
IP=127.0.0.1
NETMASK=255.0.0.0
BROADCAST=127.255.255.255
ifconfig $IP netmask $NETMASK broadcast $BROADCAST dev $DEV
接下来
. my_source
或
source my_source
便可执行该script,而不需要"chmod 755 my_source"
*************************
alias [name[=value] ...]
昵称命令
例如您如果来自DOS的世界,对UNIX的指令不习惯,可用alias来修改,以符合您的习惯。
例
alias ls="ls --color"
alias dir="ls"
alias cd..="cd .."
alias copy="cp -f" # dangerous, recommend, "cp -i"
alias del="rm -f" # dangerous, recommend, "rm -i"
alias move="mv -f" # dangerous, recommend, "mv -i"
alias md="mkdir"
alias rd="rmdir"
*************************
unalias [-a] [name ...]
unalias取消alias的设定。"unalias -a"将全部alias取消。
例
unalias copy
*************************
bg [jobspec]
将指定任务放到背景中,如果jobspec未指定,内定为目前的。
*************************
fg [jobspec]
将指定任务放到前景中,如果jobsepc没有指定,那么内定为目前的。
*************************
jobs [-lnp] [ jobspec ... ]
第一种形式列出目前正在工作的任务。
-l : 除了列出一般资讯外,还列出Process IDs。
-p : 仅列出该工作群"首脑"(Process group leader)的Process ID.
-n : 则仅列出有改变的jobs的状态。
如果给定jobspec,输出资讯则只有该jobspec。
返回值为零,除非有非法的选项发生。
jobs -x command [ args ... ]
如果使用第二种形式(-x),jobs取代指定的command及args,并执行返回其Exit Status。
*************************
kill [-s sigspec | -sigspec] [pid | jobspec] ...
将sigspec的信号送到pid或jobspec。
sigspec可以是SIGKILL/KILL这种形式或是信号号码。如果sigspec是signal name,则大小写无关,而且可以没有SIG。
kill -l [signum]
列出信号名称。
[foxman@foxman bash]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGIOT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR
*************************
wait [n]
等待指定的行程,并返回其结束状态。n可以是个jobspec或Process ID。如果n未指定,则等待所有的子行程,及返回值为零。若n为不存在的job或process,则返回127。否则,返回值为最后一个 job/process的Exit Status。
*************************
bind [-m keymap] [-lvd] [-q name]
bind [-m keymap] -f filename
bind [-m keymap] keyseq:function-name
显示出目前readline的按键及链结函数设定或是巨集。
-m keymap : 设定keymap binding。
-l : 显示出所有readline function的名称。
-v : 显示出目前的function name及bindings。
-d : 显示出function name及bindings。
-f filename : 从filename读取key bindings。
-q function : 询问那个按键触发function。
*************************
break [n]
跳出控制回圈for/while/until中使用。如果有指定n,则跳出n层。n必须是大于等于1。若n大于巢状圈数,则所有的圈都会跳离。返回值回零。
*************************
continue [n]
还原控制回圈for/while/until中使用。如果有指定n,则返回n层。n必须是大于等于1。若n大于巢状圈数,则还原到最上层。返回值回零。
*************************
exit [n]
离开程式。n是Exit Status。
*************************
return [n]
在function中使用。n为返回值,其作用与Exit Status一样。
*************************
builtin shell-builtin [arguments]
执行内建函数。当您定义了与内建函数相同的指令时,可用此命令来执行内建函数。
*************************
cd [dir]
更换目录到dir。如果没有指定,内定为HOME所指定的目录。
*************************
command [-pVv] command [arg ...]
用command指定可取消正常的shell function寻找。只有内建命令及在PATH中找得到的才会被执行。"-p"选项,搜寻命令的方式是用PATH来找。"-V"或"-v"选项,会显示出该命令的一些简约描述。
*************************
declare [-frxi] [name[=value]]
typeset [-frxi] [name[=value]]
宣告参数并给它们设定属性。如果没有给定名称,将会显示各参数值。
-f : 仅使用函数名称。
-r : 将name设为readonly。
-x : 将name输出给后续环境使用。
-i : 该参数被设为integer来使用,可用于算术表述。
用"+"时,关闭该属性。
*************************
dirs [-l] [+/-n]
显示目前记忆的目录。目录可透过pushd/popd来操作。
+n : 显示开始的记录n个。
-n : 显示结尾的记录n个。
-l : 显示较多的资讯。
*************************
echo [-neE] [arg ...]
输出显示args,由空白分隔。返回值永为零。
-n : 不跳行。
-e : 启动"\"符号的解译。
-E : 将ESC解译功能取消。
"\a" : alert(bell),发出声响。
"\b" : backspace,倒退。
"\c" : suppress trailing newline,不跳行。
"\f" : form feed,跳行跳格。
"\n" : new line,新行。
"\r" : carriage return,回到行起点。
"\t" : horizontal tab,水平跳位。
"\v" : vertical tab,垂直跳位。
"\\" : 输出"\"。
"\nnn" : 输出ASCII Code号码nnn(八进位)。
*************************
enable [-n] [-all] [name ...]
启动或关闭内建函数命令。使用"-n"将所有指定命令皆关闭,否则都是启动的。如果只有"-n"参数,它将会显示所有关闭的函数。如果只有"-all",它将会显示所有内建命令。
*************************
eval [arg ...]
读取args,并将args合为一个命令,然后执行。其返回值成为eval的返回值。如果没有参数,eval返回True。
*************************
exec [[-] command [arguments]]
当命令执行时,该命令取代shell,没有新的process产生。如果第一个参数是"-",shell会将"-"放入第零个参数,传给command。
*************************
export [-nf] [name[=word]] ...
export -p
将name输出给环境,给往后的命令使用。"-f"选项表示name是函数。"-p"显示出所有export的名称。"-n"移除name。
*************************
set [--abefhkmnptuvxldCHP] [-o option] [arg ...]
-a : 自动将变数标记为可让后面环境所使用。
-b : 立即报告被终结的背景程式状态。
-e : 当命令(simple-command,见后面)返回非零值时,立即跳出。
-f : 取消pathname expansion。
-h : 找出所记忆的函数命令位置。
-k : 所有keyword参数都放到环境中。
-m : 监督模式。
-n : 读取命令,但不要执行。可用于语法检查。
-p : 打开privileged模式。
-t : 当读取一个命令并执行后,立即离开。
-u : 当参数展开时,把unset参数当成是错误。
-v : 列出shell input lines。
-x : 在展开每个simple-command后,bash显示展开值在PS4上。
-l : 储存并还原name binding在for语法中。
-d : 关闭hasing command搜寻。
-C : 跟`noclobber=`一样。请见内定参数一节。
-H : 启动! style history substitution。
-P : 在使用像cd这种指令时,不要跟随symbolic links。
-- : "--"之后,没有参数跟在后面。
- : 指定将所有后面的参数当成是位置参数。
-o option-name : option-name可以是以下之一
allexport : 与"-a"相同。
braceexpand : 启动Brace Expansion。这是内定设定。
emacs : 使用emacs-style命令列编辑界面。
errexit : 与"-e"相同。
histexpand : 与"-H"相同。
ignoreeof : 效果跟`IGNOREEOF=10`一样。
interactive-commands : 允许#做为解。
monitor : 与"-m"相同。
noclobber : 与"-C"相同。
noexec : 与"-n"相同。
noglob : 与"-f"相同。
nohash : 与"-d"相同。
notify : 与"-b"相同。
nounset : 与"-u"相同。
physical : 与"-P"相同。
posix : Bash行为修改为Posix 1003.2标准。
privileged : 与"-p"相同。
verbose : 与"-v"相同。
vi : 使用vi-style命令列编辑程式。
xtrace : 与"-x"相同。
*************************
unset [-fv] [name ...]
移除对映于name的参数。要注意PATH、IFS、PPID、PS1、PS2、UID、EUID不能unset。若RANDOM、SECONDS、 LINENO、HISTCMD被unset,它们会丧失原有意义,既始它们后来被重设也一样。返回值为True,除非name是不能被unset的。
*************************
fc [-e ename] [-nlr] [first] [last]
fc -s [pat=rep] [cmd]
修正命令。
*************************
getopts optstring name [args]
解析位置参数。
*************************
help [pattern]
显示协助资讯。
*************************
history [n]
history -rwan [filename]
没有参数时,会显示所下命令的历史记录。带有参数"n"则显示最后n个。
其它参数如下:
-a : 新增"新历史"到历史档中。
-n : 读取尚未读到历史中的记录。
-r : 读取filename做为历史档,并用它为目前历史记录。
-w : 将现有历史记录写到filename中。
*************************
let arg [arg ...]
算术表述。请参考算术表述一节。
*************************
local [name[=value] ...]
产生一个局部参数。如果用于function,则其作用围在function内及其子函数。
*************************
logout
离开login shell。
*************************
popd [+/-n]
移除目录堆叠。"+n"移除上面n个,"-n"移除下面n个。
*************************
pushd [dir]
pushd +/-n
将目录新增到目录堆叠的最上面。"+n"旋转该堆叠,使第n个目录变成最上面。"-n"旋转该堆叠,使倒数第n个目录变成最上面。
*************************
pwd
列出目前工作目录的绝对路径。
*************************
read [-r] [name ...]
读进一行,然后第一个字设到第一个name,第二个设到第二个name,依此类推。如果没有name在参数中,则read会将值设到REPLY。返回值为零,除非遇到End-Of-File。若有"-r"选项,则"\n"被考虑为该行的一部份。
*************************
readonly [-f] [name ...]
readonly -p
将给定的name标记为readonly。如果是"-f"选项,则函数也一样被标记为readonly。"-p"会列出所有readonly的name。"--"取消检查剩余的参数。
*************************
shift [n]
Positional Parameters从n+1...开始,会被改为$1...。n若为零,则没有改变。n若未给定,则内定为1。n必须是非负数,并且小于或等于$#。若n大于$#,则没有改变。返回值为零,除非n大于$#或小于零。
*************************
suspend [-f]
暂停这个shell的执行,直到它收到SIGCONT信号。"-f"选项则是叫login shell不要抱怨,不过还是一样暂停。返回状态零,除非该shell是个login shell,而且没有"-f"选项。
*************************
test expr
[ expr ]
我们在Exit Status的部份已经说过了,不再重。
*************************
times
列出该shell的累积的使用者及系统时间及从shell执行的process时间,返回值为零。
------------------------------------------------------------------------------
trap [-l] [arg] [sigspec]
当收到sigspec信号时,执行arg命令。"-l"显示出信号名称及号码。
*************************
type [-all] [-type | -path] name [name ...]
没有参数的状况下,它会显示出shell如何解译name做为命令。如果有"-type",它将会显示alias、keyword、 function、builtin或file。如果有"-path"的参数,它将会显示该命令的路径,找不到的话,不显示任何东西。如果有"-all"的参数,它将会显示所有可执行name的可能路径。type接受"-a"、"-t"、"-p"做为缩写。
*************************
ulimit [-SHacdfmstpnuv [limit]]
ulimit提供了对shell的可获取资源控制的功能。
-a : 报告目前所有限制。
-c : 设定最大可产生的core档案。
-d : 行程资料段(process's data segment)最大值。
-f : 可被这个shell产生的最大档案。
-m : resident set size最大值。
-s : 堆叠最大值。
-t : CPU TIME最大值(以秒计算)。
-p : pipe size in 512-byte blocks的最大值。
-n : 可开启的file descriptors最大值。
-u : 单一使用者可使用的最大process数。
-v : 该shell最大虚拟记忆体可用值。
所有项目是以1024做为单位。
*************************
umask [-S] [mode]
将使用者的file-creation mask设为mode。"-S"选项将mask印成符号形式。
Bash内建参数
PPID : 该bash的呼叫者process ID.
PWD : 目前的工作目录。
OLDPWD : 上一个工作目录。
REPLY : 当read命令没有参数时,直接设在REPLY上。
UID : User ID。
EUID : Effective User ID。
BASH : Bash的完整路径。
BASH_VERSION : Bash版本。
SHLVL : 每次有Bash执行时,数字加一。
RANDOM : 每次这个参数被用到时,就会产生一个乱数在RANDOM上。
SECONDS : 从这个Shell一开始启动后的时间。
LINENO : Script的行数。
HISTCMD : 历史记录数。
OPTARG : getopts处理的最后一个选项参数。
OPTIND : 下一个要由getopts所处理的参数号码。
HOSTTYPE : 机器种类。
OSTYPE : 作业系统名称。
IFS : Internal Field Separator。
PATH : 命令搜寻路径。
PATH="/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin:."
HOME : 目前使用者的home directory;
CDPATH : cd命令的搜寻路径。
ENV : 如果这个参数被设定,每次有shell script被执行时,将会执行它所设定的档名做为环境设定。
MAIL : 如果这个参数被设定,而且MAILPATH没有被设定,那么有信件进来时,bash会通知使用者。
MAILCHECK : 设定多久时间检查邮件一次。
MAILPATH : 一串的邮件检查路径。
MAIL_WARNING : 如果有设定的话,邮件被读取后,将会显示讯息。
PS1 : 提示讯息设定,内定为"bash\$ "。(请详见提示讯息一节。)
PS2 : 第二提示讯息设定,内定为"> "。
PS3 : select命令所使用的提示讯息。
PS4 : 执行追踪时用的提示讯息设定,内定为"+ "。
HISTSIZE : 命令历史记录量,内定为500。
HISTFILE : 历史记录档,内定~/.bash_history。
HISTFILESIZE : 历史记录档行数最大值,内定500。
OPTERR : 如果设为1,bash会显示getopts的错误。
PROMPT_COMMAND : 如果设定的话,该值会在每次执行命令前都显示。
IGNOREEOF : 将EOF值当成输入,内定为10。
TMOUT : 如果设为大于零,该值被解译为输入等待秒数。若无输入,当成没有输入。
FCEDIT : fc命令的内定编辑器。
FIGNORE : 请详见READLINE。
INPUTRC : readline的startup file,内定~/.inputrc
notify : 如果设定了,bash立即报告被终结的背景程式。
history_control, HISTCONTROL : history使用。
command_oriented_history : 存入多行指令。
glob_dot_filenames : 如果设定了,bash将会把"."包含入档案路径中。
allow_null_glob_expansion : 如果设定了,bash允许路径明称为null string。
histchars : history使用。
nolinks : 如果设定了,执行指令时,不会跟随symbolic links。
hostname_completion_file, HOSTFILE : 包含与/etc/hosts相同格式的档名。
noclobber : 如果设定了,Bash不会覆写任何由">"、">&"及"<>"所操作的档案。
auto_resume : 请见任务控制一节。
no_exit_on_failed_exec : 如果该值存在,非互动的shell不会因为exec失败而跳出。
cdable_vars : 如果启动,而cd命令找不到目录,可切换到参数形态指定的目录下。
提示符号
Bash使用PS1~PS4来显示提示符号,其格式如下:
*************************
\t : 现在时间。
\d : 现在日期。
\n : 新行。
\s : shell的名称。
\w : 目前工作目录。
\W : 目前工作目录完整路径。
\u : 使用者名称。
\h : Hostname。
\# : 这个命令的号码。
\! : 历史号码。
\$ : 如果EUID是0,则#,否则为$。
\nnn : 八进位的字元。
\\ : "\"符号。
\[ : 开始一序列不可列印的字元。
\] : 结束一序列不可列印的字元。
算术表述
- +
! ~
* / %
+ -
<< >>
<= >= < >
== !=
&
^
|
&&
||
= *= /= %= += -= <<= >>= &= ^= |=
重导Redirection
>
>>
1>
.
.
语法
Simple Command
Pipelines
Lists
(list)
{ list; }