细说Linux系统管理
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.3 Bash的基本功能

我们已经知道了Shell是什么,也写了一个简单的Shell脚本,那么Bash还有哪些非常方便的功能呢?Bash能够支持非常多的功能,我们在这里主要介绍历史命令、命令与文件补全、命令别名、常用快捷键、输入/输出重定向、多命令顺序执行、管道符、通配符和其他特殊符号。其他的一些内容,比如工作管理(也就是前台、后台控制,参考第6章),我们将在其他章节中介绍。

2.3.1 历史命令

1.历史命令的查看

Bash有完善的历史命令,这对于简化管理操作、排查系统错误都有重要的作用,而且使用简单方便,建议大家多使用历史命令。系统保存的历史命令可以使用history命令查询,命令格式如下:

    [root@localhost ~]# history [选项] [历史命令保存文件]
    选项:
        -c:    清空历史命令
        -w:    把缓存中的历史命令写入历史命令保存文件中。如果不手工指定历史命令保存文
                件,则放入默认历史命令保存文件~/.bash_history中

如果history命令直接回车,则用于查询系统中的历史命令,命令如下:

    [root@localhost ~]# history
    …省略部分输出…
      421  chmod 755 hello.sh
      422  /root/sh/hello.sh
      423  ./hello.sh
      424  bash hello.sh
      425  history

这样就可以查询我们刚刚输入的系统命令,而且每条命令都是有编号的。历史命令默认会保存1000条,这是通过环境变量(环境变量的含义参见2.4.3节)HISTSIZE来进行设置的,我们可以在环境变量配置文件/etc/profile中进行修改。命令如下:

    [root@localhost ~]# vi /etc/profile
    …省略部分输出…
    HISTSIZE=1000
    …省略部分输出…

如果觉得1000条历史命令不够日常管理使用,那么是否可以增加呢?只需修改/etc/profile环境变量配置文件中的HISTSIZE字段即可,不过我们需要考虑一个问题:这些历史命令是保存在哪里的呢?如果历史命令是保存在文件中的,那么历史命令的保存数量可以放心地增加,因为哪怕有几万条历史命令,也不会占用多大的硬盘空间。但是,如果历史命令是保存在内存当中的,就要小心了。好在历史命令是保存在~/.bash_history文件中的,所以可以放心地把总历史命令条数改大,比如10000条,命令如下:

    [root@localhost ~]# vi /etc/profile
    …省略部分输出…
    HISTSIZE=10000
    …省略部分输出…

大家需要注意,每个用户的历史命令是单独保存的,所以每个用户的家目录中都有.bash_history这个历史命令文件。

如果某个用户的历史命令总条数超过了历史命令保存条数,那么新命令会变成最后一条命令,而最早的命令则被删除。假设系统保存1000条历史命令,而我已经保存了1000条历史命令,那么我新输入的命令会被保存成第1000条命令,而最早的第1条命令会被删除。

还要注意一下,我们使用history命令查看的历史命令和~/.bash_history文件中保存的历史命令是不同的。那是因为当前登录操作的命令并没有直接写入~/.bash_history文件中,而是保存在缓存当中的,需要等当前用户注销之后,缓存中的命令才会写入~/.bash_history文件中。如果我们需要把内存中的命令直接写入~/.bash_history文件中,而不等用户注销时再写入,就需要使用“-w”选项。命令如下:

    [root@localhost ~]# history -w
    #把缓存中的历史命令直接写入~/.bash_history

这时再去查询~/.bash_history文件,历史命令就和history命令查询的结果一致了。

如果需要清空历史命令,则只需要执行如下命令:

    [root@localhost ~]# history -c
    #清空历史命令

这样就会把缓存和~/.bash_history文件中的历史命令全部清空。

2.历史命令的调用

如果想要使用原先的历史命令,则有这样几种方法:

· 使用上、下箭头调用以前的历史命令。

· 使用“! n”重复执行第n条历史命令。

    [root@localhost ~]# history
    …省略部分输出…
      421  chmod 755 hello.sh
      422  /root/sh/hello.sh
      423  ./hello.sh
      424  bash hello.sh
      425  history

    [root@localhost sh]# !424
    #重复执行第424条命令

· 使用“! ! ”重复执行上一条命令

    [root@localhost sh]# ! !
    #如果接着上一条命令,则会把424命令再执行一遍

· 使用“!字符串”重复执行最后一条以该字符串开头的命令。

    [root@localhost sh]# ! bash
    #重复执行最后一条以bash开头的命令,也就是第424条命令bash hello.sh

· 使用“! $”重复上一条命令的最后一个参数。

    [root@localhost ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
    #查看网卡配置文件内容
    [root@localhost ~]# vi ! $
    #“! $”代表上一条命令的最后一个参数,也就是/etc/sysconfig/network-scripts/ifcfg-eth0

2.3.2 命令与文件补全

在Bash中,命令与文件补全是非常方便与常用的功能,我们只要在输入命令或文件时按Tab键,就会自动进行补全。命令补全是按照PATH环境变量所定义的路径查找命令的(在2.4.3节中我们会仔细介绍),而文件补全是按照文件位置查找文件的。

比如,想要知道以user开头的命令有多少,就可以执行以下操作:

    [root@localhost ~]# user
    #输入user,按Tab键,如果以user开头的只有一条命令,就会补全这条命令
    #如果以user开头的有多条命令,则只要按两次Tab键,就会列出所有以user开头的命令
    useradd    userdel    userhelper  usermod    usernetctl  users

不仅命令可以补全,文件和目录也可以用Tab键进行补全。大家一定要多用Tab键进行补全,这样不仅可以加快输入速度,而且会减少输入错误。所以,养成使用Tab键的习惯,对Linux的管理和使用都有很大的帮助。

2.3.3 命令别名

命令别名是什么呢?你可以把它当作命令的“小名”,但是这样做有什么意义呢?

比如超哥刚接触Linux时,使用的编辑器是Vi,但是现在Vim的功能明显比Vi的功能更加强大,所以现在流行的编辑器变成了Vim。但是超哥已经习惯了输入vi命令,而不习惯输入vim命令,别看小小的一个“m”的区别,在执行命令时总觉得别扭,这时别名就可以起作用了。只要定义vim命令的别名为vi,这样以后执行的vi命令实际上运行的是vim命令。具体命令如下:

    命令格式:
    [root@localhost ~]# alias
    #查询命令别名
    [root@localhost ~]# alias别名=’原命令’
    #设定命令别名

    例如:
    [root@localhost ~]# alias
    #查询系统中已经定义好的别名
    alias cp='cp -i'
    alias l.='ls -d .* --color=auto'
    alias ll='ls -l --color=auto'
    alias ls='ls --color=auto'
    alias mv='mv -i'
    alias rm='rm -i'
    alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

    [root@localhost ~]# alias vi='vim'
    #定义vim命令的别名是vi
    [root@localhost ~]# alias
    #重新查询别名
    alias cp='cp -i'
    alias l.='ls -d .* --color=auto'
    alias ll='ls -l --color=auto'
    alias ls='ls --color=auto'
    alias mv='mv -i'
    alias rm='rm -i'
    alias vi='vim'                         ←别名已经生效
    alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

大家需要注意一点,命令别名的优先级要高于命令本身。所以,一旦给vim命令设置了别名vi,那么原始的vi命令就不能使用了。所以,除非你确定原命令是不需要的,否则别名不能和系统命令重名。再举个例子:

    [root@localhost ~]# alias sto='/usr/local/apache2/bin/apachectl stop'
    [root@localhost ~]# alias sta='/usr/local/apache2/bin/apachectl start'

我们在配置和使用apache时,需要不断地重启apache服务。这时定义“sta”为apache启动命令的别名,“sto”为apache停止命令的别名,可以有效地加快apache服务的重启速度。当然,我已经确定了系统中没有“sta”和“sto”命令,所以这两个别名不会覆盖系统命令。

补充:如何确定系统中没有“sta”和“sto”命令呢?还记得whereis和which命令吗?

当然,使用Tab键命令补全功能也能够确定是否有这两个命令。

既然我们说别名的优先级比命令高,那么命令执行时具体的顺序是什么呢?命令执行时的顺序是这样的:

(1)第一顺位执行用绝对路径或相对路径执行的命令。

(2)第二顺位执行别名。

(3)第三顺位执行Bash的内部命令。

(4)第四顺位执行按照$PATH环境变量定义的目录查找顺序找到的第一条命令。

别名就是这样简单,不过如果我们使用命令直接定义别名,那么这个别名只是临时生效,一旦注销或重启系统,这个别名就马上消失了。为了让这个别名永久生效,可以把别名写入环境变量配置文件~/.bashrc中。命令如下:

    [root@localhost ~]# vi /root/.bashrc
    # .bashrc

    # User specific aliases and functions

    alias rm='rm -i'
    alias cp='cp -i'
    alias mv='mv -i'
    alias vi='vim'
    alias sto='/usr/local/apache2/bin/apachectl stop'
    alias sta='/usr/local/apache2/bin/apachectl start'

    # Source global definitions
    if [ -f /etc/bashrc ]; then
          . /etc/bashrc
    fi

这样,这些别名就可以永久生效了。那么,环境变量配置文件又是什么呢?所谓环境变量配置文件,顾名思义,就是用来定义我们的操作环境的,别名当然也是操作环境,我们在2.5.2节中再来详细了解这个文件的作用吧。设定好的别名可以删除吗?只要执行unalias命令就可以方便地删除别名,命令如下:

    [root@localhost ~]# unalias vi

当然,如果确定要删除别名,则也要删除环境变量配置文件中的相关项才可以。

2.3.4 Bash常用快捷键

在Bash中有非常多的快捷键,如果可以熟练地使用这些快捷键,则可以有效地提高我们的工作效率。只是快捷键相对较多,不太好记忆,这就要多加练习和使用。这些快捷键如表2-3所示。

表2-3 Bash常用快捷键

在这些快捷键中,加粗的快捷键比较常用,大家要熟练使用。

2.3.5 输入/输出重定向

1.Bash的标准输入/输出

输入/输出重定向从字面上看就是改变输入与输出的方向的意思,但是标准的输入与输出方向是什么呢?我们先来解释一下输入设备和输出设备各有哪些。现在计算机的输入设备非常多,常见的有键盘、鼠标、麦克风、手写板等,常见的输出设备有显示器、投影仪、打印机等。不过,在Linux中,标准输入设备指的是键盘,标准输出设备指的是显示器。

在Linux中,所有的内容都是文件,计算机硬件也是文件,那么标准输入设备(键盘)和标准输出设备(显示器)当然也是文件了。我们通过表2-4来看看这些设备的设备文件名。

表2-4 标准输入/输出设备

Linux是使用设备文件名来表示硬件的(比如/dev/sda1就代表第一块SATA硬盘的第一个主分区),但是键盘和显示器的设备文件名并不好记忆,那么我们就用“0”“1”“2”来分别代表标准输入、标准输出和标准错误输出。

我们知道了标准输入和标准输出,那么输出重定向指的是改变输出方向,不再输出到屏幕上,而是输出到文件或其他设备中;输入重定向则是指不再使用键盘作为输入设备,而是把文件的内容作为命令的输入。

2.输出重定向

输出重定向指的是把命令的结果不再输出到屏幕中,而是输出文件中。这样做最大的好处就是把命令结果保存到指定的文件中,当我们需要的时候可以随时调用。Bash支持的输出重定向符号如表2-5所示。

表2-5 Bash支持的输出重定向符号

1)标准输出重定向

在输出重定向中,“>”代表的是覆盖,“>>”代表的是追加。举个例子:

    [root@localhost ~]# ls -l > out.log
    #ls命令的输出并没有显示到屏幕上
    [root@localhost ~]# cat out.log
总用量 48
   -rw-------. 1 root root  1168 4月  10 21:49 anaconda-ks.cfg
   -rw-r--r--. 1 root root 24772 4月  10 21:49 install.log
   -rw-r--r--. 1 root root  7690 4月  10 21:49 install.log.syslog
   -rw-r--r--. 1 root root    0 6月   6 16:45 out.log
    drwxr-xr-x. 2 root root  4096 6月   6 10:39 sh
    #在当前目录中出现了out.log文件,在这个文件中保存着刚刚ls命令的输出

    [root@localhost ~]# pwd > out.log
    #把pwd命令的输出也放入文件out.log中。也就是说,任何命令只要有输出,都可以使用输出重定向
    [root@localhost ~]# cat out.log
    /root
    #在out.log文件中只有pwd命令的输出,而ls命令的被覆盖了

这就是“>”的作用,任何有输出的命令都可以使用输出重定向把命令的输出保存到文件中。不过覆盖保存会把这个文件中的原有内容清空,所以追加重定向更加实用。

    [root@localhost ~]# date >> out.log
    #把date命令的输出追加到out.log文件中
    [root@localhost ~]# cat out.log
    /root
    2013年 10月 17日 星期四 16:51:36 CST
    #把日期写入了文件中,但是并没有覆盖pwd命令的输出

2)标准错误输出重定向

如果想要把命令的错误输出保存到文件中,则用正确的输出重定向是不行的,比如:

    [root@localhost ~]# ls test >> err.log
    ls: 无法访问test: 没有那个文件或目录

因为当前目录下没有test文件或目录,所以“ls test”命令报错了。不过命令的错误输出并没有保存到err.log文件中,而是输出到屏幕上。这时需要这样来写这条命令:

    [root@localhost ~]# ls test 2>> err.log
    #错误输出重定向,错误输出没有输出到屏幕上,而是写入了err.log文件中
    [root@localhost ~]# cat err.log
    ls: 无法访问test: 没有那个文件或目录

2代表错误输出,只有这样才能把命令的错误输出保存到指定的文件中。这里大家需要注意“2>>”一定不能有空格,否则会报错。

3)正确输出和错误输出同时保存

在实际的使用中,以上两种方法都不实用,因为正确的命令和错误的命令的保存方法是分开的。那么最常用的还是可以把正确输出和错误输出都保存下来的方法。命令如下:

    [root@localhost ~]# ls >> out.log 2>&1
    [root@localhost ~]# ls test &>>out.log
    #正确输出和错误输出都保存到out.log文件中
    #这两种方法都可以使用,看个人习惯

    [root@localhost ~]# cat out.log
    /root
    2013年 06月 06日 星期四 16:51:36 CST
    anaconda-ks.cfg                        ←第一条正确命令的输出追加保存了
    err.log
    install.log
    install.log.syslog
    out.log
    sh
    ls: 无法访问test: 没有那个文件或目录       ←第二条错误命令的输出也追加保存了

这两种把错误命令和正确命令写入同一个文件中的方法——“命令 >> 文件2>&1”和“命令 &>>文件”,按照个人习惯都可以使用。不过我们还可以把正确输出和错误输出分开保存到不同的文件中,命令如下:

    [root@localhost ~]# ls >> list.log 2>>err.log

如果这样写,则命令的正确输出会写入文件list.log中,而错误输出则会写入err.log文件中。超哥个人觉得,如果要保存命令的执行结果,那么这种方法更加清晰。

如果我们并不想把命令的输出保存下来,也不想把命令的执行结果输出到屏幕上,干扰命令的执行,就可以把命令的所有执行结果放入/dev/null中。大家可以把/dev/null当成Linux系统的垃圾箱,任何放入垃圾箱的数据都会被丢弃,但是不能被恢复。命令如下:

    [root@localhost ~]# ls &>/dev/null

3.输入重定向

什么又是输入重定向呢?既然输出重定向是改变输出的方向,把命令的输出重定向到文件中。那么输入重定向就是改变输入的方向,不再使用键盘作为命令的输入,而是使用文件作为命令的输入。我们先讲一个命令wc,命令格式如下:

    [root@localhost ~]# wc [选项] [文件名]
    选项:
        -c:     统计字节数
        -w:     统计单词数
        -l:     统计行数
    例如:
    [root@localhost ~]# wc
    hello,
    how are you?              ←使用Ctrl+D快捷键保存退出,在这里Ctrl+D不是退出登录的快捷键
          2      4     20
    #统计出刚刚输入的数据有2行、4个单词、20字节

wc命令非常简单,可以统计我们通过键盘输入的数据。如果使用输入重定向符“<”,则可以统计文件的内容。命令如下:

    [root@localhost ~]# wc < anaconda-ks.cfg
      49  1171168
    #anaconda-ks.cfg安装配置文件共有49行、117个单词、1168字节

其实,如果我们直接使用“wc anaconda-ks.cfg”命令也是可以统计这个文件的数据的,不过在这里只是演示一下输入重定向的作用。那么,输入重定向在实际工作中有用处吗?输入重定向确实不如输出重定向常见,但是当我们需要使用patch命令打入补丁时,就会用到输入重定向。

还有一个输入重定向符号“<<”,这个符号的作用是使用字符关键字作为命令输入的结束,而不使用Ctrl+D快捷键。命令如下:

    [root@localhost ~]# wc << "hello"  ←定义关键字
    > This is a test.
    > Welcome you to learn Linux
    > hello                          ←当再碰到这个关键字时,命令结束,而不用Ctrl+D快捷键
     2  9 43

“<<”之后的关键字可以自由定义,只要再碰到相同的关键字,两个关键字之间的内容将作为命令的输入(不包括关键字本身)。

2.3.6 多命令顺序执行

在Bash中,如果需要让多条命令顺序执行,则有这样几种方法,如表2-6所示。

表2-6 多命令顺序执行的方法

1.“; ”多命令顺序执行

如果使用“; ”连接多条命令,那么这些命令会依次执行,但是各条命令之间没有任何逻辑关系,也就是说,不论哪条命令报错了,后面的命令还是会依次执行的。举个例子:

    [root@localhost ~]# ls ; date ; cd /user ; pwd
    anaconda-ks.cfg  err.log  install.log  install.log.syslog  list.log  out.log
    sh
    #ls命令正确执行
    2013年 10月 21日 星期一 11:35:57 CST
    #date命令正确执行
    -bash: cd: /user: 没有那个文件或目录
    #cd命令报错,因为没有/user目录
    /root
    #虽然cd命令报错,但是并不影响pwd命令的执行

这就是“; ”的作用,不论前一条命令是否正确执行,都不影响后续命令的执行。再举一个例子:

    [root@localhost ~]# date ; dd if=/dev/zero of=/root/testfile bs=1k count=100000 ;
    date
    #创建一个大小为100MB的文件,通过“; ”可以确定需要多长时间
    2013年 10月 21日 星期一 11:41:54 CST
    #第一条date命令执行
    记录了100000+0 的读入
    记录了100000+0 的写出
    102400000字节(102 MB)已复制,2.09394 秒,48.9 MB/秒
    #dd命令执行
    2013年 10月 21日 星期一 11:41:56 CST
    #第二条date命令执行,可以判断dd命令用时2秒

    [root@localhost ~]# ll -h testfile
   -rw-r--r--. 1 root root 98M 10月 21 11:41 testfile
    #大小为100MB的testfile文件已经建立

当我们需要一次执行多条命令,而这些命令之间又没有任何逻辑关系时,就可以使用“; ”来连接多条命令。

2.“&&”逻辑与

如果使用“&&”连接多条命令,那么这些命令之间就有逻辑关系了。只有第一条命令正确执行了,“&&”连接的第二条命令才会执行。那么,命令2是如何知道命令1正确执行了呢?这就需要Bash的预定义变量$?的支持了,如果$?返回值是0,则证明上一条命令正确执行;如果$?返回值是非0,则证明上一条命令执行错误($?参考2.4.5节)。

举个例子:

    [root@localhost ~]# cp /root/test /tmp/test && rm -rf /root/test && echo yes
    cp: 无法获取"/root/test" 的文件状态(stat): 没有那个文件或目录
    #复制/root/test到/tmp/test,如果命令成功则删除原文件,并打印“yes”
    #因为/root/test文件不存在,所以第一条命令执行不正确,第二和第三条命令也都不执行
    [root@localhost ~]# ls /tmp/
    #在/tmp/目录中并没有建立test文件

    [root@localhost ~]# touch  /root/test
    #建立/root/test文件
    [root@localhost ~]# cp /root/test /tmp/test && rm -rf /root/test && echo yes
    yes
    #第一条命令正确执行后,第二和第三条命令都正确执行
    #所以打印了“yes”
    [root@localhost ~]# ll /root/test
    ls: 无法访问/root/test: 没有那个文件或目录
    #源文件/root/test消失,因为第二条命令rm正确执行
    [root@localhost ~]# ll /tmp/test
   -rw-r--r--. 1 root root 0 10月 21 13:16 /tmp/test
    #在/tmp/目录中正确建立了test文件

再举一个例子吧!我们在安装源码包时,需要执行“./configure”“make”和“make install”命令,但是在安装软件时又需要等待较长时间,那么是否可以利用“&&”同时执行这三条命令呢?当然可以了,命令如下:

    [root@localhost ~]# cd httpd-2.2.9
    [root@localhost httpd-2.2.9]# ./configure --prefix=/usr/local/apache2 && make \
    && make install

在这里,“\”代表一行命令没有输入结束,因为命令太长了,所以加入“\”字符,可以换行输入。利用“&&”就可以让这三条命令同时执行,然后我们就可以休息片刻,等待命令结束。

不过大家请思考一下,这里是否可以把“&&”替换为“; ”或“||”呢?当然是不行的,这三条安装命令必须在前一条命令正确执行之后,才能执行后一条命令。如果把“&&”替换为“; ”,则不管前一条命令是否正确执行,后一条命令都会执行。如果把“&&”替换为“||”,则只有前一条命令执行错误,后一条命令才会执行。

3.“||”逻辑或

如果使用“||”连接多条命令,则只有前一条命令执行错误,后一条命令才能执行。举个例子:

    [root@localhost ~]# ls /root/test || mkdir /root/tdir
    ls: 无法访问/root/test: 没有那个文件或目录
    #因为已经删除了/root/test文件,所以用ls命令查看时报错了
    #因为第一条命令执行错误,所以第二条命令才正确执行
    [root@localhost ~]# ll -d /root/tdir/
    drwxr-xr-x. 2 root root 4096 10月 21 13:39 /root/tdir/
    #/root/tdir/目录已经被建立了

“&&”和“||”非常有意思,如果我们想要判断某条命令是否正确执行,就可以这样来做:

    [root@localhost ~]# 命令 && echo "yes" || echo "no"

    例如:
    [root@localhost ~]# ls /root/test && echo "yes" || echo "no"
    ls: 无法访问/root/test: 没有那个文件或目录
    no
    #因为/root/test文件不存在,第一条命令报错,所以,第二条命令不能正确执行
    #因为第二条命令执行错误,所以第三条命令正确执行,打印“no”
    [root@localhost ~]# touch /root/test
    [root@localhost ~]# ls /root/test && echo "yes" || echo "no"
    /root/test
    yes
    #因为第一条命令正确执行,所以第二条命令正确执行,打印“yes”
    #因为第二条命令正确执行,所以第三条命令执行错误

2.3.7 管道符

1.行提取命令grep

在讲管道符之前,我们必须先讲一下行提取命令grep。grep的作用是在文件中提取和匹配符合条件的字符串行。命令格式如下:

    [root@localhost ~]# grep [选项] "搜索内容" 文件名
    选项:
        -A数字:        列出符合条件的行,并列出后续的n行
        -B数字:        列出符合条件的行,并列出前面的n行
        -c:            统计找到的符合条件的字符串的次数
        -i:            忽略大小写
        -n:            输出行号
        -v:            反向查找
        --color=auto:   搜索出的关键字用颜色显示

举几个例子:

    [root@localhost ~]# grep "/bin/bash" /etc/passwd
    #查找用户信息文件/etc/passwd中有多少可以登录的用户
    root:x:0:0:root:/root:/bin/bash
    user1:x:500:500::/home/user1:/bin/bash
    user2:x:501:501::/home/user2:/bin/bash

grep是行提取命令,所以只要一行数据中包含“搜索内容”,就会列出整行的数据。在这个例子中,会在/etc/passwd文件中列出所有包含“/bin/bash”的行,而我们已知只有可登录用户的Shell才是“/bin/bash”,而伪用户的Shell是“/sbin/nologin”,所以这条命令会列出当前系统中所有可以登录的用户。

再举几个例子:

    [root@localhost ~]# grep -A 3 "root" /etc/passwd
    #查找包含“root”的行,并列出后续的3行
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin

    [root@localhost ~]# grep -n "/bin/bash" /etc/passwd
    #查找可以登录的用户,并显示行号
    1:root:x:0:0:root:/root:/bin/bash
    31:user1:x:500:500::/home/user1:/bin/bash
    32:user:x:501:501::/home/user:/bin/bash

    [root@localhost ~]# grep -v "/bin/bash" /etc/passwd
    #查找不包含“/bin/bash”的行,其实就是列出所有的伪用户
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    …省略部分输出…

find也是搜索命令,那么find命令和grep命令有什么区别呢?

1)find命令

find命令用于在系统中搜索符合条件的文件名,如果需要模糊查询,则使用通配符(参见2.3.8节)进行匹配。搜索时文件名是完全匹配的。完全匹配是什么意思呢?举个例子:

    [root@localhost ~]# touch abc
    #建立文件abc
    [root@localhost ~]# touch abcd
    #建立文件abcd
    [root@localhost ~]# find . -name "abc"
    ./abc
    #搜索文件名是abc的文件,只会找到abc文件,而不会找到abcd文件
    #虽然abcd文件名中包含abc,但是find是完全匹配的,只有和要搜索的数据完全一样,才能找到

完全匹配的意思就是:搜索的内容必须和原始文件一模一样,才能被搜索到。

如果想要找到abcd文件,就必须依靠通配符,如find . -name "abc*"。

注意:find命令是可以通过-regex选项识别正则表达式规则的,也就是说,find命令可以按照正则表达式规则匹配,而正则表达式是模糊匹配。但是对于初学者而言,find和grep命令本身就不好理解,所以在这里只按照通配符规则来进行find查询。

2)grep命令

grep命令用于在文件中搜索符合条件的字符串,如果需要模糊查询,则使用正则表达式(参见3.1节)进行匹配。搜索时字符串是包含匹配的。

grep命令就和find命令不太一样了,使用grep命令在文件中查找符合条件的字符串时,只要搜索的内容包含在数据行中,就会列出整行内容。举个例子:

    [root@localhost ~]# echo abc > test
    #在test文件中写入abc数据
    [root@localhost ~]# echo abcd >> test
    #在test文件中追加abcd数据
    [root@localhost ~]# grep "abc" test
    abc
    abcd
    #grep命令查找时,只要数据行中包含abc,就会列出
    #所以abc和abcd都可以被查询到

通过这两个例子,大家就可以知道完全匹配和包含匹配的区别了。

2.管道符介绍

在Bash中,管道符使用“|”代表。管道符也是用来连接多条命令的,如“命令1 | 命令2”。不过和多命令顺序执行不同的是,用管道符连接的命令,命令1的正确输出作为命令2的操作对象。这里需要注意,命令1必须有正确输出,而命令2必须可以处理命令1的输出结果;而且命令2只能处理命令1的正确输出,而不能处理错误输出。

举个例子,我们经常需要使用“ll”命令查看文件的长格式,不过在有些目录中文件众多,比如/etc/目录,使用“ll”命令显示的内容就会非常多,只能看到最后的内容,而不能看到前面输出的内容。这时我们马上想到more命令可以分屏显示文件内容,可是怎么让more命令分屏显示命令的输出呢?我想到了一种笨办法:

    [root@localhost ~]# ll -a /etc/ > /root/testfile
    #用输出重定向,把ll命令的输出保存到/root/testfile文件中
    [root@localhost ~]# more /root/testfile
    #既然testfile是文件,当然可以用more命令分屏显示了
    总用量 1784
    drwxr-xr-x. 105 root root  12288 10月 21 12:49 .
    dr-xr-xr-x.  26 root root   4096 6月   5 19:06 ..
    …省略部分输出…
   -rwxr-xr-x.   1 root root   687 6月  222012 auto.smb
    --More--(7%)

可是这样操作实在不方便,这时就可以利用管道符了。命令如下:

    [root@localhost ~]# ll -a /etc/  | more

这条命令大家可以这样理解:先把“ll -a /etc”命令的输出保存到某个临时文件中,再用more命令处理这个文件。也就是我们说的第一条命令的正确输出是第二条命令处理和操作的对象。

注意:ll命令操作的是文件名,所以匹配时使用的是通配符。但是一旦加入管道符,管道符之后的内容相当于操作的是文件内容,所以匹配时使用的是正则表达式。

关于管道符,我们再举几个例子:

    [root@localhost ~]# netstat -an | grep "ESTABLISHED"
    #查询一下本地所有网络连接,提取包含ESTABLISHED(已建立连接)的行
    #就可以知道我们的服务器上有多少已经成功连接的网络连接

    [root@localhost ~]# netstat -an | grep "ESTABLISHED" | wc -l
    #如果想知道具体的网络连接数量,就可以再使用wc命令统计行数

2.3.8 通配符

在Bash中,如果需要模糊匹配文件名或目录名,就要用到通配符。通过表2-7介绍一下常用的通配符。

表2-7 通配符

还是举几个例子吧:

    [root@localhost ~]# cd /tmp/
    [root@localhost tmp]# rm -rf *
    #进入临时目录,删除所有文件
    #这个“*”也是通配符,代表当前目录中的所有文件
    [root@localhost tmp]# touch abc
    [root@localhost tmp]# touch abcd
    [root@localhost tmp]# touch 012
    [root@localhost tmp]# touch 0abc
    #建立几个测试文件
    [root@localhost tmp]# ls *
    012  0abc  abc  abcd
    #“*”代表所有的文件
    [root@localhost tmp]# ls ? abc
    0abc
    #“? ”匹配任意一个字符,所以会匹配0abc
    #但是不能匹配abc,因为“? ”不能匹配空
    [root@localhost tmp]# ls [0-9]*
    012  0abc
    #匹配任何以数字开头的文件
    [root@localhost tmp]# ls [^0-9]*
    abc  abcd
    #匹配不以数字开头的文件

2.3.9 Bash中的其他特殊符号

在Bash中还有很多其他的特殊符号,我们在这一小节中集中进行说明,如表2-8所示。

表2-8 Bash中的其他特殊符号

我们举几个例子来解释一下比较常用和容易搞混的符号。

1.单引号和双引号

单引号和双引号用于变量值出现空格时,比如name=shen chao这样执行就会出现问题,而必须用引号括起来,比如name="shen chao"。不过引号有单引号和双引号之分,二者的主要区别在于,被单引号括起来的字符都是普通字符,就算特殊字符也不再有特殊含义;而被双引号括起来的字符中,“$”“\”和反引号是拥有特殊含义的,“$”代表引用变量的值,而反引号代表引用命令。还是来看例子吧:

    [root@localhost ~]# name=sc
    #定义变量name的值是sc
    [root@localhost ~]# echo '$name'
    $name
    #如果输出时使用单引号,则$name原封不动地输出
    [root@localhost ~]# echo "$name"
    sc
    #如果输出时使用双引号,则会输出变量name的值sc

    [root@localhost ~]# echo `date`
    2013年 10月 21日 星期一 18:16:33 CST
    #反引号括起来的命令会正常执行
    [root@localhost ~]# echo '`date`'
    `date`
    #但是如果反引号括起来的命令又被单引号括起来,那么这条命令不会执行,`date`会被当成普通字符输出
    [root@localhost ~]# echo "`date`"
    2013年 10月 21日 星期一 18:14:21 CST
    #如果被双引号括起来,那么这条命令又会正常执行

所以,如果需要在双引号中间输出“$”和反引号,则要在符号前加入转义符“\”。

2.反引号

如果需要调用命令的输出,或把命令的输出赋予变量,则命令必须使用反引号包含,这条命令才会执行。反引号的作用和$(命令)是一样的,但是反引号非常容易和单引号搞混,所以推荐大家使用$(命令)的方式引用命令的输出。命令如下:

    [root@localhost ~]# echo ls
    ls
    #如果命令不用反引号包含,那么命令不会执行,而是直接输出
    [root@localhost ~]# echo `ls`
    anaconda-ks.cfg  install.log  install.log.syslog  sh  test  testfile
    #只有用反引号包含命令,这条命令才会执行
    [root@localhost ~]# echo $(date)
    2013年 10月 21日 星期一 18:25:09 CST
    #使用$(命令)的方式也是可以的

还是这句话,不管是从容易混淆的角度,还是从POSIX规范的角度来说,尽量使用$(命令)的方式来引用命令的输出,而不要使用反引号。

3.小括号、中括号和大括号

中括号主要用于变量的测试,而大括号也可以用于变量的变形和替换,这两种用法我们在2.4节中再详细介绍。这里我们主要探讨在一串命令执行时小括号和大括号的作用。

在介绍小括号和大括号的区别之前,我们先解释两个概念:父Shell和子Shell。在Bash中,是可以调用新的Bash的,比如:

    [root@localhost ~]# bash
    [root@localhost ~]#

这时,可以通过pstree命令查看一下进程数,命令如下:

    [root@localhost ~]# pstree
    init─┬─abrt-dump-oops
    …省略部分输出
          ├─sshd─┬─sshd───bash───bash───pstree
    …省略部分输出

可以看到我们的命令都是通过ssh远程服务链接的,在ssh中生成了第一个Bash,就是父Shell。因为我们刚刚执行了Bash命令,所以在第一个Bash中生成了第二个Bash,这个Bash就是子Shell,我们是在子Shell中运行命令pstree的。

关于父Shell和子Shell,大家可以想象成在Windows中我们开启了一个“cmd”字符操作终端,那么Windows本身就是父Shell,而“cmd”终端则是子Shell;也可以理解为在一个操作界面中又开启了一个子操作界面。

知道了父Shell和子Shell,我们接着解释小括号和大括号的区别。如果用于一串命令的执行,那么小括号和大括号的主要区别在于:

· ()执行一串命令时,需要重新开启一个子Shell来执行。

· {}执行一串命令时,在当前Shell中执行。

· ()和{}都是把一串命令放在括号里面,并且命令之间用“; ”隔开。

· ()最后一条命令可以不用分号。

· {}最后一条命令要用分号。

· {}的第一条命令和左括号之间必须有一个空格。

· ()里的各命令不必和括号有空格。

· ()和{}中括号里面的某条命令的重定向只影响该命令,但括号外的重定向则会影响到括号里的所有命令。

还是举几个例子来看看吧,因为这样写实在太抽象了。

    [root@localhost ~]# name=sc
    #在父Shell中定义变量name的值是sc
    [root@localhost ~]# (name=liming; echo $name)
    liming
    #如果用()括起来一串命令,那么这些命令都可以执行
    #给name变量重新赋值,但是这个值只在子Shell中生效
    [root@localhost ~]# echo $name
    sc
    #父Shell中name的值还是sc,而不是liming

    [root@localhost ~]# { name=liming; echo $name; }
    liming
    #但是用大括号来进行一串命令的执行时,name变量的修改是直接在父Shell中进行的
    #注意大括号的格式
    [root@localhost ~]# echo $name
    liming
    #name变量的值已经被修改了

其实在执行一串命令时,如果使用的是小括号,则这串命令所做的修改只在子Shell中生效,一旦命令执行结束,回到父Shell中,这个修改就会丢失;而如果使用的是大括号,则此串命令直接在父Shell中执行,命令执行结束后,修改依然会生效。