特殊符号

注释

单行注释的使用方法:

> 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 | ab 之间可以有任意长度的任意字符, 也可以一个也没有, 如 aabcb, a01b, ab 等。 | | ? | 匹配任意一个字符 | a?b | ab 之间有且只有一个字符, 可以是任意字符, 如 aab, aOb, a0b 等。 | | [list] | 匹配list中的任意单一字符 | a[xyz]b | ab 之间有且只有一个字符, 且只能是 xyz , 如: axb, ayb, azb 。 | | [!list] | 匹配除list中的任意单一字符 | a[!0-9]b | ab 之间有且只有一个字符, 但不能是数字, 如 axb, aab, a-b 等。 | | [c1-c2] | 匹配c1-c2中的任意单一字符 | a[0-9]b | ab 之间有且只有一个字符,该字符是 0-9 之间的数字,如 a0b, a1b,... ,a9b 。 | | {string1,string2,...} | 匹配 sring1string2 (或更多)其一字符串 | a{abc,xyz,123}b | ab 之间只能是 abcxyz123 这三个字符串之一。 |

[info]提示:尽管通配符看起来很像正则表达式,但二者之间还是有很大区别的,我们会在之后详细介绍正则表达式的用法。

替代 stdinstdout-

- 符号的含义会根据上下文变化, 要注意这个符号并不是由 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 函数的输入输出分别重定向为 INFILEOUTFILE , 然后将整个函数的输出重定向给 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]提示:对 {}() 而言,括号内部的重定向符只影响该条命令, 而括号外的重定向符影响到括号中的所有命令。

别名

我们可以使用内置命令 aliasunalias 来设置和取消设置别名:

> 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 并不支持别名。

results matching ""

    No results matching ""