特殊符号
注释
单行注释的使用方法:
> date # 我是命令行注释
Sun 01 Mar 2020 11:25:57 AM CST
>echo "#我不是注释" # 我是注释
#我不是注释
多行注释的使用方法:
:<<EOF
Author: scarlet
... ... ...
EOF
# 结束符号不一定是EOF
:<<poi
... ...
... ...
poi
通配符
通配符是一种由 shell
实现的参数路径扩展功能,通配符由 shell
进行处理,且只能被用于命令的参数中(不能被用于命令名称里, 也不能被用于操作符)。 shell
在解析 token
(可以简单认为是每个独立的词)时如果遇到了 *
、 ?
、 [
则将这个 token
当做 PATTERN
按照如下步骤进行处理:
- 在磁盘上搜寻可能匹配的路径或文件名
- 若符合要求的匹配存在,则进行替换(路径扩展),然后根据
GLOBIGNORE
删除要忽略的匹配项,最后返回按照字母顺序排序的列表。 - 若没有匹配项:
- 如果设置了
nullglob
选项,这个token
会被移除。 - 如果没有设置
nullglob
,则将token
作为一个普通字符传递给命令,然后再由命令进行处理。
- 如果设置了
> ls
poi.c aqua.cc mana.py
> ls *.c # shell 解析 *.c 为 poi.c 然后将其传递给 ls
poi.c
> ls *.go # shell 解析 *.go 无匹配项 因此参数原样传递给 ls
ls: cannot access '*.go': No such file or directory
常用通配符如下表所示:
| 字符 | 含义 | 实例 | 解析 |
| --- | --- | --- | --- |
| *
| 匹配零或多个字符 | a*b
| a
与 b
之间可以有任意长度的任意字符, 也可以一个也没有, 如 aabcb, a01b, ab
等。 |
| ?
| 匹配任意一个字符 | a?b
| a
与 b
之间有且只有一个字符, 可以是任意字符, 如 aab, aOb, a0b
等。 |
| [list]
| 匹配list中的任意单一字符 | a[xyz]b
| a
与 b
之间有且只有一个字符, 且只能是 x
或 y
或 z
, 如: axb, ayb, azb
。 |
| [!list]
| 匹配除list中的任意单一字符 | a[!0-9]b
| a
与 b
之间有且只有一个字符, 但不能是数字, 如 axb, aab, a-b
等。 |
| [c1-c2]
| 匹配c1-c2中的任意单一字符 | a[0-9]b
| a
与 b
之间有且只有一个字符,该字符是 0-9
之间的数字,如 a0b, a1b,... ,a9b
。 |
| {string1,string2,...}
| 匹配 sring1
或 string2
(或更多)其一字符串 | a{abc,xyz,123}b
| a
与 b
之间只能是 abc
或 xyz
或 123
这三个字符串之一。 |
[info]提示:尽管通配符看起来很像正则表达式,但二者之间还是有很大区别的,我们会在之后详细介绍正则表达式的用法。
替代 stdin
或 stdout
的 -
-
符号的含义会根据上下文变化, 要注意这个符号并不是由 shell
进行解析, 而是由支持该符号的命令(如: cat
, tar
等)进行处理.
# 简单读取 stdin
> uname | cat -
Linux
# cp -a /source/directory/* /dest/directory
> (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
# 用 tar 把目录打包发送到 stdout, 然后从 stdin 接受数据并解包
表达式
小括号
单小括号 (...)
的功能包括以下几点:
- 命令组。括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
- 命令替换。等同于 \
cmd\
。shell
检查输入的命令时如果发现了$(cmd)
结构便执行cmd
,并将其输出用于替换$(cmd)
结构。 - 进程替换。使用
(command)>
或<(command)
可以将命令的输出结果视为文件描述符并进行重定向.
> diff <(ls $first_directory) <(ls $second_directory)
> cat <(ls -l) # 等价于 ls -l | cat
- 初始化数组。如:
array=(a b c d)
。
双小括号 ((...))
的功能主要是运算符表达式,语法类似于 C
语言,支持的运算符如下表所示:
运算符 | 作用 | ||
---|---|---|---|
VAR++ 和 VAR-- | 先用VAR,再自增/自减 | ||
++VAR 和 --VAR | 先自增/自减,再用VAR | ||
+ - * / % | 基础运算符 | ||
&& \ | \ | ! | 逻辑与 逻辑或 逻辑取反 |
** | 幂运算 | ||
<< 和 >> | 按位左移 按位右移 | ||
<=, >=, >, <, !=, == | 比较运算符 | ||
& \ | ~ ^ | 按位与 按位或 按位取反 按位异或 | |
expr ? expr : expr | 三目运算符 | ||
\=, *=, /=, %=, +=, -=, >=, &=, ^=, \ | = | 运算并赋值 | |
, | 逗号运算符 | ||
BASE#N | 表示 BASE 进制的数 N |
# 可以通过 $((...)) 的方式获得表达式的运算结果
> echo $((1<<2))
4
# 表达式的值与状态码
> echo $((1-1));((1-1));echo $?
0 # 表达式的结果为0
1 # 退出状态码为1
> echo $((5%3));((5%3));echo $?
2 # 表达式的结果不等于0
0 # 退出状态码为0
# 类似 C 语言的用法
> for((i=0;i<3;i++)); do echo $i; done
0
1
2
> echo $((i=0, i++, i--))
1 # 逗号运算符返回最后一个表达式的值
> echo $((i=0, j=i+1, --j))
0 # 注意自增/自减的顺序
# 区别于 C 语言的用法
> echo $[ 16#5f ]
95 # 计算时 十六进制的 5f 自动转换为十进制
> echo $(( 0x5f + 0X15 ))
116 # 特别的 十六进制数可以用 0x 或 0X 开头标识
> echo $(( 051 + 015 ))
54 # 特别的 八进制数可以用 0 开头标识
> echo $[ 3#212 + 36#21 ]
96 # 其他进制的数必须按照 BASE#N 的模式构造
中括号
中括号的作用主要有以下几点:
- 算术表达式:一对中括号包裹表达式
[表达式]
的作用和` 的作用几乎完全一致,区别在于中括号之间不能使用
;分隔多个表达式,只能使用
,` 以逗号表达式的形式完成多个表达式的计算。 - 条件判断:一对中括号
[ 2 -gt 0 ]
单独使用的效果和内置命令test
相同;两对中括号[[ 2 < 3 ]]
支持的运算符要更多,我们建议在任何情况下都使用两对中括号。if/test
结构中的左中括号是调用test
的命令标识,右中括号是关闭条件判断的标志,结果为真则返回0
,否则返回1
。 - 通配符和游标:可以用来做字符范围匹配
[a-z0-9]
,还可以用来访问数组的某个元素array[i]
。
大括号
- 代码块:在
{LIST}
中使用代码块会在当前shell
中运行代码,其本质是匿名函数,左大括号与命令间必须有空格,用法如下:
> a=1
> { echo $a;a=5;echo $a;} # 本质是匿名函数
1 # 可以访问父 shell 变量
5
> echo $a
5 # 变量修改会保留
- 代码块重定向:我们可以将整个代码块看做一个脚本文件, 对其输入和输出进行重定向:
# 输入重定向
{
read l1
echo "$l1"
# /etc/fstab: static file system information.
read l2
echo "$l2"
#
} < /etc/fstab
# 输出重定向
{
echo "line1"
echo
date
} > output.file
> cat output.file
line1
Thu 19 Mar 2020 11:35:17 PM CST
- 函数重定向:大括号可以用来包裹函数体, 函数本身也支持使用重定向符号进行函数级别的输入输出重定向.
#!/usr/bin/bash
INFILE="t1.file"
OUTFILE="t2.file"
FAKEOUTFILE="t3.file"
seq 5 > $INFILE
function nprint () {
while read line
do
echo $line
done
} <$INFILE >$OUTFILE
# 函数的重定向只有在调用时才会发生
nprint >$FAKEOUTFILE
echo OUTFILE: `cat $OUTFILE`
echo FAKEOUTFILE: `cat $FAKEOUTFILE`
nprint <(seq 8)
echo OUTFILE: `cat $OUTFILE`
在上述脚本中, 我们首先把 nprint
函数的输入输出分别重定向为 INFILE
和 OUTFILE
, 然后将整个函数的输出重定向给 FAKEOUTFILE
, 最后通过进程替换的方式把函数的输入重定向为 <(seq 8)
, 该脚本的输出结果如下:
OUTFILE: 1 2 3 4 5
FAKEOUTFILE:
OUTFILE: 1 2 3 4 5
第一行的 OUTFILE
获得了期望的输出, 但是由于函数重定向无法被覆盖, 因此 FAKEOUTFILE
的输出为空, 最后一行的 OUTFILE
也不会输出 seq 8
的结果.
- 字符串拓展:在这种用法中,大括号里不能有未转义的空格,具体用法如下:
> echo {a,c,p}.poi # 逗号分隔
a.poi c.poi p.poi
> echo {a..c}-{0..2}0.pkl # 嵌套
a-00.pkl a-10.pkl a-20.pkl b-00.pkl b-10.pkl b-20.pkl c-00.pkl c-10.pkl c-20.pkl
- 变量替换:我们可以使用常量、变量、命令的输出结果对变量的值进行替换,有以下四种替换方法:
# ${var:-newvar}模式
> var=15
> echo ${var:-19}
15 # var 非空则用 var 替换结构
> unset var; # 或 var=“”
> echo ${var:-abc}
abc # var 为空则用 newvar 替换结构
# :+ 模式的替换规则与 :- 相反
# := 模式的替换规则与 :- 相同,但在 var 为空时会额外把 newvar 的值赋给 var
> unset var;
> echo ${var:= `echo 1abt` }
1abt
> echo $var
1abt
# ${var:?newvar}模式
> var=poi
> echo ${var:?56}
poi
> unset var;
> echo ${var:?error} # var 为空则将 newvar 输出到 stderr
bash: var: error # 注意这个错误是 bash 的 stderr 输出的
# 如果在脚本中则会直接退出脚本 因此可以用于变量检查和调试
- 字符串:
- 模式删除:
# ## % %%
- 切片:
:num1:num2
- 模式替换:
/p1/p2 //p1/p2 /#p1/rep /%p1/rep
- 模式删除:
> var=~;echo $var
/home/remilia
> echo ${var#*e}
/remilia # 左侧开始匹配 删除第一个
> echo ${var##*e} # 左侧开始匹配 删除所有
milia
> echo ${var%i*} # 右侧开始匹配 删除第一个
/home/remil
> echo ${var%%i*} # 右侧开始匹配 删除所有
/home/rem
> echo ${var:5} # 从索引 5 开始截取
/remilia
> echo ${var:-5} # 截取最后 5 个字符
milia
> echo ${var:2-9} # 截取最后 7 个字符
remilia
> echo ${var:1:4} # 从索引 1 开始截取 4 个字符
home
> echo ${var:6:-3} # 从索引 6 开始截取到倒数第 3+1 个字符
remi
> echo ${var/e/o} # 从左向右寻找,替换第一个找到的
/homo/remilia
> echo ${var//e/o} # 从左向右寻找,替换全部找到的
/homo/romilia
> echo ${var//e/} # 从左向右寻找,删除全部找到的
/hom/rmilia
> echo $var
hoppohhoppoh
> echo ${var/#hop/@}
@pohhoppoh # 仅替换开头子串
> echo ${var/%poh/@}
hoppohhop@ # 仅替换结尾子串
[info]提示:对
{}
和()
而言,括号内部的重定向符只影响该条命令, 而括号外的重定向符影响到括号中的所有命令。
别名
我们可以使用内置命令 alias
和 unalias
来设置和取消设置别名:
> alias # 查看当前已设置的别名
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
> alias dh='df -h' # 设置一个别名 作用范围类似于局部变量
> unalias dh # 取消一个别名
[info]提示:在非交互式
shell
中,默认没有设置expand_aliases
选项,因此不能使用别名(需要使用set expand_aliases
开启)。而且在性能方面,由于shell
函数的搜索优先度要高于alias
,因此更建议使用函数。[warning]警告:别名是
bash
中新添加的内置命令,sh
并不支持别名。