特殊符号
注释
单行注释的使用方法:
> 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并不支持别名。