实习第一周(第三天)(linux如何查看变量,read命令,通配符, 撷取命令: cut, grep,sort,wc,uniq等等)

用 env观察环境变量与常见环境变量说明。

        env 是 environment (环境) 的简写啊,上面的例子当中,是列出来所有的环境变量。当然,如果使用 export 也会是一样的内容~只不过, export 还有其他额外的功能就是了,我们等一下再提这个 export 命令。


  • 用 set 观察所有变量 (含环境变量与自定义变量)

        bash 可不只有环境变量喔,还有一些与 bash 操作接口有关的变量,以及用户自己定义的变量存在的。那么这些变量如何观察呢?这个时候就得要使用 set 这个命令了。 set 除了环境变量之外,还会将其他在 bash 内的变量通通显示出来哩!

       一般来说,不论是否为环境变量,只要跟我们目前这个 shell 的操作接口有关的变量,通常都会被配置为大写字符,也就是说,『基本上,在 Linux 默认的情况中,使用{大写的字母}来配置的变量一般为系统内定需要的变量』。OK!OK!那么上头那些变量当中,有哪些是比较重要的?大概有这几个吧!

       PS1:(提示字符的配置)

       这是 PS1 (数字的 1 不是英文字母),这个东西就是我们的『命令提示字符』喔!当我们每次按下 [Enter] 按键去运行某个命令后,最后要再次出现提示字符时,就会主动去读取这个变量值了。上头 PS1 内显示的是一些特殊符号,这些特殊符号可以显示不同的信息,每个 distributions 的 bash 默认的 PS1 变量内容可能有些许的差异,不要紧,『习惯你自己的习惯』就好了。你可以用 man bash (注3)去查询一下 PS1 的相关说明,以理解底下的一些符号意义。

  • \d :可显示出『星期 月 日』的日期格式,如:"Mon Feb 2"
  • \H :完整的主机名。举例来说,鸟哥的练习机为『www.vbird.tsai』
  • \h :仅取主机名在第一个小数点之前的名字,如鸟哥主机则为『www』后面省略
  • \t :显示时间,为 24 小时格式的『HH:MM:SS』
  • \T :显示时间,为 12 小时格式的『HH:MM:SS』
  • \A :显示时间,为 24 小时格式的『HH:MM』
  • \@ :显示时间,为 12 小时格式的『am/pm』样式
  • \u :目前使用者的账号名称,如『root』;
  • \v :BASH 的版本信息,如鸟哥的测试主板本为 3.2.25(1),仅取『3.2』显示
  • \w :完整的工作目录名称,由根目录写起的目录名称。但家目录会以 ~ 取代;
  • \W :利用 basename 函数取得工作目录名称,所以仅会列出最后一个目录名。
  • \# :下达的第几个命令。
  • \$ :提示字符,如果是 root 时,提示字符为 # ,否则就是 $ 啰~

        好了,让我们来看看 CentOS 默认的 PS1 内容吧:『[\u@\h \W]\$ 』,现在你知道那些反斜杠后的数据意义了吧?要注意喔!那个反斜杠后的数据为 PS1 的特殊功能,与 bash 的变量配置没关系啦!不要搞混了喔!那你现在知道为何你的命令提示字符是:『 [root@www ~]# 』了吧?好了,那么假设我想要有类似底下的提示字符:
[root@www /home/dmtsai 16:50 #12]#
那个 # 代表第 12 次下达的命令。那么应该如何配置 PS1 呢?可以这样啊:
[root@www ~ ]# cd /home
[root@www home]# PS1='[\u@\h \w \A #\#]\$ '
[root@www /home 17:02 #85]# 
# 看到了吗?提示字符变了!变的很有趣吧!其中,那个 #85 比较有趣,
# 如果您再随便输入几次 ls 后,该数字就会添加喔!为啥?上面有说明滴! 

        
      $
:(关于本 shell 的 PID)

钱字号本身也是个变量喔!这个咚咚代表的是『目前这个 Shell 的线程代号』,亦即是所谓的 PID (Process ID)。更多的程序观念,我们会在第四篇的时候提及。想要知道我们的 shell 的 PID ,就可以用:『echo $$ 』即可!出现的数字就是你的 PID 号码。


        ?:(关于上个运行命令的回传值)

什么?问号也是一个特殊的变量?没错!在 bash 里面这个变量可重要的很!这个变量是:『上一个运行的命令所回传的值』,上面这句话的重点是『上一个命令』与『回传值』两个地方。当我们运行某些命令时,这些命令都会回传一个运行后的代码。一般来说,如果成功的运行该命令,则会回传一个 0 值,如果运行过程发生错误,就会回传『错误代码』才对!一般就是以非为 0 的数值来取代。我们以底下的例子来看看:

[root@www ~]# echo $SHELL
/bin/bash                                  <==可顺利显示!没有错误!
[root@www ~]# echo $?
0                                          <==因为没问题,所以回传值为 0
[root@www ~]# 12name=VBird
-bash: 12name=VBird: command not found     <==发生错误了!bash回报有问题
[root@www ~]# echo $?
127                                        <==因为有问题,回传错误代码(非为0)
# 错误代码回传值依据软件而有不同,我们可以利用这个代码来搜寻错误的原因喔!
[root@www ~]# echo $?
0
# 咦!怎么又变成正确了?这是因为 "?" 只与『上一个运行命令』有关,
# 所以,我们上一个命令是运行『 echo $? 』,当然没有错误,所以是 0 没错! 

  • export: 自定义变量转成环境变量

谈了 env 与 set 现在知道有所谓的环境变量与自定义变量,那么这两者之间有啥差异呢?其实这两者的差异在于『该变量是否会被子程序所继续引用』啦!唔!那么啥是父程序?子程序?这就得要了解一下命令的下达行为了。

当你登陆 Linux 并取得一个 bash 之后,你的 bash 就是一个独立的程序,被称为 PID 的就是。接下来你在这个 bash 底下所下达的任何命令都是由这个 bash 所衍生出来的,那些被下达的命令就被称为子程序了。我们可以用底下的图示来简单的说明一下父程序与子程序的概念:

         我们在原本的 bash 底下运行另一个 bash ,结果操作的环境接口会跑到第二个 bash 去(就是子程序),那原本的 bash 就会在暂停的情况 (睡着了,就是 sleep)。整个命令运行的环境是实线的部分!若要回到原本的 bash 去,就只有将第二个 bash 结束掉 (下达 exit 或 logout) 才行。更多的程序概念我们会在第四篇谈及,这里只要有这个概念即可。

这个程序概念与变量有啥关系啊?关系可大了!因为子程序仅会继承父程序的环境变量,子程序不会继承父程序的自定义变量啦!所以你在原本 bash 的自定义变量在进入了子程序后就会消失不见,一直到你离开子程序并回到原本的父程序后,这个变量才会又出现!

        换个角度来想,也就是说,如果我能将自定义变量变成环境变量的话,那不就可以让该变量值继续存在于子程序了?呵呵!没错!此时,那个 export 命令就很有用啦!如你想要让该变量内容继续的在子程序中使用,那么就请运行:

[root@www ~]# export 变量名称
        那如何将环境变量转成自定义变量呢?可以使用本章后续介绍的 declare 呢!

      

        在学理方面,为什么环境变量的数据可以被子程序所引用呢?这是因为内存配置的关系!理论上是这样的:

  • 当启动一个 shell,操作系统会分配一记忆区块给 shell 使用,此内存内之变量可让子程序取用
  • 若在父程序利用 export 功能,可以让自定义变量的内容写到上述的记忆区块当中(环境变量);
  • 当加载另一个 shell 时 (亦即启动子程序,而离开原本的父程序了),子 shell 可以将父 shell 的环境变量所在的记忆区块导入自己的环境变量区块当中。

       

        变量键盘读取、数组与宣告: read, array, declare
我们上面提到的变量配置功能,都是由命令列直接配置的,那么,可不可以让用户能够经由键盘输入?什么意思呢?是否记得某些程序运行的过程当中,会等待使用者输入 "yes/no" 之类的信息啊?在 bash 里面也有相对应的功能喔!此外,我们还可以宣告这个变量的属性,例如:数组或者是数字等等的。底下就来看看吧!

  • read

要读取来自键盘输入的变量,就是用 read 这个命令了。这个命令最常被用在 shell script 的撰写当中,想要跟使用者对谈?用这个命令就对了。关于 script 的写法,我们会在第十三章介绍,底下先来瞧一瞧 read 的相关语法吧!

[root@www ~]# read [-pt] variable
选项与参数:
-p  :后面可以接提示字符!
-t  :后面可以接等待的『秒数!』这个比较有趣~不会一直等待使用者啦!

范例一:让用户由键盘输入一内容,将该内容变成名为 atest 的变量
[root@www ~]# read atest
This is a test        <==此时光标会等待你输入!请输入左侧文字看看
[root@www ~]# echo $atest
This is a test          <==你刚刚输入的数据已经变成一个变量内容!

范例二:提示使用者 30 秒内输入自己的大名,将该输入字符串作为名为 named 的变量内容
[root@www ~]# read -p "Please keyin your name: " -t 30 named
Please keyin your name: VBird Tsai   <==注意看,会有提示字符喔!
[root@www ~]# echo $named
VBird Tsai        <==输入的数据又变成一个变量的内容了!
read 之后不加任何参数,直接加上变量名称,那么底下就会主动出现一个空白行等待你的输入(如范例一)。如果加上 -t 后面接秒数,例如上面的范例二,那么 30 秒之内没有任何动作时,该命令就会自动略过了~如果是加上 -p ,嘿嘿!在输入的光标前就会有比较多可以用的提示字符给我们参考!在命令的下达里面,比较美观啦! ^_^
  • declare / typeset

declare 或 typeset 是一样的功能,就是在『宣告变量的类型』。如果使用 declare 后面并没有接任何参数,那么 bash 就会主动的将所有的变量名称与内容通通叫出来,就好像使用 set 一样啦!那么 declare 还有什么语法呢?看看先:

[root@www ~]# declare [-aixr] variable
选项与参数:
-a  :将后面名为 variable 的变量定义成为数组 (array) 类型
-i  :将后面名为 variable 的变量定义成为整数数字 (integer) 类型
-x  :用法与 export 一样,就是将后面的 variable 变成环境变量;
-r  :将变量配置成为 readonly 类型,该变量不可被更改内容,也不能 unset

范例一:让变量 sum 进行 100+300+50 的加总结果
[root@www ~]# sum=100+300+50
[root@www ~]# echo $sum
100+300+50  <==咦!怎么没有帮我计算加总?因为这是文字型态的变量属性啊!
[root@www ~]# declare -i sum=100+300+50
[root@www ~]# echo $sum
450         <==瞭乎??

由于在默认的情况底下, bash 对于变量有几个基本的定义:

  • 变量类型默认为『字符串』,所以若不指定变量类型,则 1+2 为一个『字符串』而不是『计算式』。所以上述第一个运行的结果才会出现那个情况的;
  • bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0;

现在你晓得为啥你需要进行变量宣告了吧?如果需要非字符串类型的变量,那就得要进行变量的宣告才行啦!底下继续来玩些其他的 declare 功能。

范例二:将 sum 变成环境变量
[root@www ~]# declare -x sum
[root@www ~]# export | grep sum
declare -ix sum="450"  <==果然出现了!包括有 i 与 x 的宣告!

范例三:让 sum 变成只读属性,不可更动!
[root@www ~]# declare -r sum
[root@www ~]# sum=tesgting
-bash: sum: readonly variable  <==老天爷~不能改这个变量了!

范例四:让 sum 变成非环境变量的自定义变量吧!
[root@www ~]# declare +x sum  <== 将 - 变成 + 可以进行『取消』动作
[root@www ~]# declare -p sum  <== -p 可以单独列出变量的类型
declare -ir sum="450" <== 看吧!只剩下 i, r 的类型,不具有 x 啰!
declare 也是个很有用的功能~尤其是当我们需要使用到底下的数组功能时,他也可以帮我们宣告数组的属性喔!不过,老话一句,数组也是在 shell script 比较常用的啦!比较有趣的是,如果你不小心将变量配置为『只读』,通常得要注销再登陆才能复原该变量的类型了! @_@


  • 数组 (array) 变量类型

某些时候,我们必须使用数组来宣告一些变量,这有什么好处啊?在一般人的使用上,果然是看不出来有什么好处的!不过,如果您曾经写过程序的话,那才会比较了解数组的意义~数组对写数值程序的设计师来说,可是不能错过学习的重点之一哩!好!不啰唆~那么要如何配置数组的变量与内容呢?在 bash 里头,数组的配置方式是:

var[index]=content

意思是说,我有一个数组名为 var ,而这个数组的内容为 var[1]=小明, var[2]=大明, var[3]=好明 .... 等等,那个 index 就是一些数字啦,重点是用中刮号 ([ ]) 来配置的。目前我们 bash 提供的是一维数组。老实说,如果您不必写一些复杂的程序,那么这个数组的地方,可以先略过,等到有需要再来学习即可!因为要制作出数组,通常与循环或者其他判断式交互使用才有比较高的存在意义!

范例:配置上面提到的 var[1] ~ var[3] 的变量。
[root@www ~]# var[1]="small min"
[root@www ~]# var[2]="big min"
[root@www ~]# var[3]="nice min"
[root@www ~]# echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min

数组的变量类型比较有趣的地方在于『读取』,一般来说,建议直接以 ${数组}的方式来读取,比较正确无误的啦!

      
      与文件系统及程序的限制关系:ulimit

想象一个状况:我的 Linux 主机里面同时登陆了十个人,这十个人不知怎么搞的,同时开启了 100 个文件,每个文件的大小约 10MBytes ,请问一下,我的 Linux 主机的内存要有多大才够? 10*100*10 = 10000 MBytes = 10GBytes ...老天爷,这样,系统不挂点才有鬼哩!为了要预防这个情况的发生,所以我们的 bash 是可以『限制用户的某些系统资源』的,包括可以开启的文件数量,可以使用的 CPU 时间,可以使用的内存总量等等。如何配置?用 ulimit 吧!

此处不列举出它具体的用法了,如果要用到的时候请百度~
  • 变量内容的删除与取代

变量的内容可以很简单的透过几个咚咚来进行删除喔!我们使用 PATH 这个变量的内容来做测试好了。请你依序进行底下的几个例子来玩玩,比较容易感受的到鸟哥在这里想要表达的意义:

范例一:先让小写的 path 自定义变量配置的与 PATH 内容相同
[root@www ~]# path=${PATH}
[root@www ~]# echo $path
/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:
/usr/sbin:/usr/bin:/root/bin  <==这两行其实是同一行啦!

范例二:假设我不喜欢 kerberos,所以要将前两个目录删除掉,如何显示?
[root@www ~]# echo ${path#/*kerberos/bin:}
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

上面这个范例很有趣的!他的重点可以用底下这张表格来说明:

${variable#/*kerberos/bin:}
   上面的特殊字体部分是关键词!用在这种删除模式所必须存在的

${variable#/*kerberos/bin:}
   这就是原本的变量名称,以上面范例二来说,这里就填写 path 这个『变量名称』啦!

${variable#/*kerberos/bin:}
   这是重点!代表『从变量内容的最前面开始向右删除』,且仅删除最短的那个

${variable#/*kerberos/bin:}
   代表要被删除的部分,由于 # 代表由前面开始删除,所以这里便由开始的 / 写起。
   需要注意的是,我们还可以透过通配符 * 来取代 0 到无穷多个任意字符

   以上面范例二的结果来看, path 这个变量被删除的内容如下所示:
/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:
/usr/sbin:/usr/bin:/root/bin  <==这两行其实是同一行啦!

我们将这部份作个总结说明一下:

变量配置方式说明
${变量#关键词}
${
变量##关键词}
若变量内容从头开始的数据符合『关键词』,则将符合的最短数据删除
若变量内容从头开始的数据符合『关键词』,则将符合的最长数据删除
${变量%关键词}
${
变量%%关键词}
若变量内容从尾向前的数据符合『关键词』,则将符合的最短数据删除
若变量内容从尾向前的数据符合『关键词』,则将符合的最长数据删除
${变量/旧字符串/新字符串}
${
变量//旧字符串/新字符串}
若变量内容符合『旧字符串』则『第一个旧字符串会被新字符串取代』
若变量内容符合『旧字符串』则『全部的旧字符串会被新字符串取代』

  • 变量的测试与内容替换

在某些时刻我们常常需要『判断』某个变量是否存在,若变量存在则使用既有的配置,若变量不存在则给予一个常用的配置。我们举底下的例子来说明好了,看看能不能较容易被你所理解呢!

变量配置方式str 没有配置str 为空字符串str 已配置非为空字符串
var=${str-expr}var=exprvar=var=$str
var=${str:-expr}var=exprvar=exprvar=$str
var=${str+expr}var=var=exprvar=expr
var=${str:+expr}var=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 输出至 stderrvar=var=$str
var=${str:?expr}expr 输出至 stderrexpr 输出至 stderrvar=$str

通配符与特殊符号

在 bash 的操作环境中还有一个非常有用的功能,那就是通配符 (wildcard) !我们利用 bash 处理数据就更方便了!底下我们列出一些常用的通配符喔:

符号意义
*代表『 0 个到无穷多个』任意字符
?代表『一定有一个』任意字符
[ ]同样代表『一定有一个在括号内』的字符(非任意字符)。例如 [abcd] 代表『一定有一个字符,可能是 a, b, c, d 这四个任何一个』
[ - ]若有减号在中括号内时,代表『在编码顺序内的所有字符』。例如 [0-9] 代表0 到 9 之间的所有数字,因为数字的语系编码是连续的!
[^ ]若中括号内的第一个字符为指数符号 (^) ,那表示『反向选择』,例如 [^abc] 代表一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。

接下来让我们利用通配符来玩些东西吧!首先,利用通配符配合 ls 找檔名看看:

[root@www ~]# LANG=C              <==由于与编码有关,先配置语系一下

范例一:找出 /etc/ 底下以 cron 为开头的档名
[root@www ~]# ll -d /etc/cron*    <==加上 -d 是为了仅显示目录而已

范例二:找出 /etc/ 底下文件名『刚好是五个字母』的文件名
[root@www ~]# ll -d /etc/?????    <==由于 ? 一定有一个,所以五个 ? 就对了

范例三:找出 /etc/ 底下文件名含有数字的文件名
[root@www ~]# ll -d /etc/*[0-9]*  <==记得中括号左右两边均需 *

范例四:找出 /etc/ 底下,档名开头非为小写字母的文件名:
[root@www ~]# ll -d /etc/[^a-z]*  <==注意中括号左边没有 *

范例五:将范例四找到的文件复制到 /tmp 中
[root@www ~]# cp -a /etc/[^a-z]* /tmp

除了通配符之外,bash 环境中的特殊符号有哪些呢?底下我们先汇整一下:

符号内容
#批注符号:这个最常被使用在 script 当中,视为说明!在后的数据均不运行
\跳脱符号:将『特殊字符或通配符』还原成一般字符
|管线 (pipe):分隔两个管线命令的界定(后两节介绍);
;连续命令下达分隔符:连续性命令的界定 (注意!与管线命令并不相同)
~用户的家目录
$取用变量前导符:亦即是变量之前需要加的变量取代值
&工作控制 (job control):将命令变成背景下工作
!逻辑运算意义上的『非』 not 的意思!
/目录符号:路径分隔的符号
>, >>数据流重导向:输出导向,分别是『取代』与『累加』
<, <<数据流重导向:输入导向 (这两个留待下节介绍)
' '单引号,不具有变量置换的功能
" "具有变量置换的功能!
` `两个『 ` 』中间为可以先运行的命令,亦可使用 $( )
( )在中间为子 shell 的起始与结束
{ }在中间为命令区块的组合!

以上为 bash 环境中常见的特殊符号汇整!理论上,你的『档名』尽量不要使用到上述的字符啦!

        撷取命令: cut, grep
什么是撷取命令啊?说穿了,就是将一段数据经过分析后,取出我们所想要的。或者是经由分析关键词,取得我们所想要的那一行!不过,要注意的是,一般来说,撷取信息通常是针对『一行一行』来分析的,并不是整篇信息分析的喔~底下我们介绍两个很常用的信息撷取命令:

  • cut

cut 不就是『切』吗?没错啦!这个命令可以将一段信息的某一段给他『切』出来~处理的信息是以『行』为单位喔!底下我们就来谈一谈

[root@www ~]# cut -d'分隔字符' -f fields <==用于有特定分隔字符
[root@www ~]# cut -c 字符区间            <==用于排列整齐的信息
选项与参数:
-d  :后面接分隔字符。与 -f 一起使用;
-f  :依据 -d 的分隔字符将一段信息分割成为数段,用 -f 取出第几段的意思;
-c  :以字符 (characters) 的单位取出固定字符区间;

范例一:将 PATH 变量取出,我要找出第五个路径。
[root@www ~]# echo $PATH
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:
# 1 |    2   |  3  |    4    |       5      |     6        |    7

[root@www ~]# echo $PATH | cut -d ':' -f 5
# 如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /usr/local/bin 
# 那么如果想要列出第 3 与第 5 呢?,就是这样:
[root@www ~]# echo $PATH | cut -d ':' -f 3,5

范例二:将 export 输出的信息,取得第 12 字符以后的所有字符串
[root@www ~]# export
declare -x HISTSIZE="1000"
declare -x INPUTRC="/etc/inputrc"
declare -x KDEDIR="/usr"
declare -x LANG="zh_TW.big5"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要『 declare -x 』时,
# 就得这么做:

[root@www ~]# export | cut -c 12-
HISTSIZE="1000"
INPUTRC="/etc/inputrc"
KDEDIR="/usr"
LANG="zh_TW.big5"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!

范例三:用 last 将显示的登陆者的信息中,仅留下用户大名
[root@www ~]# last
root   pts/1    192.168.201.101  Sat Feb  7 12:35   still logged in
root   pts/1    192.168.201.101  Fri Feb  6 12:13 - 18:46  (06:33)
root   pts/1    192.168.201.254  Thu Feb  5 22:37 - 23:53  (01:16)
# last 可以输出『账号/终端机/来源/日期时间』的数据,并且是排列整齐的

[root@www ~]# last | cut -d ' ' -f 1
# 由输出的结果我们可以发现第一个空白分隔的字段代表账号,所以使用如上命令:
# 但是因为 root   pts/1 之间空格有好几个,并非仅有一个,所以,如果要找出 
# pts/1 其实不能以 cut -d ' ' -f 1,2 喔!输出的结果会不是我们想要的。
cut 主要的用途在于将『同一行里面的数据进行分解!』最常使用在分析一些数据或文字数据的时候!这是因为有时候我们会以某些字符当作分割的参数,然后来将数据加以切割,以取得我们所需要的数据。鸟哥也很常使用这个功能呢!尤其是在分析 log 文件的时候!不过,cut 在处理多空格相连的数据时,可能会比较吃力一点。


  • grep

刚刚的 cut 是将一行信息当中,取出某部分我们想要的,而 grep 则是分析一行信息,若当中有我们所需要的信息,就将该行拿出来~简单的语法是这样的:

[root@www ~]# grep [-acinv] [--color=auto] '搜寻字符串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字符串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行!
--color=auto :可以将找到的关键词部分加上颜色的显示喔!

范例一:将 last 当中,有出现 root 的那一行就取出来;
[root@www ~]# last | grep 'root'

范例二:与范例一相反,只要没有 root 的就取出!
[root@www ~]# last | grep -v 'root'

范例三:在 last 的输出信息中,只要有 root 就取出,并且仅取第一栏
[root@www ~]# last | grep 'root' |cut -d ' ' -f1
# 在取出 root 之后,利用上个命令 cut 的处理,就能够仅取得第一栏啰!

范例四:取出 /etc/man.config 内含 MANPATH 的那几行
[root@www ~]# grep --color=auto 'MANPATH' /etc/man.config
....(前面省略)....
MANPATH_MAP     /usr/X11R6/bin          /usr/X11R6/man
MANPATH_MAP     /usr/bin/X11            /usr/X11R6/man
MANPATH_MAP     /usr/bin/mh             /usr/share/man
# 神奇的是,如果加上 --color=auto 的选项,找到的关键词部分会用特殊颜色显示喔!

grep 是个很棒的命令喔!他支持的语法实在是太多了~用在正规表示法里头,能够处理的数据实在是多的很~不过,我们这里先不谈正规表示法~下一章再来说明~您先了解一下, grep 可以解析一行文字,取得关键词,若该行有存在关键词,就会整行列出来!


        排序命令: sort, wc, uniq
很多时候,我们都会去计算一次数据里头的相同型态的数据总数,举例来说,使用 last 可以查得这个月份有登陆主机者的身份。那么我可以针对每个使用者查出他们的总登陆次数吗?此时就得要排序与计算之类的命令来辅助了!底下我们介绍几个好用的排序与统计命令喔!

       
  • sort

sort 是很有趣的命令,他可以帮我们进行排序,而且可以依据不同的数据型态来排序喔!例如数字与文字的排序就不一样。此外,排序的字符与语系的编码有关,因此,如果您需要排序时,建议使用 LANG=C 来让语系统一,数据排序比较好一些。

[root@www ~]# sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f  :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b  :忽略最前面的空格符部分;
-M  :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n  :使用『纯数字』进行排序(默认是以文字型态来排序的);
-r  :反向排序;
-u  :就是 uniq ,相同的数据中,仅出现一行代表;
-t  :分隔符,默认是用 [tab] 键来分隔;
-k  :以那个区间 (field) 来进行排序的意思

范例一:个人账号都记录在 /etc/passwd 下,请将账号进行排序。
[root@www ~]# cat /etc/passwd | sort
adm:x:3:4:adm:/var/adm:/sbin/nologin
apache:x:48:48:Apache:/var/www:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
# 鸟哥省略很多的输出~由上面的数据看起来, sort 是默认『以第一个』数据来排序,
# 而且默认是以『文字』型态来排序的喔!所以由 a 开始排到最后啰!

范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该如何?
[root@www ~]# cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
# 看到特殊字体的输出部分了吧?怎么会这样排列啊?呵呵!没错啦~
# 如果是以文字型态来排序的话,原本就会是这样,想要使用数字排序:
# cat /etc/passwd | sort -t ':' -k 3 -n
# 这样才行啊!用那个 -n 来告知 sort 以数字来排序啊!

范例三:利用 last ,将输出的数据仅取账号,并加以排序
[root@www ~]# last | cut -d ' ' -f1 | sort
sort 同样是很常用的命令呢!因为我们常常需要比较一些信息啦!举个上面的第二个例子来说好了!今天假设你有很多的账号,而且你想要知道最大的使用者 ID 目前到哪一号了!呵呵!使用 sort 一下子就可以知道答案咯!当然其使用还不止此啦!有空的话不妨玩一玩!

  • uniq

如果我排序完成了,想要将重复的数据仅列出一个显示,可以怎么做呢?

[root@www ~]# uniq [-ic]
选项与参数:
-i  :忽略大小写字符的不同;
-c  :进行计数

范例一:使用 last 将账号列出,仅取出账号栏,进行排序后仅取出一位;
[root@www ~]# last | cut -d ' ' -f1 | sort | uniq

范例二:承上题,如果我还想要知道每个人的登陆总次数呢?
[root@www ~]# last | cut -d ' ' -f1 | sort | uniq -c
      1
     12 reboot
     41 root
      1 wtmp
# 从上面的结果可以发现 reboot 有 12 次, root 登陆则有 41 次!
# wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略的!
这个命令用来将『重复的行删除掉只显示一个』,举个例子来说,你要知道这个月份登陆你主机的用户有谁,而不在乎他的登陆次数,那么就使用上面的范例,(1)先将所有的数据列出;(2)再将人名独立出来;(3)经过排序;(4)只显示一个!由于这个命令是在将重复的东西减少,所以当然需要『配合排序过的文件』来处理啰!
  • wc

如果我想要知道 /etc/man.config 这个文件里面有多少字?多少行?多少字符的话,可以怎么做呢?其实可以利用 wc 这个命令来达成喔!他可以帮我们计算输出的信息的整体数据!

[root@www ~]# wc [-lwm]
选项与参数:
-l  :仅列出行;
-w  :仅列出多少字(英文单字);
-m  :多少字符;

范例一:那个 /etc/man.config 里面到底有多少相关字、行、字符数?
[root@www ~]# cat /etc/man.config | wc 
    141     722    4617
# 输出的三个数字中,分别代表: 『行、字数、字符数』

范例二:我知道使用 last 可以输出登陆者,但是 last 最后两行并非账号内容,
        那么请问,我该如何以一行命令串取得这个月份登陆系统的总人次?
[root@www ~]# last | grep [a-zA-Z] | grep -v 'wtmp' | wc -l 
# 由于 last 会输出空白行与 wtmp 字样在最底下两行,因此,我利用
# grep 取出非空白行,以及去除 wtmp 那一行,在计算行数,就能够了解啰!

     wc 也可以当作命令?这可不是上洗手间的 WC 呢!这是相当有用的计算文件内容的一个工具组喔!举个例子来说,当你要知道目前你的账号文件中有多少个账号时,就使用这个方法:『cat /etc/passwd | wc -l 』啦!因为 /etc/passwd 里头一行代表一个使用者呀!所以知道行数就晓得有多少的账号在里头了!而如果要计算一个文件里头有多少个字符时,就使用wc -m 这个选项吧!


       双向重导向: tee

想个简单的东西,我们由前一节知道 > 会将数据流整个传送给文件或装置,因此我们除非去读取该文件或装置,否则就无法继续利用这个数据流。万一我想要将这个数据流的处理过程中将某段信息存下来,应该怎么做?利用 tee 就可以啰

tee 会同时将数据流分送到文件去与屏幕 (screen);而输出到屏幕的,其实就是 stdout ,可以让下个命令继续处理喔!

[root@www ~]# tee [-a] file
选项与参数:
-a  :以累加 (append) 的方式,将数据加入 file 当中!

[root@www ~]# last | tee last.list | cut -d " " -f1
# 这个范例可以让我们将 last 的输出存一份到 last.list 文件中;

[root@www ~]# ls -l /home | tee ~/homefile | more
# 这个范例则是将 ls 的数据存一份到 ~/homefile ,同时屏幕也有输出信息!

[root@www ~]# ls -l / | tee -a ~/homefile | more
# 要注意! tee 后接的文件会被覆盖,若加上 -a 这个选项则能将信息累加。

tee 可以让 standard output 转存一份到文件内并将同样的数据继续送到屏幕去处理!这样除了可以让我们同时分析一份数据并记录下来之外,还可以作为处理一份数据的中间缓存盘记录之用!tee 这家伙在很多选择/填充的认证考试中很容易考呢!


        重点回顾
  • 由于核心在内存中是受保护的区块,因此我们必须要透过『 Shell 』将我们输入的命令与 Kernel 沟通,好让 Kernel 可以控制硬件来正确无误的工作
  • 学习 shell 的原因主要有:文字接口的 shell 在各大 distribution 都一样;远程管理时文字接口速度较快;shell 是管理 Linux 系统非常重要的一环,因为 Linux 内很多控制都是以 shell 撰写的。
  • 系统合法的 shell 均写在 /etc/shells 文件中;
  • 用户默认登陆取得的 shell 记录于 /etc/passwd 的最后一个字段;
  • bash 的功能主要有:命令编修能力;命令与文件补全功能;命令别名配置功能;工作控制、前景背景控制;程序化脚本;通配符
  • type 可以用来找到运行命令为何种类型,亦可用于与 which 相同的功能;
  • 变量就是以一组文字或符号等,来取代一些配置或者是一串保留的数据
  • 变量主要有环境变量与自定义变量,或称为全局变量与局部变量
  • 使用 env 与 export 可观察环境变量,其中 export 可以将自定义变量转成环境变量;
  • set 可以观察目前 bash 环境下的所有变量;
  • $? 亦为变量,是前一个命令运行完毕后的回传值。在 Linux 回传值为 0 代表运行成功;
  • locale 可用于观察语系数据;
  • 可用 read 让用户由键盘输入变量的值
  • ulimit 可用以限制用户使用系统的资源情况
  • bash 的配置文件主要分为 login shell 与 non-login shell。login shell 主要读取 /etc/profile 与 ~/.bash_profile,non-login shell 则仅读取 ~/.bashrc
  • 通配符主要有: *, ?, [] 等等
  • 数据流重导向透过 >, 2>, < 之类的符号将输出的信息转到其他文件或装置去;
  • 连续命令的下达可透过 ; && || 等符号来处理
  • 管线命令的重点是:『管线命令仅会处理 standard output,对于 standard error output 会予以忽略』『管线命令必须要能够接受来自前一个命令的数据成为 standard input 继续处理才行。』
  • 本章介绍的管线命令主要有:cut, grep, sort, wc, uniq, tee, tr, col, join, paste, expand, split, xargs 等。
        延伸正规表示法

事实上,一般读者只要了解基础型的正规表示法大概就已经相当足够了,不过,某些时刻为了要简化整个命令操作,了解一下使用范围更广的延伸型正规表示法的表示式会更方便呢!举个简单的例子好了,在上节的例题三的最后一个例子中,我们要去除空白行与行首为 # 的行列,使用的是

grep -v '^$' regular_express.txt | grep -v '^#'

需要使用到管线命令来搜寻两次!那么如果使用延伸型的正规表示法,我们可以简化为:

egrep -v '^$|^#' regular_express.txt

延伸型正规表示法可以透过群组功能『 | 』来进行一次搜寻!那个在单引号内的管线意义为『或 or』啦!是否变的更简单呢?此外,grep 默认仅支持基础正规表示法,如果要使用延伸型正规表示法,你可以使用 grep -E ,不过更建议直接使用 egrep !直接区分命令比较好记忆!其实 egrep 与 grep -E 是类似命令别名的关系啦!

熟悉了正规表示法之后,到这个延伸型的正规表示法,你应该也会想到,不就是多几个重要的特殊符号吗? ^_^y 是的~所以,我们就直接来说明一下,延伸型正规表示法有哪几个特殊符号?由於底下的范例还是有使用到 regular_express.txt,不巧的是刚刚我们可能将该文件修改过了 @_@,所以,请重新下载该文件来练习喔!

RE 字符意义与范例
+意义:重复『一个或一个以上』的前一个 RE 字符
范例:搜寻 (god) (good) (goood)... 等等的字串。 那个 o+ 代表『一个以上的 o 』所以,底下的运行成果会将第 1, 9, 13 行列出来。
egrep -n 'go+d' regular_express.txt
?意义:『零个或一个』的前一个 RE 字符
范例:搜寻 (gd) (god) 这两个字串。 那个 o? 代表『空的或 1 个 o 』所以,上面的运行成果会将第 13, 14 行列出来。有没有发现到,这两个案例( 'go+d' 与 'go?d' )的结果集合与 'go*d' 相同? 想想看,这是为什么喔! ^_^
egrep -n 'go?d' regular_express.txt
|意义:用或( or )的方式找出数个字串
范例:搜寻 gd 或 good 这两个字串,注意,是『或』! 所以,第 1,9,14 这三行都可以被列印出来喔!那如果还想要找出 dog 呢?
egrep -n 'gd|good' regular_express.txt
egrep -n 'gd|good|dog' regular_express.txt
()意义:找出『群组』字串
范例:搜寻 (glad) 或 (good) 这两个字串,因为 g 与 d 是重复的,所以, 我就可以将 la 与 oo 列於 ( ) 当中,并以 | 来分隔开来,就可以啦!
egrep -n 'g(la|oo)d' regular_express.txt
()+意义:多个重复群组的判别
范例:将『AxyzxyzxyzxyzC』用 echo 叫出,然后再使用如下的方法搜寻一下!
echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'
上面的例子意思是说,我要找开头是 A 结尾是 C ,中间有一个以上的 "xyz" 字串的意思~

以上这些就是延伸型的正规表示法的特殊字节。另外,要特别强调的是,那个 ! 在正规表示法当中并不是特殊字节,所以,如果你想要查出来文件中含有 ! 与 > 的字行时,可以这样:

grep -n '[!>]' regular_express.txt
  • 撰写第一支 script(shell script类似于windows下的批处理程序,就是将一系列的命令写到脚本中,然后运行脚本来执行)

在武侠世界中,不论是那个门派,要学武功要从扫地做起,那么要学程序呢?呵呵,肯定是由『秀出 Hello World!』这个字眼开始的!OK!那么鸟哥就先写一支 script 给大家瞧一瞧:

[root@www ~]# mkdir scripts; cd scripts
[root@www scripts]# vi sh01.sh
#!/bin/bash
# Program:
#       This program shows "Hello World!" in your screen.
# History:
# 2005/08/23	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0

在本章当中,请将所有撰写的 script 放置到你家目录的 ~/scripts 这个目录内,未来比较好管理啦!上面的写法当中,鸟哥主要将整个程序的撰写分成数段,大致是这样:

  1. 第一行 #!/bin/bash 在宣告这个 script 使用的 shell 名称:
    因为我们使用的是 bash ,所以,必须要以『 #!/bin/bash 』来宣告这个文件内的语法使用bash 的语法!那么当这个程序被运行时,他就能够加载 bash 的相关环境配置档 (一般来说就是non-login shell 的 ~/.bashrc),并且运行 bash 来使我们底下的命令能够运行!这很重要的!(在很多状况中,如果没有配置好这一行,那么该程序很可能会无法运行,因为系统可能无法判断该程序需要使用什么 shell 来运行啊!)

  2. 程序内容的说明:
    整个 script 当中,除了第一行的『 #! 』是用来宣告 shell 的之外,其他的 # 都是『注解』用途!所以上面的程序当中,第二行以下就是用来说明整个程序的基本数据。一般来说,建议你一定要养成说明该 script 的:1. 内容与功能; 2. 版本资讯; 3. 作者与联络方式; 4. 建档日期;5. 历史纪录 等等。这将有助於未来程序的改写与 debug 呢!

  3. 主要环境变量的宣告:
    建议务必要将一些重要的环境变量配置好,鸟哥个人认为, PATH 与 LANG (如果有使用到输出相关的资讯时) 是当中最重要的!如此一来,则可让我们这支程序在进行时,可以直接下达一些外部命令,而不必写绝对路径呢!比较好啦!

  4. 主要程序部分
    就将主要的程序写好即可!在这个例子当中,就是 echo 那一行啦!

  5. 运行成果告知 (定义回传值)
    是否记得我们在第十一章里面要讨论一个命令的运行成功与否,可以使用$? 这个变量来观察~那么我们也可以利用 exit 这个命令来让程序中断,并且回传一个数值给系统。在我们这个例子当中,鸟哥使用 exit 0 ,这代表离开 script 并且回传一个 0 给系统,所以我运行完这个 script 后,若接著下达 echo $? 则可得到 0 的值喔!更聪明的读者应该也知道了,呵呵!利用这个 exit n (n 是数字) 的功能,我们还可以自订错误信息,让这支程序变得更加的 smart 呢!

接下来透过刚刚上头介绍的运行方法来运行看看结果吧!

[root@www scripts]# sh sh01.sh
Hello World !

你会看到萤幕是这样,而且应该还会听到『咚』的一声,为什么呢?还记得前一章提到的 printf 吧?用 echo 接著那些特殊的按键也可以发生同样的事情~不过, echo 必须要加上 -e 的选项才行!呵呵!在你写完这个小 script 之后,你就可以大声的说:『我也会写程序了』!哈哈!很简单有趣吧~ ^_^

(PS:今天刚申请了跳板机,需要用ssh指令序连接跳板机,但是指令特别长,不能每次登陆都输入那么长的指令啊,于是宝宝作为练习写了个脚本,但是密码还是要自己输入的,不知道继续学习后续的知识是否可以解决此问题)


  • 对谈式脚本:变量内容由使用者决定(个人理解为脚本接收bash输入的参数)

很多时候我们需要使用者输入一些内容,好让程序可以顺利运行。简单的来说,大家应该都有安装过软件的经验,安装的时候,他不是会问你『要安装到那个目录去』吗?那个让使用者输入数据的动作,就是让使用者输入变量内容啦。

你应该还记得在十一章 bash 的时候,我们有学到一个read 命令吧?现在,请你以 read 命令的用途,撰写一个 script ,他可以让使用者输入:1. first name 与 2. last name,最后并且在萤幕上显示:『Your full name is: 』的内容:

[root@www scripts]# vi sh02.sh
#!/bin/bash
# Program:
#	User inputs his first name and last name.  Program shows his full name.
# History:
# 2005/08/23	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input your first name: " firstname  # 提示使用者输入
read -p "Please input your last name:  " lastname   # 提示使用者输入
echo -e "\nYour full name is: $firstname $lastname" # 结果由萤幕输出
将上面这个 sh02.sh 运行一下,你就能够发现使用者自己输入的变量可以让程序所取用,并且将他显示到萤幕上!接下来,如果想要制作一个每次运行都会依据不同的日期而变化结果的脚本呢?
  • 随日期变化:利用 date 进行文件的创建

想像一个状况,假设我的服务器内有数据库,数据库每天的数据都不太一样,因此当我备份时,希望将每天的数据都备份成不同的档名,这样才能够让旧的数据也能够保存下来不被覆盖。哇!不同档名呢!这真困扰啊?难道要我每天去修改 script ?

不需要啊!考虑每天的『日期』并不相同,所以我可以将档名取成类似: backup.2009-02-14.data ,不就可以每天一个不同档名了吗?呵呵!确实如此。那个 2009-02-14 怎么来的?那就是重点啦!接下来出个相关的例子:假设我想要创建三个空的文件 (透过touch),档名最开头由使用者输入决定,假设使用者输入 filename 好了,那今天的日期是 2009/02/14 ,我想要以前天、昨天、今天的日期来创建这些文件,亦即 filename_20090212, filename_20090213, filename_20090214 ,该如何是好?

[root@www scripts]# vi sh03.sh
#!/bin/bash
# Program:
#	Program creates three files, which named by user's input 
#	and date command.
# History:
# 2005/08/23	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 让使用者输入文件名称,并取得 fileuser 这个变量;
echo -e "I will use 'touch' command to create 3 files." # 纯粹显示资讯
read -p "Please input your filename: " fileuser         # 提示使用者输入

# 2. 为了避免使用者随意按 Enter ,利用变量功能分析档名是否有配置?
filename=${fileuser:-"filename"}           # 开始判断有否配置档名

# 3. 开始利用 date 命令来取得所需要的档名了;
date1=$(date --date='2 days ago' +%Y%m%d)  # 前两天的日期
date2=$(date --date='1 days ago' +%Y%m%d)  # 前一天的日期
date3=$(date +%Y%m%d)                      # 今天的日期
file1=${filename}${date1}                  # 底下三行在配置档名
file2=${filename}${date2}
file3=${filename}${date3}

# 4. 将档名创建吧!
touch "$file1"                             # 底下三行在创建文件
touch "$file2"
touch "$file3"
上面的范例鸟哥使用了很多在十一章介绍过的概念:包括小命令『 $(command) 』的取得信息、变量的配置功能、变量的累加以及利用 touch 命令辅助!如果你开始运行这个 sh03.sh 之后,你可以进行两次运行:一次直接按 [Enter] 来查阅档名是啥?一次可以输入一些字节,这样可以判断你的脚本是否设计正确喔!


  • 数值运算:简单的加减乘除

各位看官应该还记得,我们可以使用 declare 来定义变量的类型吧?当变量定义成为整数后才能够进行加减运算啊!此外,我们也可以利用『 $((计算式)) 』来进行数值运算的。可惜的是, bash shell 里头默认仅支持到整数的数据而已。OK!那我们来玩玩看,如果我们要使用者输入两个变量,然后将两个变量的内容相乘,最后输出相乘的结果,那可以怎么做?

[root@www scripts]# vi sh04.sh
#!/bin/bash
# Program:
#	User inputs 2 integer numbers; program will cross these two numbers.
# History:
# 2005/08/23	VBird	First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "You SHOULD input 2 numbers, I will cross them! \n"
read -p "first number:  " firstnu
read -p "second number: " secnu
total=$(($firstnu*$secnu))
echo -e "\nThe result of $firstnu x $secnu is ==> $total"

在数值的运算上,我们可以使用『 declare -i total=$firstnu*$secnu 』也可以使用上面的方式来进行!基本上,鸟哥比较建议使用这样的方式来进行运算:

var=$((运算内容))

不但容易记忆,而且也比较方便的多,因为两个小括号内可以加上空白字节喔!未来你可以使用这种方式来计算的呀!至於数值运算上的处理,则有:『+, -, *, /, % 』等等。那个 % 是取余数啦~举例来说, 13 对 3 取余数,结果是 13=4*3+1,所以余数是 1 啊!就是:

[root@www scripts]# echo $(( 13 % 3 ))
1

这样了解了吧?多多学习与应用喔! ^_^


  • 利用 source 来运行脚本:在父程序中运行(而利用sh运行脚本则是在子程序中进行)

如果你使用 source 来运行命令那就不一样了!同样的脚本我们来运行看看:

[root@www scripts]# source sh02.sh
Please input your first name: VBird
Please input your last name:  Tsai

Your full name is: VBird Tsai
[root@www scripts]# echo $firstname $lastname
VBird Tsai  <==嘿嘿!有数据产生喔!

竟然生效了!没错啊!因为 source 对 script 的运行方式可以使用底下的图示来说明!sh02.sh 会在父程序中运行的,因此各项动作都会在原本的 bash 内生效!这也是为啥你不注销系统而要让某些写入 ~/.bashrc 的配置生效时,需要使用『 source ~/.bashrc 』而不能使用『 bash ~/.bashrc 』是一样的啊!



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值