设为首页 收藏本站
查看: 731|回复: 0

认识shell-1

[复制链接]

尚未签到

发表于 2018-8-24 11:03:45 | 显示全部楼层 |阅读模式
  学习shell时,无意中看到了这么好的一篇文章,于是就copy下来了。这篇文章可以使大家很好的对shell有所了解。 此文来源于http://www.study-area.org/linux/system/linux_shell.htm
  认识SHELL
  或许﹐许多人都已经听过shell 或bash 这些名字﹐但不知道您是否知道它们究竟是什么东东呢﹖
  先回到电脑基础常识上吧﹕所有的电脑都是由硬体和软体构成的﹐硬体就是大家能摸得着看得见的部份﹐例如﹕键盘﹑荧幕﹑CPU﹑记忆体﹑硬碟﹑等等。离开了硬体﹐所谓的电脑是不存在的﹐因为整个系统的输入和输出以及运算都离不开硬体。请问﹕如果没有键盘和荧幕您是怎样使用电脑的﹖ 但是﹐您透过键盘进行的输入﹐以及从荧幕看到的输出﹐真正发挥功能的﹐是软体的功劳。而直接负责和这些硬体进行沟通的软体﹐就是所谓的核心(kernel)﹐kernel 必须能够接管键盘的输入﹐然后交由CPU 进行处理﹐最后将执行结果输出到荧幕上。当然﹐除了键盘和荧幕外﹐所有的硬体都必须获得kernel 的支援才能使用。
  

  那么﹐kernel 又如何知道我们键盘输入的东西是什么呢﹖ 那就是我们这里介绍的shell 所负责的事情了。因为电脑本身所处理的数据﹐都是二进位的机器码﹐和我们人类习惯使用的语言很不一样。比方说﹐输入pwd 命令﹐我们知道这是print working directory 的意思(非常简单的人类语音)﹐但作为kernel 来说﹐它并不知道pwd 是什么﹐kernel 只会看机器码﹐这时候﹐shell 就会帮我们将pwd 翻译为kernel 能理解的程式码。所以﹐我们在使用电脑的时候﹐基本上就是和shell 打交道﹐而不是直接和kernel 沟通﹐更不是直接控制硬体。
  简单来看﹐我们就这样来看待它们的关系﹕光从字面来解析的话﹐shell 就是“壳”﹐kernel 就是“核”。好比一个果实一样﹐您第一眼看到的就是壳﹐把壳扒开才看的到里面的核。shell 就是使用者和kernel 之间的界面﹐将使用者下的命令翻译给kernel 处理﹐关系如下图﹕

DSC0000.gif   我们在shell 输入一个命令﹐shell 会尝试搜索整个命令行﹐并对其中的一些特殊字符做出处理﹐如果遇到CR 字符( Enter ) 的时候﹐就尝试重组整行命令﹐并解释给kernel 执行。而一般的命令格式(syntax)大致如下﹕

# command parameter1 patrameter2 ...  各命令都有自己的选项(options, 通常用“ - ”符号带领)﹐可输入也可以不输入﹐如果没有额外指定﹐命令通常都有自己的预设选项﹔而参数(argument)则视各程式要求而定﹐有些很严格﹐有些也有预设的参数。例如"ls -l" 这个命令﹐选项是-l (long list)﹐而预设的参数则是当前目录。在命令行中,选项和参数都被称为参项(parameter)。
  我们经常谈到的Linux﹐其实是指kernel这部份﹐而在kernel之外﹐则是各种各样的程式和工具﹐整合起来才成为一个完整的Linux发行套件。无论如何﹐Linux的kernel只有一个(尽管有许多不同的版本﹐都由Linus Tovalds负责维护)﹐但kernel之外的shell却有许多种﹐例如bourne Shell﹑C Shell﹑Korn Shell﹑Zsh Shell﹑等等﹐但我们最常接触到的名叫BASH (Bourne Again SHell)﹐为GNU所加强的一个burne shell版本﹐也是大多数Linux套件的预设shell 。不同的shell都各自有其不同的优缺点﹐有兴趣您可以自行找这方面的资料来看﹐我这里就不一一介绍了。
  BASH 这个优秀的shell﹐之所以会被各大Linux 套件采用为预设的shell﹐除了它本身是open source 程式之外﹐它的强大功能应该是吸引大家目光的重要因素之一。BASH 的功能很多﹐实在很难全部介绍﹐下面只列举其中一少部份而已﹕

命令补全功能﹕当您输入命令的时候﹐您可以输入目录或档案的开首字面﹐然后按'tab'键将您的命令路径补全。比方说﹐您要ls 一下/etc/sysconfig 这个目录的内容(假设您已经在/etc 目录下了)﹐您可以只输入ls sy 然后接连按两下tab 键﹐然后就会将/etc/ 目录下所有以sy 开头的档案和目录显示出来﹐您或许可以看到sysconfig﹑sysctl.conf ﹑syslog.conf 这三个结果﹔如果您只输入ls sys 再按两下tab 的话﹐结果是是一样的﹐因为在/etc/ 目录下面﹐所有以sy 开头的档案﹐第3 个字面都是s 而没有其它字面了﹔如果您输入ls sysc 再重复这个动作﹐那么显示结果就剩下sysconfig 和sysctl.conf 而已﹐因为以sysc 开头的只有这两个档﹐如果您再按ls sysco 接一个tab﹐那就会帮您将sysconfig 这个唯一以sysco 开头的档案补全。  如果您所输入的路径﹐是唯一的﹐那么只要按一下tab 就能补全﹐否则﹐会听到一下beat 声﹐这时您再补一下tab ﹐就会将所有以此路径开头的档案列出来﹔假如符号条件的档案太多﹐那系统会先将符号条件的档案数目告诉您﹐例如242 possibilities﹐然后您按y 才显示﹐如果按n 则让您增加命令的输入﹐然后您可以重复这些动作﹐直到您所输入的路径只剩唯一的对应﹐才可以用一个tab 补全。
  同样的﹐这个功能也可以用在输入命令的时候﹐比方说﹐您要输入Xconfigurator 命令﹐那您只需输入Xc 然后按一下tab 就可以了﹗ 是否很方便呢﹖ ^_^

Tip﹕用tab来补全命令﹐不但方便迅速﹐而且也比较保险。因为﹐如果您前面的路径输入不正确﹐用tab是不能完成补全的﹐这可以避免您输入错误的路径而执行错误的程式。我强烈建议您执行每一个命令都常试用tab补全功能﹐以确保其正确性。(多敲这个tab键没什么坏处啦)
命令记录表﹕每次您输入一个命令﹐并按Enter执行之后﹐那您这个命令就被存放在命令记录表(command history)中﹐而每个命令都有一个记录号码﹐您可以用history命令来看看当前的命令历史表。这样﹐您只要用向上方向键﹐就可以依次呼叫出您最近所输入的命令﹐按下方向键则退回最新的命令﹐找到您想要重新输入的命令﹐然后再按Enter即可。  不过﹐也有一下更便利的办法﹕您可以输入! nnn (其中的nnn是history命令找到的命令记录号码)﹐就能执行指定的旧命令了﹔如果您输入!!再Enter的话﹐那就是重复上一个命令(和按向上方向键再Enter一样)﹔如果您输入!ls的话﹐则是最后一次的ls开头的命令﹐如果是!cd那就是上一个cd开头的命令﹐如此类推﹔如果您按着Ctrl和R两个键之后﹐然后输入您以前曾经输入过的命令﹐那它会和上面介绍的补全功能一样﹐将您以前输入过的命令补全起来。呵~~太厉害啦﹗
  Bash会将您登录之后的所有命记录在记cache里面﹐然后﹐只要您成功退出这个shell之后﹐那这些记录就会存放到家目录的~/.bash_history这个档里面(小心看﹐它是以.开头的档案哦﹐也就是隐藏档是也﹐您要用ls -a才看得到。)不过﹐这个档只保持一定数量的命令记录而已﹐您可以透过$HISTFILESIZE这个变数(我们马上会介绍变数)﹐来获得或改变档案的记录数量。


alias 功能﹕在Linux 里面﹐您可以透过alias (别名) 的功能﹐来定义出一个命令的预设参数﹐甚至用另​​外一个名称来简化一个命令(及参数)。如果您输入alias 这个命令﹐您就会看到目前的alias 有哪些。您或许会看到其中有一个﹕ alias rm='rm -i' 这行﹐它的意思是﹕如果您执行rm 这个命令﹐那么系统实际执行的命令会带上-i 的参数﹐也就是以interactive模式进行﹐结果是在您进行删除档案的时候﹐会经过您的确认才真正删除。在某些没有这个alias 的系统中﹐那您执行rm 而不另行指定-i 的话﹐那就无声无息的将您能砍的档案给砍掉。小心哦﹐在Linux 上面﹐档案一旦删除就没办法救回了﹗ 所以﹐用心的系统﹐会帮您做这个alias。  在另外一种情形之下﹐当您发现某些长命令会经常使用到﹐但打字起来挺麻烦的﹐那您就可以用alias 来解决。比方说﹐您每次关机要输入的命令是shutdown -h now 这么一串﹐那您先输入which shd (目的是确定现有的命令名称)﹐如果您并没有发现这个命令出现在您的命令路径之中的话﹐那您可以输入alias shd='shutdown -h now'﹐然后再输入shd 就可以关机了﹗ 不过﹐现在不要执行它﹗ ﹗ 因为您这样真的会把机器关掉哦~~ 请您用alias 替换其它的长命令看看﹖
  如果您要取消一个alias﹐可以使用unalias命令﹐如﹕unalias shd 。
  一旦您满意您的新alias ﹐那您可以修改您的~/.bashrc 这个档﹐将它加在其它alias 命令之后﹔假如您想系统上所有使用者都能使获得这个alias ﹐那就将它放到/etc/bashrc 里面吧。(如果您目前还不会编辑档案﹐那就回到上一章补习vi 吧:-)


强大的script 能力玩过DOS 的朋友﹐一定会知道batch 档案的功能﹐在BASH 本身可以帮您执行一系列根据条件判断的命令﹐其功能比DOS 的batch 强大多了。在本章的后面部份﹐会详细讨论shell script 的基本技巧。事实上﹐bash 还有许多厉害的功能﹐恐怕很难全部介绍了﹐还是留给您自己去找寻了。  环境变数
  还记得上一章里面﹐我曾经提到过﹕当我们登入系统的时候﹐首先就获得一shell﹐而且它也占据一个行程﹐然后再输入的命令都属于这个shell 的子程式。如果您学习够细心﹐不难发现我们的shell 都在/etc/passwd 这个档里面设定的﹐也就是帐号设定的最后一栏﹐预设是/bin/bash 。
  事实上﹐当我们获得一个shell之后﹐我们才真正能和系统沟通﹐例如输入您的命令﹑执行您的程式﹑等等。您也可以在获得一个shell之后﹐再进入另外一个shell (也就是启动一个子程式)﹐然后还可以再进入更深一层的shell (再进入子程式的子程式)﹐直到您输入exit才退回到上一个shell里面(退回上一级的父程式)。假如您已经阅读过上一章所说过的子程式概念﹐应该不难理解。不过﹐您的行为也不是无限制的﹐而且﹐有许多设定都必须事先得到定义。所以﹐当您获得shell的时候﹐同时也获得一些环境设定﹐或称为“ 环境变数( Environment variables) ”。
  所谓的变数( variable ) ﹐就是用特定的名称(或标签)保存一定的设定值﹐然后供程式将来使用。例如﹐姓=chen ﹔名=kenny ﹐那么'姓'和'名'就是变数名称﹐而chen和kenny就是变数所保存的值。由shell所定义和管理的变数﹐我们称为环境变数﹐因为这些变数可以供shell所产生的所有子程式使用。环境变数名称一般都用大写字母表示﹐例如﹐我们常用的环境变数有这些﹕
  

变数名称
代表意思
HISTCMD
当前命令的记录号码。
HISTFILE
命令记录表之存放档案。
HISTSIZE
命令记录表体积。
HOME
预设登录家目录。
IFS
预设分隔符号。
LINENO
当前命令在shell script 中的行数。
MAIL
邮件信箱的路径。
MAILCHECK
检查邮件的秒数。
OLDPWD
上次进入的目录路径。
OSTYPE
作业系统类型。
PATH
预设命令搜索路径。
PPID
父程式之PID。
PWD
当前工作目录路径。
SECONDS
当前shell 之持续启动时间。
SHELL
当前shell 之执行路径。
TMOUT
自动登出之最高闲置时间。
UID
使用者之UID。
$
当前shell 之PID。

最后一个命令之返回状态。  假如您想看看这些变数值是什么﹐只要在变数名称前面加上一个“ $ ”符号﹐然后用echo命令来查看就可以了﹕

# echo $PWD  
/root
  
# echo $$
  
1206
  
# echo $?
  
0
  第一个命令就是将当前目录的路径显示出来﹐和您执行pwd 命令的结果是一样的﹔第二个命令将当前这个shell 的PID 显示出来﹐也就是1206。如果您这时候输入kill -9 1206 的话﹐会将当前的shell 砍掉﹐那您就要重新登录才能获得另外一个shell﹐而它的PID 也是新的﹔第三行命令是上一个命令的返回状态﹕如果命令顺利执行﹐并没有错误﹐那通常是0﹔如果命令遇到错误﹐那返回状态则是非0 ﹐其值视程式设计者而定(我们在后面的shell script 的时候会介绍)。关于最后一个命令﹐不妨比较一下如下结果﹕

# ls mbox  
mbox
  
# echo $?
  
0
  
# ls no_mbox
  
ls: no_mbox: No such file or directory
  
# echo $?
  
1
  您会发现﹕第一命令成功执行﹐所以其返回状态是0 ﹔而第二个命令执行失败﹐其返回状态是1 。假如程式设计者为不同的错误设定不同的返回状态等级﹐那您可以根据返回值推算出问题是哪种错误引起的。

Tips﹕如果您日后写程式或script﹐要养成一个习惯﹐为每一种命令结果设定返回状态。这非常重要﹐尤其在进行debug的时候。这个我们在后面学习script的时候再谈。  我们随时都可以用一个= (等号)来定义一个新的变数或改变一个原有变数。例如﹕

# MYNAME=kenny  
# echo $MYNAME
  
kenny
  假如您要取消一个定义好的变数﹐那么﹐您可以使用unset命令﹕

# unset MYNAME  不过﹐环境变数的特性之一﹐是单向输出的。也就是说﹕一个shell的特定变数﹐只能在这个shell里面使用。如果您要分享给同一个shell里面的其它程式﹑script﹑命令使用﹐或它们的子程式使用﹐那您必须用export命令将这个变数进行输出。但无论如何﹐如果您在一个子程式中定义了一个变数﹐那么这个变数的值﹐只影响这个子程式本身以及它自己的子程式﹐而永远不会影像到父程式或父程式产生的其它子程式。
  比方说﹐您在一个程式中定义一个新的变数﹐或改变一个原有变数值﹐在程式结束的时候﹐那它所设定的变数均被取消﹔如果您想将变数值分享给该程式所产生的子程式﹐您必须用export 命令才能保留这个变数值﹐除非子程式另外重新定义。但无论如何﹐当前程式所定义的变数值﹐是无法传回父程式那边的。不妨做做如下的实验﹕

# MYNAME=kenny  
# echo $MYNAME
  
kenny
  
# export MYNAME

#设定一个变数。  
#
  
#当前的设定值。
  
#用export输出变数值。

# /bin/bash
# 再开一个shell﹐也就是进入子程式中。
# echo $MYNAME  
kenny
  #
  # 保留原有设定值。

# export MYNAME=netman  
# echo $MYNAME
  
netman
  # 重新定义设定值﹐同时也用export 输出。
  #
  
#变数值被新值取代。

# exit  # 退出子程式﹐返回父程式。

# echo $MYNAME  
kenny
  #
  # 父程式的变数值并没有改变。
  关于变数的另一个特性﹐是的变数值是可以继承的。也就是说﹐您可以将一个变数值来设定另外一个变数名称。比方说﹕

  # FIRST_NAME="Kenny"
  
# MYNAME=$FIRST_NAME
  # echo $MYNAME
  
Kenny
  # 定义一个变数。
  # 再定义另一个变数﹐但它的值是第一个变数。
  #
  
#第二个变数继承了第一个变数的值。
  另外﹐在定义变数的时候您还要注意一些规则﹕


  • 定义变数时﹐“=”号两边没有空白键﹔
  • 作为变数的名称﹐只能是字母和数字﹐但不能以数字开头﹔如果名称太长﹐可以用“_”分隔﹔
  • 预先定义的变数均为大写﹐自定义变数可以混合大小写﹐以更好区别﹔
  • 只有Shell 本身定义的变数才能称为环境变数;
  • 如果变数中带有特殊字符﹐必须先行用“\”符号跳脱﹔
  • 如果变数中带有空白﹐必须使用引号﹐或进行跳脱。
  关于后两项﹐或许我们再找些例子来体会一下﹕

  # TOPIC='Q & A'
  # 用单引号保留特殊符号和空白
  # Q1=What\'s\ your\ \"topic\"\?
  # echo $Q1
  
What's your "topic"?
  # 用\ 将特殊符号(含引号)和空白跳脱出来
  #
  # 跳脱后﹐特殊符号和空白都保留下来。
  # ANS="It is $TOPIC."
  # echo $ANS
  
It is Q & A.
  # 用双引号保留变数值($)
  #
  # 用双引号﹐显示出变数值。
  # WRONG_ANS='It is "$TOPIC".'
  # echo $WRONG_ANS
  
It is "$TOPIC".
  # 用单引号保留特殊符号和空白(同第一行)
  #
  
#用单引号﹐全部保留﹔同时﹕
  # $ 也当成一般符号保留﹐而非变数值。
  # ALT_ANS='the $TOPIC'\ is\ "'$TOPIC'"\.
  # echo $ALT_ANS
  
The $TOPIC is 'Q & A'.
  # 同时混合单引号﹑双引号﹑和跳脱字符 \
  #
  #单引号保留全部﹔双引号保留变数值﹔
  
# \将特殊符号跳脱出来。
  我这里解释一下最后面的例子好了﹕ 'the $TOPIC is '"$TOPIC"\.。首先用单引号将'the $TOPIC is '这段文字括好﹐其中用3个空白键和一个$符号﹔然后用双引号保留$TOPIC的变数值﹔最后用\跳脱小数点。
  在引用" " 和' ' 符号的时候﹐基本上﹐ ' ' 所包括的内容﹐会变成单一的字串﹐任何特殊字符都失去其特殊的功能﹐而变成一般字符而已﹐但其中不能再使用'符号﹐而在" " 中间﹐则没有' ' 那么严格﹐某些特殊字符﹐例如$ 号﹐仍然保留着它特殊的功能。您不妨实作一下﹐比较看看echo ' "$HOME" ' 和echo " '$HOME' " 的差别。

Tips﹕在shell命令行的跳脱字符“ \ ”其实我们会经常用到的。例如﹐您的一个命令太长﹐一直打下去可能超过一行﹐或是想要整洁的输入命令行﹐您或许想按Enter键敲到下一行继续输入。但是﹐当您敲Enter键的时候﹐事实上是输入一个CR (Carriage-Return)字符﹐一但shell读到CR字符﹐就会尝试执行这个命令。这时﹐您就可以在输入Enter之前先输入\符号﹐就能将CR字符也跳脱出来﹐这样shell就不会马上执行命令了。这样的命令行﹐我们在script中经常看到﹐但您必须知道那代表什么意思。  如果﹐您想对一些变数值进行过滤﹐例如﹕MY_FILE=' ~/tmp/test.sh' ﹐而您想将变数值换成test.sh (也就是将前面的路径去掉)﹐那您可以将$MY_FILE换成${MY_FILE##*/}。这是一个变数值字串过滤﹕##是用来比对变数前端部份﹐然后*/是比对的色样(也就是任何字母到/之间)﹐然后将最长的部份删除掉。您可以参考如下范例﹕
  当FNAME="/home/kenny/tmp/test.1.sh" 的时候﹕

变数名称
代表意思
结果
${FNAME}
显示变数值的全部。  

  /home/kenny/tmp/test.1.sh
  


${FNAME##/*/}
比对变数值开端﹐如果以/*/ 开头的话﹐砍掉最长的部份。  

  test.1.sh
  


${FNAME#/*/}
比对变数值开端﹐如果以/*/ 开头的话﹐砍掉最短的部份。  

  kenny/tmp/test.1.sh
  


${FNAME%.*}
比对变数值末端﹐如果以.* 结尾的话﹐砍掉最短的部份。  

  /home/kenny/tmp/test.1
  


${FNAME%%.*}
比对变数值末端﹐如果以.* 结尾的话﹐砍掉最长的部份。  

  /home/kenny/tmp/test
  


${FNAME/sh/bash}
如果在变数值中找到sh 的话﹐将第一个sh 换成bash。  

  /home/kenny/tmp/test.1.bash
  


${FNAME//sh/bash}
如果在变数值中找到sh 的话﹐将全部sh 换成bash。  

  /home/kenny/tmp/test.1.bash
  

  您除了能够对变数进行过滤之外﹐您也能对变数做出限制﹑和改变其变数值﹕
  

 
字串没设定
空字串
非空字串
使用预设值
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  一开始或许比较难理解上面的两个表格说明的意思﹐真的很混乱~~ 但只要多做一些练习﹐那您就知道怎么使用了。比方说﹕

# 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!
  

  

  

  

  

  

  

  

  

  请记住这些变数的习性﹐日后您要写shell script的时候就不会将变数搞混乱了。假如您想看看当前shell的环境变数有哪些﹐您可以输入set命令﹔如果只想检查export出来的变数﹐可以输入exportenv (前者是shell预设的输出变数)。
  Bash 设定
  到这里﹐您或许会问﹕shell 的环境变数在哪里定义呢﹖ 可以调整吗﹖
  嗯﹐第一个问题我不大了解﹐我猜那是shell 设计者预设定义好的﹐我们一登录获得shell 之后就有了。不过﹐第二个问题﹐我却可以肯定答复您﹕您可以随时调整您的环境变数。您可以在进入shell 之后用在命令行里面重新定义﹐也可以透过一些shell 设定档来设定。
  先让我们看看﹐当您在进行登录的时候﹐系统会检查哪些档案吧﹕


  • /etc/profile﹕首先﹐系统会检查这个档﹐以定义如下这些变数﹕PATH﹑USER﹑LOGNAME﹑MAIL﹑HOSTNAME﹑HISTSIZE﹑INPUTRC。如果您会shell script (我们后面再讨论)﹐那您应该看得出这些变数是如何定义的。另外﹐还指定了umask和ulimit的设定﹕umask大家应该知道了﹐而ulmimit呢﹖ 它是用来限制一个shell做能建立的行程数目﹐以避免系统资源被无限制的消耗。最后﹐它还会检查并执行/etc/profile.d/*.sh那些script﹐有兴趣您可以追踪看看。
  • ~/.bash_profile﹕这里会定义好USERNAME﹑BASH_ENV﹑PATH。其中的PATH除了现有的$PATH之外﹐还会再加入使用者相关的路径﹐您会发现root和普通帐号的路径是不一样的﹔而BASH_ENV呢﹐仔细点看﹐是下一个要检查的档案﹕
  • ~/.bashrc﹕在这个档里面﹐您可以发现一些alias设定(哦~~原来在这里﹗)。然后﹐您会发现有一行﹕ . /etc/bashrc。在shell script中﹐用一个小数点然后然后一个空白键再指向另外一个script﹐意思是同时执行那个script并采用那里的变数设定。
  • /etc/bashrc﹕基本上﹐这里的设定﹐是所有使用者在获得shell的时候都会采用的。这里指定了一些terminal设定﹐以及shell提示字符等等。
  • ~/.bash_login﹕如果~/.bash_profile不存在﹐则使用这个档。
  • ~/.profile﹕如果~/.bash_profile和~/.bash_login都不存在﹐则使用这个档。
  • ~/.bash_logout﹕这个档通常只有一个命令﹕ clear ﹐也就是把荧幕显示的内容清掉。如果您想要在登出shell的时候﹐会执行一些动作﹐例如﹕清空临时档(假如您有使用到临时档)﹑还原某些设定﹑或是执行某些备份之类的。
  您可以透过修改上面提到的档案﹐来调整您进入shell之后的变数值。一般使用者可以修改其家目录( ~/ )中的档案﹐以进行个人化的设定﹔而作为root﹐您可以修改/etc/下面的档案﹐设定大家共用的变数值。至于bash的变数值如何设定﹖ 有哪些变数﹖ 各变数的功能如何﹖ 您打可以执行man bash参考手册资料。

Tips﹕一旦您修改了/etc/profile或~/.bash_profile档案﹐其新设定要在下次登录的时候才生效。如果您不想退出﹐又想使用新设定﹐那可以用source命令来抓取﹕
source ~/.bash_profile
  命令重导向
  好了﹐相信您已经对您的shell有一定的了解了。然后﹐让我们看看shell上面的一些命令功能吧﹐这些技巧都是作为一个系统管理员基本要素。其中之一就是﹕ 命令重导向(command redirection)和命令管线 (command pipe) 。
  在深入讲解这两个技巧之前﹐先让我们了解一下shell 命令的基本概念﹕
  

名称
代号
代表意思
设备
STDIN
0
标准输入
键盘
STDOUT
1
标准输出
荧幕
STDERR
2
标准错误
荧幕  表格中分别是我们在shell 中一个命令的标准I/O (输出与输入)。当我们执行一个命令的时候﹐先读入输入(STDIN)﹐然后进行处理﹐最后将结果进行输出(STDOUT)﹔如果处理过程中遇到错误﹐那么命令也会显示错误(STDERR)。我们可以很容易发现﹕一般的标准输入﹐都是从我们的键盘读取﹔而标准输出和标准错误﹐都从我们的银幕显示​​。
  同时﹐在系统上﹐我们通常用号码来代表各不同的I/O﹕STDIN 是0﹑STDOUT 是1﹑STDERR 是2。
  当您了解各个I/O 的意思和所代表号码之后﹐让我们看比较如下命令的结果﹕

# ls mbox  
mbox
  
# ls mbox 1> file.stdout
  
#
  请小心看第二个命令﹕在命令的后面多了一个1 ﹐而紧接着(没有空白﹗)是一个大于符号(> )﹐然后是另外一个档案名称。但是﹐荧幕上却没有显示命令的执行结果﹐也就是说﹕ STDOUT不见了﹗ 那到底发生什么事情了呢﹖
  呵﹐相信您不会这么快忘记了STDOUT 的代号是1 吧﹗ 没错了﹐因为我们这里将1 用一个> 符号重导到一个档案中了。结果过是﹕我们将标准输出从荧幕改变到档案中﹐所以我们在银幕就看不到STDOUT﹐而原先的STDOUT 结果则保存在大于符号右边的档中了。不信﹐您看看这个档案的内容就知道了﹕

# cat file.stdout  
mbox
  当我们用一个>将命令的STDOUT导向到一个档案的时候﹐如果档案不存在﹐则会建立一个新档﹔如果档案已经存在﹐那么﹐这个档案的内容就换成STDOUT的结果。有时候﹐您或许想保留原有档案的内容﹐而将结果增加在档案末端而已。那您可以多加一个>﹐也就是使用>>就是了。您可以自己玩玩看哦~~﹐通常﹐我们要将一些命令或错误记录下来﹐都用这个方法。

Tips﹕如果您不希望>意外的盖掉一个原有档﹐那您可以执行这个命令﹕
set -o noclobber  不过﹐仍可以用>| 来强迫写入。

  上前面的例子中﹐我们指定了I/O 1 (STDOUT) 进行重导向﹐这也是预设值﹐如果您没有指定代号﹐那么就是进行STDOUT 的重导向﹐所以1> 和> 是一样的﹔1 >> 和>> 也是一样的。但如果您使用了数字﹐那么数字和> 之间一定不能有空白存在。
  好了﹐下面再比较两个命令﹕

# ls no_mbox  
ls: no_mbox: No such file or directory
  
# ls no_mbox 2>> file.stderr
  

  嗯﹐相信不用我多解释了吧﹖ (如果档案不存在﹐>> 和> 都会建立新的。)
  事实上﹐在我们的日常管理中﹐重导向的应用是非常普遍的。我只举下面这个例子就好了﹕
  当我们进行核心编译的时候(我们下一章再介绍)﹐荧幕会飞快的显示出成千上万行信息﹔其中有大部份是STDOUT﹐但也有些是STDERR。除非您的眼睛真的那么厉害﹐否则您很难分辩出哪些是正常信息﹐哪些是错误信息。当您要编译失败﹐尝试找错误的时候﹐如果已经将STDERR 重导出来﹐就非常方便了﹕

# make dep clean bzImage modules 1>/dev/null 2>/tmp/kernel.err &  这里﹐我一共有三个打算﹕(1) 将标准输出送到一个叫nu​​ll 的设备上﹐如果您记性够好﹐我在前面的文章中曾比喻它为黑洞﹕所有东西进去之后都会消失掉。凭我个人的习惯﹐我会觉得编译核心时跑出来的信息﹐如果您不感兴趣的话﹐那都是垃圾﹐所以我将STDOUT 给重导到null 去﹐眼不见为干净﹔ (2) 然后﹐我将STDERR 重导到/tmp/kernel.err 这个档去﹐等命令结束后﹐我就可以到那里看看究竟有部份有问题。有些问题可能​​不是很重要﹐有些则可能需要重新再编核心﹐看您经验啦。(3) 最后﹐我将命令送到background 中执行(呵~~ 相信您还没忘记吧﹗)。因为﹐编译核心都比较花时间﹐所以我将之送到背景去﹐这样我可以继续做其它事情。

Tips﹕这时﹐因为系统太忙了﹐可能反应速度上会比较慢些﹐如果您真的很在意﹐不妨考虑把make的nice level提高。(忘记怎么做了﹖那翻看前一章吧)  前面的例子﹐我们是分开将STDOUT 和STDERR 重导到不同的档案去﹐那么﹐我们能否把两者都重导到同一个档呢﹖ 当然是可以的﹐请比较下面三行﹕

# 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
  我这里告诉您﹕第一行的命令不怎么正确﹐因为这样会造成这两个输出同时在'抢'一个档案﹐写入的顺序很难控制。而第2 行和第3 行的结果都是一样的﹐看您喜欢用哪个格式了。不过﹐要小心的是﹕& 符号后面不能有空白键﹐否则会当成将命令送到背景执行﹐而不是将STDOUT 和STDERR 整合。
  好了﹐前面我们都在谈STDOUT 和STDERR 的重导向﹐那么﹐我们是否能重导STDIN 呢﹖
  当然可以啦~~~
  有些命令﹐当我们执行之后﹐它会停在那里等待键盘的STDIN输入﹐直到遇到EOF (Ctrl+D)标签才会真正结束命令。比方说﹐在同一个系统上﹐如果有多位使用者同时登入的话﹐您可以用write命令向特的使用者送出短讯。而短讯的内容就是键盘敲入的文字﹐这时候命令会进入输入模式﹐您每输入一行并按Enter之后﹐那么讯息就会在另外一端﹐直到您按Ctrl+D键才离开并结束命令。

# write user1  
Hello!
  
It is me... ^_^
  
How ru!
  
(Ctrl+D)
  这样通常都需要花一些时间输入﹐假如对方在写什么东西和查看某些资料的时候﹐就很混乱。这时候﹐您或许可以先将短讯的内容写在一个档案里面﹐例如greeting.msg﹐然后这样输入就可以了﹕

write user1 < greeting.msg  就这样﹐这里我们用小于符号( 符号的﹕

cat > file.tmp  等您按Ctrl+D 之后﹐从键盘输入的STDIN﹐就保存在file.tmp 中了。请想想看为什么会如此﹖ (我在LPI 的考试中碰到过这道题目哦~~~)
  pipe
  查字典﹐pipe 这个英文是水管﹑管道﹑管线的意思。那么﹐它和命令又有什么牵连呢﹖ 简单的说﹐一个命令管线﹐就是将一个命令的STDOUT 作为另一个命令的STDIN 。
  其实﹐这样的例子我们前面已经碰到多次了﹐例如上一章介绍tr 命令的时候﹕

# cat /path/to/old_file | tr -d '\r' > /path/to/new_file  上面这个命令行﹐事实上有两个命令﹕cat 和tr ﹐在这两个命令之间﹐我们用一个“ | ”符号作为这两个命令的管线﹐也就是将cat 命令的STDOUT 作为tr 命令的STDIN ﹔然后﹐tr 命令的STDOUT 用> 重导到另外一个档案去。
  上面只是一个非常简单的例子而已﹐事实上﹐我们可以用多个管线连接多个程式﹐最终获得我们确切想要的结果。比方说﹕我想知道目前有多少人登录在系统上面﹕

# w | tail +3 | wc -l  我们不妨解读一下这个命令行﹕(1) w 命令会显示出当前登录者的资源使用情况﹐并且每一个登录者占一行﹔(2) 再用tail 命令抓取第3 行开始的字行﹔( 3) 然后用wc -l 计算出行数。这样﹐就可以知道当前的登录人数了。
  许多朋友目前都采用拨接ADSL 上网﹐每次连线的IP 都未必一样﹐只要透过简单的命令管线﹐您就可以将当前的IP 抓出来了﹕


  • 我们不妨观察ifconfig ppp0 这个命令的输出结果﹕  

    # 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  

      


  • 不难发现IP 位址所在的句子中有着其它句子所没有的字眼﹕inet addr 。然后﹐我们就可用grep 把&#8203;&#8203;这行抓出来﹕  

    # ifconfig ppp0 | grep &quot;inet addr&quot;           inet addr:211.74.48.254 PtP:211.74.48.1 Mask:255.255.255.255  

      


  • 再来﹐我们先用相同的分隔符号将句子分成数列﹐然后抓出IP 位址所在的那列。  嗯﹐这里﹐我们可以用“ : ”来分出4 列﹔也可以用空白键来分出5 列(空因为句子开首就是一个空白键)。如果用空白键来分的话﹐由于有些间隔有多个空白键的原因﹐那么﹐我们可以用tr 命令﹐将多个空白键集合成一个空白键﹕

      

    # ifconfig ppp0 | grep &quot;inet addr&quot; | tr -s ' ' ' '  inet addr:211.74.48.254 PtP:211.74.48.1 Mask:255.255.255.255  

      


    (注意﹕在' ' 之间是一个空白键﹗)
  • 然后用cut 命令抓出IP 所在的列﹐细心数一数﹐应该是第3 列﹕  

    # ifconfig ppp0 | grep &quot;inet addr&quot; | tr -s ' ' ' ' | cut -d ' ' -f3 addr:211.74.48.254  

      


  • 然后我们用“ : ”再分两列﹐抓第2 列就是IP 了﹕  

    # ifconfig ppp0 | grep &quot;inet addr&quot; | tr -s ' ' ' ' \ | cut -d ' ' -f3 | cut -d ':' -f2 211.74.48.254  

      


  这里﹐我们一共用5 个pipe 将4 个命令连接起来﹐就抓出机器当前的IP 位址了。是否很好用呢﹖
  在同一个命令行里面出现多个命令的情形﹐除了“ | ”之外﹐或许您会看到&quot; ` ` &quot; 符号﹐也就是和~ 键同一个键的符号(不用按Shift )。它必须是一对使用的﹐其中可以包括单一命令﹐或命令管线。那它的效果和命令管线又有什么分别呢﹖
  我们使用pipe 将一个命令的STDOUT 传给下一个命令的STDIN﹐但使用`` 的时候﹐它所产生的STDOUT 或STDERR 仅作为命令行中的一个参数而已。嗯﹐不如看看下面命令好了﹕

# TODAY=`date +%D`  
# echo Today is $TODAY.
  
Today is 08/17/01.
  从结果我们可以看出﹐我们用`` 将date 这个命令括起来(可含参数)﹐那么它的执行结果可以作为TODAY 的变数值。我们甚至还可以将一串命令管线直接用在命令行上面﹕

# echo My IP is `ifconfig ppp0 | grep &quot;inet addr&quot; \   
    | tr -s ' ' ' ' | cut -d ' ' -f3 | cut -d ':' -f2`
  
My IP is 211.74.48.254.
  注意﹕第一行的CR 被\ 跳脱了﹐所以这个命令行'看起来'有两行。我之所以弄这么复杂﹐是告诉您这对`` 符号可以适用的范围。

Tips﹕在变数中使用``可以将命令的执行结果当成变数值的部份。事实上﹐除了用``之外﹐您也可以用这样的格式﹕ VAR_NAME=$(command) ﹐那是和VAR_NAME=`command`的结果是一样的。  除了这对`` 和| 之外﹐还有另外一个符号“ ; ”来分隔命令的。不过﹐这个比较简单﹕就是当第一命令结束之后﹐再执行第二个命令﹐如此类推﹕

# ./configure; make; make install  呵~&#8203;&#8203;~ 如果您对您的安装程式有绝对信心﹐用上面一行命令就够了﹗
  Shell Script
  当我们对shell 变数和命令行有一定认识之后﹐那么﹐我们就可以尝试写自己的shell script 啰~~ 这可是非常好玩而又有成就感的事情呢﹗ ^_^
  在linux 里面的shell script 可真是无处不在﹕我们开机执行的run level 基本上都是一些script ﹔登录之后的环境设定﹐也是些script ﹔甚至工作排程和记录维护也都是script 。您不妨随便到/etc/rc.d/init.d 里面抓两个程式回来看看﹐不难发现它们都有一个共同的之处﹕第一行一定是如下这样的﹕

  

#!/bin/sh 或﹕ #!/bin/bash  

  

  其实﹐这里的#! 后面要定义的就是命令的解释器(command interpreter)﹐如果是/bin/bash 的话﹐那下面的句子就都用bash 来解释﹔如果是/usr/bin/perl 的话﹐那就用perl 来解释。不同的解释器所使用的句子语法都不一样﹐非常严格﹐就算同是用shell 来解释﹐不同的shell 之间的格式也不仅相同。所以﹐如果您看到script 的解释器是/bin/sh 的话﹐那就要小心了﹕如果您仔细看这个档案﹐事实上它仅是一个link 而已﹐有些系统或许会将它link 到其它shell 去。假如您的script 句子使用的语法是bash 的话﹐而这个sh 却link 到csh ﹐那执行起来可能会有问题。所以﹐最好还是直接指定shell 的路径比较安全一些﹕在这里的范例都使用/bin/bash 来作为script 的解释器。
  在真正开始写script 之前﹐先让我们认识script 的一些基本概念﹕
  简单来说﹐shell script 里面就是一连串命令行而已﹐再加上条件判断﹑流程控制﹑回圈﹑等技巧﹐聪明地执行正确的命令和使用正确的参数选项。和我们在shell 里面输入命令一样﹐shell script 也有这样的特性﹕


  • 当读到一个CR 字符的时候﹐就尝试执行该行命令﹔
  • 它会忽略空白行﹔句子前面的空白和tab 也不理会﹔
  • CR 字符也同样可以用“ \ ”符号跳脱﹔
  • 另外﹐“ # ”符号是注解符号﹐从这个符号至句子末端的内容全被忽略﹐程式本身不会读入这部份﹐但我们经常用来给使用者阅读﹐因而名为注解﹔
  • 等等。
  一个良好的script 作者﹐在程式开头的时候﹐都会用注解说明script 的名称﹑用途﹑作者﹑日期﹑版本﹑等信息。如果您有这个机会写自己的script﹐也应该有这个良好习惯。
  shell script档的命名没一定规则﹐可以使用任何档案名称(参考档案系统)﹐但如果您喜欢的话﹐可以用.sh来做它的副档名﹐不过这不是硬性规定的。不过﹐要执行一个shell script﹐使用者必须对它有执行权限( x )﹐用文件编辑器新建立的档案都是没有x permission的﹐请用chmod命令加上。执行的时候﹐除非该script已经至于PATH环境变数之内的路径内﹐否则您必须指定路径。例如﹐您写了一个叫test.sh的shell script﹐放在家目录内﹐假设这也是您的当前工作目录﹐您必须加上路径才能执行﹕./test.sh或~/test.sh 。所以﹐建议您在script测试无误之后﹐放在~/bin目录里面﹐那就可以在任何地方执行自己的script了﹐当然﹐您要确定~/bin已经出现在您的PATH变数里面。
  script之所以聪明﹐在于它能够对一些条件进行测试( test )。您可以直接用test命令﹐也可以用if叙述﹐例如﹕test -f ~/test.sh 。它的意思是测试一下~/test.sh这个档案是否存在﹐这个-f通常用在档案上面的测试﹐除了它﹐还有很多﹕
  

标签
代表意思
-G
存在﹐并且由GID 所执行的行程所拥有。
-L
存在﹐并且是symbolic link 。
-O
存在﹐并且由UID 所执行的行程所拥有。
-S
存在﹐并且是一个socke 。
-b
存在﹐并且是block 档案﹐例如磁碟等。
-c
存在﹐并且是character 档案﹐例如终端或磁带机。
-d
存在﹐并且是一个目录。
-e
存在。
-f
存在﹐并且是一个档案。
-g
存在﹐并且有SGID 属性。
-k
存在﹐并且有sticky bit 属性。
-p
存在﹐并且是用于行程间传送资讯的name pipe 或是FIFO。
-r
存在﹐并且是可读的。
-s
存在﹐并且体积大于0 (非空档)。
-u
存在﹐并且有SUID 属性。
-w
存在﹐并且可写入。
-x
存在﹐并且可执行。  事实上﹐关于这些测试项目还有很多很多﹐您可以man bash 然后参考CONDITIONAL EXPRESSIONS 那部份。另外﹐我们还可以同时对两个档案进行测试﹐例如﹕test file1 -nt file2 就是测试file1 是否比file2 要新。这种测试使用的标签是﹕
  

标签
代表意思
-nt
Newer Than﹕第一个档案比第二个档案要新。
-ot
Older Than﹕第一个档案比第二个档案要旧。
-ef
Equal File﹕第一个档案和第二个档案其实都是同一个档案(如link)。  我们这里所说的这些测试﹐不单只用来测试档案﹐而且还常会用来比对' 字串(string)'或数字(整数)。那什么是字串呢﹖ 字面来介绍就是一串文字嘛。在一个测试中﹐~/test.sh本身是一个档案﹔但'~/test.sh' ﹐则是在引号里面(单引号或双引号)﹐那就是字串了。
  在数字和字串上面的比对(或测试)﹐所使用的标签大约有﹕
  

标签
代表意思
=
等于
!=
不等于
<
小于
>
大于
-eq
等于
-ne
不等于
-lt
小于
-gt
大于
-le
小于或等于
-ge
大于或等于
-a
双方都成立
-o
单方成立
-z
空字串
-n
非空字串  在上面提到的比对中﹐虽然有些意思一样﹐但使用场合却不尽相同。例如= 和-eq 都是'等于'的意思﹐但= 只能比对字串﹐而-eq 则可以用来比对字串﹐也能用来比对表示色样(我们在regular expression 会碰到)。
  后续!!!!



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-555901-1-1.html 上篇帖子: shell批量 下篇帖子: 安装zabbix agent shell 脚本
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表