Bash Shell 学习之一

Posted on 2008-07-21 17:40 把大海藏到心里 阅读(80) 评论(0)  编辑 收藏 所属分类: LinuxUbuntu

    对于任何想适当精通一些系统管理知识的人来说,掌握shell脚本知识都是最基本的,即使这些人可能并不打算真正的编写一些脚本.想一下Linux机器的启动过程,在这个过程中,必将运行 /etc/rc.d目录下的脚本来存储系统配置和建立服务.详细的理解这些启动脚本对于分析系统的行为是非常重要的,并且有时候可能必须修改它.

 

    Bash是"Bourne-Again shell"首字母的缩写,也是Stephen Bourne的经典 的Bourne shell的一个双关语,(译者:说实话,我一直搞不清这个双关语是什么意思,为什么叫 "Bourn-Again shell",这其中应该有个什么典故吧,哪位好心,告诉我一下^^).Bash已经成为了 所有UNIX中shell脚本的事实上的标准了 。

 

    C Shell和它的变种.(注意:C Shell 编程是不被推荐的,因为一些特定的内在问题,Tom Christiansen在1993年10月指出了这个问题 请在http://www.etext.org/Quartz/computer/unix/csh.harmful.gz中查看具体内容.)

 

 

现在,让我们看一下一个真正意义的脚本.而且我们可以走得更远...
Example 2-3. cleanup:一个增强的和广义的删除logfile的脚本
################################Start Script#######################################
 1 #!/bin/bash
 2 # 清除, 版本 3
 3  
 4 #  Warning:
 5 #  -------
 6 #  这个脚本有好多特征,这些特征是在后边章节进行解释的,大概是进行到本书的一半的
 7 #  时候,
 8 #  你就会觉得它没有什么神秘的了.
 9 #
10  
11  
12  
13 LOG_DIR=/var/log
14 ROOT_UID=0     # $UID为0的时候,用户才具有根用户的权限
15 LINES=50       # 默认的保存行数
16 E_XCD=66       # 不能修改目录?
17 E_NOTROOT=67   # 非根用户将以error退出 这些常数是liinux下定义的么?有什么具体含义呢?
18  
19  
20 # 当然要使用根用户来运行
21 if [ "$UID" -ne "$ROOT_UID" ]      -ne 表示 非?
22 then
23   echo "Must be root to run this script."
24   exit $E_NOTROOT
25 fi   注意语法格式
26  
27 if [ -n "$1" ] $1表示脚本的第一个参数
28 # 测试是否有命令行参数(非空).
29 then
30   lines=$1
31 else   
32   lines=$LINES # 默认,如果不在命令行中指定
33 fi   
34  
35  
36 #  Stephane Chazelas 建议使用下边
37 #+ 的更好方法来检测命令行参数.
38 #+ 但对于这章来说还是有点超前.
39 #
40 #    E_WRONGARGS=65  # 非数值参数(错误的参数格式)
41 #
42 #    case "$1" in 注意语法格式
43 #    ""      ) lines=50;;
44 #    *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 #    *       ) lines=$1;;
46 #    esac
47 #
48 #* 直到"Loops"的章节才会对上边的内容进行详细的描述.
49  
50  
51 cd $LOG_DIR
52  
53 if [ `pwd` != "$LOG_DIR" ]  # 或者    if[ "$PWD" != "$LOG_DIR" ]    上面的-ne和这里的可以互换不?有什么异同呢?
54                             # 不在 /var/log中?
55 then
56   echo "Can't change to $LOG_DIR."
57   exit $E_XCD
58 fi  # 在处理log file之前,再确认一遍当前目录是否正确.
59  
60 # 更有效率的做法是
61 #
62 # cd /var/log || {
63 #   echo "Cannot change to necessary directory." >&2
64 #   exit $E_XCD;
65 # }
66  
67  
68  
69  
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages             # 变为新的log目录.
72  
73  
74 # cat /dev/null > messages
75 #* 不再需要了,使用上边的方法更安全.
76  
77 cat /dev/null > wtmp  #  ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79  
80 exit 0
81 #  退出之前返回0,返回0表示成功.
82 #

################################End Script#########################################

 

 

 

echo ${PATH#*:}       # 参数替换,不是一个注释 ,这里有什么语法规则呢?

echo $(( 2#101011 ))  # 数制转换,不是一个注释

 

if [ -x "$filename" ]; then    # 注意:"if"和"then"需要分隔 , 为啥?

 

 

  case "$variable" in 注意语法格式
         abc)  echo ""$variable = abc" ;;
         xyz)  echo ""$variable = xyz" ;;
         esac

 

.命令经常作为一个文件移动命令的目的地

bash$ cp /home/bozo/current_work/junk/* .

 

        1 a=123
        2 ( a=321; )           
        3  
        4 echo "a = $a"   # a = 123

        5 # 在圆括号中a变量,更像是一个局部变量.

 

 

代码块.又被称为内部组.事实上,这个结构创建了一个匿名的函数.但是与函数不同的
        是,在其中声明的变量,对于脚本其他部分的代码来说还是可见的.如:
        bash$  
        {
            local a;
            a= 123;
        }

        bash中的local申请的变量只能够用在函数中.

 

 

 

#!/bin/bash

echo "$1"
echo

File=io_output

{
read line1
read line2
} < $File    把文件中前两行读入到line1.line2变量中

echo "line1:$line1"
echo "line2:$line2"

exit 0

 

 

 

{} ";    路径名.一般都在find命令中使用.这不是一个shell内建命令.
        注意: ";"用来结束find命令序列的-exec选项.
 
[]        test.
        test的表达式将在[]中.
        值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令
        的一个连接.
 
[[]]    test.
        test表达式放在[[]]中.(shell关键字)
        具体查看[[]]结构的讨论.
 
[]        数组元素
        Array[1]=slot_1
        echo ${Array[1]}
 
[]        字符范围
        在正则表达式中使用,作为字符匹配的一个范围
 
(())    数学计算的扩展
        在(())结构中可以使用一些数字计算.
        具体参阅((...))结构.
 
>&>>&>><
        重定向.
        scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.
        command &>filename 重定向stdout和stderr到文件中
        command >&2    重定向command的stdout到stderr
        scriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,
        则创建这个文件.
 
        进程替换,具体见"进程替换部分",跟命令替换极其类似.
        (command)>
        <(command)
 
        <和> 可用来做字符串比较
        <和> 可用在数学计算比较
 
<<        重定向,用在"here document"
 
<<<        重定向,用在"here string"
 
<,>        ASCII比较
         1 veg1=carrots
         2 veg2=tomatoes
         3  
         4 if [[ "$veg1" < "$veg2" ]]
         5 then
         6   echo "Although $veg1 precede $veg2 in the dictionary,"
         7   echo "this implies nothing about my culinary preferences."
         8 else
         9   echo "What kind of dictionary are you using, anyhow?"
        10 fi
 
"<,">    正则表达式中的单词边界.如:
        bash$grep '"<the">' textfile
    
|        管道.分析前边命令的输出,并将输出作为后边命令的输入.这是一种产生命令链的
        好方法.
        1 echo ls -l | sh
        2 #  传递"echo ls -l"的输出到shell中,
        3 #+ 与一个简单的"ls -l"结果相同.
        4  
        5  
        6 cat *.lst | sort | uniq
        7 # 合并和排序所有的".lst"文件,然后删除所有重复的行.
        
        管道是进程间通讯的一个典型办法,将一个进程的stdout放到另一个进程的stdin中.
        标准的方法是将一个一般命令的输出,比如cat或echo,传递到一个过滤命令中(在这个
        过滤命令中将处理输入),得到结果,如:
        cat $filename1 | $filename2 | grep $search_word

当然输出的命令也可以传递到脚本中.如:
################################Start Script#######################################
1 #!/bin/bash
2 # uppercase.sh : 修改输出,全部转换为大写
3  
4 tr 'a-z' 'A-Z'
5 #  字符范围必须被""引用起来
6 #+ 来阻止产生单字符的文件名.
7  
8 exit 0
################################End Script#########################################
        
        现在让我们输送ls -l的输出到一个脚本中.
        bash$ ls -l | ./uppercase.sh
        -RW-RW-R--    1 BOZO  BOZO       109 APR  7 19:49 1.TXT
        -RW-RW-R--    1 BOZO  BOZO       109 APR 14 16:48 2.TXT
        -RW-R--R--    1 BOZO  BOZO       725 APR 20 20:56 DATA-FILE
 
        注意:管道中的一个进程的stdout必须被下一个进程作为stdin读入.否则,数据流会阻
        塞,并且管道将产生非预期的行为.
        如:
        1 cat file1 file2 | ls -l | sort
        2 #从"cat file1 file2"中的输出并没出现
 
        作为子进程的运行的管道,不能够改变脚本的变量.
        1 variable="initial_value"
        2 echo "new_value" | read variable
        3 echo "variable = $variable"            #variable = initial_value
        如果管道中的某个命令产生了一个异常,并中途失败,那么这个管道将过早的终止.
        这种行为被叫做a broken pipe,并且这种状态下将发送一个SIGPIPE信号.
 
>|        强制重定向(即使设置了noclobber选项--就是-C选项).这将强制的覆盖一个现存文件.
 
||        或-逻辑操作.
 
&        后台运行命令.一个命令后边跟一个&,将表示在后台运行.
        bash$sleep 10 &
        [1] 850
        [1]+    Done            sleep 10

        在一个脚本中,命令和循环都可能运行在后台.

        注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
            响应.幸运的是,我们可以在Example 11-24附近,看到这个问题的解决办法.
 
&&        与-逻辑操作.
 
-        选项,前缀.在所有的命令内如果想使用选项参数的话,前边都要加上"-".
 
        COMMAND -[Option1][Option2][...]
        ls -al
        sort -dfu $filename
        set -- $variable
 
         1 if [ $file1 -ot $file2 ]
         2 then
         3   echo "File $file1 is older than $file2."
         4 fi
         5  
         6 if [ "$a" -eq "$b" ]
         7 then
         8   echo "$a is equal to $b."
         9 fi
        10  
        11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
        12 then
        13   echo "$c equals 24 and $d equals 47."
        14 fi
 
-        用于重定向 stdin 或 stdout.

 

注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
            响应.

 

 

 1 variable1_=$1_  # 而不是 variable1=$1

 2 # 这将阻止一个错误,即使在调用时没使用这个位置参数.

 

注意:如果一个脚本以|(管道字符)结束.那么一个"(转义符),就不用非加上不可了.

    但是一个好的shell脚本编写风格,还是应该在行尾加上",以增加可读性.

 

 

 

 exit命令被用来结束脚本,就像C语言一样.他也会返回一个值来传给父进程,父进程会判断是否
可用.
每个命令都会返回一个exit状态(有时候也叫return状态).成功返回0,如果返回一个非0值,通
常情况下都会被认为是一个错误码.一个编写良好的UNIX命令,程序,和工具都会返回一个0作为
退出码来表示成功,虽然偶尔也会有例外.
同样的,脚本中的函数和脚本本身都会返回退出状态.在脚本或者是脚本函数中执行的最后的命
令会决定退出状态.在脚本中,exit nnn命令将会把nnn退出码传递给shell(nnn必须是10进制数
0-255).
当一个脚本以不带参数exit来结束时,脚本的退出状态就由脚本中最后执行命令来决定.

 $?读取最后执行命令的退出码.函数返回后,$?给出函数最后执行的那条命令的退出码.这种给
函数返回值的方法是Bash的方法.对于脚本来说也一样.总之,一般情况下,0为成功,非0失败W.

注意: !逻辑非操作,将会反转test命令的结果,并且这会影响exit状态.

注意事项:

    特定的退出码都有预定的含义(见附录D),用户不应该在自己的脚本中指定他.

 

 

 

Bash具有test命令,不同的[]和()操作,和
if/then结构.

 一个if/then结构可以测试命令的返回值是否为0(因为0表示成功),如果是的话,执行更多命令.
有一个专用命令"["(左中括号,特殊字符).这个命令与test命令等价,但是出于效率上的考虑,
它是一个内建命令.这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,
返回一个退出码.
在版本2.02的Bash中,推出了一个新的[[...]]扩展test命令.因为这种表现形式可能对某些语
言的程序员来说更加熟悉.注意"[["是一个关键字,并不是一个命令.
Bash把[[ $a -lt $b ]]看作一个单独的元素,并且返回一个退出码.
((...))和let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0的时候,

他们的退出码将返回0.这些算术扩展(见第15章)结构被用来做算术比较.

 

 

 注意:当if和then在一个条件测试的同一行中的话,必须使用";"来终止if表达式.if和then都是
    关键字.关键字(或者命令)作为一个表达式的开头,并且在一个新的表达式开始之前,必须
    结束上一个表达式.
    1 if [ -x "$filename" ]; then

 

 使用if test condition-true这种形式和if[condition-true]这种形式是等价的.向我们前边
所说的"["是test的标记.并且以"]"结束.在if/test中并不应该这么严厉,但是新版本的Bash
需要它.
注意:test命令是Bash的内建命令,用来测试文件类型和比较字符串.因此,在Bash脚本中,test
并不调用/usr/bin/test的二进制版本(这是sh-utils工具包的一部分).同样的,[并不调用
/usr/bin/[,被连接到/usr/bin/test.
        bash$ type test
        test is a shell builtin
        bash$ type '['
        [ is a shell builtin
        bash$ type '[['
        [[ is a shell keyword
        bash$ type ']]'
        ]] is a shell keyword
        bash$ type ']'

        bash: type: ]: not found

 

 

 [[]]结构比Bash的[]更加灵活,这是一个扩展的test命令,从ksh88继承过来的.
注意:在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换.
    1 file=/etc/passwd
    2  
    3 if [[ -e $file ]]
    4 then
    5   echo "Password file exists."
    6 fi

注意:使用[[]],而不是[],能够阻止脚本中的许多逻辑错误.比如,尽管在[]中将给出一个错误, 但是&&,||,<>操作还是能够工作在一个[[]]test之中.

 

注意:在if后边,test命令和[]或[[]]都不是必须的.如下:
    1 dir=/home/bozo
    2  
    3 if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
    4   echo "Now in $dir."
    5 else
    6   echo "Can't change to $dir."
    7 fi
if命令将返回if后边的命令的退出码.
与此相似,当在一个在使用与或列表结构的时候,test或中括号的使用,也并不一定非的有if不可
    1 var1=20
    2 var2=22
    3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
    4  
    5 home=/home/bozo
    6 [ -d "$home" ] || echo "$home directory does not exist."
(())结构扩展并计算一个算术表达式的结果.如果表达式的结果为0,它将返回1作为退出码,或
者是"false".而一个非0表达式的结果将返回0作为退出码,或者是"true".

 

 

 

文件测试操作
返回true如果...
-e        文件存在
-a        文件存在
        这个选项的效果与-e相同.但是它已经被弃用了,并且不鼓励使用
-f        file是一个regular文件(不是目录或者设备文件)
-s        文件长度不为0
-d        文件是个目录
-b        文件是个块设备(软盘,cdrom等等)
-c        文件是个字符设备(键盘,modem,声卡等等)
-p        文件是个管道
-h        文件是个符号链接
-L        文件是个符号链接
-S        文件是个socket
-t        关联到一个终端设备的文件描述符
        这个选项一般都用来检测是否在一个给定脚本中的stdin[-t0]或[-t1]是一个终端
-r        文件具有读权限(对于用户运行这个test)
-w        文件具有写权限(对于用户运行这个test)
-x        文件具有执行权限(对于用户运行这个test)
-g        set-group-id(sgid)标志到文件或目录上
        如果一个目录具有sgid标志,那么一个被创建在这个目录里的文件,这个目录属于创建
        这个目录的用户组,并不一定与创建这个文件的用户的组相同.对于workgroup的目录
        共享来说,这非常有用.见<<UNIX环境高级编程中文版>>第58页.
-u        set-user-id(suid)标志到文件上
        如果运行一个具有root权限的文件,那么运行进程将取得root权限,即使你是一个普通
        用户.[1]这对于需要存取系统硬件的执行操作(比如pppd和cdrecord)非常有用.如果
        没有suid标志的话,那么普通用户(没有root权限)将无法运行这种程序.
        见<<UNIX环境高级编程中文版>>第58页.
           -rwsr-xr-t    1 root       178236 Oct  2  2000 /usr/sbin/pppd
        对于设置了suid的文件,在它的权限标志中有"s".
-k        设置粘贴位,见<<UNIX环境高级编程中文版>>第65页.
        对于"sticky bit",save-text-mode标志是一个文件权限的特殊类型.如果设置了这
        个标志,那么这个文件将被保存在交换区,为了达到快速存取的目的.如果设置在目录
        中,它将限制写权限.对于设置了sticky bit位的文件或目录,权限标志中有"t".
           drwxrwxrwt    7 root         1024 May 19 21:26 tmp/
        如果一个用户并不时具有stick bit位的目录的拥有者,但是具有写权限,那么用户只
        能在这个目录下删除自己所拥有的文件.这将防止用户在一个公开的目录中不慎覆盖
        或者删除别人的文件,比如/tmp(当然root或者是目录的所有者可以随便删除或重命名
        其中的文件).
-O        你是文件的所有者.
-G        文件的group-id和你的相同.
-N        从文件最后被阅读到现在,是否被修改.
 
f1 -nt f2
        文件f1比f2新
f1 -ot f2
        f1比f2老
f1 -ef f2
        f1和f2都硬连接到同一个文件.
 
!        非--反转上边测试的结果(如果条件缺席,将返回true)

 

 

 


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-07-21 21:54 编辑过


相关链接: