Skip to content

BASH

概述

Shell 在接收到命令后,将以空格把命令分割成一个个 token,并对每个 token 执行扩展(glob)。扩展之后,才根据结果来进行命令的实际执行。

扩展可以是:

  • 单纯的替换
  • 根据已有文件扩展
  • 字符串扩展(类似正则,但不一样)
  • 根据 bash 变量扩展

扩展的行为可以用过 shopt 命令来控制。

扩展

针对特殊字符的单纯替换

波浪线 ~string 将被字符串替换为对应的用户目录:

sh
pwd # /home/me/anywhere
echo ~ # /home/me
echo ~xiaoyan13 # /home/xiaoyan13
echo ~root # /root

echo ~+ # /hoem/me/anywhere. `~+` 被特别处理为当前目录,即 `pwd`

根据已有文件

? / * / [] 属于根据已有文件扩展,其中 ?[] 匹配单个字符,* 匹配 0 或多个字符。

sh
ll # a.txt b.txt
ls [abc].txt # a.txt b.txt
ls ?.txt # a.txt b.txt
ls *.txt # a.txt b.txt

# [] 内部可以使用 `-` 和 `!/^` 表达更丰富的语义
ls [a-c].txt # a.txt. b.txt  `a-c` 被展开为 abc
ls [-abc].txt # `-` 位于首部或尾部就不会展开了,所以这样就可以匹配到 `-` 了

ls [!ac].txt # b.txt. `!` 和 `^` 都表示排除。

[[:xxx:]][] 的另一种语法,扩展成某一类(xxx)所代表的字符之中的一个。

sh
echo [[:upper:]]* # 命令输出所有大写字母开头的文件的名字

最后,文件名扩展在不匹配时,会原样输出。警惕这一点:

sh
ls # 假设没有任何文件的话
echo * # 则输出 `*`

根据字符串处理的替换 {}

{,} 属于字符串替换(类似于正则替换,与现有文件无关)。注意,内部不要含有空格。

sh
echo {1,2,3,233} # 等价于 echo 1 2 3 233
echo d{a,b,c}g # echo dag dbg dcg

cp a.log{,.bak} # cp a.log a.log.bak

大括号和其他模式混合在一起的时候,它的优先级最高;大括号支持嵌套,类似于循环的嵌套,不过这样写很复杂了,少用:

sh
echo a{A{1,2},B{3,4}}b # aA1b aA2b aB3b aB4b

echo /bin/{cat,b*} # 等价于 echo /bin/cat /bin/b*, 再进一步解析

大括号有 .. 语法,支持范围匹配。

sh
echo {c..a} # c b a

量词语法

基于上面几种匹配模式,引入量词语法。

  • ?(pattern-list):模式匹配零次或一次。
  • *(pattern-list):模式匹配零次或多次。
  • +(pattern-list):模式匹配一次或多次。
  • @(pattern-list):只匹配一次模式。
  • !(pattern-list):匹配给定模式以外的任何内容。

基于 shell 变量的扩展

$ 开头类型的 token 将被匹配为 bash 的一类特殊模式,有 shell 变量、命令、甚至运算。

sh
echo $SHELL # echo /bin/bash

${}{} 内部有自己的语法。其内部的具体语法稍后讨论。这涉及到 bash 脚本。

想要输出 $ 而不是进行扩展,使用 \$ 来转义一下。

sh
echo \$date # $date

其他必要知识

文件名可以使用通配符

他们被视为普通的字符处理。他们在显示和使用中被引号引住即可。bash 规定,任何被单引号引住的 token 都不会再被解析和模式匹配。

sh
touch 'fo*'
ls # 'fo*'

?* 都不能匹配 / 字符

由于这两个字符是基于文件替换的,所以,默认情况下 ?* 都不能匹配 / 字符,即不匹配当前目录更深的子目录下的文件。如果要匹配子目录的文件,则像下面这样写:

sh
ls */*.txt # 显式的指出 `/`

ls **/*.txt # `**` 将作为 `*` 的增强版,它能够匹配到 `/`,所以 `**` 可以匹配任意多层次的目录

转义字符在命令行中的处理

由前面介绍的那样,很多本来是普通字符的字符,被 bash 当做语法特殊处理了,所以他们本身就不能表达原来的普通字符了。所以转义字符就是用来解决这一点的,所有转义字符处理后将会被原样输出,而不会再被特殊处理:

  • \\n 表示 '' (用作换行)
  • \\ 表示 \
  • \$ 表示 $

引号

  • ''(单引号)内的东西,均不会被 bash 解释,即单纯的字符串。
  • $'' 内的东西,只有 \ 会被识别。也就是说,此字符串的识别和 bash 无关,完全取决于环境。它不会识别 bash 内置的转义字符和 $ 等特殊语法,只会识别通用的转义字符。
  • ""(双引号)在我们需要借助 bash 提供的变量等功能的时候使用。它内部,bash 内置的三个特殊字符 $, \, ` 会被识别;\ 也只会识别 bash 内置的那部分转义字符,不会识别我们通用的转义字符,比如 \n, \t 之类。也就是说,双引号内部的识别完全取决于 bash 的实现,而与外部无关,这和 $'' 刚好对立。

最后,只要被引号引住,就意味着其本身作为一个整体 token 存在,所以,空格不会被删除:

sh
a="1 2  3"
echo $a # 1 2 3
echo "$a" # 1 2  3