《Shell 脚本学习指南》读书笔记 第二章 入门

2.2 为什么要使用Shell脚本

使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象

缺点是:它们的效率通常不如编译型语言。

之所以要使用Shell脚本是基于:

简单性:

            Shell是一个高级语言;通过它,你可以简洁地表达复杂的操作。

可移植性:

           使用POSIX所定义的功能,可以做到脚本无需修改就可在不同的系统上执行

开发容易:

           可以在短时间内完成一个功能强大又好用的脚本。

2.3 一个简单的脚本

who命令可以告诉你现在系统有谁登陆:

  
  
  
  
  1. [root@localhost ~]# who  
  2. root     pts/2        2012-05-05 09:38 (192.168.0.83)  
  3. root     pts/3        2012-05-05 09:38 (192.168.0.83) 

利用wc(字数计算)程序,它可以算出行数(line),字数(word),字符数(character)。在此例中,我们用的是wc -l 

  
  
  
  
  1. [root@localhost ~]# who|wc -l  
  2. 2                                      //计算用户个数 

|(管道)符号可以在两个程序之间建立管道(pipeline):who的输出,成了wc的输入,wc所列出的结果就是已登录用户的个数

下一步则是把管道转变成一个独立的命令。方法是把这条命令输入一个一般的文件中,然后使用chmod为该文件设置执行权限,如

  
  
  
  
  1. [root@localhost ~]# cat > nusers //建立文件,使用cat复制终端的输入  
  2. who | wc -l                      //程序的内容 
  3. ^D                               //此处按Ctrl+D 表示 end-of-file
    [root@localhost ~]# chmod +x nusers //让文件拥有执行的权限
    [root@localhost ~]# ./nusers //执行测试
    2                            //输出我们要的结果

Shell脚本的典型开发周期:

 

1、直接在命令行(command line)上测试

2、一旦找到能够完成工作的适当语法,再将它们放进一个独立的脚本里

3、为该脚本设置执行的权限

 

2.4 自给自足的脚本:位于第一行的#!

当一个文件中开头的两个字符是#!时,内核会扫描该行其余的部分,看是否存在可以用来执行程序的解释器的完整路径。(中间如果出现如何空白符号都会略过)。此外,内核还会扫描是否有一个选项要传递给解释器。内核会以备指定的选项来引用解释器,再搭配命令行的其他部分。如:假如有一个shell脚本,名为/usr/ucb/whizprog,它的第一行如下所示:

  
  
  
  
  1. #!/bin/csh -f 

再者,如果Shell的查找路径(后面会介绍)里有/usr/ucb,当用户键入whizprog -q /dev/tty01这条命令,内核解释#!这行后,便会以如下的方式来引用csh

 

/bin/csh -f /usr/ucb/whizprog -q /dev/tty01

/usr/ucb/这个路径是如何加上去的?
 

Shell脚本通常一开始都是#!/bin/bash

下面是几个初级的陷阱(gotchas)

1、对#!这一行长度尽量不要超过64个字符

2、脚本是否具有可移植性取决于是否有完整的路径名称

3、别在选项(option)之后放置任何空白,因为空白也会跟着选项一起传递给被引用的程序

4、你要知道解释器的完整路径名称

一些较旧的系统上,内核不具备解释#!的能力,有些Shell会自行处理,这些Shell对于#!与紧随其后的解释器名称之间是否可以有空白,可能有不同的解释。

修订后的nusers程序如下

 

 
  
  
  
  
  1. [root@localhost ucb]# cat nusers   
  2. #!/bin/bash -   
  3. who|wc -l 

选项- 表示没有Shell选项;这是基于安全上的考虑,可避免某种程度的欺骗式攻击(spoofing attack)

如何避免?

2.5 Shell的基本元素

Shell最基本的工作就是执行命令

  
  
  
  
  1. [root@localhost ucb]# cd work;ls -l whizprog,c 

以空白(Space键或Tab键)隔开命令行中各个组成部分

 

命令名称是命令行的第一个项目。通常后面会跟着选项(option),任何额外的参数都会放在选项之后

选项的开头是一个破折号(或者减号),后面接着一个字母。选项是可有可无的,有可能需要加上参数(例如 cc -o whizprog whizprog.c),不需要参数的选项可以合并

例如ls -lt whizprog.c

长选项的开头是一个破折号还是两个,视情况而定

以两个破折号(--)来表示选项结尾的用法

分号(;)可以用来分隔同一行里的多条命令。Shell会依次执行这些命令。

如果你使用的是&符号而不是分号,则Shell将在后台执行其前面的命令,这意味着,Shell不用等到该命令完成,就可以继续执行下一个命令

Shell识别三种基本命令:内建命令、Shell函数以及外部命令;

2.5.2 变量

在Shell的世界里,变量值可以是(而且通常是)空值,也就是不含任何字符

Shell变量名称的开头是一个字母或下划线符号,后面可以接着任意长度的字母,数字或者下划线。

  
  
  
  
  1. [root@localhost ~]# myvar=this_is_a_long_string_that_does_not_mean_much //分配变量值  
  2. [root@localhost ~]# echo $myvar //打印变量值  
  3. this_is_a_long_string_that_does_not_mean_much 

变量赋值的方式为:先写变量名称,紧接着=字符,最后是新值,中间完全没有任何空格。当你想取出Shell变量的值时,需要变量名称前面加上$字符,当所赋予的值内含空格时,请加上引号:

  
  
  
  
  1. [root@localhost ~]# first=isaaac middle=bashevis last=singer  //单行可进行多次赋值  
  2. [root@localhost ~]# fullname="isaac bashevis singer"          //值中包含空格时使用引号  
  3. [root@localhost ~]# oldname=$fullname                         //此处不需要引号 

将几个变量连接起来时,就需要使用引号了:

  
  
  
  
  1. [root@localhost ~]# fullname1="$first $middle $last" 
  2. [root@localhost ~]# echo $fullname1  
  3. isaaac bashevis singer 

2.5.3 简单的echo输出

 

echo的任务就是产生输出

原始的echo命令只会将参数打印到标准输出,参数之间以一个空格隔开,并以换行符号(newline)结尾。

  
  
  
  
  1. [root@localhost ~]# echo Now is the time for all good men  
  2. Now is the time for all good men 

BSD版本的echo看到第一个参数为-n时,会省略结尾的换行符号

  
  
  
  
  1. [root@localhost ~]# echo -n "Enter your name" 

表 2-2 echo的转义序列

 

 

序列 说明
\a 警示字符,通常是ASCII的BEL字符
\b 退格
\c 输出中忽略最后的换行符(Newline)。这个参数之后的任何字符,包括接下来的参数,都会被忽略掉(不打印)
\f 清除屏幕(Formfeed)
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\\ 反斜杠字符
\a序列通常用来引起用户的注意;\0ddd序列最有用的地方,就是通过送出终端转义序列进行(非常)原始的光标操作。但不建议这么做。

 

2.5.4 华丽的printf输出

printf不像echo那样会自动提供一个换行符号。你必须显示地将换行符号指定成\n

第一部分是一个字符串,用来描述输出的排列方式,最好为此字符串加上引号。此字符串包含了按 字面显示的字符(characters to be printed literally)以及格式声明(format specifications),后者是特殊的占位符(placeholders),用来描述如何显示相应的参数(argument)。

第二部分是与格式声明相对应的参数列表(argument list),例如一系列的字符串或变量值。格式声明分成两部分:百分比符号(%)和指示符(specifier)。最常用的格式指示符(format specifier)有两个,%s用于字符串,而%d用于十进制整数。

  
  
  
  
  1. [root@localhost ~]#  printf "The first program always prints ' %s,%s! '  \n" Hello world  
  2. The first program always prints ' Hello,world! '  
  
  
  
  
  1. <SPAN style="COLOR: #ff0000">' %s,%s! '  \n  //注意!后面和'之间要有空格隔开,否则会提示错误</SPAN> 

 2.5.5 基本的I/O重定向

 

程序应该有数据的来源端,数据的目的端(数据要去的地方)以及报告问题的地方,它们分别被称为 标准输入(standard input)、标准输出(standard output)、以及标准错误输出(standard error)

默认的标准输入、标准输出以及标准错误输出都是终端

  
  
  
  
  1. [root@localhost ~]# cat  //未指定任何参数,读取标准输入、写入标准输出  
  2. <STRONG>now is the time      //用户键入</STRONG>  
  3. now is the time         //由cat返回  
  4. <STRONG>for all goog men</STRONG>  
  5. for all goog men  
  6. <STRONG>to come to he aid of their country</STRONG>  
  7. to come to he aid of their country 
  8. ^D                     //Ctrl+d  文件结尾

在用户登录系统时,UNIX变将默认的标准输入、输出及错误输出安排成你的终端。

 

2.5.5.1 重定向与管道

Shell提供了数种语法标记,可用来改变默认I/O的来源端与目的端

以<改变标准输入

program < file 可将program的标准输入修改为file:

tr  -d '\r' < dos-file.txt

以>改变标准输出

program > file 可将program的标准输出修改为file:

tr -d '\r' < dos-file.txt > UNIX-file.txt

这条命令会先以tr将dos-file.txt里的ASCII carriage-return(回车)删除,在将转换完成的数据输出到UNIX-file.txt。dos-file.txt里的原始数据不会有变化。

> 重定向符(redirector)在目的文件不存在时,会新建一个。然而,如果目的文件已存在,它就会被覆盖;原本的数据都会丢失

以>>附加到文件

program>>file可将program的标准输出附加到file的结尾处。

如同>,如果目的文件不存在,>>重定向符便会新建一个。然而,如果目的文件存在,它不会直接覆盖掉文件,而是将程序所产生的数据附加到文件结尾处:

        for fin  dos-file*.txt

do

tr  -d '\r' <$f >>big-unix-file.txt

done

以|建立管道

program1 | program2 可将program1的标准输出修改为program2的标准输入。


 

tr

语法

tr [options]source-char-list replace-char-list

用途

转换字符。例如,将大写字符转换成小写。选项可让你指定所要删除的字符,以及将一串重复出现的字符浓缩成一个

常用选项

-c

取source-char-list的反义。tr要转换的字符,变成未列在source-char-list中的字符。此项通常和-d或-s配合使用

        -C

        与-c相似,但所处理的是字符(可能是包含多个字节的宽字符),而非二进制的字节值。

        -d

自标准输入删除source-char-list里所列的字符,而不是转换它们。

-s

浓缩重复的字符。如果标准输入中连续重复出现source-char-list里所列出的字符,则将其浓缩成一个。

行为模式

如同过滤器:自标准输入读取字符,再将结果写到标准输出

举例:

   
   
   
   
  1. [root@localhost ~]# vi test1.txt  
  2. test1 1  
  3.  
  4. test2 2  
  5.  
  6. test3 3  
  7.  
  8. test4 4  
  9.  
  10. test5 5 
  11. [root@localhost ~]# tr -d '\n' < test1.txt
    test1 1test2 2test3 3test4 4test5 5

警告

根据POSIX标准的定义,-c处理的是二进制字节值,而-C处理的是现行locale所定义的字符。

注意:构造管道时,应该试着让每个阶段的数据量变的更小

例如,使用sort排序之前,先以grep找出相关的行;这样可以让sort少做些事。

2.5.5.2  特殊文件 /dev/null与/dev/tty

/dev/null就是大家所熟知的位桶(bit bucket),传送到此文件的数据都会被系统丢掉。也就是说,当程序将数据写到此文件时,会认为它已成功完成写入数据的操作,但实际上什么事都没做

如果你需要的是命令的退出状态,而非它的输出,此功能会很有用。

if grep pattren myfile > /dev/null

then

... 找到模式时

else

... 找不到模式时

fi

相对的,读取/dev/null则会立即返回文件结束符号(end-of-file)

/dev/tty。当程序打开此文件时,UNIX会自动将它重定向到一个终端[一个实体的控制台(console)或串行端口(serial port),也可能是一个通过网络与窗口登录的伪终端(pseudoterminal)]再与程序结合。这在读取人工输入和产生错误信息时很方便。
 

  
  
  
  
  1. [root@localhost ~]# cat teststty   
  2. printf "Enter new password:\n";  //提示输入  
  3. stty -echo;<SPAN style="WHITE-SPACE: pre">          </SPAN>//关闭自动打印输入字符的功能 
  4. read pass < /dev/tty;<SPAN style="WHITE-SPACE: pre">  </SPAN>//读取密码
    printf "Enter agina:\n";<SPAN style="WHITE-SPACE: pre"> </SPAN>//提示再读取一次
    read pass2 < /dev/tty;<SPAN style="WHITE-SPACE: pre">  </SPAN>//再读取一次以确认
    stty echo;<SPAN style="WHITE-SPACE: pre">   </SPAN>//别忘了打开自动打印输入字符的功能

stty(set tty)命令用来控制终端的各种设置。-echo 选项用来关闭自动打印每个输入字符的功能;stty echo用来恢复该功能

 

2.5.6 基本命令查找

Shell会沿着查找路径$PATH来寻找命令。$PATH是一个以冒号分隔的目录列表,你可以在列表所指定的目录下找到所要执行的命令。

默认路径(default path)至少包含/bin与/usr/bin,或许还包含存放X Windows程序的/usr/X11R6/bin,以及供本地系统管理人员安装程序的/usr/local/bin

名称为bin的目录用来保存可执行文件,bin是binary的缩写,也可以直接把bin解释成相应的英文字义--存储东西的容器;

如果你要编写自己的脚本,最好准备自己的bin目录来存放它们,并且让Shell能够自动找到它们。这不难,只要建立自己的bin目录,并将它加入$PATH中的列表即可:

  
  
  
  
  1. [root@localhost ~]# cd ~  
  2. [root@localhost ~]# mkdir bin  
  3. [root@localhost ~]# mv /usr/  
  4. bin/      include/  lib/      man/      src/      X11R6/      
  5. etc/      java/     libexec/  sbin/     tmp/        
  6. games/    kerberos/ local/    share/    ucb/        
  7. [root@localhost ~]# mv /usr/ucb/nusers ./bin/  
  8. [root@localhost ~]# PATH=$PATH:$HOME/bin  
  9. [root@localhost ~]# nusers  
  10. 1  
  11. [root@localhost ~]#  

要让修改永久生效,在.profile文件中把你的bin目录加入$PATH,而每次登陆时,Shell都将读取.profile文件,例如:

 

PATH=$PATH:$HOME/bin

在$PATH里的空项目(empty component)表示当前目录(current directory)。空项目位于路径值中间时,可以用两个连续的冒号来表示,如果将冒号直接置于最前端或者尾端,可以分别表示查找时最优先查找或最后查找当前目录:

PATH=:/bin:/usr/bin //先找当前目录

PATH=/bin:/usr/bin: //最后找当前目录

PATH=/bin:/usr/bin //当前目录居中

如果你希望将当前目录纳入查找路径(search path),更好的做法是在$PATH中使用点号(dot)

空项目在可移植性上有点问题

一般来说,你根本就不应该在查找路径中放进当前目录,因为这会有安全上的问题。

2.6 访问Shell脚本的参数

所谓的位置参数(positional parameters)指的也就是Shell脚本的命令行参数(command-line arguments)。在Shell函数里,它们同时也可以是函数的参数。

当它超过9,就应该用大括号把数字框起来。

echo first arg is $1

echo tenth arg is ${10}

  
  
  
  
  1. [root@localhost ~]# who  
  2. root     pts/2        2012-05-05 15:45 (192.168.0.83)  
  3. [root@localhost ~]# cat > finduser //建立新文件  
  4. #!/bin/bash  
  5. # finduser ---查看第一个参数所指定的用户是否登录  
  6. who | grep $1 
  7. ^d<SPAN style="WHITE-SPACE: pre">  </SPAN>//以End-of-file结尾
  8. [root@localhost ~]# chmod +x finduser <SPAN style="WHITE-SPACE: pre"> </SPAN>//设置执行权限
  9. [root@localhost ~]# ./finduser root<SPAN style="WHITE-SPACE: pre"> </SPAN>//测试:寻找root
  10. root     pts/2        2012-05-05 15:45 (192.168.0.83)
  11.  
  
  
  
  
  1. <PRE class=plain name="code">[root@localhost ~]# mv finduser $HOME/bin</PRE><P></P>  
  2. <PRE></PRE>  
  3. 2.7 简单的执行跟踪  
  4. <P></P>  
  5. <P><SPAN style="WHITE-SPACE: pre">打开执行跟踪(execution tracing)的功能打开。这会使得Shell显示每个被执行到的命令,并在前面加上"+"。一个加号后面跟着一个空格。</SPAN></P>  
  6. <P><SPAN style="WHITE-SPACE: pre">在脚本中,可以用set -x命令将执行跟踪的功能打开,然后再用set +x命令关闭它</SPAN></P>  
  7. <P><SPAN style="WHITE-SPACE: pre"></SPAN><PRE class=plain name="code">[root@localhost ~]# cat > trac1.sh //建立脚本  
  8. #!/bin/bash  
  9. set -x<SPAN style="WHITE-SPACE: pre">               </SPAN>   //打开跟踪功能  
  10. echo 1st echo<SPAN style="WHITE-SPACE: pre">            </SPAN>   //做些事  
  11. set +x<SPAN style="WHITE-SPACE: pre">               </SPAN>   //关闭跟踪功能  
  12. echo 2nd echo<SPAN style="WHITE-SPACE: pre">            </SPAN>  //再做些事</PRE><PRE class=plain name="code">^d<SPAN style="WHITE-SPACE: pre">             </SPAN>  //ctrl+d 以end-of-file结尾</PRE><PRE class=plain name="code">[root@localhost ~]# chmod +x trac1.sh //设置执行权限  
  13. [root@localhost ~]# ./trac1.sh <SPAN style="WHITE-SPACE: pre">  </SPAN>  //执行  
  14. + echo 1st echo<SPAN style="WHITE-SPACE: pre">          </SPAN>  //被跟踪的第一行  
  15. 1st echo<SPAN style="WHITE-SPACE: pre">         </SPAN>  
  16. set +x<SPAN style="WHITE-SPACE: pre">         </SPAN>  //被跟踪的下一行  
  17. 2nd echo  
  18. </PRE>执行时,set -x 不会被跟踪,因为跟踪功能是在这条命令执行后才打开的。同理,set +x会被跟踪,因为跟踪功能是在这条命令执行后才关闭的。最后的echo命令不会被跟踪,因为此时跟踪功能已经关闭。<P></P>  
  19. <P><SPAN style="WHITE-SPACE: pre">2.8 国际化</SPAN></P>  
  20. <P><SPAN style="WHITE-SPACE: pre">国际化(internationalization,缩写为i18n)</SPAN></P>  
  21. <P><SPAN style="WHITE-SPACE: pre">本地化(localization,缩写为l10n,理由同前)</SPAN></P>  
  22. <P><SPAN style="WHITE-SPACE: pre">对用户而言,用来控制让哪种语言或文化环境生效的功能就叫做locale</SPAN></P>  
  23. <P><SPAN style="WHITE-SPACE: pre">表 2-3 各种Locale环境变量</SPAN></P>  
  24. <P><SPAN style="WHITE-SPACE: pre"></SPAN>  
  25. </P><TABLE border=1 cellSpacing=1 cellPadding=1 width=700><TBODY><TR><TD>名称</TD><TD>说明</TD></TR><TR><TD>LANG</TD><TD>未设置任何LC_xxx变量时所使用的默认值</TD></TR><TR><TD>LC_ALL</TD><TD>用来覆盖掉所有其他LC_xxx变量的值</TD></TR><TR><TD>LC_COLLATE</TD><TD>使用所指定地区的排序规则</TD></TR><TR><TD>LC_CTYPE</TD><TD>使用所指定地区的字符集(字母、数字、标点符号)</TD></TR><TR><TD>LC_MESSAGES</TD><TD>使用所指定地区的响应与信息;仅POSIX适用</TD></TR><TR><TD>LC_MONETARY</TD><TD>使用所指定地区的货币格式</TD></TR><TR><TD>LC_NUMERIC</TD><TD>使用所指定地区的数字格式</TD></TR><TR><TD>LC_TIME</TD><TD>使用所指定地区的日期与时间格式</TD></TR></TBODY></TABLE>  
  26. 一般来说,可以用LC_ALL来强制设置单一local;而LANG则是用来设置locale的默认值<P></P>  
  27. <P><SPAN style="WHITE-SPACE: pre">通过下面命令,列出系统认得哪些locale名称:</SPAN></P>  
  28. <P><SPAN style="WHITE-SPACE: pre"></SPAN><PRE class=plain name="code">[root@localhost ~]#locale -a <SPAN style="WHITE-SPACE: pre">  </SPAN>//列出所有locale名称</PRE><BR>  
  29. <P></P> 

 

你可能感兴趣的:(linux,shell,开发,读书笔记)