BASH 学习小记


什么是 shell ?

其实 shell 只是提供用户操作系统的一个接口,因此这个 shell 需要可以调用其他软件。 很多指令,包括 man、chmod、chown、vi 等等,都是独立的应用程序,但是我们可以通过 shell(就是命令行模式)操作这些应用程序,让这些应用程序调用核心运行。shell 极其重要的特性就是效率很高。

Bash shell 的功能

可直接通过以下命令学习,但是^_^

$ man bash

bash 的优点有很多:

  • 命令编修能力(history):记忆使用过的指令

  • 命令与档案补全功能:[tab] 按键的好处

  • 命令别名设定功能:alias。如:

    $ alias lm='ls -al'
    
  • 工作控制、前景背景控制:(job control, foreground, background)

  • 程序化脚本:(shell scripts)

  • 通配符: (Wildcard)

变量

环境变量

如 PATH、 HOME、MAIL、SHELL 等,都是很重要的,为了区别于自定义变量,环境变量通常以大写字符表示。

环境变量可以帮我们达到很多功能~包括家目录的变换、提示字符的显示、执行文件搜寻的路径等。我们可以利用两个指令查阅目前的 shell 环境中有多少默认环境变量,分别是 env 与 export。

set 除了环境变量外, 还会将其他在 bash 内的变量通通显示出来。

  • PS1:(提示字符的设定)

    这个东西就是我们的『命令提示字符』,当我们每次按下 [Enter] 按键执行某个指令后,最后要再次出现提示字符时,就会主动去读取这个变量值。可以通过如下方法修改:

    $ cd /home
    $ PS1='[\u@\h \w \a #\#]\$'
    [root@www /home 17:02 #85]$
    
  • $:(关于本 shell 的 PID)

  • ?:(关于上个执行指令的回传值)

当你登入 Linux 并取得一个 bash 后,你的 bash 就是一个独立的程序,被称为 PID 的就是。 接下来你在这个 bash 底下所下达的任何指令都是由这个 bash 所衍生出来的,那些被下达的指令就被称为子程序。

我们在原本的 bash 底下执行另一个 bash ,结果操作的环境接口会跑到第二个 bash 去(就是子程序), 那原本的 bash 就会处于暂停的情况 (睡着了,就是 sleep)。若要回到原本的 bash , 就必须将第二个 bash 结束掉 (下达 exit 或 logout) 。

子程序仅会继承父程序的环境变量, 子程序不会继承父程序的自定义变量。

变量的取用:echo

echo $variable
echo $PATH
echo ${PATH}

注意变量名称前加上 $ ,echo 功能不止于此,可以 man echo 查看。

变量的设定规则

  1. 变量与变量内容以一个等号『=』相连结

  2. 等号两边不能直接接空格符

  3. 变量名称只能是英文字母与数字,但是开头字符不能是数字

  4. 变量内容若有空格符可使用双引号『”』或单引号『’』将变量内容结合起来,但:

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

      $ var="lang is $LANG"
      $ echo $var
      lang is en_US
      
    • 单引号内的特殊字符则仅为一般字符 (纯文本),如:

      $ var='lang is $LANG'
      $ echo $var
      lang is $LANG
      
  5. 可用跳脱字符『 \ 』将特殊符号(如 [Enter], $, , 空格符等)变成一般字符

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

    $ version=$(uname -r)
    $ echo $version
    4.15.0-140-generic
    
  7. 若该变量为扩增变量内容时,则可用 “$变量名称” 或 ${变量} 累加内容,如:

    $ PATH="$PATH":/home/bin
    
  8. 若该变量需要在其他子程序执行,则需要以 export 使变量变成环境变量:

    $ export PATH
    
  9. 通常大写字符为系统默认变量,自行设定变量可以使用小写字符,方便判断 (纯粹依照使用者兴趣与嗜好)

  10. 取消变量的方法为使用 unset :『unset 变量名称』

变量键盘读取、数组与宣告:read, array, declare

  • read :读取需要键盘输入的变量

    选项与参数:

    -p :后面可以接提示字符

    -t :后面可以接等待的『秒数』

    $ read -p "Please keyin your name: " -t 30 named
    Please keyin your name: Metric
    $ echo $named
    Metric
    (提示使用者 30 秒内输入自己的名字,将该输入字符串作为 named 的变量内容)
    
  • declare / typeset :宣告变量的类型。

    如果使用 declare 后面没有接任何参数,那么 bash 就会主动将所有变量名称与内容显示出来,类似使用 set 。

    $ declare [-aixr] variable
    

    选项与参数:

    -a :将后面名为 variable 的变量定义成为数组 (array) 类型

    -i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型

    -x :用法与 export 一样,就是将后面的 variable 变成环境变量

    -r :将变量定义成为 readonly 类型,该变量不可被更改内容,也不能 unset

    tip:将 - 变成 + 可以进行『取消』动作。但如果你不小心将变量定义为『只读』,通常得要注销再登入才能复原该变量的类型。

    在默认情况下,bash 对于变量有基本定义:

    • 变量类型默认为『字符串』,所以若不指定变量类型,则 1+2 为一个『字符串』而不是『计算式』。
    • bash 环境中的数值运算,预设最多仅能到达整数形态,所以 1/3 结果是 0 。
  • 数组(array)

    $ var[1]="small"
    $ var[2]="normal"
    $ var[3]="large"
    $ echo "${var[1]}, ${var[2]}, ${var[3]}"
    small, normal, large
    

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

  • # :从变量内容的最前面开始向右,删除符合取代文字的『最短的』那一个;

  • ## :从变量内容的最前面开始向右,删除符合取代文字的『最长的』那一个。

    $ path=${PATH}
    $ echo $path
    /usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
    
    # 删去前两个含 kerberos 的目录(通过通配符 * 来取代 0 到无穷多个任意字符)(在 PATH 这个变量的内容中,每个目录都是以冒号『:』隔开)
    $ echo ${path#/*kerberos/bin:}
    /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
    
  • 如果想要『从后面向前删除变量内容』呢? 这个时候就得使用百分比 (%) 符号了。% 与 %% 的意义其实与 # 及 ## 类似。


# 将 path 的变量内容中的 sbin 替换成大写 SBIN :
$ echo ${path/sbin/SBIN}
/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

$ echo ${path//sbin/SBIN}
/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/SBIN:/usr/local/bin:/SBIN:/bin:/usr/SBIN:/usr/bin:/root/bin
# 如果是两条斜线,那么就变成所有符合的内容都会被替换。

  • 变量的测试与内容替换

    底下的例子当中,那个 var 与 str 为变量,我们想要针对 str 是否有设定来决定 var 的值!一般来说,str: 代表『str 没设定或为空的字符串时』;至于 str 则仅为 『没有该变数』。

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

    基本上这种变量的测试也能够通过 shell script 内的 if…then… 来处理, 不过既然 bash 提供了这么简单的方法来测试变量,那我们也可以多尝试一下!

命令

命令别名设定:alias, unalias

$ alias lm='ls -al | more'

此时多出了一个可以执行的指令,这个指令名称为 lm ,其实他是执行 ls -al | more 。

要注意的是:『alias 的定义规则于变量定义的规则几乎相同』。

只使用 alias 可以知道目前有哪些命令别名。另外:

$ unalias lm

这样就取消了别名。

注意,命令别名是『新创一个指令, 你可以直接下达该指令』,变量则需要使用类似『 echo 』指令才能够调出变量的内容。

历史命令:history

选项与参数:

n :数字,意思是『列出最近的 n 个命令行表』

-c :将目前的 shell 中的所有 history 内容全部消除

-a :将目前新增的 history 指令新增入 histfiles 中,若没有加 histfiles , 则预设写入 ~/.bash_history

-r :将 histfiles 的内容读到目前这个 shell 的 history 记忆中

-w :将目前的 history 记忆内容写入 histfiles 中

Bash shell 的操作环境

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

在开始介绍 bash 的配置文件前,我们一定要先知道的就是 login shell 与 non-login shell! 重点在于有没有登入 (login)。

login shell

一般来说,login shell 其实只会读取这两个配置文件:

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

bash 的 login shell 所读取的整体环境配置文件其实只有 /etc/profile,但是 /etc/profile 还会呼唤出其他的配置文件,所以让我们的 bash 操作接口变得非常友善。


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

  1. ~/.bash_profile
  2. ~/.bash_login
  3. ~/.profile

其实 bash 的 login shell 设定只会读取上面三个档案其中的一个, 而读取的顺序则是依照上面的顺序。也就是说,如果 ~/.bash_profile 存在,那么其他两个档案不论是否存在,都不会被读取。

bash 配置文件的读入方式比较有趣,主要是透过一个指令『 source 』 来进行的! 也就是说~/.bash_profile 其实会再呼叫 ~/.bashrc 的设定内容,所以最终被读取的配置文件是『 ~/.bashrc 』这个档案,你将自己的偏好设定写入该档案即可。


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

$ source 配置文件名

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

若是需要使用不同环境配置文件,可以针对不同的环境分别编写属于该环境的配置文件,当需要该环境时,就直接『 source 变量配置文件 』。

non-login shell

当你打开 non-login shell 时,该 bash 配置文件仅会读取 ~/.bashrc 而已。

bash 默认的组合键

组合按键 执行结果
Ctrl + C 终止目前的命令
Ctrl + D 输入结束 (EOF),例如邮件结束的时候
Ctrl + M 就是 Enter
Ctrl + S 暂停屏幕的输出
Ctrl + Q 恢复屏幕的输出
Ctrl + U 在提示字符下,将整列命令删除
Ctrl + Z 『暂停』目前的命令

通配符(wildcard)与特殊符号

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

# 找出 /etc/ 底下以 cron 为开头的文档名
$ ll -d /etc/cron*        <==加上 -d 是为了仅显示目录而已

# 找出 /etc/ 底下文件名『刚好是五个字母』的文件名
$ ll -d /etc/?????        <==由于 ? 表示一定有一个,所以五个 ? 就对了

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

# 找出 /etc/ 底下,文档名开头不是小写字母的文件名
$ ll -d /etc/[^a-z]*    <==注意中括号左边没有 *

# 将上一个例子找到的档案复制到 /tmp 中
$ cp -a /etc/[^a-z]* /tmp

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

数据流重导向(Redirection)

数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据,传输到其他地方,如文档或是装置(例如打印机)。

standard output 与 standard error output

标准输出指的是『指令执行所回传的正确的讯息』,而标准错误输出可理解为『 指令执行失败后,所回传的错误信息』。

数据流重导向可以将 standard output (简称 stdout) 与 standard error output (简称 stderr) 分别传送到其他的档案或装置去,而分别传送所用的特殊字符如下所示:

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

若以 > 输出到一个已存在的档案中,那个档案就会被覆盖。利用两个大于号 (>>) 时,若文档不存在,系统会主动建立这个文档;若该文档已存在, 则数据会在该文档的最下方累加进去。

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

  • 1> :以覆盖的方法将『正确的数据』输出到指定的档案或装置上;
  • 1>>:以累加的方法将『正确的数据』输出到指定的档案或装置上;
  • 2> :以覆盖的方法将『错误的数据』输出到指定的档案或装置上;
  • 2>>:以累加的方法将『错误的数据』输出到指定的档案或装置上。

/dev/null 垃圾桶黑洞装置与特殊写法

如果我知道错误信息会产生,所以要将错误信息忽略掉而不显示或储存呢? 这个时候黑洞装置 /dev/null 就很重要了, /dev/null 可以吃掉任何导向这个装置的信息。

如果我要将正确与错误数据通通写入同一个档案去呢?

$ find /home -name .bashrc > list 2> list    <==错误
$ find /home -name .bashrc > list 2>&1        <==正确
$ find /home -name .bashrc &> list            <==正确

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

standard input : < 与 <<

< 就是『将原本需要由键盘输入的数据,改由档案内容来替代』的意思。

# 利用 cat 指令建立一个档案的简单流程
$ cat > catfile
testing
cat file test
<==这里按下 [ctrl]+d 离开

$ cat catfile
testing
cat file test

由于加入 > 在 cat 后,所以那个 catfile 会被主动建立,而内容就是刚刚键盘上面输入的那两行数据。那我能不能用纯文本文件替代键盘输入呢?

# 用 stdin 替代键盘的输入以建立新档案的简单流程
$ cat > catfile < ~/.bashrc
$ ll catfile ~/.bashrc
-rw-r--r-- 1 root root 194 Sep 26 13:36 /root/.bashrc
-rw-r--r-- 1 root root 194 Apr 16 18:29 catfile
# 这两个档案的大小会一模一样!几乎像是使用 cp 复制一样!

理解 < 之后,再来看 << 这连续两个小于号。它代表的是『结束的输入字符』。举个例子:『我要用 cat 直接将输入的信息输出到 catfile 中, 且当由键盘输入 eof 时,该次输入就结束』,那我可以这样做:

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

$ cat catfile
This is a test.
OK now stop. <==只有这两行,不会存在关键词那一行

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

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

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

在某些情况下,很多指令需要一次输入去执行,而不要分次执行时,可以通过 shell script 去执行,或者是通过下面介绍的一次输入多重指令。

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

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

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

    两个指令之间有相依性,而这个相依性主要判断的地方就在于前一个指令执行的结果是否正确。如我想要在某个目录底下建立一个档案,也就是说,如果该目录存在的话, 那我才建立这个档案,如果不存在,那就算了。这样就要用到之前我们曾介绍过的指令回传值:『若前一个指令执行的结果为正确,在 Linux 底下会回传一个 $? = 0 的值』。

    指令下达情况 说明
    cmd1 && cmd2 1. 若 cmd1 执行完毕且正确执行($?=0),则开始执行 cmd2。
    2. 若 cmd1 执行完毕且为错误 ($?≠0),则 cmd2 不执行。
    cmd1 || cmd2 1. 若 cmd1 执行完毕且正确执行($?=0),则 cmd2 不执行。
    2. 若 cmd1 执行完毕且为错误 ($?≠0),则开始执行 cmd2。

    上述的 cmd1 及 cmd2 都是指令。现在:(1)先判断一个目录是否存在; (2)若存在才在该目录底下建立一个档案。由于我们尚未介绍判断式 (test) 的使用,在这里我们使用 ls 以及回传值来判断目录是否存在:

    # 使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 建立 /tmp/abc/hehe
    $ ls /tmp/abc && touch /tmp/abc/hehe
    ls: /tmp/abc: No such file or directory
    # ls 很干脆地说明找不到该目录,但并没有 touch 的错误,表示 touch 并没有执行
    
    $ mkdir /tmp/abc
    $ ls /tmp/abc && touch /tmp/abc/hehe
    $ ll /tmp/abc
    -rw-r--r-- 1 root root 0 Apr 17 12:43 hehe
    

    能不能自动判断,如果没有该目录就建立呢?

    # 测试 /tmp/abc 是否存在,若不存在则予以建立,若存在就不做任何事情
    $ rm -r /tmp/abc    <==先删除此目录以方便测试
    $ ls /tmp/abc || mkdir /tmp/abc
    ls: /tmp/abc: No such file or directory
    $ ll /tmp/abc
    total 0             <==结果出现了!有进行 mkdir
    

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

    如果我想要建立 /tmp/abc/hehe 这个档案, 但我并不知道 /tmp/abc 是否存在:

    # 我不清楚 /tmp/abc 是否存在,但就是要建立 /tmp/abc/hehe 档案
    $ ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe
    

    由于 Linux 底下的指令都是由左往右执行的,若 /tmp/abc 存在故回传 $?=0,则因为 || 遇到 0 的 $? 不会进行,此时 $?=0 继续向后传,故 && 遇到 $?=0 就开始建立 /tmp/abc/hehe 了!最终 /tmp/abc/hehe 被建立起来。

    # 以 ls 测试 /tmp/vbirding 是否存在,若存在则显示 "exist" ,若不存在,则显示 "not exist"!
    $ ls /tmp/vbirding && echo "exist" || echo "not exist"
    
    # 注意,若写成如下形式会同时出现 not exist 与 exist 
    $ ls /tmp/vbirding || echo "not exist" && echo "exist"
    

    由于指令是一个接着一个去执行的,因此,如果真要使用判断, 那么这个 && 与 || 的顺序就不能搞错。一般来说,假如判断式有三个,也就是:command1 && command2 || command3,而且通常顺序不会变,因为一般来说, command2 与 command3 会放置肯定可以执行成功的指令。

管线命令(pipe)

如同前面所说, bash 命令执行的时候有输出的数据会出现,那么如果这群数据必需要经过几道手续之后才能得到我们所想要的格式,应该如何来设定? 这就牵涉到管线命令的问题了 (pipe) ,管线命令使用的是『 | 』这个界定符号。

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

$ 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 继续处理才行

撷取命令:cut, grep

撷取命令就是将一段数据经过分析后,取出我们所想要的,或者是经由分析关键词,取得我们所想要的那一行。要注意的是,一般来说,撷取信息通常是针对『一行一行』分析的, 并不是整篇信息分析的。

  • cut

    选项与参数:

    -d :后面接分隔字符。与 -f 一起使用

    -f :依据 -d 的分隔字符将一段信息分割成为数段,用 -f 取出第几段的意思

    -c :以字符 (characters) 的单位取出固定字符区间

    # 将 PATH 变量取出,我要找出第五个路径。
    $ echo $PATH
    /bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/X11R6/bin:/usr/games:
    # 1 |    2   |  3  |    4    |       5      |       6      | 7
    
    $ echo $PATH | cut -d ':' -f 5
    # 如同上面的数字显示,我们是以『 : 』作为分隔,因此会出现 /usr/local/bin
    # 那么如果想要列出第 3 与第 5 呢?,就是这样:
    $ echo $PATH | cut -d ':' -f 3,5
    
    # 将 export 输出的信息,取第 12 字符以后的所有字符串
    $ export
    declare -x HISTSIZE="1000"
    declare -x INPUTRC="/etc/inputrc"
    declare -x KDEDIR="/usr"
    declare -x LANG="zh_TW.big5"
    ···(其它省略)···
    # 如果我们不想要『 declare -x 』时:
    $ export | cut -c 12-
    HISTSIZE="1000"
    INPUTRC="/etc/inputrc"
    KDEDIR="/usr"
    LANG="zh_TW.big5"
    ···(其他省略)···
    # 用 -c 可以处理具有一定格式的输出数据
    # 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等
    
    # 用 last 使登入信息仅留下用户名
    $ 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 可以输出『账号/终端机/杢源/日期时间』的数据,幵且是排列整齐的
    
    $ last | cut -d ' ' -f 1
    #由输出的结果我们可以发现第一个空格分隔的字段代表账号,所以使用如上指令,但是因为 root pts/1 之间空格有好几个,并非仅有一个,所以,如果要找出 pts/1 其实不能用 cut -d ' ' -f 1,2,输出的结果不是我们想要的。
    

    cut 主要的用途在于将『同一行里面的数据进行分解!』最常使用在分析一些数据或文字数据的时候! 这是因为有时候我们会以某些字符当作分割的参数,然后将数据加以切割,以获得我们所需要的数据。


  • grep

    grep 是分析一行信息, 若当中有我们所需要的,就将该行拿出来。

    选项与参数:

    -a :将 binary 档案以 text 档案的方式搜寻数据

    -c :计算找到 ‘搜寻字符串’ 的次数

    -i :忽略大小写的不同,所以大小写视为相同

    -n :顺便输出行号

    -v :反向选择,亦即显示出没有 ‘搜寻字符串’ 内容的那一行

    –color=auto :可以将找到的关键词部分加上颜色显示


排序命令:sort, wc, uniq

  • sort

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

    选项与参数:

    -f :忽略大小写的差异

    -b :忽略最前面的空格符部分

    -M :以月份的名字排序,例如 JAN, DEC 等等的排序方法

    -n :使用『纯数字』进行排序(默认是以文字型态排序)

    -r :反向排序

    -u :就是 uniq ,相同的数据中,仅出现一行代表

    -t :分隔符,预设是用 [tab] 键分隔

    -k :以那个区间 (field) 进行排序的意思

  • uniq

    选项与参数:

    -i :忽略大小写字符的不同

    -c :进行计数

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

    # 使用 last 将账号列出,仅取出账号栏,进行排序后仅取出一位:
    $ last | cut -d ' ' -f1 | sort | uniq
    
  • wc

    wc 这个指令可以帮我们计算输出的信息的整体数据。

    选项与参数:

    -l :仅列出行

    -w :仅列出多少字(英文单字)

    -m :多少字符

    # 那个 /etc/man.config 里面到底有多少相关字、行、字符数
    $ cat /etc/man.config | wc
    141 722 4617
    # 输出的三个数字中,分别代表: 『行、字数、字符数』
    

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

DOS 断行字符与 Unix 断行字符不同,并且可以使用 dos2unix 与 unix2dos 完成转换。那么是否还有其他常用的字符替代? 举个例子,要将大写改成小写,或者是将数据中的 [tab] 按键转成空格键?还有,如何将两篇信息整合成一篇? 下面是这些字符转换命令在管线当中的使用方法。

  • tr

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

    $ tr [-ds] SET1 ...
    选项与参数:
    -d :删除信息当中的 SET1 这个字符串
    -s :取代掉重复的字符
    
    # 将 /etc/passwd 输出的信息中,将冒号 (:) 删除
    $ cat /etc/passwd | tr -d ':'
    
    # 将 /etc/passwd 转存成 dos 断行到 /root/passwd 中,再将 ^M 符号删除
    $ cp /etc/passwd /root/passwd && unix2dos /root/passwd
    $ file /etc/passwd /root/passwd
    /etc/passwd: ASCII text
    /root/passwd: ASCII text, with CRLF line terminators <==就是 DOS 断行
    $ cat /root/passwd | tr -d '\r' > /root/passwd.linux
    # 那个 \r 指的是 DOS 的断行字符,关于更多的字符,请参考 man tr
    

    如上面例子当中,可以去除 DOS 档案留下的 ^M 这个断行符号!这东西相当有用,相信处理 Linux & Windows 系统中人们最麻烦的一件事就是这个事情,亦即是 DOS 底下会自动在每行行尾加入 ^M 这个断行符号。这个时候我们可以使用 tr 将 ^M 去除! ^M 可以使用 \r 代替!


  • col

    选项与参数:

    -x :将 tab 键转换成对等的空格键

    -b :在文字内有反斜杠 (/) 时,仅保留反斜杠最后接的那个字符

    # 利用 cat -A 显示出所有特殊按键,最后以 col 将 [tab] 转成空格
    $ cat -A /etc/man.config    <==此时会看到很多 ^I 的符号,那就是 tab
    $ cat /etc/man.config | col -x | cat -A | more
    如此一来, [tab] 键会被替代为空格键,输出就美观多了
    

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


  • join

    在处理两个档案之间的数据, 而且,主要是在处理 『两个档案当中,有 “相同数据” 的那一行,才将它加在一起』的意思。

    $ join [-ti12] file1 file2
    选项与参数:
    -t :join 默认以空格符分隔数据,并且比对『第一个字段』的数据,如果两个档案相同,则将两笔数据联成一行,且第一个字段放在第一个
    -i :忽略大小写的差异
    -1 :这个是数字 1 ,代表『第一个档案要用那个字段来分析』的意思
    -2 :代表『第二个档案要用那个字段来分析』的意思。
    

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


  • paste

    这个 paste 就要比 join 简单多了!相对于 join 必须要比对两个档案的数据相关性, paste 就直接『将两行贴在一起,且中间以 [tab] 键隔开』。

    $ paste [-d] file1 file2
    选项与参数:
    -d :后面可以接分隔字符。预设是以 [tab] 来分隔的
    - :如果 file 部分写成 - ,表示来自 standard input 的资料的意思。
    

  • expand

    就是在将 [tab] 转成空格。

    $ expand [-t] file
    选项与参数:
    -t :后面可以接数字。一般来说,一个 tab 可以用 8 个空格替代。 我们也可以自行定义一个 [tab] 按键代表多少个字符。
    
    # 将 /etc/man.config 内行首为 MANPATH 字样的行取出;仅取前三行
    $ grep '^MANPATH' /etc/man.config | head -n 3
    MANPATH /usr/man
    MANPATH /usr/share/man
    MANPATH /usr/local/man
    # 行首的代表标志为 ^ 
    
    # 承上,如果我想要将所有的符号都列出来呢?(用 cat)
    $ grep '^MANPATH' /etc/man.config | head -n 3 |cat -A
    MANPATH^I/usr/man$
    MANPATH^I/usr/share/man$
    MANPATH^I/usr/local/man$
    # 发现差别了吗? [tab] 键可以被 cat -A 显示成为 ^I
    
    # 承上,我将 [tab] 设定成 6 个字符
    $  grep '^MANPATH' /etc/man.config | head -n 3 | expand -t 6 - | cat -A
    MANPATH      /usr/man$
    MANPATH      /usr/share/man$
    MANPATH      /usr/local/man$
           123456
    

    unexpand 可以将空格转成 [tab] 。

分割命令:split

split 可以帮你将一个大档案,依据档案大小或行数来分割,就可以将大档案分割成为小档案了。

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

参数代换:xargs

就以字面意义来看, x 是加减乘除的乘号,args 则是 arguments (参数) 的意思,所以说,这个指令就是在产生某个指令的参数的意思! xargs 可以读入 stdin 的数据,并且以空格符或断行字符作为分辨,将 stdin 的资料分隔成为 arguments 。 因为是以空格符作为分隔,所 以,如果有一些文件名或者是其他意义的名词内含有空格符的时候, xargs 可能就会误判了。

使用 xargs 的原因是, 很多指令其实并不支持管线命令,因此我们可以透过 xargs 来提供该指令引用 standard input 之用。

关于减号的用途

在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的 stdin , 某些指令需要用到文件名 (例如 tar) 来进行处理时,该 stdin 与 stdout 可以利用减号 “-“ 替代。

$ tar -cvf - /home | tar -xvf -
# 上面这个例子是说:『我将 /home 里面的档案打包,但打包的数据不是记录成文件,而是传送到 stdout; 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf - 』。后面的这个 - 则是取用前一个指令的 stdout, 因此,我们就不需要使用 file 了。

文章作者: Metric.H
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Metric.H !
  目录