初识Linux(七)------ 认识与学习BASH

  在 Linux 的环境下,如果你不懂 bash 是什么,那么其他的东西就不用学了!bash 的东西非常的多,包括变量的设置与使用、 bash 操作环境的创建、数据流重定向的功能,还有那好用的管道命令。

1. 认识 BASH 这个 Shell

   管理整个计算机硬件的其实是操作系统的内核 (kernel),这个内核是需要被保护的,所以我们一般用户就只能通过 shell 来跟内核沟通,以让内核完成我们所想要达到的任务。

 为何要学命令行的 shell?

1. 通用,几乎各家 distributions 使用的 bash 都是一样的;

2. 远程管理快,较不容易出现掉线或者是信息外流的问题;

系统的合法 shell 与 /etc/shells 功能

  知道什么是 Shell 之后,那么我们来了解一下 Linux 使用的是哪一个 shell 呢?什么!哪一个?难道说 shell 不就是“一个 shell 吗?”哈哈!那可不!由于早年的 Unix 年代,发展者众,所以由于 shell 依据发展者的不同就有许多的版本,例如常听到的 Bourne SHell (sh) 、在 Sun 里头默认的 C SHell、 商业上常用的 K SHell、, 还有 TCSH 等等,每一种 Shell 都各有其特点。至于 Linux 使用的这一种版本就称为“ Bourne Again SHell (简称 bash) ”,这个 Shell 是 Bourne Shell 的增强版本,也是基准于 GNU 的架构下发展出来的。

  在介绍 shell 的优点之前,先来说一说 shell 的简单历史:第一个流行的 shell 是由 Steven Bourne 发展出来的,为了纪念他所以就称为 Bourne shell ,或直接简称为 sh !而后来另一个广为流传的 shell 是由柏克莱大学的 Bill Joy (Sun 公司创始人)设计依附于 BSD 版的 Unix 系统中的 shell ,这个 shell 的语法有点类似 C 语言,所以才得名为 C shell ,简称为 csh !由于在学术界 Sun 主机势力相当的庞大,而 Sun 主要是 BSD 的分支之一,所以 C shell 也是另一个很重要而且流传很广的 shell 之一 。

  那么目前我们的 Linux (以 CentOS 7.x 为例) 有多少我们可以使用的 shells 呢? 可以检查一下 /etc/shells 这个文件,至少就有下面这几个可以用的 shells :

  • /bin/sh (已经被 /bin/bash 所取代)
  • /bin/bash (就是 Linux 默认的 shell)
  • /bin/tcsh (整合 C Shell ,提供更多的功能)
  • /bin/csh (已经被 /bin/tcsh 所取代)

  虽然各家 shell 的功能都差不多,但是在某些语法方面则有所不同,因此建议还是得要选择某一种 shell 来熟悉一下, Linux 默认就是使用 bash。

  为什么我们系统上合法的 shell 要写入 /etc/shells 这个文件啊? 这是因为系统某些服务在运行过程中,会去检查使用者能够使用的 shells ,而这些 shell 的查询就是借由 /etc/shells 这个文件。

  那么,再想一想,我这个使用者什么时候可以取得 shell 来工作呢?还有, 我这个使用者默认会取得哪一个 shell 啊?当我登陆的时候,系统就会给我一个 shell 让我来工作了。 而这个登陆取得的 shell 就记录在 /etc/passwd 这个文件内!这个文件的内容是啥?

[dmtsai@study ~]$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
.....(下面省略).....

  关于使用者这部分的内容之后再说。

Bash shell 的功能

   bash 主要的优点有下面几个:

  • 历史命令(history)

   bash能记忆使用过的指令,只要在命令行按“上下键”就可以找到前/后一个输入的指令。而在很多 distribution 里头,默认的指令记忆功能可以到达 1000 个!也就是说,你曾经下达过的指令几乎都被记录下来了。

   这么多的指令记录在主文件夹内的 .bash_history ,不过需要留意的是,~/.bash_history 记录的是前一次登陆以前所执行过的指令, 而至于这一次登陆所执行的指令都被缓存在内存中,当你成功的登出系统后,该指令记忆才会记录到 .bash_history 当中!最大的好处就是可以追踪以前的命令。

  • 命令与文件补全功能,【TAB】

  可以让你少打很多字并确定输入的数据是正确的! 使用 [tab] 按键的时机依据 [tab] 接在指令后或参数后而有所不同:  

  1. [Tab] 接在一串指令的第一个字的后面,则为命令补全;
  2. [Tab] 接在一串指令的第二个字以后时,则为“文件补齐”!
  3. 若安装 bash-completion 软件,则在某些指令后面使用 [tab] 按键时,可以进行“选项/参数的补齐”功能!

  如果我想要知道我的环境当中所有以 c 为开头的指令呢?就按下“ c[tab][tab] ”就好啦。

  • 命令别名设置功能: (alias)

  在命令行输入 alias 就可以知道目前的命令别名有哪些了!也可以直接下达命令来设置别名:

alias lm='ls -al'
  • 任务管理、前台、后台控制:(job control、foreground、background)
  • 程序化脚本:(shell scripts)
  • 通配符:(Wildcard)

查询指令是否为 Bash shell 的内置命令: type

[dmtsai@study ~]$ type [-tpa] name
选项与参数:
    :不加任何选项与参数时,type 会显示出 name 是外部指令还是 bash 内置指令
-t  :当加入 -t 参数时,type 会将 name 以下面这些字眼显示出他的意义:
      file    :表示为外部指令;
      alias   :表示该指令为命令别名所设置的名称;
      builtin :表示该指令为 bash 内置的指令功能;
-p  :如果后面接的 name 为外部指令时,才会显示完整文件名;
-a  :会由 PATH 变量定义的路径中,将所有含 name 的指令都列出来,包含 alias

  在之前提到查看一些命令的说明文档可以用 man …,那bash的说明文档也可以用man page来查看。

  为了方便 shell 的操作,其实 bash 已经“内置”了很多指令了,例如 cd , 还有例如 umask 等等的指令,都是内置在 bash 当中。

  那我怎么知道这个指令是来自于外部指令(指的是其他非 bash 所提供的指令) 或是内置在 bash 当中的呢?利用 type 这个指令来查看即可。举例来说:

范例一:查询一下 ls 这个指令是否为 bash 内置?
[dmtsai@study ~]$ type ls
ls is aliased to `ls --color=auto' <==未加任何参数,列出 ls 的最主要使用情况
[dmtsai@study ~]$ type -t ls
alias                              <==仅列出 ls 执行时的依据
[dmtsai@study ~]$ type -a ls
ls is aliased to `ls --color=auto' <==最先使用 aliase
ls is /usr/bin/ls                  <==还有找到外部指令在 /bin/ls

范例二:那么 cd 呢?
[dmtsai@study ~]$ type cd
cd is a shell builtin              <==看到了吗? cd 是 shell 内置指令

  此外,由于利用 type 查询后面的名称时,如果后面接的名称并不能以可执行文件的状态被找到, 那么该名称是不会被显示出来的。也就是说, type 主要在找出“可执行文件”而不是一般文件文件名。所以,这个 type 也可以用来作为类似 which 指令的用途找指令用。

 快速编辑

  有时命令太长,可以按下 \ +【Enter】来换行,\ 是吧【Enter】转义,让【enter】不再有执行的功能。

  别的常用按键:

2. Shell 的变量

  变量是 bash 环境中非常重要的东西,我们知道 Linux 是多用户多任务的环境,每个人登陆系统都能取得一个 bash shell, 每个人都能够使用 bash 下达 mail 这个指令来收受“自己”的邮件等等。问题是, bash 是如何得知你的邮件信箱是哪个文件? 这就需要“变量”的帮助。下面我们将介绍重要的环境变量、变量的使用与设置等数据。

  在 Linux System 下面,所有的线程都是需要一个执行码,你“真正以 shell 来跟 Linux 沟通,是在正确的登陆 Linux 之后!”这个时候你就有一个 bash 的执行程序,也才可以真正的经由 bash 来跟系统沟通。而在进入 shell 之前,由于系统需要一些变量来提供他数据的读写 (或者是一些环境的设置参数值, 例如是否要显示彩色等等的) ,所以就有一些所谓的“环境变量” 需要来读入系统中了。这些环境变量例如 PATH、HOME、MAIL、SHELL 等等,都是很重要的, 为了区别与自定义变量的不同,环境变量通常以大写字符来表示。

  变量: 变量就是以一组文字或符号等,来取代一些设置或者是一串保留的数据, 例如:我设置了“myname”就是“VBird”,所以当你读取 myname 这个变量的时候,系统自然就会知道那就是 VBird,那么如何“显示变量”呢?这就需要使用到 echo 这个指令。

2.1 变量的使用与设置:echo;变量设置规则, unset

变量的使用: echo

[dmtsai@study ~]$ echo $variable

范例:
[dmtsai@study ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
[dmtsai@study ~]$ echo ${PATH}  #或这种

  利用 echo 就能够读出,只是需要在变量名称前面加上 $ , 或者是以 ${变量} 的方式来取用都可以,当然 echo 的功能很多的, 我们这里单纯是拿 echo 来读出变量的内容而已,更多的 echo 使用,请自行 man echo 。

  用等号(=)连接变量与内容,举例来说: 我要将 myname 这个变量名称的内容设置为 VBird ,那么:

[dmtsai@study ~]$ echo ${myname}
       <==这里并没有任何数据~因为这个变量尚未被设置!是空的!
[dmtsai@study ~]$ myname=VBird
[dmtsai@study ~]$ echo ${myname}
VBird  <==出现了!因为这个变量已经被设置了!

变量的设置规则
1. 变量与变量内容以一个等号“=”来链接,如下所示:
  myname=VBird

2. 等号两边不能直接接空白字符,如下所示为错误:
  myname = VBird 或 myname=VBird Tsai

3. 变量名称只能是英文字母与数字,但是开头字符不能是数字,如下为错误:
  2myname=VBird

4. 变量内容若有空白字符可使用双引号“"”或单引号“'”将变量内容结合起来,但

  • 双引号内的特殊字符如 $ 等,可以保有原本的特性,如下所示:

   var="lang is $LANG"   当 echo $var 可得 lang is zh_TW.UTF-8

  • 单引号内的特殊字符则仅为一般字符 (纯文本),如下所示:

   var='lang is $LANG'    则 echo $var可得 lang is $LANG

5. 可用转义字符 “ \ ” 将特殊符号(如 [Enter], $, \, 空白字符, 等)变成一般字符,如:
  myname=VBird\ Tsai

6. 在一串指令的执行中,还需要借由其他额外的指令所提供的信息时,可以使用反单引号“ `指令` ”或 “ $(指令)”。特别注意,那个 ` 是键盘上方的数字键 1 左边那个按键,而不是单引号! 例如想要取得核心版本的设置:

  version=$(uname -r)echo $version 可得  3.10.0-229.el7.x86_64

7. 若该变量为扩增变量内容时,则可用 "$变量名称" 或 ${变量} 累加内容,如下所示:
  PATH="$PATH":/home/bin”  或   PATH=${PATH}:/home/bin

8. 若该变量需要在其他子程序执行,则需要以 export 来使变量变成环境变量:
  export PATH

  什么是“子程序”呢?就是说,在我目前这个 shell 的情况下,去启用另一个新的 shell ,新的那个 shell 就是子程序。在一般的状态下,父程序的自订变量是无法在子程序内使用的,但是通过 export 将变量变成环境变量后,就能够在子程序下面应用了。

9. 通常大写字符为系统默认变量,自行设置变量可以使用小写字符,方便判断 (纯粹依照用户兴趣) ;

10. 取消变量的方法为使用 unset :“unset 变量名称”例如取消 myname 的设置:
  unset myname

范例:如何进入到目前核心的模块目录?
[dmtsai@study ~]$ cd /lib/modules/`uname -r`/kernel
[dmtsai@study ~]$ cd /lib/modules/$(uname -r)/kernel  # 以此例较佳!

上面的指令可以说是作了两次动作,亦即是:

  • 先进行反单引号内的动作“uname -r”并得到核心版本为 3.10.0-229.el7.x86_64
  • 将上述的结果带入原指令,故得指令为:“cd /lib/modules/3.10.0-229.el7.x86_64/kernel/

例题:
  在指令下达的过程中,反单引号( ` )这个符号代表的意义?
答:
  在一串指令中,在 ` 之内的指令将会被先执行,而其执行出来的结果将做为外部的输入信息!例如 uname -r 会显示出目前的核心版本,而我们的核心版本在 /lib/modules 里面,因此,你可以先执行 uname -r 找出核心版本,然后再以“ cd 目录”到该目录下,当然也可以执行如同上面范例六的执行内容啰。

另外再举个例子,我们也知道, locate 指令可以列出所有的相关文件文件名,但是,如果我想要知道各个文件的权限呢?举例来说,我想要知道每个 crontab 相关文件名的权限:
[dmtsai@study ~]$ ls -ld `locate crontab`
[dmtsai@study ~]$ ls -ld $(locate crontab)
如此一来,先以 locate 将文件名数据都列出来,再以 ls 指令来处理的意思。例题:

  若你有一个常去的工作目录名称为:“/cluster/server/work/taiwan_2015/003/”,如何进行该目录的简化?
答: 
[dmtsai@study ~]$ work="/cluster/server/work/taiwan_2015/003/"
[dmtsai@study ~]$ cd $work
未来我想要使用其他目录作为我的模式工作目录时,只要变更 work 这个变量即可。

 2.2 环境变量

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

范例一:列出目前的 shell 环境下的所有环境变量与其内容。
[dmtsai@study ~]$ env
HOSTNAME=study.centos.vbird    <== 主机名称
TERM=xterm                     <== 这个终端使用的环境是什么类型
SHELL=/bin/bash                <== 目前这个环境下,使用的 Shell 是哪一个程序?
HISTSIZE=1000                  <== “记录历史命令数”在 CentOS 默认可记录 1000 笔
OLDPWD=/home/dmtsai            <== 上一个工作目录的所在
LC_ALL=en_US.utf8              <== 语系
USER=dmtsai                    <== 用户名
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:
or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:
*.tar=01...                    <== 一些颜色显示
MAIL=/var/spool/mail/dmtsai    <== 用户的 mailbox 位置
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
PWD=/home/dmtsai               <== 目前用户所在的工作目录 (利用 pwd 读取)
LANG=zh_TW.UTF-8               <== 与语系有关
HOME=/home/dmtsai              <== 这个用户的主文件夹
LOGNAME=dmtsai                 <== 登陆者用来登陆的帐号名称
_=/usr/bin/env                 <== 上一次使用的指令的最后一个参数(或指令本身)

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

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

  bash 可不只有环境变量,还有一些与 bash 操作接口有关的变量,以及用户自定义的变量存在的。 那么这些变量如何查看呢?这个时候就得要使用 set 这个指令了。

[dmtsai@study ~]$ set
BASH=/bin/bash                        <== bash 的主程序放置路径
BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
BASH_VERSION='4.2.46(1)-release'      <== 这两行是 bash 的版本
COLUMNS=90                            <== 在目前的终端机环境下,使用的字段有几个字符长度
HISTFILE=/home/dmtsai/.bash_history   <== 历史命令记录的放置文件,隐藏文件
HISTFILESIZE=1000                     <== 存起来(与上个变量有关)的文件之指令的最大记录笔数。
HISTSIZE=1000                         <== 目前环境下,内存中记录的历史命令最大笔数。
IFS=$' \t\n'                          <== 默认的分隔符号
LINES=20                              <== 目前的终端机下的最大行数
MACHTYPE=x86_64-redhat-linux-gnu      <== 安装的机器类型
OSTYPE=linux-gnu                      <== 操作系统的类型
PS1='[\u@\h \W]\$ '                   <== PS1 就厉害了。这个是命令提示字符,也就是我们常见的
                                          [root@www ~]# 或 [dmtsai ~]$ 的设置值啦!可以更动的。
PS2='> '                              <== 如果你使用转义符号 (\) 第二行以后的提示字符
$                                     <== 目前这个 shell 所使用的 PID
?                                     <== 刚刚执行完指令的返回值。
...  #还有,略  

  PS1 (数字的 1 ),这个东西就是“命令提示字符”。当我们每次按下 [Enter] 按键去执行某个指令后,最后要再次出现提示字符时, 就会主动去读取这个变量值了。 PS1 内显示的是一些特殊符号,这些特殊符号可以显示不同的信息, 每个 distributions 的 bash 默认的 PS1 变量内容可能有些许的差异。一些符号意义:

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

  范例里的 PS1 的内容是 [\u@\h \W]\$  ,现在知道为何命令提示字符是: [dmtsai@study ~]$  了吧。

  

  (本 shell 的 PID,Process ID), echo $$  出现的数字就是 PID

  (上个执行指令的返回值),当我们执行某些指令时, 这些指令都会返回一个执行后的代码。一般来说,如果成功的执行该指令, 则会返回一个 0 ,如果执行过程发生错误,就会返回错误代码。

[dmtsai@study ~]$ echo $SHELL
/bin/bash                                  <==可顺利显示!没有错误!

[dmtsai@study ~]$ echo $?
0                                          <==因为没问题,所以返回值为 0

[dmtsai@study ~]$ 12name=VBird
bash: 12name=VBird: command not found...   <==发生错误了!bash返回有问题
[dmtsai@study ~]$ echo $?
127                                        <==因为有问题,返回错误代码(非0)
# 错误代码返回值依据软件而有不同,我们可以利用这个代码来搜寻错误的原因

[dmtsai@study ~]$ echo $?
0
# 咦!怎么又变成正确了?这是因为 "?" 只与“上一个执行指令”有关,
# 所以,我们上一个指令是执行“ echo $? ”,当然没有错误,所以是 0 没错!

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

  env 与 set 有所谓的环境变量与自定义变量,那么这两者之间有啥差异呢?其实这两者的差异在于该变量是否会被子程序所继续引用。当你登陆 Linux 并取得一个 bash 之后,你的 bash 就是一个独立的程序,这个程序的识别使用的是一个称为程序识别码,被称为 PID 。 接下来你在这个 bash 下面所下达的任何指令都是由这个 bash 所衍生出来的,那些被下达的指令就被称为子程序了。

  使用 export可以将自定义变量转成环境变量。ps:和c++声明全局变量一样的意思

[dmtsai@study ~]$ export 变量名称

  那如何将环境变量转成自定义变量呢?可以使用 declare 。

2.3 语系变量 locale

  目前大多数的 Linux distributions 已经都支持了Unicode,也都支持大部分的国家语系。 那么我们的 Linux 到底支持了多少的语系呢?这可以由 locale 这个指令来查询。

[dmtsai@study ~]$ locale -a

  如何修改这些编码呢?其实可以通过下面这些变量:

[dmtsai@study ~]$ locale  <==后面不加任何选项与参数即可!
LANG=en_US                   <==主语言的环境
LC_CTYPE="en_US"             <==字符(文字)辨识的编码
LC_NUMERIC="en_US"           <==数字系统的显示信息
LC_TIME="en_US"              <==时间系统的显示数据
LC_COLLATE="en_US"           <==字串的比较与排序等
LC_MONETARY="en_US"          <==币值格式的显示等
LC_MESSAGES="en_US"          <==信息显示的内容,如功能表、错误讯息等
LC_ALL=                      <==整体语系的环境
....(后面省略)....

  基本上,你可以逐一设置每个与语系有关的变量数据,但事实上,如果其他的语系变量都未设置, 且你有设置 LANG 或者是 LC_ALL 时,则其他的语系变量就会被这两个变量所取代!

  语系文件都放置在: /usr/lib/locale/ 这个目录中。

  系统默认的语系定义在 /etc/locale.conf 

2.4 变量键盘读取、数组与声明:read、array、declare

read

  要读取来自键盘输入的变量,就是用 read 这个指令了。这个指令最常被用在 shell script 的编写当中, 想要用户互动?用这个指令就对了。

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

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

范例二:提示使用者 30 秒内输入自己的大名,将该输入字串作为名为 named 的变量内容
[dmtsai@study ~]$ read -p "Please keyin your name: " -t 30 named
Please keyin your name: VBird Tsai   <==注意看,会有提示字符喔!
[dmtsai@study ~]$ echo ${named}
VBird Tsai        <==输入的数据又变成一个变量的内容了!

 declare / typeset

   declare 或 typeset 是一样的功能,就是在“宣告变量的类型”。如果使用 declare 后面并没有接任何参数,那么 bash 就会主动的将所有的变量名称与内容通通列出,就好像使用 set 一样。

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

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

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

  • 变量类型默认为“字符串”,所以若不指定变量类型,则 1+2 为一个“字串”而不是“计算式”。 所以上述第一个执行的结果才会出现那个情况的;
  • bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0;
范例二:将 sum 变成环境变量
[dmtsai@study ~]$ declare -x sum
[dmtsai@study ~]$ export | grep sum
declare -ix sum="450"  <==果然出现了!包括有 i 与 x 的声明!

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

范例四:让 sum 变成自定义变量
[dmtsai@study ~]$ declare +x sum  <== 将 - 变成 + 可以进行“取消”动作
[dmtsai@study ~]$ declare -p sum  <== -p 可以单独列出变量的类型
declare -ir sum="450" <== 看吧!只剩下 i, r 的类型,不具有 x

  declare 也是个很有用的功能~尤其是当我们需要使用到下面的数组功能时, 他也可以帮我们声明数组的属性。如果你不小心将变量设置为“只读”,通常得要注销再登陆才能复原该变量的类型。

数组(array) 变量类型

  数组的设置方式是:var[index]=content

范例:设置 var[1] ~ var[3] 的变量。
[dmtsai@study ~]$ var[1]="small min"
[dmtsai@study ~]$ var[2]="big min"
[dmtsai@study ~]$ var[3]="nice min"
[dmtsai@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min

  一般来说,建议直接以  ${数组}  的方式来读取。

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

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

[dmtsai@study ~]$ ulimit [-SHacdfltu] [配额]
选项与参数:
-H  :hard limit ,严格的设置,必定不能超过这个设置的数值;
-S  :soft limit ,警告的设置,可以超过这个设置值,但是若超过则有警告讯息。
      在设置上,通常 soft 会比 hard 小,举例来说,soft 可设置为 80 而 hard 
      设置为 100,那么你可以使用到 90 (因为没有超过 100),但介于 80~100 之间时,
      系统会有警告讯息通知你!
-a  :后面不接任何选项与参数,可列出所有的限制额度;
-c  :当某些程序发生错误时,系统可能会将该程序在内存中的信息写成文件(除错用),
      这种文件就被称为核心文件(core file)。此为限制每个核心文件的最大容量。
-f  :此 shell 可以创建的最大文件大小(一般可能设置为 2GB)单位为 KBytes
-d  :程序可使用的最大段内存(segment)容量;
-l  :可用于锁定 (lock) 的内存量
-t  :可使用的最大 CPU 时间 (单位为秒)
-u  :单一使用者可以使用的最大程序(process)数量。

  单一 filesystem 能够支持的单一文件大小与 block 的大小有关。但是文件系统的限制容量都允许的太大了!如果想要让使用者创建的文件不要太大时,可以考虑用 ulimit 来限制使用者可以创建的文件大小。

2.6 变量内容的删除、取代与替换

 变量内容的删除与替换

范例一:先让小写的 path 自订变量设置的与 PATH 内容相同
[dmtsai@study ~]$ path=${PATH}
[dmtsai@study ~]$ echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

范例二:删除 local/bin,所以要将前 1 个目录删除掉,如何显示?
[dmtsai@study ~]$ echo ${path#/*local/bin:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

 


范例三:我想要删除前面所有的目录,仅保留最后一个目录
[dmtsai@study ~]$ echo ${path#/*:}
/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 由于一个 # 仅删除掉最短的那个,因此他删除的情况可以用下面的删除线来看:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

[dmtsai@study ~]$ echo ${path##/*:}
/home/dmtsai/bin
# 嘿!多加了一个 # 变成 ## 之后,他变成“删除掉最长的那个数据”!亦即是:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

 

范例四:我想要删除最后面那个目录,亦即从 : 到 bin 为止的字串
[dmtsai@study ~]$ echo ${path%:*bin}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin
# 注意啊!最后面一个目录不见去!
# 这个 % 符号代表由最后面开始向前删除!所以上面得到的结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

范例五:那如果我只想要保留第一个目录呢?
[dmtsai@study ~]$ echo ${path%%:*bin}
/usr/local/bin
# 同样的, %% 代表的则是最长的符合字串,所以结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin

 

范例六:将 path 的变量内容内的 sbin 取代成大写 SBIN:
[dmtsai@study ~]$ echo ${path/sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 这个部分就容易理解的多了!关键字在于那两个斜线,两斜线中间的是旧字串
# 后面的是新字串,所以结果就会出现如上述的特殊字体部分

[dmtsai@study ~]$ echo ${path//sbin/SBIN}
/usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 如果是两条斜线,那么就变成所有符合的内容都会被取代

 

变量的测试与内容替换

  在某些时刻我们常常需要“判断”某个变量是否存在,若变量存在则使用既有的设置,若变量不存在则给予一个常用的设置。

范例一:测试一下是否存在 username 这个变量,若不存在则给予 username 内容为 root
[dmtsai@study ~]$ echo ${username}
           <==由于出现空白,所以 username 可能不存在,也可能是空字串
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
root       <==因为 username 没有设置,所以主动给予名为 root 的内容。
[dmtsai@study ~]$ username="vbird tsai" <==主动设置 username 的内容
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
vbird tsai <==因为 username 已经设置了,所以使用旧有的设置而不以 root 取代

 

范例二:若 username 未设置或为空字串,则将 username 内容设置为 root
[dmtsai@study ~]$ username=""
[dmtsai@study ~]$ username=${username-root}
[dmtsai@study ~]$ echo ${username}
      <==因为 username 被设置为空字串了!所以当然还是保留为空字串!
[dmtsai@study ~]$ username=${username:-root}
[dmtsai@study ~]$ echo ${username}
root  <==加上“ : ”后若变量内容为空或者是未设置,都能够以后面的内容替换!

  在大括号内有没有冒号“ : ”的差别是很大的!加上冒号后,被测试的变量未被设置或者是已被设置为空字串时, 都能够用后面的内容 (本例中是使用 root 为内容) 来替换与设置。

3. 命令别名与历史命令

3.1 命令别名设置: alias, unalias

  举个例子来说,如果你要查询隐藏文件,并且需要长的列出与一页一页翻看,那么需要下达“ ls -al | more ”这个指令,觉得很烦。 要输入好几个单词,那可不可以使用 lm 来简化呢?当然可以,你可以在命令列下面下达:

[dmtsai@study ~]$ alias lm='ls -al | more'

  立刻多出了一个可以执行的指令,这个指令名称为 lm ,且其实他是执行 ls -al | more 。不过, 要注意的是:“alias 的定义规则与变量定义规则几乎相同”, 所以你只要在 alias 后面加上你的 {“别名”='指令 选项...' }, 以后你只要输入 lm 就相当于输入了 ls -al|more 这一串指令!很方便吧!

  另外,命令别名的设置还可以取代已有的指令。举例来说,我们知道 root 可以移除 (rm) 任何数据!所以当你以 root 的身份在进行工作时,需要特别小心, 但是总有失手的时候,那么 rm 提供了一个选项来让我们确认是否要移除该文件,那就是 -i 这个选项!所以,你可以这样做:

[dmtsai@study ~]$ alias rm='rm -i'

  那么如何知道目前有哪些的命令别名呢?就直接使用 alias 。

[dmtsai@study ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias lm='ls -al | more'
alias ls='ls --color=auto'
alias rm='rm -i'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

   由上面的数据当中,你也会发现一件事情,我们 vim 程序编辑器 里面提到 vi 与 vim 是不太一样的,vim 可以多作一些额外的语法检验与颜色显示。一般用户会有 vi=vim 的命令别名,但是 root 则是单纯使用 vi 而已。 如果你想要使用 vi 就直接以 vim 来打开文件的话,使用“ alias vi='vim' ”这个设置即可。

   至于如果要取消命令别名的话,那么就使用 unalias 吧!例如要将刚刚的 lm 命令别名拿掉,就使用:

[dmtsai@study ~]$ unalias lm

3.2 历史命令:history

[dmtsai@study ~]$ history [n]
[dmtsai@study ~]$ history [-c]
[dmtsai@study ~]$ history [-raw] histfiles
选项与参数:
n   :数字,意思是“要列出最近的 n 笔命令列表”的意思!
-c  :将目前的 shell 中的所有 history 内容全部消除
-a  :将目前新增的 history 指令新增入 histfiles 中,若没有加 histfiles ,
      则默认写入 ~/.bash_history
-r  :将 histfiles 的内容读到目前这个 shell 的 history 记忆中;
-w  :将目前的 history 记忆内容写入 histfiles 中!

  在正常的情况下,历史命令的读取与记录是这样的:

  • 当我们以 bash 登陆 Linux 主机之后,系统会主动的由主文件夹的 ~/.bash_history 读取以前曾经下过的指令,那么 ~/.bash_history 会记录几条数据呢?这就与你 bash 的 HISTFILESIZE 这个变量设置值有关了;
  • 假设我这次登陆主机后,共下达过 100 次指令,“等我注销时, 系统就会将 101~1100 这总共 1000 条历史命令更新到 ~/.bash_history 当中。” 也就是说,历史命令在我登出时,会将最近的 HISTFILESIZE 条记录到我的记录文件当中;
  • 当然,也可以用 history -w 强制立刻写入的。那为何用“更新”两个字呢? 因为 ~/.bash_history 记录的笔数永远都是 HISTFILESIZE 那么多,旧的信息会被主动的删除,仅保留最新的!

  那么 history 这个历史命令只可以让我查询命令而已吗?当然不止,我们可以利用相关的功能来帮我们执行命令,举例来说:

[dmtsai@study ~]$ !number
[dmtsai@study ~]$ !command
[dmtsai@study ~]$ !!
选项与参数:
number  :执行第几笔指令的意思;
command :由最近的指令向前搜寻“指令串开头为 command”的那个指令,并执行;
!!      :就是执行上一个指令(相当于按↑按键后,按 Enter)

[dmtsai@study ~]$ history
   66  man rm
   67  alias
   68  man history
   69  history 
[dmtsai@study ~]$ !66  <==执行第 66 笔指令
[dmtsai@study ~]$ !!   <==执行上一个指令,本例中亦即 !66 
[dmtsai@study ~]$ !al  <==执行最近以 al 为开头的指令(上头列出的第 67 个)

同一帐号同时多次登陆的 history 写入问题

  有些人喜欢同时开好几个 bash 接口,这些 bash 的身份都是 root 。 这样会有 ~/.bash_history 的写入问题吗?想一想,因为这些 bash 在同时以 root 的身份登陆, 因此所有的 bash 都有自己的 1000 笔记录在内存中。因为等到登出时才会更新记录文件,所以, 最后登出的那个 bash 才会是最后写入的数据。如此一来其他 bash 的指令操作就不会被记录下来了 (其实有被记录,只是被后来的最后一个 bash 所覆盖更新了) 。

  由于多重登陆有这样的问题,所以很多人都习惯单一 bash 登陆,再用工作控制 (job control, 之后会介绍) 来切换不同工作! 这样才能够将所有曾经下达过的指令记录下来,也才方便未来系统管理员进行指令的 debug 。

无法记录时间

   历史命令还有一个问题,那就是无法记录指令下达的时间。由于这 1000 笔历史命令是依序记录的, 但是并没有记录时间,所以在查询方面会有一些不方便。如果有需要,其实可以通~/.bash_logout 来进行 history 的记录,并加上 date 来增加时间参数。

4. Bash Shell 的操作环境

4.1 路径与命令查找顺序

  我们曾谈过“相对路径与绝对路径”的关系, 在本章的前几小节也谈到了 alias 与 bash 的内置命令。现在我们知道系统里面其实有不少的 ls 指令, 或者是包括内置的 echo 指令,那么来想一想,如果一个指令 (例如 ls) 被下达时, 到底是哪一个 ls 被拿来运行?很有趣吧!基本上,指令运行的顺序可以这样看:

  •  以相对/绝对路径执行指令,例如“ /bin/ls ”或“ ./ls ”;
  • 由 alias 找到该指令来执行;
  • 由 bash 内置的 (builtin) 指令来执行;
  • 通过 $PATH 这个变量的顺序查找到的第一个指令来执行。

  举例来说,你可以下达 /bin/ls 及单纯的 ls 看看,会发现使用 ls 有颜色但是 /bin/ls 则没有颜色。 因为 /bin/ls 是直接取用该指令来下达,而 ls 会因为“ alias ls='ls --color=auto' ”这个命令别名而先使用! 如果想要了解指令搜寻的顺序,其实通过 type -a ls 也可以查询的到。

范例:设置 echo 的命令别名成为 echo -n ,然后再观察 echo 执行的顺序
[dmtsai@study ~]$ alias echo='echo -n'
[dmtsai@study ~]$ type -a echo
echo is aliased to `echo -n'
echo is a shell builtin
echo is /usr/bin/echo
#先 alias 再 builtin 再由 $PATH 找到 /bin/echo 

4.2 bash的登录与欢迎信息:/etc/issue、/etc/motd

  bash 也有登录画面与欢迎信息?还记得在终端界面 (tty1 ~ tty6) 登陆的时候,会有几行提示的字串吗?那就是登录画面,那个字串写在 /etc/issue 里面。

[dmtsai@study ~]$ cat /etc/issue
\S
Kernel \r on an \m

   就如同 $PS1 这变量一样,issue 这个文件的内容也是可以使用反斜线作为变量取用。你可以 man issue 配合 man agetty 得到下面的结果:

 

 

   除了 /etc/issue 之外还有个 /etc/issue.net ,这个是提供给 telnet 这个远端登陆程序用的。 当我们使用 telnet 连接到主机时,主机的登陆画面就会显示 /etc/issue.net 而不是 /etc/issue 。

  如果想要让用户登陆后收到一些信息,例如想要让大家都知道的信息, 那么可以将信息加入 /etc/motd 里面去!例如:当登陆后,告诉登陆者, 系统将会在某个固定时间进行维护工作,可以这样做 (要用 root 的身份才能修改)

#范例:root
[root@study ~]# vim /etc/motd
Hello everyone,
Our server will be maintained at 2015/07/10 0:00 ~ 24:00.
Please don't login server at that time. ^_^

#范例:用户包括root
Last login: Wed Jul  8 23:22:25 2015 from 127.0.0.1
Hello everyone,
Our server will be maintained at 2015/07/10 0:00 ~ 24:00.
Please don't login server at that time. ^_^

 4.3 bash 的环境配置文件

  系统有一些环境设置文件的存在,让 bash 在启动时直接读取这些配置文件,以规划好 bash 的操作环境。而这些配置文件又可以分为全体系统的配置文件以及使用者个人偏好配置文件。要注意的是, 我们前几个小节谈到的命令别名、自定义变量,在你注销 bash 后就会失效,所以你想要保留你的设置, 就得要将这些设置写入配置文件才行。

login 与 non-login shell

  • login shell:取得 bash 时需要完整的登陆流程的,就称为 login shell。举例来说,你要由 tty1 ~ tty6 登陆,需要输入使用者的帐号与密码,此时取得的 bash 就称为“ login shell ”;
  • non-login shell:取得 bash 接口的方法不需要重复登陆的操作,举例来说,(1)你以 X window 登陆 Linux 后, 再以 X 的图形化接口启动终端机,此时那个终端接口并没有需要再次的输入帐号与密码,那个 bash 的环境就称为 non-login shell了。(2)你在原本的 bash 环境下再次下达 bash 这个指令,同样的也没有输入帐号密码, 那第二个 bash (子程序) 也是 non-login shell 。

 

  为什么要介绍 login, non-login shell 呢?这是因为这两个取得 bash 的情况中,读取的配置文件数据并不一样所致。 由于我们需要登陆系统,所以先谈谈 login shell 会读取哪些配置文件?一般来说,login shell 其实只会读取这两个配置文件:

  • /etc/profile:这是系统整体的设置,你最好不要修改这个文件;
  • ~/.bash_profile 或 ~/.bash_login 或 ~/.profile:属于使用者个人设置,你要改自己的数据,就写入这里

 

 /etc/profile

  用 vim 去阅读一下这个文件的内容。这个配置文件可以利用用户的识别码 (UID) 来决定很多重要的变量数据, 这也是每个用户登陆取得 bash 时一定会读取的配置文件! 所以如果你想要帮所有用户设置整体环境,那就是改这里。不过,没事还是不要随便改这个文件。这个文件设置的变量主要有:

  • PATH:会依据 UID 决定 PATH 变量要不要含有 sbin 的系统指令目录;
  • MAIL:依据帐号设置好使用者的 mailbox 到 /var/spool/mail/帐号名;
  • USER:根据使用者的帐号设置此一变量内容;
  • HOSTNAME:依据主机的 hostname 指令决定此一变量内容;
  • HISTSIZE:历史命令记录笔数。CentOS 7.x 设置为 1000 ;
  • umask:包括 root 默认为 022 而一般用户为 002 等。

  /etc/profile 可不止会做这些事,他还会去调用外部的设置数据。在 CentOS 7.x 默认的情况下,下面这些数据会依序的被调用进来:

  • /etc/profile.d/*.sh

  其实这是个目录内的众多文件!只要在 /etc/profile.d/ 这个目录内且扩展名为 .sh ,另外,使用者能够具有 r 的权限, 那么该文件就会被 /etc/profile 调用进来。在 CentOS 7.x 中,这个目录下面的文件规范了 bash 操作接口的颜色、 语系、ll 与 ls 指令的命令别名、vi 的命令别名、which 的命令别名等等。如果你需要帮所有使用者设置一些共享的命令别名时, 可以在这个目录下面自行创建扩展名为 .sh 的文件,并将所需要的数据写入即可。

  • /etc/locale.conf

  这个文件是由 /etc/profile.d/lang.sh 调用进来的,这也是我们决定 bash 默认使用何种语系的重要配置文件! 文件里最重要的就是 LANG/LC_ALL 这些个变量的设置。我们在前面的 locale 讨论过这个文件。

  • /usr/share/bash-completion/completions/*

  记得我们之前谈过 [tab] 的妙用吧?除了命令补齐、文件名补齐之外,还可以进行指令的选项/参数补齐功能,那就是从这个目录里面找到相对应的指令来处理的! 其实这个目录下面的内容是由 /etc/profile.d/bash_completion.sh 这个文件载入的。


~/.bash_profile

  bash 在读完了整体环境设置的 /etc/profile 并借此调用其他配置文件后,接下来则是会读取使用者的个人配置文件。 在 login shell 的 bash 环境中,所读取的个人偏好配置文件其实主要有三个,依序分别是:

  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile

  其实 bash 的 login shell 设置只会读取上面三个文件的其中一个, 而读取的顺序则是依照上面的顺序。也就是说,如果 ~/.bash_profile 存在,那么其他两个文件不论存不存在,都不会被读取。 如果 ~/.bash_profile 不存在才会去读取 ~/.bash_login,而前两者都不存在才会读取 ~/.profile 的意思。 会有这么多的文件,其实是因应其他 shell 转换过来的使用者的习惯而已。

[dmtsai@study ~]$ cat ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then    <==下面这三行在判断并读取 ~/.bashrc
        . ~/.bashrc
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin    <==下面这几行在处理个性化设置
export PATH

 最后,我们来看看整个 login shell 的读取流程:

 

 实线的的方向是主线流程,虚线的方向则是被调用的配置文件。


source :读入环境配置文件的命令

  由于 /etc/profile 与 ~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件,所以, 如果你将自己的偏好设置写入上述的文件后,通常都是得注销再登陆后,该设置才会生效。那么,能不能直接读取配置文件而不注销登陆呢? 可以的!那就得要利用 source 这个指令了!

[dmtsai@study ~]$ source 配置文件文件名

范例:将主文件夹的 ~/.bashrc 的设置读入目前的 bash 环境中
[dmtsai@study ~]$ source ~/.bashrc  <==下面这两个指令是一样的!
[dmtsai@study ~]$  .  ~/.bashrc

  利用 source 或小数点 (.) 都可以将配置文件的内容读进来目前的 shell 环境中。

  可用在不同的环境中调用不同的环境变量。


~/.bashrc (non-login shell 会读)

  谈完了 login shell 后,那么 non-login shell 这种非登陆情况取得 bash 操作接口的环境配置文件又是什么? 当你取得 non-login shell 时,该 bash 配置文件仅会读取 ~/.bashrc 而已。那么默认的 ~/.bashrc 内容是如何?

[root@study ~]# cat ~/.bashrc
# .bashrc

# User specific aliases and functions
alias rm='rm -i'             <==使用者的个人设置
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
if [ -f /etc/bashrc ]; then  <==整体的环境设置
        . /etc/bashrc
fi

  特别注意一下,由于 root 的身份与一般使用者不同,我们是以 root 的身份取得上述的数据, 如果是一般使用者的 ~/.bashrc 会有些许不同。看一下,你会发现在 root 的 ~/.bashrc 中其实已经规范了较为保险的命令别名了。CentOS 7.x 还会主动的调用 /etc/bashrc 这个文件,为什么需要调用 /etc/bashrc 呢? 因为 /etc/bashrc 帮我们的 bash 定义出下面的数据:

  • 依据不同的 UID 规范出 umask 的值;
  • 依据不同的 UID 规范出提示字符 (就是 PS1 变量);
  • 调用 /etc/profile.d/*.sh 的设置

  要注意的是,这个 /etc/bashrc 是 CentOS 特有的 (其实是 Red Hat 系统特有的),其他不同的 distributions 可能会放置在不同的文件名。由于这个 ~/.bashrc 会调用 /etc/bashrc 及 /etc/profile.d/*.sh , 所以,万一你没有 ~/.bashrc (可能自己不小心将他删除了),那么你会发现你的 bash 提示字符可能会变成这个样子:

-bash-4.2$

  这是正常的,因为你并没有调用 /etc/bashrc 来规范 PS1 变量。而且这样的情况也不会影响你的 bash 使用。 如果你想要将命令提示字符捉回来,那么可以复制 /etc/skel/.bashrc 到你的主文件夹,再修订一下你所想要的内容, 并使用 source 去调用 ~/.bashrc ,那你的命令提示字符就会回来。


其他相关配置文件

  • /etc/man_db.conf

  对于系统管理员来说, 这是很重要的一个文件。这的文件的内容规范了使用 man 的时候, man page 的路径到哪里去寻找。也就是说这个文件规定了下达 man 的时候,该去哪里查看数据的路径设置。

  那么什么时候要来修改这个文件呢?如果你是以 tarball 的方式来安装你的数据,那么你的 man page 可能会放置在 /usr/local/softpackage/man 里头,那个 softpackage 是你的软件名称, 这个时候你就得以手动的方式将该路径加到 /etc/man_db.conf 里头,否则使用 man 的时候就会找不到相关的说明文档。

  • ~/.bash_history

  我们的历史命令就记录在这里,而这个文件能够记录几笔数据,则与 HISTFILESIZE 这个变量有关。每次登录 bash 后,bash 会先读取这个文件,将所有的历史指令读入内存, 因此,当我们登录 bash 后就可以查知上次使用过哪些指令。

  • ~/.bash_logout

  这个文件则记录了“当我登出 bash 后,系统再帮我做完什么动作后才离开”的意思。 你可以去读取一下这个文件的内容,默认的情况下,登出时, bash 只是帮我们清掉屏幕的信息而已。 

4.4 终端的环境设置: stty, set

   当在 tty1 ~ tty6 这六个命令行的终端(terminal) 环境中登录,登录的时候我们可以取得一些字符设置的功能。举例来说,我们可以利用退格键 (backspace,就是那个←符号的按键) 来删除命令列上的字符, 也可以使用 [ctrl]+c 来强制终止一个指令的运行,当输入错误时,就会有声音跑出来警告。这是怎么办到的呢? 这是因为登录终端的时候,会自动的取得一些终端的输入环境的设置。

  事实上,目前我们使用的 Linux distributions 都帮我们完成了很好的使用者环境了, 所以可以不用担心操作环境的问题。不过,在某些 Unix like 的机器中,还是可能需要一些操作, 才能够让我们的输入比较快乐~举例来说,利用[backspace] 删除,要比利用 [Del] 按键来的顺手吧! 但是某些 Unix 偏偏是以 [del] 来进行字符的删除,所以,这个时候就需要手动设置了。

  那么如何查阅目前的一些按键设置呢?可以利用 stty (setting tty 终端的意思)。

[dmtsai@study ~]$ stty [-a]
选项与参数:
-a  :将目前所有的 stty 参数列出来;

范例一:列出所有的按键与按键内容
[dmtsai@study ~]$ stty -a
speed 38400 baud; rows 20; columns 90; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
flush = ^O; min = 1; time = 0;
....(以下省略)....

 

   出现 ^ 表示 [Ctrl] 那个按键的意思。举例来说, intr = ^C 表示利用 [ctrl] + c 来达成的。几个重要的代表意义是:

  • intr : 送出一个 interrupt (中断) 的讯号给目前正在 run 的程序 (就是终止啰!);
  • quit : 送出一个 quit 的讯号给目前正在 run 的程序;
  • erase : 向后删除字符,
  • kill : 删除在目前命令行上的所有文字;
  • eof : End of file 的意思,代表“结束输入”。
  • start : 在某个程序停止后,重新启动他的 output
  • stop : 停止目前屏幕的输出;
  • susp : 送出一个 terminal stop 的讯号给正在 run 的程序。

   如果你想要用 [ctrl]+h 来进行字符的删除,那么可以下达:

[dmtsai@study ~]$ stty erase ^h 

 

 问:

在 windows 下面,很多软件默认的储存快捷按钮是 [crtl]+s ,所以我们习惯按这个按钮来处理。 不过,在 Linux 下面使用 vim 时,却也经常不小心就按下 [crtl]+s !问题来了,按下这个组合钮之后,整个 vim 就不能动了 (整个画面锁死)! 请问该如何处置?
答:
参考一下 stty -a 的输出中,有个 stop 的项目就是按下 [crtl]+s 的!那么恢复成 start 就是 [crtl]+q 。因此, 尝试按下 [crtl]+q 应该就可以让整个画面重新恢复正常。

   除了 stty 之外,其实bash 还有自己的一些终端设置值。那就是利用 set 来设置的! 我们之前提到一些变量时,可以利用 set 来显示,除此之外,其实 set 还可以帮我们设置整个指令输出/输入的环境。 例如记录历史命令、显示错误内容等等。

[dmtsai@study ~]$ set [-uvCHhmBx]
选项与参数:
-u  :默认不启用。若启用后,当使用未设置变量时,会显示错误讯息;
-v  :默认不启用。若启用后,在信息被输出前,会先显示信息的原始内容;
-x  :默认不启用。若启用后,在指令被执行前,会显示指令内容(前面有 ++ 符号)
-h  :默认启用。与历史命令有关;
-H  :默认启用。与历史命令有关;
-m  :默认启用。与工作管理有关;
-B  :默认启用。与刮号 [] 的作用有关;
-C  :默认不启用。若使用 > 等,则若文件存在时,该文件不会被覆盖。

 

范例一:显示目前所有的 set 设置值
[dmtsai@study ~]$ echo $-
himBH
# 那个 $- 变量内容就是 set 的所有设置啦! bash 默认是 himBH 喔!

范例二:设置 "若使用未定义变量时,则显示错误信息" 
[dmtsai@study ~]$ set -u
[dmtsai@study ~]$ echo $vbirding
-bash: vbirding: unbound variable
# 默认情况下,未设置/未宣告 的变量都会是“空的”,不过,若设置 -u 参数,
# 那么当使用未设置的变量时,就会有问题啦!很多的 shell 都默认启用 -u 参数。
# 若要取消这个参数,输入 set +u 即可!

范例三:执行前,显示该指令内容。
[dmtsai@study ~]$ set -x
++ printf '\033]0;%s@%s:%s\007' dmtsai study '~'    # 这个是在列出提示字符的控制码!
[dmtsai@study ~]$ echo ${HOME}
+ echo /home/dmtsai
/home/dmtsai
++ printf '\033]0;%s@%s:%s\007' dmtsai study '~'
# 看见没?要输出的指令都会先被打印到屏幕上,前面会多出 + 的符号!

 

   另外,其实我们还有其他的按键设置功能,就是在前一小节提到的 /etc/inputrc 这个文件里面设置。 还有例如 /etc/DIR_COLORS* 与 /usr/share/terminfo/* 等,也都是与终端有关的环境设置文件。 不过,事实上,并不建议您修改 tty 的环境,这是因为 bash 的环境已经设置的很友好了, 我们不需要额外的设置或者修改,否则反而会产生一些困扰。不过,写在这里的数据, 只是希望大家能够清楚的知道我们的终端是如何进行设置的 ^_^! 最后,我们将 bash 默认的组合键汇整如下:

 

 

4.5 通配符与特殊符号

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

 

[dmtsai@study ~]$ LANG=C              <==由于与编码有关,先设置语系一下

范例一:找出 /etc/ 下面以 cron 为开头的文件名
[dmtsai@study ~]$ ll -d /etc/cron*    <==加上 -d 是为了仅显示目录而已

范例二:找出 /etc/ 下面文件名“刚好是五个字母”的文件名
[dmtsai@study ~]$ ll -d /etc/?????    <==由于 ? 一定有一个,所以五个 ? 就对了

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

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

范例五:将范例四找到的文件复制到 /tmp/upper 中
[dmtsai@study ~]$ mkdir /tmp/upper; cp -a /etc/[^a-z]* /tmp/upper

 

   bash 环境中的特殊符号有哪些?

 

ps:因为我看的是台湾的版本,所以解释一下。 注解 = 注释;  跳脱符号= 转义符;  万用字符=通配符;  管线=管道;  置换=替换。

5. 数据流重导向

5.1 数据流重导向

  一般来说,如果你要执行一个指令,通常他会是这样的:

 

   我们执行一个指令的时候,这个指令可能会由文件读入数据,经过处理之后,再将数据输出到屏幕上。 在上图当中, standard output 与 standard error output 分别代表“标准输出 (STDOUT)”与“标准错误输出 (STDERR)”, 这两个玩意儿默认都是输出到屏幕上面来的。


standard output 与 standard error output

  简单的说,标准输出指的是“指令执行所返回的正确信息”,而标准错误输出可理解为“ 指令执行失败后,所返回的错误信息”。举个简单例子来说,我们的系统默认有 /etc/crontab 但却无 /etc/vbirdsay, 此时若下达“ cat /etc/crontab /etc/vbirdsay ”这个指令时,cat 会进行:

  • 标准输出:读取 /etc/crontab 后,将该文件内容显示到屏幕上;
  • 标准错误输出:因为无法找到 /etc/vbirdsay,因此在屏幕上显示错误信息

  不管正确或错误的数据都是默认输出到屏幕上,所以屏幕当然是乱乱的!那能不能通过某些机制将这两股数据分开呢? 这就是数据流重导向的功能啊!数据流重导向可以将 standard output (简称 stdout) 与 standard error output (简称 stderr) 分别传送到其他的文件或设备去,而分别传送所用的特殊字符则如下所示:

  1. 标准输入  (stdin)  :代码为 0 ,使用 < 或 << ;
  2. 标准输出  (stdout):代码为 1 ,使用 > 或 >> ;
  3. 标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ;

为了理解 stdout 与 stderr ,我们先来进行一个范例的练习:

范例一:观察你的系统根目录 (/) 下各目录的文件名、权限与属性,并记录下来
[dmtsai@study ~]$ ll /  <==此时屏幕会显示出文件名信息

[dmtsai@study ~]$ ll / > ~/rootfile <==屏幕并无任何信息
[dmtsai@study ~]$ ll  ~/rootfile <==有个新文件被创建了!
-rw-rw-r--. 1 dmtsai dmtsai 1078 Jul  9 18:51 /home/dmtsai/rootfile

  屏幕怎么会完全没有数据呢?这是因为原本“ ll / ”所显示的数据已经被重新导向到 ~/rootfile 文件中了! 那个 ~/rootfile 的文件名可以随便你取。如果你下达“ cat ~/rootfile ”那就可以看到原本应该在屏幕上面的数据。 如果我再次下达:“ ll /home > ~/rootfile ”后,那个 ~/rootfile 文件的内容变成什么? 他将变成“仅有 ll /home 的数据” 了。原本的“ ll / ”数据就不见了,这是因为该文件的创建方式是:

  • 该文件 (本例中是 ~/rootfile) 若不存在,系统会自动的将他创建起来,但是
  • 当这个文件存在的时候,那么系统就会先将这个文件内容清空,然后再将数据写入!
  • 也就是若以 > 输出到一个已存在的文件中,那个文件就会被覆盖掉。

  那如果我想要将数据累加而不想要将旧的数据删除,那该如何是好?利用两个大于的符号 (>>) 就好啦!以上面的范例来说,你应该要改成“ ll / >> ~/rootfile ”即可。 如此一来,当 (1) ~/rootfile 不存在时系统会主动创建这个文件;(2)若该文件已存在, 则数据会在该文件的最下方累加进去!

  上面谈到的是 standard output 的正确数据,那如果是 standard error output 的错误数据呢?那就通过 2> 及 2>> 。同样是覆盖 (2>) 与累加 (2>>) 的特性!我们在刚刚才谈到 stdout 代码是 1 而 stderr 代码是 2 , 所以这个 2> 是很容易理解的,而如果仅存在 > 时,则代表默认的代码 1 。也就是说:

  • 1> :以覆盖的方法将“正确的数据”输出到指定的文件或设备上;
  • 1>>:以累加的方法将“正确的数据”输出到指定的文件或设备上;
  • 2> :以覆盖的方法将“错误的数据”输出到指定的文件或设备上;
  • 2>>:以累加的方法将“错误的数据”输出到指定的文件或设备上;

  要注意,“ 1>> ”以及“ 2>> ”中间是没有空格的!

范例二:利用一般身份帐号搜寻 /home 下面是否有名为 .bashrc 的文件存在
[dmtsai@study ~]$ find /home -name .bashrc <==身份是 dmtsai 喔!
find: '/home/arod': Permission denied    <== Standard error output
find: '/home/alex': Permission denied    <== Standard error output
/home/dmtsai/.bashrc                     <== Standard output


范例三:承范例二,将 stdout 与 stderr 分存到不同的文件去
[dmtsai@study ~]$ find /home -name .bashrc > list_right 2> list_error

  此时“屏幕上不会出现任何信息”!因为刚刚执行的结果中,有 Permission 的那几行错误信息都会跑到 list_error 这个文件中,至于正确的输出数据则会存到 list_right 这个文件中。


dev/null 垃圾桶黑洞设备与特殊写法

   想像一下,如果我知道错误信息会发生,所以要将错误信息忽略掉而不显示或储存呢? 这个时候黑洞设备 /dev/null 就很重要了。这个 /dev/null 可以吃掉任何导向这个设备的信息,将上述的范例修改一下:

范例四:承范例三,将错误的数据丢弃,屏幕上显示正确的数据
[dmtsai@study ~]$ find /home -name .bashrc 2> /dev/null
/home/dmtsai/.bashrc  <==只有 stdout 会显示到屏幕上, stderr 被丢弃了

  如果我要将正确与错误数据通通写入同一个文件去呢?这个时候就得要使用特殊的写法了:

范例五:将指令的数据全部写入名为 list 的文件中
[dmtsai@study ~]$ find /home -name .bashrc > list 2> list  <==错误
[dmtsai@study ~]$ find /home -name .bashrc > list 2>&1     <==正确
[dmtsai@study ~]$ find /home -name .bashrc &> list         <==正确

  上述第一行错误的原因是,由于两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。所以虽然最终 list 文件还是会产生,但是里面的数据排列就会怪怪的,而不是原本屏幕上的输出排序。 至于写入同一个文件的特殊语法如上所示,你可以使用 2>&1 也可以使用 &> 。


standard input : < 与 <<

  了解了 stderr 与 stdout 后,那么那个 < 又是什么?以最简单的说法来说, 那就是“将原本需要由键盘输入的数据,改由文件内容来取代”的意思。 我们先由下面的 cat 指令操作来了解一下什么叫做“键盘输入”:

范例六:利用 cat 指令来创建一个文件的简单流程
[dmtsai@study ~]$ cat > catfile
testing
cat file test
<==这里按下 [ctrl]+d 来离开

[dmtsai@study ~]$ cat catfile
testing
cat file test

  由于加入 > 在 cat 后,所以那个 catfile 会被主动的创建,而内容就是刚刚键盘上面输入的那两行数据了。 那我能不能用纯文本文件取代键盘的输入,也就是说,用某个文件的内容来取代键盘的敲击呢? 可以的!如下所示:

范例七:用 stdin 取代键盘的输入以创建新文件的简单流程
[dmtsai@study ~]$ cat > catfile < ~/.bashrc
[dmtsai@study ~]$ ll catfile ~/.bashrc
-rw-r--r--. 1 dmtsai dmtsai 231 Mar  6 06:06 /home/dmtsai/.bashrc
-rw-rw-r--. 1 dmtsai dmtsai 231 Jul  9 18:58 catfile
# 注意看,这两个文件的大小会一模一样!几乎像是使用 cp 来复制一般!

  这个常用在类似 mail 这种指令上。

  << 这个连续两个小于的符号,他代表的是“结束的输入字符”的意思!举例来讲:“我要用 cat 直接将输入的讯息输出到 catfile 中, 且当由键盘输入 eof 时,该次输入就结束”,那我可以这样做:

[dmtsai@study ~]$ cat > catfile << "eof"
> This is a test.
> OK now stop
> eof  <==输入这关键字,立刻就结束而不需要输入 [ctrl]+d

[dmtsai@study ~]$ cat catfile
This is a test.
OK now stop     <==只有这两行,不会存在关键字那一行!

 

  那么为何要使用命令输出重导向呢?

  • 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
  • 背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
  • 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
  • 一些执行命令的可能已知错误信息时,想以“ 2> /dev/null ”将他丢掉时;
  • 错误信息与正确信息需要分别输出时。

5.2 命令执行的判断依据: ; , &&, ||                                    

  在某些情况下,很多指令我想要一次输入去执行,而不想要分次执行时,该如何是好?基本上你有两个选择, 一个是通过之后要介绍的 shell script 撰写脚本去执行,一种则是通过下面的介绍来一次输入多重指令。


cmd ; cmd (不考虑指令相关性的连续指令下达)

  在某些时候,我们希望可以一次执行多个指令,例如在关机的时候我希望可以先执行两次 sync 同步化写入磁盘后才 shutdown 计算机,那可以这么做:

[root@study ~]# sync; sync; shutdown -h now

  在指令与指令中间利用分号 (;) 来隔开,这样一来,分号前的指令执行完后就会立刻接着执行后面的指令了。

  如果我想要在某个目录下面创建一个文件,也就是说,如果该目录存在的话, 那我才创建这个文件,如果不存在,那就算了。也就是说这两个指令彼此之间是有相关性的, 前一个指令是否成功的执行与后一个指令是否要执行有关!那就得动用到 && 或 || 。


 

$? (指令返回值) 与 && 或 ||

   每条指令都会有一个返回值,若前一个指令执行的结果为正确,在 Linux 下面会回传一个 $? = 0 的值。

范例一:使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 创建 /tmp/abc/hehe 
[dmtsai@study ~]$ ls /tmp/abc && touch /tmp/abc/hehe
ls: cannot access /tmp/abc: No such file or directory
# ls 很干脆的说明找不到该目录,但并没有 touch 的错误,表示 touch 并没有执行

[dmtsai@study ~]$ mkdir /tmp/abc
[dmtsai@study ~]$ ls /tmp/abc && touch /tmp/abc/hehe
[dmtsai@study ~]$ ll /tmp/abc
-rw-rw-r--. 1 dmtsai dmtsai 0 Jul  9 19:16 hehe

 

范例二:测试 /tmp/abc 是否存在,若不存在则予以创建,若存在就不作任何事情
[dmtsai@study ~]$ rm -r /tmp/abc                <==先删除此目录以方便测试
[dmtsai@study ~]$ ls /tmp/abc || mkdir /tmp/abc
ls: cannot access /tmp/abc: No such file or directory  <==真的不存在喔!
[dmtsai@study ~]$ ll -d /tmp/abc                  
drwxrwxr-x. 2 dmtsai dmtsai 6 Jul  9 19:17 /tmp/abca   <==结果出现了!有进行 mkdir

 

   如果你一再重复“ ls /tmp/abc || mkdir /tmp/abc ”,也不会出现重复 mkdir 的错误!这是因为 /tmp/abc 已经存在, 所以后续的 mkdir 就不会进行。

范例三:我不清楚 /tmp/abc 是否存在,但就是要创建 /tmp/abc/hehe 文件
[dmtsai@study ~]$ ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe

 

   Linux 下面的指令都是由左往右执行的,所以范例三有以下几种结果:

 

 

 例题:

  以 ls 测试 /tmp/vbirding 是否存在,若存在则显示 "exist" ,若不存在,则显示 "not exist"!
答:
  这又牵涉到逻辑判断的问题,如果存在就显示某个数据,若不存在就显示其他数据,那我可以这样做:
    ls /tmp/vbirding && echo "exist" || echo "not exist"
  意思是说,当 ls /tmp/vbirding 执行后,若正确,就执行 echo "exist" ,若有问题,就执行 echo "not exist" !那如果写成如下的状况会出现什么?
    ls /tmp/vbirding || echo "not exist" && echo "exist"
  这其实是有问题的,为什么呢?由上图的流程介绍我们知道指令是一个一个往后执行, 因此在上面的例子当中,如果 /tmp/vbirding 不存在时,他会进行如下动作:
  1. 若 ls /tmp/vbirding 不存在,因此回传一个非为 0 的数值;
  2. 接下来经过 || 的判断,发现前一个指令回传非为 0 的数值,因此,程序开始执行 echo "not exist" ,而 echo "not exist" 程序肯定可以执行成功,因此会回传一个 0 值给后面的指令;
  3. 经过 && 的判断,咦!是 0 啊!所以就开始执行 echo "exist" 。
  所以,第二个例子里面竟然会同时出现 not exist 与 exist。

 

  一般来说,假设判断式有三个,也就是:

    command1 && command2 || command3

 6. 管道命令 (pipe)

  就如同前面所说的, bash 命令执行的时候有输出的数据会出现! 那么如果这群数据必需要经过几道处理之后才能得到我们所想要的格式,应该如何来设置? 这就牵涉到管道命令的问题了 (pipe) ,管道命令使用的是“ | ”这个界定符号! 另外,管道命令与“连续下达命令”是不一样的。

  假设我们想要知道 /etc/ 下面有多少文件,那么可以利用 ls /etc 来查阅,不过, 因为 /etc 下面的文件太多,导致一口气就将屏幕塞满了~不知道前面输出的内容是啥?此时,我们可以通过 less 指令的协助,利用:

[dmtsai@study ~]$ ls -al /etc | less

  如此一来,使用 ls 指令输出后的内容,就能够被 less 读取,并且利用 less 的功能,我们就能够前后翻动相关的信息了。

  这个管道命令“ | ”仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的信息,对于 stdandard error 并没有直接处理的能力。那么整体的管道命令可以使用下图表示:

  在每个管道后面接的第一个数据必定是“指令”。而且这个指令必须要能够接受 standard input 的数据才行,这样的指令才可以是为“管道命令”,例如 less, more, head, tail 等都是可以接受 standard input 的管道命令。至于例如 ls, cp, mv 等就不是管道命令了!因为 ls, cp, mv 并不会接受来自 stdin 的数据。 也就是说,管线命令主要有两个比较需要注意的地方:

  • 管道命令仅会处理 standard output,对于 standard error output 会予以忽略
  • 管道命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。

  如果你硬要让 standard error 可以被管线命令所使用,那该如何处理?其实就是通过上一小节的数据流重导向即可! 让 2>&1 加入指令中~就可以让 2> 变成 1> 。

6.1 选取命令: cut, grep

  什么是选取命令?说白了,就是将一段数据经过分析后,取出我们所想要的。或者是经由分析关键字,取得我们所想要的那一行! 不过,要注意的是,一般来说,选取信息通常是针对“一行一行”来分析的, 并不是整篇信息分析~下面我们介绍两个很常用的信息选取命令:


cut

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

 

范例一:将 PATH 变量取出,我要找出第五个路径。
[dmtsai@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
#      1      |    2   |       3       |    4    |           5           |      6         |

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

范例二:将 export 输出的讯息,取得第 12 字符以后的所有字串
[dmtsai@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要“ declare -x ”时,就得这么做:

[dmtsai@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/dmtsai"
HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!

范例三:用 last 将显示的登陆者的信息中,仅留下使用者大名
[dmtsai@study ~]$ 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 可以输出“帐号/终端机/来源/日期时间”的数据,并且是排列整齐的

[dmtsai@study ~]$ last | cut -d ' ' -f 1
# 由输出的结果我们可以发现第一个空白分隔的字段代表帐号,所以使用如上指令:
# 但是因为 root   pts/1 之间空格有好几个,并非仅有一个,所以,如果要找出 
# pts/1 其实不能以 cut -d ' ' -f 1,2 喔!输出的结果会不是我们想要的。

  cut 主要的用途在于将“同一行里面的数据进行分解!”最常使用在分析一些数据或文字数据的时候! 这是因为有时候我们会以某些字符当作分区的参数,然后来将数据加以切割,以取得我们所需要的数据。 尤其是在分析 log 文件的时候,这个功能很实用。不过,cut 在处理多空格相连的数据时,可能会比较吃力一点,所以某些时刻可能会使用下一章的 awk 来取代的!


grep

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

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

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

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

范例四:取出 /etc/man_db.conf 内含 MANPATH 的那几行
[dmtsai@study ~]$ grep --color=auto 'MANPATH' /etc/man_db.conf
....(前面省略)....
MANPATH_MAP     /usr/games              /usr/share/man
MANPATH_MAP     /opt/bin                /opt/man
MANPATH_MAP     /opt/sbin               /opt/man
# 神奇的是,如果加上 --color=auto 的选项,找到的关键字部分会用特殊颜色显示!

   CentOS 7 当中,默认的 grep 已经主动加上 --color=auto 在 alias 内了。

6.2 排序命令: sort, wc, uniq

  举例来说, 使用 last 可以查得系统上面有登陆主机者的身份。那么我可以针对每个使用者查出他们的总登陆次数吗? 此时就得要排序与计算之类的指令来辅助了!下面我们介绍几个好用的排序与统计指令:


sort

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

[dmtsai@study ~]$ sort [-fbMnrtuk] [file or stdin]
选项与参数:
-f  :忽略大小写的差异,例如 A 与 a 视为编码相同;
-b  :忽略最前面的空白字符部分;
-M  :以月份的名字来排序,例如 JAN, DEC 等等的排序方法;
-n  :使用“纯数字”进行排序(默认是以文字体态来排序的);
-r  :反向排序;
-u  :就是 uniq ,相同的数据中,仅出现一行代表;
-t  :分隔符号,默认是用 [tab] 键来分隔;
-k  :以那个区间 (field) 来进行排序的意思
范例一:个人帐号都记录在 /etc/passwd 下,请将帐号进行排序。
[dmtsai@study ~]$ cat /etc/passwd | sort
abrt:x:173:173::/etc/abrt:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
alex:x:1001:1002::/home/alex:/bin/bash
# 鸟哥省略很多的输出~由上面的数据看起来, sort 是默认“以第一个”数据来排序,
# 而且默认是以“文字”型态来排序的喔!所以由 a 开始排到最后啰!

范例二:/etc/passwd 内容是以 : 来分隔的,我想以第三栏来排序,该如何?
[dmtsai@study ~]$ cat /etc/passwd | sort -t ':' -k 3
root:x:0:0:root:/root:/bin/bash
dmtsai:x:1000:1000:dmtsai:/home/dmtsai:/bin/bash
alex:x:1001:1002::/home/alex:/bin/bash
arod:x:1002:1003::/home/arod:/bin/bash
# 看到特殊字体的输出部分了吧?怎么会这样排列啊?呵呵!没错啦~
# 如果是以文字体态来排序的话,原本就会是这样,想要使用数字排序:
# cat /etc/passwd | sort -t ':' -k 3 -n
# 这样才行啊!用那个 -n 来告知 sort 以数字来排序啊!

范例三:利用 last ,将输出的数据仅取帐号,并加以排序
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort

uniq

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

[dmtsai@study ~]$ uniq [-ic]
选项与参数:
-i  :忽略大小写字符的不同;
-c  :进行计数
范例一:使用 last 将帐号列出,仅取出帐号栏,进行排序后仅取出一位;
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort | uniq

范例二:承上题,如果我还想要知道每个人的登陆总次数呢?
[dmtsai@study ~]$ last | cut -d ' ' -f1 | sort | uniq -c
      1
      6 (unknown
     47 dmtsai
      4 reboot
      7 root
      1 wtmp
# 从上面的结果可以发现 reboot 有 4 次, root 登陆则有 7 次!大部分是以 dmtsai 来操作!
# wtmp 与第一行的空白都是 last 的默认字符,那两个可以忽略的!

wc

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

[dmtsai@study ~]$ wc [-lwm]
选项与参数:
-l  :仅列出行;
-w  :仅列出多少字(英文单字);
-m  :多少字符;
范例一:那个 /etc/man_db.conf 里面到底有多少相关字、行、字符数?
[dmtsai@study ~]$ cat /etc/man_db.conf | wc 
    131     723    5171
# 输出的三个数字中,分别代表: “行、字数、字符数”

范例二:我知道使用 last 可以输出登陆者,但是 last 最后两行并非帐号内容,那么请问,
        我该如何以一行指令串取得登陆系统的总人次?
[dmtsai@study ~]$ last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | \
> grep -v 'unknown' |wc -l 
# 由于 last 会输出空白行, wtmp, unknown, reboot 等无关帐号登陆的信息,因此,我利用
# grep 取出非空白行,以及去除上述关键字那几行,再计算行数,就能够了解!

6.3 双向重导向: tee

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

 

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

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

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

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

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

6.4 字符转换命令: tr, col, join, paste, expand

  我们在 vim 程序编辑器当中,提到过 DOS 断行字符与 Unix 断行字符的不同,并且可以使用 dos2unix 与 unix2dos 来完成转换。好了,那么思考一下,是否还有其他常用的字符替代? 举例来说,要将大写改成小写,或者是将数据中的 [tab] 按键转成空白键?还有,如何将两篇信息整合成一篇? 下面我们就来介绍一下这些字符转换命令在管道当中的使用方法:


tr

  tr 可以用来删除一段信息当中的文字,或者是进行文字信息的替换。

[dmtsai@study ~]$ tr [-ds] SET1 ...
选项与参数:
-d  :删除信息当中的 SET1 这个字串;
-s  :取代掉重复的字符!

 

范例一:将 last 输出的讯息中,所有的小写变成大写字符:
[dmtsai@study ~]$ last | tr '[a-z]' '[A-Z]'
# 事实上,没有加上单引号也是可以执行的,如:“ last | tr [a-z] [A-Z] ”

范例二:将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
[dmtsai@study ~]$ cat /etc/passwd | tr -d ':'

范例三:将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
[dmtsai@study ~]$ cp /etc/passwd ~/passwd && unix2dos ~/passwd
[dmtsai@study ~]$ file /etc/passwd ~/passwd
/etc/passwd:         ASCII text
/home/dmtsai/passwd: ASCII text, with CRLF line terminators  <==就是 DOS 断行
[dmtsai@study ~]$ cat ~/passwd | tr -d '\r' > ~/passwd.linux
# 那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr
[dmtsai@study ~]$ ll /etc/passwd ~/passwd*
-rw-r--r--. 1 root   root   2092 Jun 17 00:20 /etc/passwd
-rw-r--r--. 1 dmtsai dmtsai 2133 Jul  9 22:13 /home/dmtsai/passwd
-rw-rw-r--. 1 dmtsai dmtsai 2092 Jul  9 22:13 /home/dmtsai/passwd.linux
# 处理过后,发现文件大小与原本的 /etc/passwd 就一致了!

  其实这个指令也可以写在“正则表达式”里头,因为他也是由正则表达式的方式来取代数据的。


col

[dmtsai@study ~]$ col [-xb]
选项与参数:
-x  :将 tab 键转换成对等的空白键

范例一:利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空白
[dmtsai@study ~]$ cat -A /etc/man_db.conf  <==此时会看到很多 ^I 的符号,那就是 tab
[dmtsai@study ~]$ cat /etc/man_db.conf | col -x | cat -A | more
# 嘿嘿!如此一来, [tab] 按键会被取代成为空白键,输出就美观多了!

  虽然 col 有他特殊的用途,不过,很多时候,他可以用来简单的处理将 [tab] 按键取代成为空白键! 例如上面的例子当中,如果使用 cat -A 则 [tab] 会以 ^I 来表示。 但经过 col -x 的处理,则会将 [tab] 取代成为对等的空白键。


join

  join 是在处理两个文件之间的数据, 而且,主要是在处理“两个文件当中,有 "相同数据" 的那一行,才将他加在一起”的意思。我们利用下面的简单例子来说明:

[dmtsai@study ~]$ join [-ti12] file1 file2
选项与参数:
-t  :join 默认以空白字符分隔数据,并且比对“第一个字段”的数据,
      如果两个文件相同,则将两笔数据联成一行,且第一个字段放在第一个!
-i  :忽略大小写的差异;
-1  :这个是数字的 1 ,代表“第一个文件要用那个字段来分析”的意思;
-2  :代表“第二个文件要用那个字段来分析”的意思。
范例一:用 root 的身份,将 /etc/passwd 与 /etc/shadow 相关数据整合成一栏
[root@study ~]$ head -n 3 /etc/passwd /etc/shadow
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/shadow <==
root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:*:16372:0:99999:7:::
daemon:*:16372:0:99999:7:::
# 由输出的数据可以发现这两个文件的最左边字段都是相同帐号!且以 : 分隔

[root@study ~]$ join -t ':' /etc/passwd /etc/shadow | head -n 3
root:x:0:0:root:/root:/bin/bash:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:*:16372:0:99999:7:::
# 通过上面这个动作,我们可以将两个文件第一字段相同者整合成一列!
# 第二个文件的相同字段并不会显示(因为已经在最左边的字段出现了啊!)

范例二:我们知道 /etc/passwd 第四个字段是 GID ,那个 GID 记录在 
        /etc/group 当中的第三个字段,请问如何将两个文件整合?
[root@study ~]$ head -n 3 /etc/passwd /etc/group
==> /etc/passwd <==
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

==> /etc/group <==
root:x:0:
bin:x:1:
daemon:x:2:
# 从上面可以看到,确实有相同的部分喔!赶紧来整合一下!

[root@study ~]$ join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3
0:root:x:0:root:/root:/bin/bash:root:x:
1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:
2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:
# 同样的,相同的字段部分被移动到最前面了!所以第二个文件的内容就没再显示。

  此外,需要特别注意的是,在使用 join 之前,你所需要处理的文件应该要事先经过排序 (sort) 处理! 否则有些比对的项目会被略过。


paste

  这个 paste 就要比 join 简单多了!相对于 join 必须要比对两个文件的数据相关性, paste 就直接“将两行贴在一起,且中间以 [tab] 键隔开”而已!简单的使用方法:

[dmtsai@study ~]$ paste [-d] file1 file2
选项与参数:
-d  :后面可以接分隔字符。默认是以 [tab] 来分隔的!
-   :如果 file 部分写成 - ,表示来自 standard input 的数据的意思。

范例一:用 root 身份,将 /etc/passwd 与 /etc/shadow 同一行贴在一起
[root@study ~]# paste /etc/passwd /etc/shadow
root:x:0:0:root:/root:/bin/bash root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin        bin:*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:*:16372:0:99999:7:::
# 注意喔!同一行中间是以 [tab] 按键隔开的!

范例二:先将 /etc/group 读出(用 cat),然后与范例一贴上一起!且仅取出前三行
[root@study ~]# cat /etc/group|paste /etc/passwd /etc/shadow -|head -n 3
# 这个例子的重点在那个 - 的使用!那玩意儿常常代表 stdin 喔!

expand

  这玩意儿就是在将 [tab] 按键转成空白键啦~可以这样玩:

[dmtsai@study ~]$ expand [-t] file
选项与参数:
-t  :后面可以接数字。一般来说,一个 tab 按键可以用 8 个空白键取代。
      我们也可以自行定义一个 [tab] 按键代表多少个字符呢!

范例一:将 /etc/man_db.conf 内行首为 MANPATH 的字样就取出;仅取前三行;
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3
MANPATH_MAP     /bin                    /usr/share/man
MANPATH_MAP     /usr/bin                /usr/share/man
MANPATH_MAP     /sbin                   /usr/share/man
# 行首的代表标志为 ^ ,这个我们留待下节介绍!先有概念即可!

范例二:承上,如果我想要将所有的符号都列出来?(用 cat)
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3 |cat -A
MANPATH_MAP^I/bin^I^I^I/usr/share/man$
MANPATH_MAP^I/usr/bin^I^I/usr/share/man$
MANPATH_MAP^I/sbin^I^I^I/usr/share/man$
# 发现差别了吗?没错~ [tab] 按键可以被 cat -A 显示成为 ^I 

范例三:承上,我将 [tab] 按键设置成 6 个字符的话?
[dmtsai@study ~]$ grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A
MANPATH_MAP /bin              /usr/share/man$
MANPATH_MAP /usr/bin          /usr/share/man$
MANPATH_MAP /sbin             /usr/share/man$
123456123456123456123456123456123456123456123456...
# 仔细看一下上面的数字说明,因为我是以 6 个字符来代表一个 [tab] 的长度,所以,
# MAN... 到 /usr 之间会隔 12 (两个 [tab]) 个字符喔!如果 tab 改成 9 的话,
# 情况就又不同了!这里也不好理解~您可以多设置几个数字来查阅就晓得!

 

6.5 分区命令: split

  如果你有文件太大,导致一些携带式设备无法复制的问题,嘿嘿!找 split 就对了! 他可以帮你将一个大文件,依据文件大小或行数来分区,就可以将大文件分区成为小文件了。

[dmtsai@study ~]$ split [-bl] file PREFIX
选项与参数:
-b  :后面可接欲分区成的文件大小,可加单位,例如 b, k, m 等;
-l  :以行数来进行分区。
PREFIX :代表前置字符的意思,可作为分区文件的前导文字。

范例一:我的 /etc/services 有六百多K,若想要分成 300K 一个文件时?
[dmtsai@study ~]$ cd /tmp; split -b 300k /etc/services services
[dmtsai@study tmp]$ ll -k services*
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul  9 22:52 servicesaa
-rw-rw-r--. 1 dmtsai dmtsai 307200 Jul  9 22:52 servicesab
-rw-rw-r--. 1 dmtsai dmtsai  55893 Jul  9 22:52 servicesac
# 那个文件名可以随意取的啦!我们只要写上前导文字,小文件就会以
# xxxaa, xxxab, xxxac 等方式来创建小文件的!

范例二:如何将上面的三个小文件合成一个文件,文件名为 servicesback
[dmtsai@study tmp]$ cat services* >> servicesback
# 很简单吧?就用数据流重导向就好啦!简单!

范例三:使用 ls -al / 输出的信息中,每十行记录成一个文件
[dmtsai@study tmp]$ ls -al / | split -l 10 - lsroot
[dmtsai@study tmp]$ wc -l lsroot*
  10 lsrootaa
  10 lsrootab
   4 lsrootac
  24 total
# 重点在那个 - 啦!一般来说,如果需要 stdout/stdin 时,但偏偏又没有文件,
# 有的只是 - 时,那么那个 - 就会被当成 stdin 或 stdout ~

  在 Windows 操作系统下,你要将文件分区需要如何作?伤脑筋吧!在 Linux 下面就简单的多了!你要将文件分区的话,那么就使用 -b size 来将一个分区的文件限制其大小,如果是行数的话,那么就使用 -l line 来分区!好用的很!如此一来,你就可以轻易的将你的文件分区成某些软件能够支持的最大容量 。

6.6 参数代换: xargs

  可以产生参数,xargs 可以读入 stdin 的数据,并且以空白字符或断行字符作为分辨,将 stdin 的数据分隔成为 arguments 。 因为是以空白字符作为分隔,所以,如果有一些文件名或者是其他意义的名词内含有空白字符的时候, xargs 可能就会误判。

[dmtsai@study ~]$ xargs [-0epn] command
选项与参数:
-0  :如果输入的 stdin 含有特殊字符,例如 `, \, 空白键等等字符时,这个 -0 参数
      可以将他还原成一般字符。这个参数可以用于特殊状态喔!
-e  :这个是 EOF (end of file) 的意思。后面可以接一个字串,当 xargs 分析到这个字串时,
      就会停止继续工作!
-p  :在执行每个指令的 argument 时,都会询问使用者的意思;
-n  :后面接次数,每次 command 指令执行时,要使用几个参数的意思。
当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!
范例一:将 /etc/passwd 内的第一栏取出,仅取三行,使用 id 这个指令将每个帐号内容秀出来
[dmtsai@study ~]$ id root
uid=0(root) gid=0(root) groups=0(root)   # 这个 id 指令可以查询使用者的 UID/GID 等信息

[dmtsai@study ~]$ id $(cut -d ':' -f 1 /etc/passwd | head -n 3)
# 虽然使用 $(cmd) 可以预先取得参数,但可惜的是, id 这个指令“仅”能接受一个参数而已!
# 所以上述的这个指令执行会出现错误!根本不会显示用户的 ID 啊!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | id
uid=1000(dmtsai) gid=1000(dmtsai) groups=1000(dmtsai),10(wheel)   # 我不是要查自己啊!
# 因为 id 并不是管线命令,因此在上面这个指令执行后,前面的东西通通不见!只会执行 id!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs id
# 依旧会出现错误!这是因为 xargs 一口气将全部的数据通通丢给 id 处理~但 id 就接受 1 个啊最多!

[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -n 1 id
uid=0(root) gid=0(root) groups=0(root)
uid=1(bin) gid=1(bin) groups=1(bin)
uid=2(daemon) gid=2(daemon) groups=2(daemon)
# 通过 -n 来处理,一次给予一个参数,因此上述的结果就 OK 正常的显示啰!

范例二:同上,但是每次执行 id 时,都要询问使用者是否动作?
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
id root ?...y
uid=0(root) gid=0(root) groups=0(root)
id bin ?...y
.....(下面省略).....
# 呵呵!这个 -p 的选项可以让使用者的使用过程中,被询问到每个指令是否执行!

范例三:将所有的 /etc/passwd 内的帐号都以 id 查阅,但查到 sync 就结束指令串
[dmtsai@study ~]$ cut -d ':' -f 1 /etc/passwd | xargs -e'sync' -n 1 id
# 仔细与上面的案例做比较。也同时注意,那个 -e'sync' 是连在一起的,中间没有空白键。
# 上个例子当中,第六个参数是 sync 啊,那么我们下达 -e'sync' 后,则分析到 sync 这个字串时,
# 后面的其他 stdin 的内容就会被 xargs 舍弃掉了!

 

6.7 关于减号 - 的用途

  管道命令在 bash 的连续的处理程序中是相当重要的!另外,在 log file 的分析当中也是相当重要的一环。在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的 stdin , 某些指令需要用到文件名称 (例如 tar) 来进行处理时,该 stdin 与 stdout 可以利用减号 "-" 来替代, 举例来说:

[root@study ~]# mkdir /tmp/homeback
[root@study ~]# tar -cvf - /home | tar -xvf - -C /tmp/homeback

  上面这个例子是说:“我将 /home 里面的文件给他打包,但打包的数据不是记录到文件,而是传送到 stdout; 经过管道后,将 tar -cvf - /home 传送给后面的 tar -xvf - ”。后面的这个 - 则是取用前一个指令的 stdout, 因此,我们就不需要使用 filename 了。

 

  

posted @ 2022-10-29 15:41  莫莫君不恋爱  阅读(123)  评论(0编辑  收藏  举报