Bash Shell初学

来源:互联网 发布:如何查看淘宝消费总额 编辑:程序博客网 时间:2024/05/17 15:36

Bash Shell初学

关键字:Shell 

一:编一个shell脚本前应该注意的几个问题

当开始一个新的脚本的时候,问自己以下几个问题:

· 需要从用户或者用户环境来取得任何信息吗?

· 怎么样来存放那些信息?

· 需要创建文件吗?哪里和文件需要拥有什么样的权限和所有权?

· 我将用到什么命令?当在一个不同的系统使用脚本的时候,所有这些系统在要求的版本下有这些命令吗?用户需要什么提示吗?什么时候和为什么? 

建立一个目录 ~/scripts来存放你的脚本将会是个好主意。把此目录添加到PATH变量中:

export PATH=”$PATH:~/scripts”

如果不加入PATH的话就需要用./scripts来执行。

如果你想在当前脚本执行脚本而不想启动一个新的shell,你可以使用source

source script_name.sh

二:调试Bash脚本

1调试整个脚本

当一些事情不能按照计划进行,你需要确定到底是什么导致了脚本运行失败。Bash提供了大量的调试特性。最通常的做法是使用 -x选项来启动子shell,这将让整个脚本在调试模式下进行。每个命令和他附加参数的信息会在执行之前被展开并且送到标准输出打印。现在有一个全新的Bash调式工具出现在SourceForge。然而现在,你需要一个已经打过补丁的Bash-2.05。新的特性可能会在bash-3.0中出现。

 

2调试部分脚本

使用bash内建命令set可以让那些确定没有错误的部分以正常模式运行,而只对有错误的部分显示其debug信息。比如我们不确定在commented-script1.sh里面w命令会做些什么,那么我们可以把它象这样包含起来:

set -x                 # activate debugging from here
w
set +x                 # stop debugging from here

你可以在同样的脚本里多次打开关闭调试模式。

下表给出了其他有用的bash选项概貌:

 

 

 

 

2.1. 设置调试选项概览

短符号

长符号

结果

set -f

set -o noglob

禁止特殊字符用于文件名扩展。

set -v

set -o verbose

打印读入shell的输入行。

set -x

set -o xtrace

执行命令之前打印命令。

横线用来激活一个shell选项,再使用一次将解除。

以下例子,我们在命令行里面证明这些选项:

willy:~/scripts> set -v
willy:~/scripts> ls
ls 
commented-scripts.sh   script1.sh
 
willy:~/scripts> set +v
set +v
 
willy:~/scripts> ls *
commented-scripts.sh    script1.sh
 
willy:~/scripts> set -f
willy:~/scripts> ls *
ls: *: No such file or directory
 
willy:~/scripts> touch *
willy:~/scripts> ls
       commented-scripts.sh    script1.sh
 
willy:~/scripts> rm *
willy:~/scripts> ls
commented-scripts.sh    script1.sh

或者,这些模式也可以在脚本里面指定,只需在第一行shell的声明中加入需要的选项。选项可以叠加,和通常UNIX命令一样:

#!/bin/bash -xv

每当你找到脚本中的错误部分,你可以在每个你不确定的命令之前增加echo语句,这样你就会明确的看到哪里或者为什么脚本没有正常工作。在更高级的脚本中, echo可以插入到在不同的阶段显示变量表,因此可以检查到错误:

echo “Variable VARNAME is now set to $VARNAME.”

 

三:Bash环境

3.1. 全局变量

全局变量或者环境变量存在于所有的shell里面。env或者printenv命令能够通常用于显示环境变量。

3.2. 本地变量

本地变量只存在于当前shell。使用内建的不带选项的set命令将显示所有变量的列表(包括环境变量)和函数。在shell中设置一个变量,使用

VARNAME=”value”

在等号周围放置空格会造成错误。在对变量赋值得时候把内容字符串用引号引起来是一个良好的习惯:这样会降低出错的机会。

3.3. 导出变量

一个变量的建立后就像上面的例子那样仅仅存在于当前shell。它是本地变量:当前shell的子进程不会意识到这个的存在。为了把变量传递到子shell,我们需要使用内建的export命令把他们 输出 出来。被输出出来的变量就像环境变量一样,设置和输出变量通常用下面一步来完成:

export VARNAME=”value

一个子shell能够改变从父shell变量继承过来的变量,但是在子shell所作的改变对父shell也没有影响。

特殊Bash变量

字符

定义

$*

展开为位置参数,从1开始。当扩展发生在双引号时,它展开成一个单独的词,每个参数的值由IFS特殊变量的第一个字符分隔。

$@

展开为位置参数,从1开始。当在双引号里展开时,每个参数展开成独立的词。

$#

把位置参数展开为十进制数字。

$?

展开成最近执行的前台管道程序的退出状态。

$-

一个连字符展开为当前选项标志 内部命令集 或者那些shell自己的集(如-i)。A hyphen expands to the current option flags as specified upon invocation, by the set built-in command, or those set by the shell itself (such as the -i).

$$

展开成shell的进程ID

$!

展开成最近在后台(异步)执行的命令的进程ID

$0

展开成shell或者shell脚本名。

3.4. 转义字符

转义字符用于去除一个单个字符的特殊意义。一个非引用的反斜杠,/,在Bash中被用作转义字符。

3.5. 括号扩展

括号扩展是一种可以生成任意字符串的机制。括号扩展可以嵌套。每个扩展开的字符串的结果是未分类的;从左至右的顺序是保留的:

franky ~> echo sp{el,il,al}l
spell spill spall

括号扩展在任何其他扩展之前执行,而且在结果中保留对其它扩展有特殊意义的任何字符。完全是严格按照原文。Bash不对扩展的上下文或者括号之间的文本进行任何句法的解释。为了防止和参数扩展的冲突,字符串 “${” 被认为对于括号扩展是非法的。

一个正确形式的括号扩展必须包含未引用的一对括号,且至少一个未被引用的逗号。错误形式的括号扩展保留不做改变。

3.6. ~扩展

如果一个字以非引用的波浪线 (~)开始,所有在第一个未引用的斜杠之前的字符(或者所有字符,如果没有未引用的斜杠)被认为是一个tilde-prefixtilde前缀)。如果在tilde前缀中的没有字符被引用,那么在tilde前缀中的跟随tilde的字符就被当作一个可能的登陆名。如果这个登陆名是空字符串,这个tilde就使用shell变量HOME的值来代替。如果HOME没有预先设置,就用用户执行shellhome目录来进行替换。除非,tilde前缀已经用指定的登陆名替换掉了。

例子:

franky ~> export PATH=”$PATH:~/testdir”

~/testdir将会扩展成为 $HOME/testdir,所以如果 $HOME/var/home/franky,目录 /var/home/franky/testdir将会加入到PATH变量中去。

3.7.      Shell参数和变量扩展

参数扩展的基本形式是 “${PARAMETER}”。“PARAMETER” 的值是要被替换的。当 “PARAMETER” 是一个含有多于一个数字的位置参数的时候,需要使用括号,或者当 “PARAMETER” 后接一个不会被解释成它名字一部分的字符的时候。 以下结构允许建立尚不存在的变量:

${VAR:=value}

例子:

franky ~> echo $FRANKY
 
franky ~> echo ${FRANKY:=Franky}
Franky

3.8. 命令替换

命令替换允许一个命令的输出来替换这个命令本身。命令替换在一个命令这样封装的时候发生:

$(command)

或者象这样使用:

‘command‘

3.9. 算术扩展

算术扩展允许一个算术表达式的赋值和结果的替换。算术扩展的格式是:

$(( EXPRESSION ))

无论在什么地方,Bash用户应该尝试带上方括号使用这个语法:

$[ EXPRESSION ]

然而,这样将仅仅计算EXPRESSION的结果,且不进行测试:

franky ~> echo $[365*24]
8760

3.10. 别名

一个别名允许使用一个字符串来代替一个字当它作为一个简单命令的第一个字时候。shell维护一个可以用aliasunalias内建命令来设置或者取消的别名列表。

 

3.11. 更多Bash选项

我们已经讨论了一些对调试脚本很有用处的Bash选项。本节,我们将更深入地了解Bash的选项。

使用 -o选项来set显示所有的shell选项:

willy:~> set -o
allexport              off
braceexpand            on
emacs                  on
errexit                off
hashall                on
histexpand             on
history                on
ignoreeof              off
interactive-comments   on
keyword                off
monitor                on
noclobber              off
noexec                 off
noglob                 off
nolog                  off
notify                 off
nounset                off
onecmd                 off
physical               off
posix                  off
privileged             off
verbose                off
vi                     off
xtrace                 off

 

12. 改变选项

需要临时改变当前的环境,或者需要在脚本中使用,我们不如使用set。使用 - 来开启一个选项 + 来关闭:

willy:~/test> set -o noclobber
willy:~/test> touch test
willy:~/test> date > test
bash: test: cannot overwrite existing file
 
willy:~/test> set +o noclobber
willy:~/test> date > test

四:程序结构

4.1 if使用的表达式

下表包含了一个组成TEST-COMMAND命令或者命令列表,称作 “要素primaries” 的概览。这些primaries放置在方括号中来表示一个条件表达式的测试。

Primary

意义

[ -a FILE ]

如果FILE存在则为真。

[ -b FILE ]

如果FILE存在且是一个块特殊文件则为真。

[ -c FILE ]

如果FILE存在且是一个字特殊文件则为真。

[ -d FILE ]

如果FILE存在且是一个目录则为真。

[ -e FILE ]

如果FILE存在则为真。

[ -f FILE ]

如果FILE存在且是一个普通文件则为真。

[ -g FILE ]

如果FILE存在且已经设置了SGID则为真。

[ -h FILE ]

如果FILE存在且是一个符号连接则为真。

[ -k FILE ]

如果FILE存在且已经设置了粘制位则为真。

[ -p FILE ]

如果FILE存在且是一个名字管道(F如果O)则为真。

[ -r FILE ]

如果FILE存在且是可读的则为真。

[ -s FILE ]

如果FILE存在且大小不为0则为真。

[ -t FD ]

如果文件描述符FD打开且指向一个终端则为真。

[ -u FILE ]

如果FILE存在且设置了SUID (set user ID)则为真。

[ -w FILE ]

如果FILE如果FILE存在且是可写的则为真。

[ -x FILE ]

如果FILE存在且是可执行的则为真。

[ -O FILE ]

如果FILE存在且属有效用户ID则为真。

[ -G FILE ]

如果FILE存在且属有效用户组则为真。

[ -L FILE ]

如果FILE存在且是一个符号连接则为真。

[ -N FILE ]

如果FILE存在and has been mod如果ied since it was last read则为真。

[ -S FILE ]

如果FILE存在且是一个套接字则为真。

[ FILE1 -nt FILE2 ]

如果FILE1 has been changed more recently than FILE2, or如果FILE1 exists and FILE2 does not则为真。

[ FILE1 -ot FILE2 ]

如果FILE1FILE2要老, 或者FILE2存在且FILE1不存在则为真。

[ FILE1 -ef FILE2 ]

如果FILE1FILE2指向相同的设备和节点号则为真。

[ -o OPTIONNAME ]

如果shell选项OPTIONNAME开启则为真。

[ -z STRING ]

STRING的长度为零则为真。

[ -n STRING ] or [ STRING ]

STRING的长度为非零non-zero则为真。

[ STRING1 == STRING2 ]

如果2个字符串相同。=” may be used instead of “==” for strict POSIX compliance则为真。

 

 

 

[ STRING1 < STRING2 ]

如果STRING1” sorts before “STRING2” lexicographically in the current locale则为真。

[ STRING1 > STRING2 ]

如果STRING1” sorts after “STRING2” lexicographically in the current locale则为真。

[ ARG1 OP ARG2 ]

OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators return true if “ARG1” is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to “ARG2”, respectively. “ARG1” and “ARG2” are integers.

表达式可以借以下操作符组合起来

操作

效果

[ ! EXPR ]

如果EXPRfalse则为真。

[ ( EXPR ) ]

返回EXPR的值。这样可以用来忽略正常的操作符优先级。

[ EXPR1 -a EXPR2 ]

如果EXPR1 and EXPR2全真则为真。

[ EXPR1 -o EXPR2 ]

如果EXPR1或者EXPR2为真则为真。

4.2. 使用case语句

嵌套if语句可能比较美观,但是只要你面临可能采取的一系列的不同动作时,你可能会迷惑。要处理复杂条件时,使用case语法:

case EXPRESSION in CASE1} COMMAND-LIST;; CASE2) COMMAND-LIST;; ... CASEN) COMMAND-LIST;; esac

每个分支是一个符合pattern的表达式。在COMMAND-LIST中首先符合的的命令就执行。 “|” 符号用来分割多个pattern, “)” 操作符中断一个pattern。每个分支加上他们的后继命令称作一个 子句 。每个 子句 必须以 “;;” 结尾。每个case语句以esac语句结束。

case $space in
[1-6]*)
Message=”All is quiet.”
  ;;
[7-8]*)
Message=”Start thinking about cleaning out some stuff.  There’s a partition that is $space % full.”
  ;;
9[1-8])
Message=”Better hurry with that new disk...  One partition is $space % full.”
  ;;
99)
Message=”I’m drowning here!  There’s a partition at $space %!”
  ;;
*)
Message=”I seem to be running with an nonexitent amount of disk space...”
  ;;
esac

4.3.使用内建命令read

read [options] NAME1 NAME2 ... NAMEN

4.4.for循环

for NAME [in LIST ]; do COMMANDS; done

4.5. while循环

while CONTROL-COMMAND; do CONSEQUENT-COMMANDS; done

4.6.until循环

until循环和while循环非常相似,除了循环执行直到TEST-COMMAND执行成功。只要这个命令测试失败,循环就继续。语法和while循环一样:

until TEST-COMMAND; do CONSEQUENT-COMMANDS; done

4.7.shift内建命令

shift语句通常在事先不知道参数个数的情况下使用,比如用户可以随他们喜欢给出任意数量的参数。这种情况下,参数通常在一个while循环中用 (( $# )) 测试条件来处理。只要参数的数量大于零那么条件测试就为真。 $1变量和shift语句处理每个参数。

4.8.declare内建命令

选项

含义

-a

变量为数组。

-f

仅使用函数名。

-i

把变量当作整数来对待;变量被赋值之后就进行算术计算(参见 第3.4.6节 “算术扩展”)

-p

显示每个变量的属性和值。当使用 -p选项,其他选项就被忽略。

-r

使得变量变为只读。这些变量不能被后来的赋值与语句赋值,同样也不可以unset

-t

给于每个变量trace属性。

-x

Mark each variable for export to subsequent commands via the environment.

默认下,如果没有特别指明,变量可以拥有任何类型的数据

4.9.数组

ARRAY[INDEXNR]=value

ARRAY=(value1 value2 ... valueN)

对数组的变量解引用

为了指明在一个数组中的项目的内容,为了指向一个数组中的一个项目的内容,使用{}。这样是必须的,正如你可以从下面的例子看出,来绕过扩展操作符的shell解释。如果索引的数字是 @ 或者 *,一个数组的所有的成员都将被引用。

[bob in ~] ARRAY=(one two three)
[bob in ~] echo ${ARRAY[*]}
one two three
 
[bob in ~] echo $ARRAY[*]
one[*]
 
[bob in ~] echo ${ARRAY[2]}
three
 
[bob in ~] ARRAY[3]=four
[bob in ~] echo ${ARRAY[*]}
one two three four

删除数组变量

unset内建命令用来删除数组或者数组成员

4.10.函数语法

函数使用以下2种形式。

function FUNCTION { COMMANDS; }

或者

FUNCTION () { COMMANDS; }

函数体必须以分号或者新行结尾。

五:使用shell来传送信号

5.1.Bash中的控制信号

标准组合键

意义

Ctrl+C

中断信号,向在前台运行的作业发送SIGINT

Ctrl+Y

延迟悬挂特征,使一个试图从终端读取输入的运行中的进程停止。控制权返回到shell,用户可以从前台,后台或者杀死进程。延迟挂起只有在支持这种特性的操作系统才存在。

Ctrl+Z

suspend信号,向正在运行的程序发送SIGTSTP,因此停止程序并且把控制权返回给shell

5.2. 陷阱

可能有这样的情况,你不想使用你的脚本的用户不合时宜地通过键盘来结束进程,比如因为必须提供输入或者必须进行某些清理工作。 trap语句捕获到这些序列且能够被编制出来在不活这些信号时候执行一系列的命令。

trap语句的语法是这样的:

trap [COMMANDS] [SIGNALS]

意味着trap命令会捕捉在SIGNALS列出的可能带有或者没有SIG前缀的信号,或者信号数字。如果一个信号是0或者EXIT,那么COMMANDSshell退出时候执行。如果其中一个信号是DEBUGCOMMANDS列表在每个简单命令后执行。一个信号也可以指定为ERR;这样的情况下COMMANDS在每次一个简单命令以非零状态退出时执行。注意这些命令不会在非零退出状态来自一个if语句时执行,或者来自一个while或者until循环。如果一个逻辑AND (&&) 或者OR (||) 出现在非零退出状态中,所有都不会执行,或者当一个命令的退出状态使用 ! 操作符进行取反。

除非遭遇一个非法的信号,否则trap命令的返回状态是0trap命令带一组选项,在Bashinfo页面中有记录。

这里有个非常简单的例子,从用户处捕捉Ctrl+C upon which a message is printed. 当你尝试着不指定KILL信号来杀掉这个程序时,什么都不会发生:

#!/bin/bash
# traptest.sh
 
trap “echo Booh!” SIGINT SIGTERM
echo “pid is $”
 
while :                # This is the same as “while true”.
do
sleep 60       # This script is not really doing anything.
done

 

六:总结

       以上只是对shell编程一个框架的介绍,其中还有很多深入的细节,例如正则表达式,grepsedawk,还需要深入学习。在此基础上基本顺利阅读大部分shell脚本程序了。

 
原创粉丝点击