BASH
概述
Shell
在接收到命令后,将以空格把命令分割成一个个 token
,并对每个 token
执行扩展(glob)。扩展之后,才根据结果来进行命令的实际执行。
扩展可以是:
- 单纯的替换
- 根据已有文件扩展
- 字符串扩展(类似正则,但不一样)
- 根据 bash 变量扩展
扩展的行为可以用过 shopt
命令来控制。
扩展
针对特殊字符的单纯替换
波浪线 ~string
将被字符串替换为对应的用户目录:
pwd # /home/me/anywhere
echo ~ # /home/me
echo ~xiaoyan13 # /home/xiaoyan13
echo ~root # /root
echo ~+ # /hoem/me/anywhere. `~+` 被特别处理为当前目录,即 `pwd`
根据已有文件
?
/ *
/ []
属于根据已有文件扩展,其中 ?
和 []
匹配单个字符,*
匹配 0 或多个字符。
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
)所代表的字符之中的一个。
echo [[:upper:]]* # 命令输出所有大写字母开头的文件的名字
最后,文件名扩展在不匹配时,会原样输出。警惕这一点:
ls # 假设没有任何文件的话
echo * # 则输出 `*`
根据字符串处理的替换 {}
{,}
属于字符串替换(类似于正则替换,与现有文件无关)。注意,内部不要含有空格。
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
大括号和其他模式混合在一起的时候,它的优先级最高;大括号支持嵌套,类似于循环的嵌套,不过这样写很复杂了,少用:
echo a{A{1,2},B{3,4}}b # aA1b aA2b aB3b aB4b
echo /bin/{cat,b*} # 等价于 echo /bin/cat /bin/b*, 再进一步解析
大括号有 ..
语法,支持范围匹配。
echo {c..a} # c b a
量词语法
基于上面几种匹配模式,引入量词语法。
- ?(pattern-list):模式匹配零次或一次。
- *(pattern-list):模式匹配零次或多次。
- +(pattern-list):模式匹配一次或多次。
- @(pattern-list):只匹配一次模式。
- !(pattern-list):匹配给定模式以外的任何内容。
基于 shell 变量的扩展
$
开头类型的 token
将被匹配为 bash 的一类特殊模式,有 shell 变量、命令、甚至运算。
echo $SHELL # echo /bin/bash
${}
的 {}
内部有自己的语法。其内部的具体语法稍后讨论。这涉及到 bash 脚本。
想要输出 $
而不是进行扩展,使用 \$
来转义一下。
echo \$date # $date
其他必要知识
文件名可以使用通配符
他们被视为普通的字符处理。他们在显示和使用中被引号引住即可。bash 规定,任何被单引号引住的 token
都不会再被解析和模式匹配。
touch 'fo*'
ls # 'fo*'
?
和 *
都不能匹配 /
字符
由于这两个字符是基于文件替换的,所以,默认情况下 ?
和 *
都不能匹配 /
字符,即不匹配当前目录更深的子目录下的文件。如果要匹配子目录的文件,则像下面这样写:
ls */*.txt # 显式的指出 `/`
ls **/*.txt # `**` 将作为 `*` 的增强版,它能够匹配到 `/`,所以 `**` 可以匹配任意多层次的目录
转义字符在命令行中的处理
由前面介绍的那样,很多本来是普通字符的字符,被 bash
当做语法特殊处理了,所以他们本身就不能表达原来的普通字符了。所以转义字符就是用来解决这一点的,所有转义字符处理后将会被原样输出,而不会再被特殊处理:
\\n
表示 '' (用作换行)\\
表示\
\$
表示$
引号
''
(单引号)内的东西,均不会被 bash 解释,即单纯的字符串。$''
内的东西,只有\
会被识别。也就是说,此字符串的识别和bash
无关,完全取决于环境。它不会识别 bash 内置的转义字符和$
等特殊语法,只会识别通用的转义字符。""
(双引号)在我们需要借助bash
提供的变量等功能的时候使用。它内部,bash
内置的三个特殊字符$
,\
,`
会被识别;\
也只会识别 bash 内置的那部分转义字符,不会识别我们通用的转义字符,比如\n
,\t
之类。也就是说,双引号内部的识别完全取决于bash
的实现,而与外部无关,这和$''
刚好对立。
最后,只要被引号引住,就意味着其本身作为一个整体 token
存在,所以,空格不会被删除:
a="1 2 3"
echo $a # 1 2 3
echo "$a" # 1 2 3