学习shell时,无意中看到了这么好的一篇文章,于是就copy下来了。这篇文章可以使大家很好的对shell有所了解。 此文来源于http://www.study-area.org/linux/system/linux_shell.htm
认识SHELL
或许�o许多人都已经听过shell 或bash 这些名字�o但不知道您是否知道它们究竟是什么东东呢�t
先回到电脑基础常识上吧�s所有的电脑都是由硬体和软体构成的�o硬体就是大家能摸得着看得见的部份�o例如�s键盘�p荧幕�pCPU�p记忆体�p硬碟�p等等。离开了硬体�o所谓的电脑是不存在的�o因为整个系统的输入和输出以及运算都离不开硬体。请问�s如果没有键盘和荧幕您是怎样使用电脑的�t 但是�o您透过键盘进行的输入�o以及从荧幕看到的输出�o真正发挥功能的�o是软体的功劳。而直接负责和这些硬体进行沟通的软体�o就是所谓的核心(kernel)�okernel 必须能够接管键盘的输入�o然后交由CPU 进行处理�o最后将执行结果输出到荧幕上。当然�o除了键盘和荧幕外�o所有的硬体都必须获得kernel 的支援才能使用。
那么�okernel 又如何知道我们键盘输入的东西是什么呢�t 那就是我们这里介绍的shell 所负责的事情了。因为电脑本身所处理的数据�o都是二进位的机器码�o和我们人类习惯使用的语言很不一样。比方说�o输入pwd 命令�o我们知道这是print working directory 的意思(非常简单的人类语音)�o但作为kernel 来说�o它并不知道pwd 是什么�okernel 只会看机器码�o这时候�oshell 就会帮我们将pwd 翻译为kernel 能理解的程式码。所以�o我们在使用电脑的时候�o基本上就是和shell 打交道�o而不是直接和kernel 沟通�o更不是直接控制硬体。
简单来看�o我们就这样来看待它们的关系�s光从字面来解析的话�oshell 就是“壳”�okernel 就是“核”。好比一个果实一样�o您第一眼看到的就是壳�o把壳扒开才看的到里面的核。shell 就是使用者和kernel 之间的界面�o将使用者下的命令翻译给kernel 处理�o关系如下图�s
我们在shell 输入一个命令�oshell 会尝试搜索整个命令行�o并对其中的一些特殊字符做出处理�o如果遇到CR 字符( Enter ) 的时候�o就尝试重组整行命令�o并解释给kernel 执行。而一般的命令格式(syntax)大致如下�s
# command parameter1 patrameter2 ... |
各命令都有自己的选项(options, 通常用“ - ”符号带领)�o可输入也可以不输入�o如果没有额外指定�o命令通常都有自己的预设选项�r而参数(argument)则视各程式要求而定�o有些很严格�o有些也有预设的参数。例如"ls -l" 这个命令�o选项是-l (long list)�o而预设的参数则是当前目录。在命令行中,选项和参数都被称为参项(parameter)。
我们经常谈到的Linux�o其实是指kernel这部份�o而在kernel之外�o则是各种各样的程式和工具�o整合起来才成为一个完整的Linux发行套件。无论如何�oLinux的kernel只有一个(尽管有许多不同的版本�o都由Linus Tovalds负责维护)�o但kernel之外的shell却有许多种�o例如bourne Shell�pC Shell�pKorn Shell�pZsh Shell�p等等�o但我们最常接触到的名叫BASH (Bourne Again SHell)�o为GNU所加强的一个burne shell版本�o也是大多数Linux套件的预设shell 。不同的shell都各自有其不同的优缺点�o有兴趣您可以自行找这方面的资料来看�o我这里就不一一介绍了。
BASH 这个优秀的shell�o之所以会被各大Linux 套件采用为预设的shell�o除了它本身是open source 程式之外�o它的强大功能应该是吸引大家目光的重要因素之一。BASH 的功能很多�o实在很难全部介绍�o下面只列举其中一少部份而已�s
如果您所输入的路径�o是唯一的�o那么只要按一下tab 就能补全�o否则�o会听到一下beat 声�o这时您再补一下tab �o就会将所有以此路径开头的档案列出来�r假如符号条件的档案太多�o那系统会先将符号条件的档案数目告诉您�o例如242 possibilities�o然后您按y 才显示�o如果按n 则让您增加命令的输入�o然后您可以重复这些动作�o直到您所输入的路径只剩唯一的对应�o才可以用一个tab 补全。
同样的�o这个功能也可以用在输入命令的时候�o比方说�o您要输入Xconfigurator 命令�o那您只需输入Xc 然后按一下tab 就可以了�u 是否很方便呢�t ^_^
Tip�s 用tab来补全命令�o不但方便迅速�o而且也比较保险。因为�o如果您前面的路径输入不正确�o用tab是不能完成补全的�o这可以避免您输入错误的路径而执行错误的程式。我强烈建议您执行每一个命令都常试用tab补全功能�o以确保其正确性。(多敲这个tab键没什么坏处啦)
不过�o也有一下更便利的办法�s您可以输入! nnn (其中的nnn是history命令找到的命令记录号码)�o就能执行指定的旧命令了�r如果您输入!!再Enter的话�o那就是重复上一个命令(和按向上方向键再Enter一样)�r如果您输入!ls的话�o则是最后一次的ls开头的命令�o如果是!cd那就是上一个cd开头的命令�o如此类推�r如果您按着Ctrl和R两个键之后�o然后输入您以前曾经输入过的命令�o那它会和上面介绍的补全功能一样�o将您以前输入过的命令补全起来。呵~~太厉害啦�u
Bash会将您登录之后的所有命记录在记cache里面�o然后�o只要您成功退出这个shell之后�o那这些记录就会存放到家目录的~/.bash_history这个档里面(小心看�o它是以.开头的档案哦�o也就是隐藏档是也�o您要用ls -a才看得到。)不过�o这个档只保持一定数量的命令记录而已�o您可以透过$HISTFILESIZE这个变数(我们马上会介绍变数)�o来获得或改变档案的记录数量。
在另外一种情形之下�o当您发现某些长命令会经常使用到�o但打字起来挺麻烦的�o那您就可以用alias 来解决。比方说�o您每次关机要输入的命令是shutdown -h now 这么一串�o那您先输入which shd (目的是确定现有的命令名称)�o如果您并没有发现这个命令出现在您的命令路径之中的话�o那您可以输入alias shd='shutdown -h now'�o然后再输入shd 就可以关机了�u 不过�o现在不要执行它�u �u 因为您这样真的会把机器关掉哦~~ 请您用alias 替换其它的长命令看看�t
如果您要取消一个alias�o可以使用unalias命令�o如�sunalias shd 。
一旦您满意您的新alias �o那您可以修改您的~/.bashrc 这个档�o将它加在其它alias 命令之后�r假如您想系统上所有使用者都能使获得这个alias �o那就将它放到/etc/bashrc 里面吧。(如果您目前还不会编辑档案�o那就回到上一章补习vi 吧:-)
环境变数
还记得上一章里面�o我曾经提到过�s当我们登入系统的时候�o首先就获得一shell�o而且它也占据一个行程�o然后再输入的命令都属于这个shell 的子程式。如果您学习够细心�o不难发现我们的shell 都在/etc/passwd 这个档里面设定的�o也就是帐号设定的最后一栏�o预设是/bin/bash 。
事实上�o当我们获得一个shell之后�o我们才真正能和系统沟通�o例如输入您的命令�p执行您的程式�p等等。您也可以在获得一个shell之后�o再进入另外一个shell (也就是启动一个子程式)�o然后还可以再进入更深一层的shell (再进入子程式的子程式)�o直到您输入exit才退回到上一个shell里面(退回上一级的父程式)。假如您已经阅读过上一章所说过的子程式概念�o应该不难理解。不过�o您的行为也不是无限制的�o而且�o有许多设定都必须事先得到定义。所以�o当您获得shell的时候�o同时也获得一些环境设定�o或称为“ 环境变数( Environment variables) ”。
所谓的变数( variable ) �o就是用特定的名称(或标签)保存一定的设定值�o然后供程式将来使用。例如�o姓=chen �r名=kenny �o那么'姓'和'名'就是变数名称�o而chen和kenny就是变数所保存的值。由shell所定义和管理的变数�o我们称为环境变数�o因为这些变数可以供shell所产生的所有子程式使用。环境变数名称一般都用大写字母表示�o例如�o我们常用的环境变数有这些�s
变数名称 | 代表意思 |
HISTCMD | 当前命令的记录号码。 |
HISTFILE | 命令记录表之存放档案。 |
HISTSIZE | 命令记录表体积。 |
HOME | 预设登录家目录。 |
IFS | 预设分隔符号。 |
LINENO | 当前命令在shell script 中的行数。 |
邮件信箱的路径。 | |
MAILCHECK | 检查邮件的秒数。 |
OLDPWD | 上次进入的目录路径。 |
OSTYPE | 作业系统类型。 |
PATH | 预设命令搜索路径。 |
PPID | 父程式之PID。 |
PWD | 当前工作目录路径。 |
SECONDS | 当前shell 之持续启动时间。 |
SHELL | 当前shell 之执行路径。 |
TMOUT | 自动登出之最高闲置时间。 |
UID | 使用者之UID。 |
$ | 当前shell 之PID。 |
�t | 最后一个命令之返回状态。 |
假如您想看看这些变数值是什么�o只要在变数名称前面加上一个“ $ ”符号�o然后用echo命令来查看就可以了�s
# echo $PWD /root # echo $$ 1206 # echo $? 0 |
第一个命令就是将当前目录的路径显示出来�o和您执行pwd 命令的结果是一样的�r第二个命令将当前这个shell 的PID 显示出来�o也就是1206。如果您这时候输入kill -9 1206 的话�o会将当前的shell 砍掉�o那您就要重新登录才能获得另外一个shell�o而它的PID 也是新的�r第三行命令是上一个命令的返回状态�s如果命令顺利执行�o并没有错误�o那通常是0�r如果命令遇到错误�o那返回状态则是非0 �o其值视程式设计者而定(我们在后面的shell script 的时候会介绍)。关于最后一个命令�o不妨比较一下如下结果�s
# ls mbox mbox # echo $? 0 # ls no_mbox ls: no_mbox: No such file or directory # echo $? 1 |
您会发现�s第一命令成功执行�o所以其返回状态是0 �r而第二个命令执行失败�o其返回状态是1 。假如程式设计者为不同的错误设定不同的返回状态等级�o那您可以根据返回值推算出问题是哪种错误引起的。
Tips�s 如果您日后写程式或script�o要养成一个习惯�o为每一种命令结果设定返回状态。这非常重要�o尤其在进行debug的时候。这个我们在后面学习script的时候再谈。
我们随时都可以用一个= (等号)来定义一个新的变数或改变一个原有变数。例如�s
# MYNAME=kenny # echo $MYNAME kenny |
假如您要取消一个定义好的变数�o那么�o您可以使用unset命令�s
# unset MYNAME |
不过�o环境变数的特性之一�o是单向输出的。也就是说�s一个shell的特定变数�o只能在这个shell里面使用。如果您要分享给同一个shell里面的其它程式�pscript�p命令使用�o或它们的子程式使用�o那您必须用export命令将这个变数进行输出。但无论如何�o如果您在一个子程式中定义了一个变数�o那么这个变数的值�o只影响这个子程式本身以及它自己的子程式�o而永远不会影像到父程式或父程式产生的其它子程式。
比方说�o您在一个程式中定义一个新的变数�o或改变一个原有变数值�o在程式结束的时候�o那它所设定的变数均被取消�r如果您想将变数值分享给该程式所产生的子程式�o您必须用export 命令才能保留这个变数值�o除非子程式另外重新定义。但无论如何�o当前程式所定义的变数值�o是无法传回父程式那边的。不妨做做如下的实验�s
# MYNAME=kenny # echo $MYNAME kenny # export MYNAME |
#设定一个变数。 # #当前的设定值。 #用export输出变数值。 |
# /bin/bash | # 再开一个shell�o也就是进入子程式中。 |
# echo $MYNAME kenny |
# # 保留原有设定值。 |
# export MYNAME=netman # echo $MYNAME netman |
# 重新定义设定值�o同时也用export 输出。 # |
# exit | # 退出子程式�o返回父程式。 |
# echo $MYNAME kenny |
# # 父程式的变数值并没有改变。 |
关于变数的另一个特性�o是的变数值是可以继承的。也就是说�o您可以将一个变数值来设定另外一个变数名称。比方说�s
# FIRST_NAME="Kenny" # echo $MYNAME |
# 定义一个变数。 # 再定义另一个变数�o但它的值是第一个变数。 # |
另外�o在定义变数的时候您还要注意一些规则�s
关于后两项�o或许我们再找些例子来体会一下�s
# TOPIC='Q & A' |
# 用单引号保留特殊符号和空白 |
# Q1=What\'s\ your\ \"topic\"\? # echo $Q1 |
# 用\ 将特殊符号(含引号)和空白跳脱出来 # # 跳脱后�o特殊符号和空白都保留下来。 |
# ANS="It is $TOPIC." # echo $ANS |
# 用双引号保留变数值($) # # 用双引号�o显示出变数值。 |
# WRONG_ANS='It is "$TOPIC".' # echo $WRONG_ANS
|
# 用单引号保留特殊符号和空白(同第一行) # # $ 也当成一般符号保留�o而非变数值。 |
# ALT_ANS='the $TOPIC'\ is\ "'$TOPIC'"\. # echo $ALT_ANS
|
# 同时混合单引号�p双引号�p和跳脱字符 \ # #单引号保留全部�r双引号保留变数值�r |
我这里解释一下最后面的例子好了�s 'the $TOPIC is '"$TOPIC"\.。首先用单引号将'the $TOPIC is '这段文字括好�o其中用3个空白键和一个$符号�r然后用双引号保留$TOPIC的变数值�r最后用\跳脱小数点。
在引用" " 和' ' 符号的时候�o基本上�o ' ' 所包括的内容�o会变成单一的字串�o任何特殊字符都失去其特殊的功能�o而变成一般字符而已�o但其中不能再使用'符号�o而在" " 中间�o则没有' ' 那么严格�o某些特殊字符�o例如$ 号�o仍然保留着它特殊的功能。您不妨实作一下�o比较看看echo ' "$HOME" ' 和echo " '$HOME' " 的差别。
Tips�s 在shell命令行的跳脱字符“ \ ”其实我们会经常用到的。例如�o您的一个命令太长�o一直打下去可能超过一行�o或是想要整洁的输入命令行�o您或许想按Enter键敲到下一行继续输入。但是�o当您敲Enter键的时候�o事实上是输入一个CR (Carriage-Return)字符�o一但shell读到CR字符�o就会尝试执行这个命令。这时�o您就可以在输入Enter之前先输入\符号�o就能将CR字符也跳脱出来�o这样shell就不会马上执行命令了。这样的命令行�o我们在script中经常看到�o但您必须知道那代表什么意思。
如果�o您想对一些变数值进行过滤�o例如�sMY_FILE=' ~/tmp/test.sh' �o而您想将变数值换成test.sh (也就是将前面的路径去掉)�o那您可以将$MY_FILE换成${MY_FILE##*/}。这是一个变数值字串过滤�s##是用来比对变数前端部份�o然后*/是比对的色样(也就是任何字母到/之间)�o然后将最长的部份删除掉。您可以参考如下范例�s
当FNAME="/home/kenny/tmp/test.1.sh" 的时候�s
变数名称 | 代表意思 | 结果 |
${FNAME} | 显示变数值的全部。 |
|
${FNAME##/*/} | 比对变数值开端�o如果以/*/ 开头的话�o砍掉最长的部份。 |
|
${FNAME#/*/} | 比对变数值开端�o如果以/*/ 开头的话�o砍掉最短的部份。 |
|
${FNAME%.*} | 比对变数值末端�o如果以.* 结尾的话�o砍掉最短的部份。 |
|
${FNAME%%.*} | 比对变数值末端�o如果以.* 结尾的话�o砍掉最长的部份。 |
|
${FNAME/sh/bash} | 如果在变数值中找到sh 的话�o将第一个sh 换成bash。 |
|
${FNAME//sh/bash} | 如果在变数值中找到sh 的话�o将全部sh 换成bash。 |
|
您除了能够对变数进行过滤之外�o您也能对变数做出限制�p和改变其变数值�s
字串没设定 | 空字串 | 非空字串 | |
使用预设值 | |||
var=${str-expr} | var=expr | var= | var=$str |
var=${str:-expr} | var=expr | var=expr | var=$str |
使用其它值 | |||
var=${str+expr} | var=expr | var=expr | var=expr |
var=${str:+expr} | var=expr | var= | var=expr |
设定预设值 | |||
var=${str=expr} | str=expr var=expr |
str 不变 var= |
str 不变 var=$str |
var=${str:=expr} | str=expr var=expr |
str=expr var=expr |
str 不变 var=$str |
输出错误 | |||
var=${str?expr} | expr 输出至stderr | var= | var=str |
var=${str:?expr} | expr 输出至stderr | expr 输出至stderr | var=str |
一开始或许比较难理解上面的两个表格说明的意思�o真的很混乱~~ 但只要多做一些练习�o那您就知道怎么使用了。比方说�s
# expr=EXPR # unset str # var=${str=expr}; echo var=$var str=$str expr=$expr var=expr str=expr expr=EXPR # var=${str:=expr}; echo var=$var str=$str expr=$expr var=expr str=expr expr=EXPR # str= # var=${str=expr}; echo var=$var str=$str expr=$expr var= str= expr=EXPR # var=${str:=expr}; echo var=$var str=$str expr=$expr var=expr str=expr expr=EXPR # str=STR # var=${str=expr }; echo var=$var str=$str expr=$expr var=STR str=STR expr=EXPR # var=${str:=expr}; echo var=$var str=$str expr=$expr var= STR str=STR expr=EXPR # MYSTRING=test # echo ${MYSTRING?string not set\!} test # MYSTRING= # echo ${MYSTRING?string not set\!} # unset MYSTRING # echo ${MYSTRING?string not set\!} bash: MYSTRING: string not set! |
请记住这些变数的习性�o日后您要写shell script的时候就不会将变数搞混乱了。假如您想看看当前shell的环境变数有哪些�o您可以输入set命令�r如果只想检查export出来的变数�o可以输入export或env (前者是shell预设的输出变数)。
Bash 设定
到这里�o您或许会问�sshell 的环境变数在哪里定义呢�t 可以调整吗�t
嗯�o第一个问题我不大了解�o我猜那是shell 设计者预设定义好的�o我们一登录获得shell 之后就有了。不过�o第二个问题�o我却可以肯定答复您�s您可以随时调整您的环境变数。您可以在进入shell 之后用在命令行里面重新定义�o也可以透过一些shell 设定档来设定。
先让我们看看�o当您在进行登录的时候�o系统会检查哪些档案吧�s
您可以透过修改上面提到的档案�o来调整您进入shell之后的变数值。一般使用者可以修改其家目录( ~/ )中的档案�o以进行个人化的设定�r而作为root�o您可以修改/etc/下面的档案�o设定大家共用的变数值。至于bash的变数值如何设定�t 有哪些变数�t 各变数的功能如何�t 您打可以执行man bash参考手册资料。
Tips�s 一旦您修改了/etc/profile或~/.bash_profile档案�o其新设定要在下次登录的时候才生效。如果您不想退出�o又想使用新设定�o那可以用 source 命令来抓取�s
source ~/.bash_profile |
命令重导向
好了�o相信您已经对您的shell有一定的了解了。然后�o让我们看看shell上面的一些命令功能吧�o这些技巧都是作为一个系统管理员基本要素。其中之一就是�s 命令重导向(command redirection)和命令管线 (command pipe) 。
在深入讲解这两个技巧之前�o先让我们了解一下shell 命令的基本概念�s
名称 | 代号 | 代表意思 | 设备 |
STDIN | 0 | 标准输入 | 键盘 |
STDOUT | 1 | 标准输出 | 荧幕 |
STDERR | 2 | 标准错误 | 荧幕 |
表格中分别是我们在shell 中一个命令的标准I/O (输出与输入)。当我们执行一个命令的时候�o先读入输入(STDIN)�o然后进行处理�o最后将结果进行输出(STDOUT)�r如果处理过程中遇到错误�o那么命令也会显示错误(STDERR)。我们可以很容易发现�s一般的标准输入�o都是从我们的键盘读取�r而标准输出和标准错误�o都从我们的银幕显示。
同时�o在系统上�o我们通常用号码来代表各不同的I/O�sSTDIN 是0�pSTDOUT 是1�pSTDERR 是2。
当您了解各个I/O 的意思和所代表号码之后�o让我们看比较如下命令的结果�s
# ls mbox mbox # ls mbox 1> file.stdout # |
请小心看第二个命令�s在命令的后面多了一个1 �o而紧接着(没有空白�u)是一个大于符号(> )�o然后是另外一个档案名称。但是�o荧幕上却没有显示命令的执行结果�o也就是说�s STDOUT不见了�u 那到底发生什么事情了呢�t
呵�o相信您不会这么快忘记了STDOUT 的代号是1 吧�u 没错了�o因为我们这里将1 用一个> 符号重导到一个档案中了。结果过是�s我们将标准输出从荧幕改变到档案中�o所以我们在银幕就看不到STDOUT�o而原先的STDOUT 结果则保存在大于符号右边的档中了。不信�o您看看这个档案的内容就知道了�s
# cat file.stdout mbox |
当我们用一个>将命令的STDOUT导向到一个档案的时候�o如果档案不存在�o则会建立一个新档�r如果档案已经存在�o那么�o这个档案的内容就换成STDOUT的结果。有时候�o您或许想保留原有档案的内容�o而将结果增加在档案末端而已。那您可以多加一个>�o也就是使用>>就是了。您可以自己玩玩看哦~~�o通常�o我们要将一些命令或错误记录下来�o都用这个方法。
Tips�s 如果您不希望>意外的盖掉一个原有档�o那您可以执行这个命令�s
set -o noclobber |
不过�o仍可以用>| 来强迫写入。
上前面的例子中�o我们指定了I/O 1 (STDOUT) 进行重导向�o这也是预设值�o如果您没有指定代号�o那么就是进行STDOUT 的重导向�o所以1> 和> 是一样的�r1 >> 和>> 也是一样的。但如果您使用了数字�o那么数字和> 之间一定不能有空白存在。
好了�o下面再比较两个命令�s
# ls no_mbox ls: no_mbox: No such file or directory # ls no_mbox 2>> file.stderr |
嗯�o相信不用我多解释了吧�t (如果档案不存在�o>> 和> 都会建立新的。)
事实上�o在我们的日常管理中�o重导向的应用是非常普遍的。我只举下面这个例子就好了�s
当我们进行核心编译的时候(我们下一章再介绍)�o荧幕会飞快的显示出成千上万行信息�r其中有大部份是STDOUT�o但也有些是STDERR。除非您的眼睛真的那么厉害�o否则您很难分辩出哪些是正常信息�o哪些是错误信息。当您要编译失败�o尝试找错误的时候�o如果已经将STDERR 重导出来�o就非常方便了�s
# make dep clean bzImage modules 1>/dev/null 2>/tmp/kernel.err & |
这里�o我一共有三个打算�s(1) 将标准输出送到一个叫null 的设备上�o如果您记性够好�o我在前面的文章中曾比喻它为黑洞�s所有东西进去之后都会消失掉。凭我个人的习惯�o我会觉得编译核心时跑出来的信息�o如果您不感兴趣的话�o那都是垃圾�o所以我将STDOUT 给重导到null 去�o眼不见为干净�r (2) 然后�o我将STDERR 重导到/tmp/kernel.err 这个档去�o等命令结束后�o我就可以到那里看看究竟有部份有问题。有些问题可能不是很重要�o有些则可能需要重新再编核心�o看您经验啦。(3) 最后�o我将命令送到background 中执行(呵~~ 相信您还没忘记吧�u)。因为�o编译核心都比较花时间�o所以我将之送到背景去�o这样我可以继续做其它事情。
Tips�s 这时�o因为系统太忙了�o可能反应速度上会比较慢些�o如果您真的很在意�o不妨考虑把make的nice level提高。(忘记怎么做了�t那翻看前一章吧)
前面的例子�o我们是分开将STDOUT 和STDERR 重导到不同的档案去�o那么�o我们能否把两者都重导到同一个档呢�t 当然是可以的�o请比较下面三行�s
# make dep clean bzImage modules >/tmp/kernel.result 2>/tmp/kernel.result # make dep clean bzImage modules >/tmp/kernel.result 2>&1 # make dep clean bzImage modules &>/tmp/kernel. resultt |
我这里告诉您�s第一行的命令不怎么正确�o因为这样会造成这两个输出同时在'抢'一个档案�o写入的顺序很难控制。而第2 行和第3 行的结果都是一样的�o看您喜欢用哪个格式了。不过�o要小心的是�s& 符号后面不能有空白键�o否则会当成将命令送到背景执行�o而不是将STDOUT 和STDERR 整合。
好了�o前面我们都在谈STDOUT 和STDERR 的重导向�o那么�o我们是否能重导STDIN 呢�t
当然可以啦~~~
有些命令�o当我们执行之后�o它会停在那里等待键盘的STDIN输入�o直到遇到EOF (Ctrl+D)标签才会真正结束命令。比方说�o在同一个系统上�o如果有多位使用者同时登入的话�o您可以用write命令向特的使用者送出短讯。而短讯的内容就是键盘敲入的文字�o这时候命令会进入输入模式�o您每输入一行并按Enter之后�o那么讯息就会在另外一端�o直到您按Ctrl+D键才离开并结束命令。
# write user1 Hello! It is me... ^_^ How ru! (Ctrl+D) |
这样通常都需要花一些时间输入�o假如对方在写什么东西和查看某些资料的时候�o就很混乱。这时候�o您或许可以先将短讯的内容写在一个档案里面�o例如greeting.msg�o然后这样输入就可以了�s
write user1 < greeting.msg |
就这样�o这里我们用小于符号( < )来重导STDIN 。简单吧�t ^_^
不过�o我们用cat 命令建立简单的档案的时候�o却是使用> 符号的�s
cat > file.tmp
等您按Ctrl+D 之后�o从键盘输入的STDIN�o就保存在file.tmp 中了。请想想看为什么会如此�t (我在LPI 的考试中碰到过这道题目哦~~~) |
pipe
查字典�opipe 这个英文是水管�p管道�p管线的意思。那么�o它和命令又有什么牵连呢�t 简单的说�o一个命令管线�o就是将一个命令的STDOUT 作为另一个命令的STDIN 。
其实�o这样的例子我们前面已经碰到多次了�o例如上一章介绍tr 命令的时候�s
# cat /path/to/old_file | tr -d '\r' > /path/to/new_file |
上面这个命令行�o事实上有两个命令�scat 和tr �o在这两个命令之间�o我们用一个“ | ”符号作为这两个命令的管线�o也就是将cat 命令的STDOUT 作为tr 命令的STDIN �r然后�otr 命令的STDOUT 用> 重导到另外一个档案去。
上面只是一个非常简单的例子而已�o事实上�o我们可以用多个管线连接多个程式�o最终获得我们确切想要的结果。比方说�s我想知道目前有多少人登录在系统上面�s
# w | tail +3 | wc -l |
我们不妨解读一下这个命令行�s(1) w 命令会显示出当前登录者的资源使用情况�o并且每一个登录者占一行�r(2) 再用tail 命令抓取第3 行开始的字行�r( 3) 然后用wc -l 计算出行数。这样�o就可以知道当前的登录人数了。
许多朋友目前都采用拨接ADSL 上网�o每次连线的IP 都未必一样�o只要透过简单的命令管线�o您就可以将当前的IP 抓出来了�s
# ifconfig ppp0 ppp0 Link encap:Point-to-Point Protocol inet addr:211.74.48.254 PtP:211.74.48.1 Mask:255.255.255.255 UP POINTOPOINT RUNNING NOARP MULTICAST MTU:1492 Metric:1 RX packets:5 errors:0 dropped:0 overruns:0 frame:0 TX packets:3 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:3 |
# ifconfig ppp0 | grep "inet addr" inet addr:211.74.48.254 PtP:211.74.48.1 Mask:255.255.255.255 |
嗯�o这里�o我们可以用“ : ”来分出4 列�r也可以用空白键来分出5 列(空因为句子开首就是一个空白键)。如果用空白键来分的话�o由于有些间隔有多个空白键的原因�o那么�o我们可以用tr 命令�o将多个空白键集合成一个空白键�s
# ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' inet addr:211.74.48.254 PtP:211.74.48.1 Mask:255.255.255.255 |
# ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' | cut -d ' ' -f3 addr:211.74.48.254 |
# ifconfig ppp0 | grep "inet addr" | tr -s ' ' ' ' \ | cut -d ' ' -f3 | cut -d ':' -f2 211.74.48.254 |
这里�o我们一共用5 个pipe 将4 个命令连接起来�o就抓出机器当前的IP 位址了。是否很好用呢�t
在同一个命令行里面出现多个命令的情形�o除了“ | ”之外�o或许您会看到" ` ` " 符号�o也就是和~ 键同一个键的符号(不用按Shift )。它必须是一对使用的�o其中可以包括单一命令�o或命令管线。那它的效果和命令管线又有什么分别呢�t
我们使用pipe 将一个命令的STDOUT 传给下一个命令的STDIN�o但使用`` 的时候�o它所产生的STDOUT 或STDERR 仅作为命令行中的一个参数而已。嗯�o不如看看下面命令好了�s
# TODAY=`date +%D` # echo Today is $TODAY. Today is 08/17/01. |
从结果我们可以看出�o我们用`` 将date 这个命令括起来(可含参数)�o那么它的执行结果可以作为TODAY 的变数值。我们甚至还可以将一串命令管线直接用在命令行上面�s
# echo My IP is `ifconfig ppp0 | grep "inet addr" \ | tr -s ' ' ' ' | cut -d ' ' -f3 | cut -d ':' -f2` My IP is 211.74.48.254. |
注意�s第一行的CR 被\ 跳脱了�o所以这个命令行'看起来'有两行。我之所以弄这么复杂�o是告诉您这对`` 符号可以适用的范围。
Tips�s 在变数中使用``可以将命令的执行结果当成变数值的部份。事实上�o除了用``之外�o您也可以用这样的格式�s VAR_NAME=$(command) �o那是和VAR_NAME=`command`的结果是一样的。
除了这对`` 和| 之外�o还有另外一个符号“ ; ”来分隔命令的。不过�o这个比较简单�s就是当第一命令结束之后�o再执行第二个命令�o如此类推�s
# ./configure; make; make install |
呵~~ 如果您对您的安装程式有绝对信心�o用上面一行命令就够了�u
Shell Script
当我们对shell 变数和命令行有一定认识之后�o那么�o我们就可以尝试写自己的shell script ��~~ 这可是非常好玩而又有成就感的事情呢�u ^_^
在linux 里面的shell script 可真是无处不在�s我们开机执行的run level 基本上都是一些script �r登录之后的环境设定�o也是些script �r甚至工作排程和记录维护也都是script 。您不妨随便到/etc/rc.d/init.d 里面抓两个程式回来看看�o不难发现它们都有一个共同的之处�s第一行一定是如下这样的�s
#!/bin/sh 或�s #!/bin/bash |
其实�o这里的#! 后面要定义的就是命令的解释器(command interpreter)�o如果是/bin/bash 的话�o那下面的句子就都用bash 来解释�r如果是/usr/bin/perl 的话�o那就用perl 来解释。不同的解释器所使用的句子语法都不一样�o非常严格�o就算同是用shell 来解释�o不同的shell 之间的格式也不仅相同。所以�o如果您看到script 的解释器是/bin/sh 的话�o那就要小心了�s如果您仔细看这个档案�o事实上它仅是一个link 而已�o有些系统或许会将它link 到其它shell 去。假如您的script 句子使用的语法是bash 的话�o而这个sh 却link 到csh �o那执行起来可能会有问题。所以�o最好还是直接指定shell 的路径比较安全一些�s在这里的范例都使用/bin/bash 来作为script 的解释器。
在真正开始写script 之前�o先让我们认识script 的一些基本概念�s
简单来说�oshell script 里面就是一连串命令行而已�o再加上条件判断�p流程控制�p回圈�p等技巧�o聪明地执行正确的命令和使用正确的参数选项。和我们在shell 里面输入命令一样�oshell script 也有这样的特性�s
一个良好的script 作者�o在程式开头的时候�o都会用注解说明script 的名称�p用途�p作者�p日期�p版本�p等信息。如果您有这个机会写自己的script�o也应该有这个良好习惯。
shell script档的命名没一定规则�o可以使用任何档案名称(参考档案系统)�o但如果您喜欢的话�o可以用.sh来做它的副档名�o不过这不是硬性规定的。不过�o要执行一个shell script�o使用者必须对它有执行权限( x )�o用文件编辑器新建立的档案都是没有x permission的�o请用chmod命令加上。执行的时候�o除非该script已经至于PATH环境变数之内的路径内�o否则您必须指定路径。例如�o您写了一个叫test.sh的shell script�o放在家目录内�o假设这也是您的当前工作目录�o您必须加上路径才能执行�s./test.sh或~/test.sh 。所以�o建议您在script测试无误之后�o放在~/bin目录里面�o那就可以在任何地方执行自己的script了�o当然�o您要确定~/bin已经出现在您的PATH变数里面。
script之所以聪明�o在于它能够对一些条件进行测试( test )。您可以直接用test命令�o也可以用if叙述�o例如�stest -f ~/test.sh 。它的意思是测试一下~/test.sh这个档案是否存在�o这个-f通常用在档案上面的测试�o除了它�o还有很多�s
标签 | 代表意思 |
-G | 存在�o并且由GID 所执行的行程所拥有。 |
-L | 存在�o并且是symbolic link 。 |
-O | 存在�o并且由UID 所执行的行程所拥有。 |
-S | 存在�o并且是一个socke 。 |
-b | 存在�o并且是block 档案�o例如磁碟等。 |
-c | 存在�o并且是character 档案�o例如终端或磁带机。 |
-d | 存在�o并且是一个目录。 |
-e | 存在。 |
-f | 存在�o并且是一个档案。 |
-g | 存在�o并且有SGID 属性。 |
-k | 存在�o并且有sticky bit 属性。 |
-p | 存在�o并且是用于行程间传送资讯的name pipe 或是FIFO。 |
-r | 存在�o并且是可读的。 |
-s | 存在�o并且体积大于0 (非空档)。 |
-u | 存在�o并且有SUID 属性。 |
-w | 存在�o并且可写入。 |
-x | 存在�o并且可执行。 |
事实上�o关于这些测试项目还有很多很多�o您可以man bash 然后参考CONDITIONAL EXPRESSIONS 那部份。另外�o我们还可以同时对两个档案进行测试�o例如�stest file1 -nt file2 就是测试file1 是否比file2 要新。这种测试使用的标签是�s
标签 | 代表意思 |
-nt | Newer Than�s第一个档案比第二个档案要新。 |
-ot | Older Than�s第一个档案比第二个档案要旧。 |
-ef | Equal File�s第一个档案和第二个档案其实都是同一个档案(如link)。 |
我们这里所说的这些测试�o不单只用来测试档案�o而且还常会用来比对' 字串(string)'或数字(整数)。那什么是字串呢�t 字面来介绍就是一串文字嘛。在一个测试中�o~/test.sh本身是一个档案�r但'~/test.sh' �o则是在引号里面(单引号或双引号)�o那就是字串了。
在数字和字串上面的比对(或测试)�o所使用的标签大约有�s
标签 | 代表意思 |
= | 等于 |
!= | 不等于 |
< | 小于 |
> | 大于 |
-eq | 等于 |
-ne | 不等于 |
-lt | 小于 |
-gt | 大于 |
-le | 小于或等于 |
-ge | 大于或等于 |
-a | 双方都成立 |
-o | 单方成立 |
-z | 空字串 |
-n | 非空字串 |
在上面提到的比对中�o虽然有些意思一样�o但使用场合却不尽相同。例如= 和-eq 都是'等于'的意思�o但= 只能比对字串�o而-eq 则可以用来比对字串�o也能用来比对表示色样(我们在regular expression 会碰到)。
后续!!!!