Shell编程基础

变量

定义、使用、删除

  • 定义变量

    • 定义时不加$符号
    • 变量名和等号之间不能有空格
    • 变量名只能由英文字母、数字、下划线,且不以数字开头, 不能用bash中的关键字
    • 已经定义变量可以重新定义
  • 使用变量

    • 即可使用, 未定义变量直接使用不报错,返回空值
    • {}可选,但是{}能够明确定义变量名范围,帮助阅读、 解释器执行
  • 匿名变量

    • 可以认为所以语句(命令)的执行结果都存储在一个匿名 变量中,可以在其之前加上$获取其结果
  • readonly:设置变量为只读变量,更改(重定义)变量报错

  • unset:删除变量,不能删除只读变量

局部变量

局部变量:在脚本、命令中定义

  • 仅在当前shell实例中有效

    • shell变量默认为global,作用域从被定义处开始到脚本 末尾(即使在函数内部定义)
    • 显式local声明作用域局限于函数内部
  • 其他shell启动的程序不能访问局部变量

环境变量

环境变量:就是shell中的普通变量,只是这些变量往往被进程读取 用于自身初始化、设置

  • 狭义:export/declare -x声明的变量,只有这样的变量 才能默认被子进程继承
  • 广义;shell中所有的变量(包括局部变量)

环境变量设置

在shell中设置环境变量有两种方式

  • export/declare -x:设置全局(环境)变量

    • 任何在该shell设置环境变量后,启动的(子)进程都会 继承该变量
    • 对于常用、许多进程需要的环境变量应该这样设置
  • <ENV_NAME>=... cmd:设置临时环境变量

    • <ENV_NAME>=...不能被子进程继承,所以必须在其后立刻 接命令
    • 只对当前语句有效,也不能覆盖同名变量

用途

  • 环境变量是某些程序正常运行的必要条件
  • 所有程序都能访问环境变量,可用作进程通信
    • 在程序中设置环境变量,供其他进程访问变量
    • 父进程为子进程设置环境变量,供其访问

Shell变量

Shell变量:shell程序设置的特殊变量,包括环境变量、局部变量, 保证shell的正常运行

  • $0:shell执行脚本名

    • 交互式bash(即普通终端中)该参数为-bash
      • source执行脚本时,该参数可能非预期
    • 以下shell变量均不考虑$0,如
      • 函数中$0也是脚本名
      • shift无法跳过该参数
  • $1,..., $9:向脚本传递的位置参数

  • $@:脚本、函数参数列表
  • $*:脚本、函数参数字符串
  • $#:脚本、函数参数数量
  • $?:上条命令执行结果
  • $$$$:脚本进程号
  • $!:执行脚本最后一条命令

字符串

拼接

  • shell中字符默认为字符串,自动拼接

通配符

  • *:匹配任意长度字符串

    • 不包括./,必须显式匹配
  • ?:匹配一个字符

  • []:匹配出现在[]中的字符

    1
    2
    ls /[eh][to][cm]*
    # 匹配`/etc`、`/home`等
  • {}

    • 枚举全部

      1
      2
      3
      4
      5
      6
      $ mkdir {user1, user2}-{home, bin}
      # 笛卡尔积(字符串连接)
      # 等价于建立`user1-home, user1-bin, user2-home, user2-bin`
      $ echo {1..10}
      # 生成序列
      # 见`for`中`RangeIterator`部分

子串

1
2
$ file=/dir1/dir2/dir3/file.txt.bak
$ null=""

切片

  • ${<var_name>:n[:m]}:从第n开始m个字符
    • nm非负值
      • n:从0开始的下标起始
      • m切片长度缺省表示到末尾
    • nm使用0-负值表示
      • n:从0开始的负向下标起始
      • m:从0开始的负向下标终止
命令 解释 结果
${file:0:5} 提取首个字符开始的5个字符 /dir1
${file:5:5} 提取第5个字符开始的5个字符 /dir2
${file:5} 提取第5个字符开始至末尾 /dir2...
${file:0-5} 反向计算下标 t.bak
${file:0-5:0-1} 反向计算下标 t.ba
${file:5:0-2} 提取第5个字符开始至-2下标处 /dir2.../t.b

子串匹配

1
2
3
4
${<var_name>%<pattern>}
${<var_name>%%<pattern>}
${<var_name>#<pattern>}
${<var_name>##<pattern>}
  • #:去掉字符串左边最短pattern
  • ##:去掉字符串左边最长pattern
  • %:去掉字符串右边最短pattern
  • %%:去掉字符串右边最长pattern
  • 注意:var_name前不要变量标志$
  • 以上4种模式返回新值,不改变原变量值
  • 仅在pattern中使用通配符时,最短、最长匹配才有区别
命令 解释 结果
${file#*/} 去除首个/及其左边 dir2/dir3/file.txt.bak
${file##*/} 仅保留最后/右边 file.txt.bak
${file#*.} 去除首个.及其左边 txt.bak
${file##*.} 仅保留最后.右边 bak
${file%/*} 去除最后/及其右边 /dir1/dir2/dir3
${file%%*/} 去除首个/及其右边 空值
${file%*.} 去除最后.及其右边 /dir1/dir2/dir3/file.txt
${file%%*.} 去除首个.及其右边 /dir1/dir2/dir3/file.txt

替换

  • /from/to:替换首个fromto
  • //from/to:替换全部fromto
命令 解释 结果
${file/dir/path} 替换首个dirpath /path1/dir2/dir3/file.txt.bak
${file/dir/path} 替换全部dirpath /path1/path2/path3/file.txt.bak

默认值替换

  • -:变量未设置返回
  • +:变量设置返回
  • =:变量未设置返回、设置变量
  • ?:变量未设置输出至stderr
  • ::以上命令条件包括空值(空值视为未设置)

shell_variable_assignment

命令 解释 示例 结果
${井<var_name>} 获取字符串长度 ${井file} 27
${<var_name>-<default>} 变量未设置返回默认值 ${invalid-file.txt.bak} file.txt.bak
${<var_name>:-<default>} 变量未设置、空值返回默认值 ${null-file.txt.bak} file.txt.bak
${<var_name>+<default>} 变量设置返回默认值 ${file-file.txt.bak} fil.txt.bak
${<var_name>:+<default>} 变量非空返回默认值 ${file-file.txt.bak} file.txt.bak
${<var_name>=<default>} 变量未设置,返回默认值、并设置变量为默认值 ${invalid=file.txt.bak} file.txt.bak
${<var_name>:=<default>} 变量未设置、空值返回默认值、并设置变量为默认值 ${null=file.txt.bak} file.txt.bak
{$<var_name>?<default>} 变量未设置输出默认值至stderr {invalid?file.txt.bak} file.txt.bak输出至stderr
{$<var_name>:?<default>} 变量未设置、空值输出默认值至stderr {$null:?file.txt.bak} file.txt.bak输出至stderr

字符串比较

  • =, !=, -z, -n:字符串相等、不相等、为空、非空

  • 使用=比较变量是否为某字符串时,其中在两侧添加字符 保证=不为空值,否则若$test为空值,表达式报错

    1
    2
    3
    if [[ "$text"x = "text"x ]]; then
    command
    fi
  • 比较变量时要在两侧加上双引号"",否则可能报错、结果不 符合预期

数组

1
$ A=(a b c def)

取值

  • []:选择数组元素
    • [n]:返回数组第n个元素
    • [*]/[@]:返回数组全部元素
命令 解释 结果
${A[@]} 返回全部元素 a b c def
${A[*]} 同上 同上
${A[0]} 返回数组第一个元素 a
${井A[@]} 返回数组元素总数 4
${井A[*]} 同上 同上
${井A[3]} 返回数组第4个元素长度 3
A[3]=xyz 设置数组第4个元素值

数值

  • (())/[]:在其中执行整数运算
  • let:执行数值运算

规则

  • 返回的数据结果相当于存储在一个匿名变量中,使用的话需要 在之前加上$

  • 1
    2
    3
    $ a=5; b=7; c=2;
    $ echo $((a + b * $c))
    # 19
  • $((N#xx))/$[$#xx]:将其他进制数据转换为10进制

    1
    2
    $ echo $((2#110))
    # 6
  • 自增、自减运算可以在(())/[]中直接执行

    1
    2
    $ a=5; ((a++)); echo $a;
    # 6

特殊运算符

  • ~:位与
  • |:位或
  • ^:位异或
  • >>:位右移
  • <<:位左移
  • **:数值平方
  • +=, ++, -=, --, *=, \=:自变化运算符

逻辑运算

  • -eq, -ne, -ge, -le, -lt, -gt:比较
  • ==, !=, >=, <=, <, >:数值比较

文件

  • -e:目标存在
  • -f:文件为一般文件(非目录、设备)
  • -s:文件大小非0
  • -d:目标为目录
  • -b:文件为块设备(软盘、光驱)
  • -c:文件为流设备(键盘、modem、声卡)
  • -p:文件为管道
  • -h/L:目标为符号链接
  • -S:目标为Socket
  • -t:文件描述符被关联到终端
    • 一般用于检测脚本的stdin是否来自终端[ -t 0 ],或 输出是否输出至终端[ -t 1 ]
  • -r:目标是否可读权限
  • -w:目标是否可写权限
  • -x:目标是否可执行权限
  • -g:目标是否设置有set-group-id
  • -u:目标是否设置有set-user-id
  • -k:目标是否设置sticky bit
  • -O:用户是否是文件所有者
  • -G:用户是否属于文件所有组
  • -N:文件从上次读取到现在为止是否被修改
  • f1 -nt f2:文件f1f2
  • f1 -ot f2:文件f1f2
  • f1 -ef f2:文件f1f2指向相似文件实体(相同的硬链接)

Lexical Analysis

  • python将读取的程序问题转换为Unicode码点
    • 源文件的文本编码可由编码声明指定
    • 默认UTF-8
  • 词法分析器将文件拆分为token
  • 解释器以词法分析器生成的token流作为输入

行结构

逻辑行

逻辑行:逻辑行的结束以NEWLINE token表示

  • 语句不能跨越逻辑行边集,除非其语法允许包含NEWLINE (如复合语句包含多个子语句)
  • 逻辑行可由一个、多个物理行按照明确、隐含行拼接规则 构成

python程序可以分为很多逻辑行

物理行

物理行:以行终止序列结束的字符序列

  • 源文件、字符串中可以使用任何标准平台上行终止序列
    • Unix:\n换行符LF
    • Win:\r\n回车加换行CR LF
    • Macintosh:\r回车CR
  • 输入结束会被作为最后物理行的隐含终止标志
  • 嵌入Python源码字符串应使用标准C传统换行符\n

显式行拼接

显式行拼接:两个、多个物理行使用\拼接为一个逻辑行

  • 物理行以不在字符串、注释内的反斜杠结尾时,将与下行 拼接构成一个单独逻辑行
    • 反斜杠、其后换行符将被删除
  • 以反斜杠结束的行不能带有注释
  • 反斜杠不能用于
    • 拼接注释
    • 拼接字符串外token
  • 不允许原文字符串以外反斜杠存在于物理行其他位置

隐式行拼接

隐式行拼接

  • 圆括号、方括号、花括号内表达式允许被分为多个物理行,无需 使用反斜杠

    • 拼接行可以带有注释
    • 后续行缩进不影响程序结构、允许为空白行
    • 拼接行之间不会有NEWLINE token
  • 三引号"""/'''字符串允许被分为多个物理行

    • 拼接行中不允许带有注释

空白行

空白行:只包含空格符、制表符、进纸符、注释的逻辑行会被忽略, 不生成NEWLINE token

  • 交互式输入语句时,对空白行处理可能因为读取-求值-打印循环 的具体实现而存在差异

    • 标准交互模式解释器中:完全空白逻辑行将会结束一条多行 复合语句

注释

注释:一不包含在字符串内的#开头,在物理行末尾结束

  • 注释标志逻辑行的结束,除非存在隐含行拼接规则
  • 注释在语法分析中被忽略,不属于token

编码声明

编码声明:位于python脚本第一、第二行,匹配正则表达式 coding[=:]\s*([-\w.]+)的注释将被作为编码声明处理

1
# -*- coding: <encoding-name> -*-
  • 表达式第一组指定了源码文件编码
    • 编码声明指定编码名称必须是python所认可的编码
    • 词法分析将使用此编码:语义字符串、注释、标识符
  • 编码声明须独占一行,若在第二行,则第一行也必须为注释
  • 没有编码声明,默认编码为UTF-8
    • 若文件首字节为UTF-8字节顺序标志b\xef\xbb\xbf, 文件编码也声明为UTF-8

缩进

缩进:逻辑行开头的空白(空格符、制表符)被用于计算该行 的缩进等级,决定语句段落组织结构

  • 首个非空白字符之前的空格总数确定该行的缩进层次
    • 缩进不能使用反斜杠进行拼接,首个反斜杠之前空格将确定 缩进层次
  • 制表符被替换为1-8个空白,使得缩进的空格总数为8倍数
    • 源文件若混合使用制表符、空格符缩进,并使得确定缩进 层次需要依赖于制表符对应空格数量设置,将引发 TabError
    • 由于非Unix平台上文本编辑器本身特性,源文件中混合使用 制表符、空格符是不明智的
  • 进纸符
    • 在行首时:在缩进层级计算中被忽略
    • 行首空格内其他位置:效果未定义,可能导致空格计数重置 为0
  • 不同平台可能会显式限制最大缩进层级

INDENT/DEDENT token

  • 读取文件第一行前,向堆栈中压入零值,不再弹出
  • 被压入栈的层级数值从底至顶持续增加
  • 每个逻辑行开头的行缩进层级将和栈顶进行比较
    • 相同:不做处理
    • 新行层级较高:压入栈中,生成INDENT token
    • 新行层级较低:应为栈中层级数值之一
      • 栈中高于该层级所有数值被弹出
      • 每弹出一级数值生成一个DEDENT token
  • 文件末尾,栈中剩余每个大于0数值生成一个DEDENT token

Tokens

型符

  • 空白字符不属于token
    • 除逻辑行开头、字符串内,空格符、制表符、进纸符等 空白符均可分隔token
    • 否则彼此相连的token会被解析为一个不同的token
  • 若存在二义性,将从左至右尽可能长读取合法字符串组成token

Indentifiers

标识符/名称:python标识符语法基于Unicode标准附件UAX-31,有 修改

  • ASCII字符集内可用于标识符与py2一致

    • 大、小写字母
    • 下划线_
    • 数字0-9
  • py3中引入ASCII字符集以外的额外字符

    • 其分类使用包含于unicodedata模块中Unicode字符数据库 版本
  • 标识符没有长度限制、大小写敏感

1
2
3
4
5
identifier   ::=  xid_start xid_continue*
id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">
  • Lu:大写字母
  • Ll:小写字母
  • Lt:词首大写字母
  • Lm:修饰字母
  • Lo:其他字母
  • Nl:字母数字
  • Mn:非空白标识
  • Mc:含空白标识
  • Nd:十进制数字
  • Pc:连接标点
  • Other_ID_Start:由PropList.txt定义的显式字符列表, 用来支持向后兼容
  • Other_ID_Continue:同上

Keywords

关键字:以下标识符作为语言的保留字、关键字,不能用作普通 标识符

1
2
3
4
5
6
7
False      await      else       import     pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yield

保留标识符类

以下划线字符开头、结尾的标识符类:具有特殊函数

  • _*:不会被from module import *导入

    • 特殊标识符_:交互式解释器中用于存放最近一次求值 结果,不处于交互模式时无特殊含义、无预定义
  • __*__:系统定义名称

    • 由解释器极其实现(包括标准库)定义
    • 任何不遵循文档指定方式使用__*__行为可能导致无警告 出错
  • __*:类私有名称

    • 在类定义中使用
    • 会以混合形式重写避免基类、派生类私有属性之间出现 名称冲突

字面值

字面值:表示一些内置类型常量

字符串、字节串字面值

1
2
3
4
5
6
7
8
9
10
stringliteral   ::=  [stringprefix](shortstring | longstring)
stringprefix ::= "r" | "u" | "R" | "U" | "f" | "F"
| "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"
shortstring ::= "'" shortstringitem* "'" | '"' shortstringitem* '"'
longstring ::= "'''" longstringitem* "'''" | '"""' longstringitem* '"""'
shortstringitem ::= shortstringchar | stringescapeseq
longstringitem ::= longstringchar | stringescapeseq
shortstringchar ::= <any source character except "\" or newline or the quote>
longstringchar ::= <any source character except "\">
stringescapeseq ::= "\" <any source character>
1
2
3
4
5
6
7
8
9
bytesliteral   ::=  bytesprefix(shortbytes | longbytes)
bytesprefix ::= "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"
shortbytes ::= "'" shortbytesitem* "'" | '"' shortbytesitem* '"'
longbytes ::= "'''" longbytesitem* "'''" | '"""' longbytesitem* '"""'
shortbytesitem ::= shortbyteschar | bytesescapeseq
longbytesitem ::= longbyteschar | bytesescapeseq
shortbyteschar ::= <any ASCII character except "\" or newline or the quote>
longbyteschar ::= <any ASCII character except "\">
bytesescapeseq ::= "\" <any ASCII character>
  • stringprefixbytesprefix与字面值剩余部分之间不允许 由空白符
  • 源字符集由编码声明定义
  • 字节串字面值只允许ASCII字符(但允许存储不大于256)
  • 两种字面值都可以使用成对(连续三个)单引号、双引号标示 首尾

    • 单引号'':允许包含双引号""
    • 双引号"":允许包含单引号''
    • 三重引号'''"""
      • 原样保留:未经转义的换行、(非三联)引号、空白符
  • 反斜杠\用于对特殊含义字符进行转义

字符串前缀
  • b/B前缀:字节串字面值

    • 创建bytes类型而非str类型实例
    • 只能包含ASCII字符
    • 字节对应数值大于128必须以转义形式表示
  • r/R:原始字符串/字节串

    • 其中反斜杠\被当作其本身字面字符处理
    • 转换序列不在有效
    • 原始字面值不能以单个\结束,会转义之后引号字符
  • f/F:格式化字符串字面值

转义规则
  • 字符串、字节串字面值中转义序列基本类似标准C转义规则

    • \xhh:必须接受2个16进制数码
  • 以下转义序列仅在字符串字面值中可用

    • \N{name}:Unicode数据库中名称为name的字符
    • \uxxxx:必须接受4个16进制数码
    • \Uxxxxxxxx:必须接受8个16进制数码
  • 无法识别的转义序列

    • py3.6之前将原样保留在字符串中
    • py3.6开始,将引发DeprecationWarning,未来可能会 引发SyntaxError
字符串字面值拼接
  • 多个相邻字符串、字符串字面值(空白符分隔),含义等同于 全部拼接为一体

    • 所用引号可以彼此不同(三引号风格也可用)
    • 每部分字符串可以分别加注释
    • 可以包括格式化字符串字面值
  • 此特性是在句法层面定义,在编译时实现

    • 在运行时拼接字符串表达式必须使用+运算符
格式化字符串字面值

格式化字符串字面值:带有f/F前缀的字符串字面值

  • 包含可替换字段,即以{}标示的格式表达式

    • 字符串{}以外部分按字面值处理
    • 双重花括号{ { } }被替换为相应单个花括号
  • 格式表达式被当作正常的包含在圆括号中python表达式处理 ,在运行时从左至右被求值

    • 不允许空表达式
    • lambda空表达式必须显式加上圆括号
    • 可以包含换行:如三引号字符串
    • 不能包含注释
    • 不能\反斜杠,考虑创建临时变量
  • 格式化字符串字面值可以拼接,但是一个替换字段不能拆分到 多个字面值中

  • 格式化字符串不能用作文档字符串,即使其中没有包含表达式

1
2
3
4
5
6
7
8
f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::= "{" f_expression ["!" conversion] [":" format_spec] "}"
f_expression ::= (conditional_expression | "*" or_expr)
("," conditional_expression | "," "*" or_expr)* [","]
| yield_expression
conversion ::= "s" | "r" | "a"
format_spec ::= (literal_char | NULL | replacement_field)*
literal_char ::= <any code point except "{", "}" or NULL>
  • !:标识转换字段

    • !s:对结果调用str()
    • !r:调用repr()
    • !a:调用ascii()
    1
    2
    3
    4
    name = "Fred"
    print(f"he said his name is {name!r}")
    print("he said his name is {repr(name)}")
    # 二者等价
  • ::标识格式说明符,结果使用format()协议格式化

    • 格式说明符被传入表达式或转换结果的.__format__() 方法
    • 省略格式说明符则传入空字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    width, precision = 4, 10
    value = decimal.Deciaml("12.345")
    print(f"result: {value: {width}.{precision}}")

    today = datetime(yeat=2017, month=1, day=27)
    print(f"{today: %B %d, %Y}")

    number = 1024
    print(f"{number: #0x}")
  • 顶层格式说明符可以包含嵌套替换字段

    • 嵌套字段可以包含有自身的转换字段、格式说明符,但不能 包含更深层嵌套替换字段

数字字面值

  • 数字字面值不包括正负号
    • 负数实际上是由单目运算符-和字面值组合而成
  • 没有专门复数字面值
    • 复数以一对浮点数表示
    • 取值范围同浮点数
  • 数字字面值可以使用下划线_将数码分组提高可读性
    • 确定数字大小时,字面值之间的下滑线被忽略
    • 下划线可以放在数码之间、基数说明符0x等之后
    • 浮点数中不能直接放在.
整形数字字面值
  • 整形数字字面值没有长度限制,只受限于内存
1
2
3
4
5
6
7
8
9
10
integer      ::=  decinteger | bininteger | octinteger | hexinteger
decinteger ::= nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
bininteger ::= "0" ("b" | "B") (["_"] bindigit)+
octinteger ::= "0" ("o" | "O") (["_"] octdigit)+
hexinteger ::= "0" ("x" | "X") (["_"] hexdigit)+
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
bindigit ::= "0" | "1"
octdigit ::= "0"..."7"
hexdigit ::= digit | "a"..."f" | "A"..."F"
浮点数字字面值
  • 整形数部分、指数部分解析时总以10为计数
  • 浮点数字面值允许范围依赖于具体实现
1
2
3
4
5
6
floatnumber   ::=  pointfloat | exponentfloat
pointfloat ::= [digitpart] fraction | digitpart "."
exponentfloat ::= (digitpart | pointfloat) exponent
digitpart ::= digit (["_"] digit)*
fraction ::= "." digitpart
exponent ::= ("e" | "E") ["+" | "-"] digitpart
虚数字面值
  • 序数字面值将生成实部为0.0的复数
1
imagnumber ::=  (floatnumber | digitpart) ("j" | "J")

运算符

1
2
3
+       -       *       **      /       //      %      @
<< >> & | ^ ~
< > <= >= == !=

分隔符

1
2
3
4
(       )       [       ]       {       }
, : . ; @ = ->
+= -= *= /= //= %= @=
&= |= ^= >>= <<= **=
  • 以上列表中后半部分为增强赋值操作符
    • 在词法中作为分隔符,同时起运算作用
1
'       "       #       \
  • 以上可打印ASCII字符
    • 作为其他token组成部分时有特殊意义
    • 或对词法分析器有特殊意义
1
$       ?
  • 以上可打印ASCII字符不再Python词法中使用
    • 出现在字符串字面值、注释之外将无条件引发错误

Shell执行控制

符号

命令执行

  • ;:连续执行指令,分割多条命令

    • 所以如果不是在一行内执行,;是可以有任意多个
  • ::内建空指令,返回值为0

  • &&, ||:逻辑与、或,全部(任意)之前的命令执行成功才会 执行下连接中的命令

“转义”

  • \\:转义字符

    • 放在指令前,取消alias
    • 特殊符号前转义,Shell将不对其后字符作特殊处理,当作 普通字符(即$, \', \"
    • 行尾表示连接下一行,回车符只起换行作用,视为空格
  • $:变量符号,表示其后字符串为一个变量

  • \', \":内部视为字符串

  • --:命令行中转义-,否则shell认为-是参数选项

    1
    2
    $ rm -- -file
    # 删除文件`-file`
    • 还可以在之前加上路径避免-被解释为参数选项
    • \''等均无效

引号

单引号''

  • 单引号括起字符都作为普通字符,变量无效

  • 单引号字串中不能出现单独的单引号,使用转义符也不行,但是 可以成对出现,作为字符串拼接使用

双引号""

  • 双引号字串中可以有变量、转义字符

反引号``

反引号扩起字符串表示Shell解释命令,执行时shell首先解释其, 以标准输出替代整个反引号

  • 括号中语句为命令,分号连接

    • ``中的表达式只需要符合C语言的运算规则 即可,甚至可以使用三目预算符、逻辑表达式
  • 命令和括号之间无空格

  • $``可以将命令输出作为变量返回值,同$()

括号

()

  • 命令组/运算表达式:其中的命令列表将在新子shell运行

    • 其中变量之后不能使用,不影响当前shell环境
    • 多个命令之间分号分隔,最后命令可以省略
    • 命令和括号之间可以无空格
  • 命令替换:等同于引号``

    • bash扫描命令行,执行$()结构中命令,将标准输出作为 匿名变量值
    • ()中出现()不需要转义,``需要转义
    • ()中不能使用C风格的运算表达式
    • 部分shell可能不支持(),如:tcsh
  • 初始化数组:参见数组

(())

  • 整数扩展:计算、扩展算数表达式结果

    • 表达式结果为0则退出状态为1、false
    • 表达式结果为1则退出状态为0、true
    1
    2
    a=5;((a++))			# 自增运算符
    b=$((16#5f)) # 进制转换
    • 退出状态和[]完全相反
    • 其中可使用任何符合C语言的运算,包括三目运算符
  • 算术比较:

    • 其中变量可以不使用$前缀、支持多个表达式逗号分隔
    • 可直接使用C风格表达式,如:forwhile循环语句中
1
2
3
4
5
6
7
for((i=0;i<5;i++))
for i in `seq 0 4`
for i iin {0..4}
# 三者等价
if ((i<5))
if [ $i -lt 5 ]
# 二者等价

[][[]]

  • []

    • 条件测试表达式:参见if
    • 数组索引
    • 正则表达式范围
  • [[]]

    • 条件测试表达式:参见if

{}

  • 创建匿名函数

    • 不开启新进程
    • 括号内变量之后可继续使用
    • 括号内命令分号分隔,最后一个语句也要有分号
    • {和第一个语句之间要有空格
  • 字符串

    • 默认值替换:参见字符串默认值替换
    • 获取子串:参见字符串子串
    • 获取切片:参见字符串子串
    • 字符串替换:参见字符串子串
  • $后包括变量名精确分隔变量名,参见变量使用

其他符号

!

Shell中!称为事件提示符,可以方便的引用历史命令:当! 后跟随字母不是空格、换行、回车、=(时,做命令替换

  • ![-]n:引用history中的正数/倒数第n个命令

    • !!:等同!-1,执行上条命令
  • !cmd:引用最近以cmd开头的命令,包括参数

    • !cmd:gs/pattern/replace:将上条cmd开头命令中 pattern替换为replace
  • !?str[?]:引用最近包含str的命令,str可以是参数

  • 参数引用

    • !$:最后一个参数
    • !:-:除最后一个参数
    • !*:所有参数

控制语句

if[]

用于条件控制结构,结构如下

1
2
3
4
if test_expression; then
then command_1
else command_2
fi
  • test <expr>:bash内部命令,判断表达式是否为真

    • 其后只能有一个表达式判断,无法使用逻辑与、或连接
  • [ <expr> ]:基本等同于test

    • 整数比较:-ne-eq-gt-lt
    • 字符串比较:转义形式大、小号\</>
    • 逻辑表达式连接:-o(或)、-a(与)、!(非) (不能使用||(或)、&&(与))
    1
    2
    3
    if [ $? == 0 ]
    if test $? == 0
    # 二者等价
    • 注意内部两边要有空格
    • 事实上是[等同于test]表示关闭条件判断,新版 bash强制要求闭合
  • [[ <expr> ]]条件测试

    • 整数比较:!==<>
    • 字符串:支持模式匹配(此时右侧模式时不能加引号)
    • 逻辑表示式连接:||(或)、&&(与)、!(非)
    1
    2
    3
    4
    if [[ $a != 0 && $b == 1 ]]
    if [ $a -eq 0 ] && [ $b -eq 1]
    if [ $a -eq 0 -a $b -eq 1]
    # 三者等价
    • [[:bash关键字,bash将其视为单独元素并返回退出 状态码

case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case chars in
char_pattern_1)
# `)`是必要的
# 匹配即停止,不会下沉
command_1
;;
# 每个模式字符串可以有多条命令,最后一条必须以`;;`结尾
char_pattern_2 | char_pattern_3)
# `|`分隔,或,可以匹配其中一个模式即可
command_2
;;
*)
# 匹配所有字符串模式,捕获所有
command_3
;;
esac
# 返回值位最后执行命令的退出值,未执行命令则为0

while

1
2
3
4
5
6
while test_expression
# test_expression是指`test`、`[]`语句
# 测试条件为真进入循环体
do
command_1
done

until

1
2
3
4
5
until test_exprssion
# 测试条件为假进入循环体
do
command_1
done

for

CStyle

1
2
3
4
5
for ((i=0; i<limit; i++))
# C-style for
do
command_1
done

ListStyle

1
2
3
4
5
for var in var_arr
# list-style for
do
command
done
FileIterator
1
2
3
4
5
6
for var in /path/to/file_regex
# `var`依次取(当前)目录下的与正则表达式匹配的文件名
# 执行循环体语句
do
command
done
  • /path/to/file_regex注意
    • 路径不存在:$var为该字符串
    • 不能用""括起,否则:$var只取路径下所有匹配合并 ,空格分隔
ParamsIterator
1
2
3
4
5
6
for var[ in $*]
# `var`依次取位置参数的值,执行循环体中命令
# `[ in $*]`可以省略
do
command
done
RangeIterator
1
2
3
4
5
6
7
8
9
for i in $(seq start end)
do
command
done

for i in {start..end}
do
command
done

break, continue, exit

  • break[n]:跳出n层循环体,默认跳出一层循环体
  • continue[n]:跳过n层循环体在其之后的语句,回到循环开头 ,默认跳过一次循环体
  • exit:退出程序,后跟返回值

SQL语法

基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT			<field>, DISTINCT <field>
INTO <new_tbl> [IN <other_db>]
FROM <tbl>
WHERE <field> <OP> <value>/<field>
ORDER BY <field> [ASC/DESC];

INSERT INTO <tbl>[<field>]
VALUES (<value>);

UPDATE <tbl>
SET <field> = <value>
WHERE <field> <OP> <value>/<field>;

DELETE
FROM <tbl>
WHERE <field> <OP> <value>/<field>;

数据库、表、索引、视图

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
!-- 创建数据库
CREATE DATABASE <db_name>;

!-- 创建表
CREATE TABLE <tbl>(
<field> <dtype>,
<field> <dtype> <CSTRT>, !-- MSSQL、ORACLE
<CSTRT> (<field>,), !-- MySQL
CONSTRAINT [<cstrt_name>] <cstrt> (<field>,) !-- common
)

!-- 创建索引
CREATE INDEX <index_name>
ON <tbl> (<field>);

!-- 创建视图
CREATE VIEW <view_name> AS
<select_expr>;
自增字段
1
2
3
4
5
6
7
8
9
10
!-- MSSQL
<field> <dtype> IDENTITY
!-- MySQL
<field> <dtype> AUTO_INCREMENT
!-- ORACLE:创建自增序列,调用`.nextval`方法获取下个自增值
CREATE SEQUENCE <seq>
MINVALUE <min>
START WITH <value>
INCREMENT BY <step>
CACHE <cache> !-- 提高性能

丢弃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
!-- 丢弃索引
!-- MSSQL
DROP INDEX <tbl>.<index_name>;
!-- ORACLE
DROP INDEX <index_name>;
!-- MySQL
ALTER TABLE <tbl>
DROP INDEX <index_name>;

!-- 丢弃表/数据
DROP TABLE <tbl>;
TRUNCATE TABLE <tbl>;

!-- 丢弃数据库
DROP DATABASE <db_name>;

!-- 丢弃视图
DROP VIEW <view>;

修改表

1
2
3
4
5
6
7
8
9
10
11
!-- 添加列
ALTER TABLE <tbl>
ADD <field> <dtype>;

!-- 删除列
ALTER TABLE <tbl>
DROP COLUMN <field>;

!-- 修改类型
ALTER TABLE <tbl>
ALTER COLUMN <field> <dtype>;

关键字

TOP

  • MSSQL:SELECT TOP <num>/<num> PERCENT *
  • MYSQL:LIMIT <num>
  • ORACLE:WHERE ROWNUM <= <num>

Alias

  • AS:指定行、列别名

Join

  • [INNER] JOIN
  • LEFT JOIN
  • RIGHT JOIN
  • FULL JOIN

Union

  • UNION:合并SELECT结果集
    • 要求结果集中列数量、类型必须相同

NULL

  • IS [NOT] NULL:比较是否为NULL
  • 比较符无法测试NULL

符号

运算符

  • =:有些方言可以使用==
  • <>:有些方言可以使用!=
  • >
  • <
  • >=
  • <=
  • BETWEEN <value> AND <value>
  • [NOT] IN (<value>)
  • [NOT] LIKE <pattern>
    • %:匹配0个、多个字符
    • _:匹配一个字符
    • [<char>]:字符列中任意字符
    • ^[<char>]/[!<char>]:非字符列中任意字符

逻辑运算

  • AND
  • OR

符号

  • ':SQL中使用单引号包括文本值
    • 大部分方言也支持"双引号

数据类型

MySQL

TEXT类型 描述
CHAR([<size>])
VARCHAR([<size>])
TINYTEXT
LONGTEXT
MEDIUMITEXT
BLOB
MEDIUMBLOB
LONGBLOB
ENUM(<val_list>)
SET
NUMBER类型 描述
TINYINT([<size>])
SMALLINT([<size>])
MEDIUMINT([<size>])
INT([<size>])
BIGINT([<size>])
FLOAT([<size>])
DOUBLE([<size>])
DECIMAL([<size>])
DATE类型 描述
DATE()
DATETIME()
TIMSTAMP()
TIME()
YEAR()

MSSQL

ASCII类型 描述
CHAR([<size>])
VARCHAR([<size>])
TEXT
UNICODE类型 描述
CHAR([<size>])
VARCHAR([<size>])
text
BINARY类型 描述
bit
binary([<n>])
varbinary([<n>])
image
NUMBER类型 描述
TINYINT
SMALLINT
MEDIUMINT
INT
BIGINT
DECIMAL(p, s)
FLOAT([<n>])
REAL
SMALLMONEY
MONEY
DATE类型 描述
DATETIME
DATETIME2
SMALLDATETIME
DATE
TIME
DATETIMEOFFSET
TIMESTAMP

约束

  • 建表时添加约束

    • MSSQL、ORACLE:可直接在字段声明后添加约束
    • MySQL:需独立指定约束
  • 向已有表添加约束

    • 可以添加匿名、具名约束
    • MSSQL、ORACLE:有COLUMN关键字
  • 删除约束

    • MySQL:使用约束关键字指定
    • MSSQL、ORACLE:使用CONSTRAINT关键字指定

NOT NULL

1
<field> <dtype> NOT NULL

DEFAULT

DEFAULT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
!-- 建表
<field> <dtype> DEFAULT <value>

!-- 已有表添加
!-- MySQL
ALTER TABLE <tbl>
ALTER <field> SET DEFAULT <value>;
!-- MSSQL、ORACLE
ALTER TABLE <tbl>
ALTER COLUMN <field> SET DEFAULT <value>;

!-- 删除
!-- MySQL
ALTER TABLE <tbl>
ALTER <field> DROP DEFAULT;
!-- MSSQL、ORACLE
ALTER TABLE <tbl>
ALTER COLUMN <field> DROP DEFAULT;

UNIQUE

UNIQUE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
!-- 建表
!-- MySQL、MSSQL、ORACLE
CONSTRAINT [<cstrt_name>] UNIQUE (<field>,)
!-- MySQL
UNIQUE [KEY] [<cstrt_name>] (<field>)
!-- MSSQL、ORACLE
<field> <dtype> UNIQUE

!-- 已有表添加
!-- MySQL、MSSQL、ORACLE
ALTER TABLE <tbl>
ADD UNIQUE(<field>);
ALTER TABLE <tbl>
ADD CONSTRAINT <cstrt_name> UNIQUE(<field>,);

!-- 删除
!-- MySQL
ALTER TABLE <tbl>
DROP INDEX <cstrt_name>;
!-- MSSQL、ORACLE
ALTER TABlE <tbl>
DROP CONSTRAINT <cstrt_name>;

PRIMARY KEY

PRIMARY KEY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
!-- 建表
!-- MySQL、MSSQL、ORACLE
CONSTRAINT [<cstrt_name>] PRIMARY KEY (<field>,)
!-- MYSQL
PRIMARY KEY (<field>,)
!-- MSSQL、ORACLE
<field> <dtype> PRIMARY KEY


!-- 已有表添加
!-- MySQL、MSSQL、ORACLE
ALTER TABLE <tbl>
ADD PRIMARY KEY (<field>,);
ALTER TABLE <tbl>
ADD CONSTRAINT <cstrt_name> PRIMARY KEY (<field>,);

!-- 删除
!-- MySQL
ALTER TABLE <tbl>
DROP PRIMARY KEY;
!-- MSSQL、ORACLE
ALTER TABLE <tbl>
DROP CONSTRAINT <cstrt_name>;

FOREIGN KEY

FOREIGN KEY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
!-- 建表
!-- MySQL、MSSQL、ORACLE
CONSTRAINT [<cstrt_name>] FOREIGN KEY (<field>,)
REFERENCES <tbl>(<field>,)
!-- MYSQL
FOREIGN KEY (<field>,)
REFERENCES <tbl>(<field>,)
!-- MSSQL、ORACLE
<field> <dtype> FOREIGN KEY
REFERENCES <tbl>(<field>,)


!-- 已有表添加
!-- MySQL、MSSQL、ORACLE
ALTER TABLE <tbl>
ADD FOREIGN KEY (<field>,)
REFERENCES <tbl>(<field>,);
ALTER TABLE <tbl>
ADD CONSTRAINT <cstrt_name> FOREIGN KEY (<field>,)
REFERENCES <tbl>(<field>);

!-- 删除
!- MySQL
ALTER TABLE <tbl>
DROP FOREIGN KEY <cstrt_name>;
!-- MSSQL、ORACLE
ALTER TABLE <tbl>
DROP CONSTRAINT <cstrt_name>;

CHECK

CHECK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
!-- 建表
!-- MySQL、MSSQL、ORACLE
CONSTRAINT [<cstrt_name>] CHECK(<condition>)
!-- MySQL
CHECK (condition)
!-- MSSQL、ORACLE
<field> <dtype> CHECK(<condition>)

!-- 已有表添加
!-- MySQL、MSSQL、ORACLE
ALTER TABLE <tbl>
ADD CHECK (condition);
ALTER TABLE <tbl>
ADD CONSTRAINT <cstrt_name> CHECK (condition);

!-- 删除
!-- MySQL
ALTER TABLE <tbl>
DROP CHECK <cstrt_name>;
!-- MSSQL、ORACLE
ALTER TABLE <tbl>
DROP CONSTRAINT <cstrt_name>;

内建函数

Date

  • MySQL

    • NOW()
    • CURDATE()
    • CURTIME()
    • DATE()
    • EXTRACT()
    • DATE_ADD()
    • DATE_SUB()
    • DATE_DIFF()
    • DATE_FORMAT()
  • MSSQL

    • GETDATE()
    • DATEPART()
    • DATEADD()
    • DATEDIFF()
    • CONVERT()

NULL

  • MSSQL

    • ISNULL(<field>, <replacement>)
  • ORACLE

    • NVL(<field>, <repalcement>)
  • MySQL

    • IFNULL(<field>, <replacement>)
    • COALESCE(<field>, <replacement>)

Aggregate聚集函数

Scalar

Vimscripts 编程

变量

普通变量

创建变量、变量赋值都需要用到let关键字

1
2
3
4
let foo = "bar"
echo foo
let foo = "foo"
echo foo

数字

  • Number32位带符号整形(整形之间除法同c)

    • :echo 0xef:16进制
    • :echo 017:8进制(鉴于以下,不建议使用)
    • :echo 019:10进制(9不可能出现,vim自动处理)
  • Float

    • :echo 5.1e-3:科学计数法)
    • :echo 5.0e3:科学计数法中一定要有小数点

类型转换:Number和Float运算时会强制转换为Float

字符串

类型转换
  • +if这些“运算”中,vim会强制转换变量类型

    • 数字开头的字符串会转为相应Number(即使符合Float也 会舍弃小数点后)
    • 而非数字开头 则转换为0
  • 连接.

    • .连接时vim可以自动将Number转换为字符串然后连接
    • 但是对于 Float,vim不能自动转换
  • 转义\

    • 注意echom "foo\nbar"类似的输出时,echom不会像 echo一样输出两行,而是将换行输出为vim默认 (即使设置了listchars)的“换行符”
  • 字符串字面量''

    • 所见即所得(py中r’’),注意连续两个单引号表示单引号
  • 内建字符串函数

    • strlen(str)(len(str)效果对字符串同)
    • split(str, token=" ")
    • join([str], token=" ")
    • tolower(str)
    • toupper(str)
  • 字符串比较====?==#

    • ==:对字符串比较是否大小写敏感取决于设置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      set noignorecase
      if "foo"=="Foo"
      echo "bar"(不会输出)
      endif

      set ignorecase
      if "foo"=="Foo"
      echo "bar"(会输出)
      endif
    • ==?:对字符串比较大小写永远不敏感

    • ==#:对字符串比较大小写永远敏感

  • <>:同上,也有3种

集合类型变量

列表

vim列表特点

  • 有序、异质
  • 索引从0开始,可以使用负数索引,使用下标得到对应元素
  • 支持切割
    • 是闭区间(这个和python不同)
    • 可以负数区间切割
    • 可以忽略起始/结尾索引表示从0开始/末尾截至
    • 切割区间越界是安全的

      字符串可以像列表一样切割、索引,但是不可以使用负数 索引,却可以使用负数切割

  • +用于连接两个列表

列表内建函数

  • add(list, item):添加新元素
  • len(list):列表长度
  • get(list, index, default_val):获取列表元素,越界则 返回default_val
  • index(list, item):返回元素索引,不存在返回-1
  • join(list, token):将列表中元素转换为字符串后,使用 toke连接,缺省为<space>
  • reverse(list):反转列表

字典

字典特性

  • 值是异质的,键可以不是字符串,但是会被强制转换为字符串, 因此,在查找值时也可以使用非字符串dict[100],同样会被 强制转换为字符串dict["100"]之后查找

  • 支持属性.查找,甚至可以后接Number

  • 添加新元素就和普通赋值一样:let dict.100 = 100

  • 移除字典中的元素

    • remove(dict, index)
    • unlet dict.index/unlet dict[index] 移除不存在的元素事报错

允许定义时多一个,

内建函数
  • get(dict, index, default_val):同列表

  • has_key(dict, index):检查字典中是否有给定键,返回 1(真)或0(假)

  • item(dict):返回字典键值对,和字典一样无序

  • keys(dict):返回字典所有键

  • values(dict):返回字典所有值

作为变量的选项

  • bool选项输出0、1

    1
    2
    :set wrap
    :set nowrap
  • 键值选项

    1
    2
    :set textwidth=80
    :echo &textwidth
  • 本地选项(l:作用域下)

    1
    let &l:number=1
  • 选项变量还可以参与运算

    1
    2
    let &textwidth=100
    let &textwidht = &textwidth + 10

作为变量的寄存器

1
2
3
4
let @a = "hello"
echo @a
echo @"
echo @/

变量作用域

<char>:开头表示作用域变量

  • 变量默认为全局变量
  • b::当前缓冲区作用域变量
  • g::全局变量

语句

条件语句

vim中没有not关键字,可以使用!表示否定

  • !:否
  • ||:或
  • &&:与
1
2
3
4
5
6
7
8
if "1one"
echo "one"(会输出)
endif
if ! "one"
echo "one"(会输出)
else
echo "two"
endif

finish关键字

finally时结束整个vimscripts的运行

循环语句

for语句

1
2
3
4
5
let c = 0
for i in [1,2,3]
let c+=i
endfor
echom c

while语句

1
2
3
4
5
6
7
let c = 1
let total = 0
while c<=4
let total+=c
let c+=1
endwhile
echom total

函数

没有作用域限制的vimscripts函数必须以大写字母开头 (有作用域限制最好也是大写字母开头)

1
2
3
4
5
6
7
8
func Func(arg1,...)
echo "Func"
echo a:arg1(arg1)
echo a:0(额外(可变)参数数量)
echo a:1(第一个额外参数)
echo a:000(所有额外参数的list
return "Func"
endfunction

function后没有紧跟!时,函数已经被定义,将会给出 错误,而function!时会直接将原函数替换,除非原函数正在 执行,此时仍然报错

  • 调用方式

    • :call Func():call直接调用(return值会被直接丢弃)
    • :echo Func():表达式中调用
  • 函数结束时没有return,隐式返回0

  • 函数参数:最多20个

    • 参数全部位于a:参数作用域下

      • a:arg1:一般参数
      • a:0:额外(可变)参数数量
      • a:n:第n个额外参数
      • a:000:包含额外参数list
    • 参数不能重新赋值

vim中函数可以赋值给变量,同样的此时变量需要大写字母开头, 当然可以作为集合变量的元素,甚至也可以作为参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function! Reversed(l)
let nl = deepcopy(a:l)
call reverse(nl)
return nl
endfunction

let Myfunc = function("Reversed")

function! Mapped(func, l)
let nl = deepcopy(a:l)
call map(nl, string(a:func) . '(v:val)')
return nl
endfunction

call Mapped(function("Reversed"), [[3,2], [1,2]])

let funcs = [function("Reversed"), function("Mapped")]

更多函数参见functions.vim(如果完成了)

Execute、Normal

  • :execute:把字符串当作vimscript命令执行(命令行输入) :execute "echom 'hello, world'"<cr>

    大多数语言中应该避免使用”eval”之类构造可执行字符串,但是 vimscripts代码大部分只接受用户输入,安全不是严重问题, 使用:execute命令能够极大程度上简化命令

    :execute命令用于配置文件时,不要忽略结尾的<cr>, 表示“执行命令”

  • :normal:接受一串键值,并当作是normal模式接受按键

    • :normal后的按键会执行映射,:normal!忽略所有映射

    • :normal无法识别“”这样的特殊字符序列 :normal /foo<cr> 这样的命令并不会搜索,因为“没有按回车”

  • :execute和:normal结合使用,让:normal接受按下 “无法打印”字符(<cr><esc>等),execute能够接受 按键

    1
    2
    :execute "normal! gg/foo\<cr>dd"
    :execute "normal! mqA;\<esc>`q"

正则表达式

vim有四种不同的解析正则表达式的“模式”

  • 默认模式下\+表示“一个或多个之前字符”的正常+意义, 其他的符号如{,},*也都需要添加转义斜杠表示正常意义, 否则表示字符字面意

    • :execute "normal! gg/for .\\+ in .\\+:\<cr>: execute接受字符串,将\\转义,然后正则表达式解析, 查找pythonfor语句

    • :execute "normal! gg".'/for .\+ in .\+:'."\<cr>": 使用字符串字面量避免\\,但是注意此时\<cr>也不会 被转义为按下回车\n才是换行符),所以需要分开 书写、连接

  • \v模式下,vim使用“very magic”正常正则解析模式

    • :execute "normal! gg".'/\vfor .+ in .+:'."\<cr>"

Rust 语法

Rust是基于表达式的语言

表达式返回一个值,而语句不返回,rust中除两种语句外,全是 表达式

  • let引入绑定
    • 可变绑定赋值是表达式,返回空tuple
    • 声明后初始化绑定?#todo
  • 表达式语句:表达式后跟分号转换为语句

代码中rust希望语句后跟语句,使用分号分隔表达式,所以rust 看起来和其他大部分分号结尾语言相似

{}包裹的代码块内最后一“句”没有以”;”结尾,那么是表达式,且 返回该表达式的值,整个代码块可以看作是表达式,否则为语句, 没有返回值,函数同

模式匹配

Refutability(可反驳性)

  • refutable(可反驳的):对某些可能值匹配失败的模式, if letwhile let只能接受可反驳的模式,因为这就用于 处理可能的失败
  • irrefutable(不可反驳的):能匹配任何传递的可能值,let 语句、函数参数、for循环只能接受不可反驳的模式,因为 通过不匹配值的程序无意义

可能值是指“类型”相同,可用于匹配的值

1
2
3
4
5
6
7
8
let Some(x) = some_optiona_value;
//`Some(x)`是refutable模式,若`some_optional_value`为
//None,则此时无法成功匹配,此语句可能无法正常工作
if let x = 5 {
//`x`是inrefutable模式,对所有可能值都可以匹配,此语句
//无意义
println!("{}", x);
}

Refutable

match控制流

  • 各分支模式同值“类型”必须完全一致才能匹配
  • 返回类型必须一致,如果有返回值
  • 匹配必须是穷尽的,可以使用通配符_(匹配所有的值)代替
    • match是匹配到就退出,不像switch一样会继续下沉
    • 通配符不总是需要的,对于枚举类型只要含有所有枚举 成员的分支即可
1
2
3
4
5
6
7
8
9
10
11
let x = Some(5);
let y = 10;

match x{
Some(50) => println!("got 50"),
//`Some(50)`这个模式规定了“值”
Some(y) => println!("matched, y = {:?}", y),
//match表达式作用域中的`y`会覆盖周围环境的`y`
_ => println!("default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);

if let[else]简洁控制流

  • 只匹配关心的一个模式
  • 可以添加else语句,类似match通配符匹配
  • if letelse ifelse if let等相互组合可以提供更多 的灵活性
    • else if可以不是模式匹配
    • if后的比较的值可以没有关联
  • 没有穷尽性检查,可能会遗漏一些情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main(){
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();

if let Some(color) = favorite_color{
//模式匹配
//注意这里是`=`而不是一般值比较`==`
//`while`同
println!("favorite color is {}", color};
}else if is_tuesday{
//普通`if`条件语句
println!("Tuesday is green day!");
}else if let Ok(age) = age{
//`age`变量覆盖原变量
//此时`age`是`u8`类型
if age > 30 {
//因此此条件不能提出,因为两个`age`变量不同,
//不能共存在同一条语句中
println!("Using purple as the background color");
}else{
println!("Using orange as the background color");
}
}else{
println!("Using blue as the background color");
}
}

Irrefutable

while let条件循环

if let条件表达式类似,循环直到模式不匹配

1
2
3
4
5
6
7
8
9
fn main(){
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop(){
println!("{}", top);
}
}

for解构

1
2
3
4
5
6
7
fn main(){
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate(){
//解构tuple
println!("{} is at index {}", value, index);
}
}

let解构

let语句本“应该“看作是模式匹配

1
2
3
4
5
6
7
8
9
let PARTTERN = EXPRESSION;
//这样就和`if let`模式匹配中`=`一致
//应该可以把`=`看作是模式匹配的符号

let x = 5;
//这里`x`是一个模式“变量”
let (x, y, z) = (1, 2, 4);
//`(x, y, z)`是模式“3元元组“
//解构元组

函数参数

类似于let语句,函数参数也“应该”看作是模式匹配

1
2
3
4
5
6
7
8
9
fn foo(x: i32){
//`x`表示模式“变量”
}
fn print_coordinates(&(x, y): &(i32, i32)){
//这里`(x, y)`是模式“元组”
//但是这里处于函数变量类型的要求,有点像元组结构体
//调用时使用元组即可
println!("Current location: ({}, {})", x, y);
}

模式匹配用法

|...“或“

  • |“或”匹配多个模式
  • ...闭区间范围模式,仅适用于数值、char
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let x = 1;
match x {
1 => println!("one"),
//`1`是模式“字面值”
2 | 3 => println!("two or three"),
//`|`分隔,匹配多个模式
5...10 => println!("through 5 to 10"),
//`...`表示匹配一个**闭区间**范围的值
//这个语法只能用于数字或者是`char`值,因为编译器会
//检查范围不为空,而只有数字、`char`值rust可以判断
_ => println!("anything"),
}

let x = 'c';
match x {
'a'...'j' => println!("early ASCII letter"),
'k'...'z' => println!("late ASCII letter"),
_ => println!("something else"),
}

_..”忽略“

  • _忽略整个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fn foo(_: i32, y: i32){
    //函数签名中忽略整个值
    println!("this code only use the y parameter: {}", y);
    }
    fn main(){
    foo(3, 4);

    let mut setting_value = Some(5); let new_setting_value = Some(10);
    match (setting_value, new_setting_value){
    (Some(_), Some()) => {
    //嵌套`_`忽略部分值
    //此时没有任何值所有权
    println!("can't overwrite an exsiting customized value");
    }
    _ => {
    setting_value = new_setting_value;
    }
    }
    println!("setting is {:?}", setting_value);
    }
  • _var变量名前添加下划线忽略未使用变量,此时值所有权 仍然会转移,只是相当于告诉编译器忽略该未使用变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    fn main(){
    let _x = 5;
    //两个变量未使用,但是编译时只有一个warning
    let y = 10;

    let s = Some(5);
    if let Some(_s) = s {
    //值所有权已转移,`s`不能继续使用
    //编译器忽略未使用`_s`,不给出warning
    println!("get a integer");
    }
    }
  • ..忽略剩余值

    • ..的使用必须无歧义
    • 对于结构体即使只有一个field,需使用..忽略剩余值, 不能使用_
      1
      2
      3
      4
      5
      6
      7
      8
      fn main(){
      let numbers = (2, 4, 6, 8, 10);
      match numbers{
      (first, .., last) => {
      println!("Some numbers: {}, {}", first, last);
      }
      }
      }
1
2
3
4
let (x, _, z) = (1, 2, 4);
//`_`忽略模式中各一个值
let (x, .., z) = (1, 2, 3, 4);
//`..`忽略模式中多个值

解构结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Point{
x: i32,
y: i32,
}

fn main(){

let p = Point{x: 0, y: 7};
let Point{x: a, y: b} = p;
//`Point{x: a, y: b}`是模式”Point结构体“
//解构结构体

let p = Point{x: 0, y: 7};
let Point{x, y} = p;
//模式匹配解构结构体简写
//只要列出结构体字段,模式创建相同名称的变量

let p = Point{x: 0, y: 7};
match p {
Point {x, y: 0} => println!("on the x axis at {}", x),
Point {x: 0, y} => println!("on the y axis at {}", y),
Point {x, y} => println!("on neither axis: ({}, {})", x, y),
//这里没有`_`通配符,因为`Point {x, y}`模式已经
//是irrefutable,不需要
}
}

解构枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
enum Message{
Quit,
Move{x: i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn main(){

let msg = Message::ChangeColor(0, 160, 255);

let p = match msg{
//这里`match`返回值必须类型完全相同
Message::Move{x, y} if x == 0 => (x, y),
//对于`Message`中的匿名结构体类型的成员,匿名
//结构体没有枚举类型外的定义、名称,无法、也
//不应该直接获取结构体
Message::Write(ref str) => {
//`Message::Write`不是`Message`的一个枚举成员
//必须`Message::Write(str)`才是(能够匹配)
println!("write {}", str);
(1,1)
},
Message::ChangeColor(..) => (1,0),
//类似的,需要使用`..`忽略值,仅`ChangeColor`
//不是`Message`成员
_ => {
println!("quit");
(0,0)
},
};
}

&refref mut”引用“

  • &匹配引用,“获得”值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let points = vec![
    Point {x: 0, y: 0},
    Point {x: 1, y: 5},
    Point {x: 10, y: -3},
    }

    let sum_of_squares: i32 = points
    .iter()
    .map(|&Point {x, y}| x * x + y * y)
    //`&`匹配一个引用
    .sum();
  • ref匹配值,“获得”不可变引用

    1
    2
    3
    4
    5
    6
    7
    8
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref name) => println!("found a name: {}", name),
    //使用`ref`获取不可变引用才能编译成功
    //否则所有权转移,之后报错
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);
  • ref mut匹配值,“获得”可变引用

    1
    2
    3
    4
    5
    6
    let robot_name = Some(String::from("Bors"));
    match robot_name{
    Some(ref mut name) => *name = String::from("NewName"),
    None => (),
    }
    println!("robot_name is: {:?}", robot_name);

ifmatch guard

匹配守卫match guard:放在=>之前的if语句,match 分支的额外条件,条件为真才会继续执行分支代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let num = Some(4);
let y = 5;
match num{
Some(x) if x < y => println!("'less than y: {}", x),
Some(x) => println!("{}", x),
None => (),
}

let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
//`4 | 5 | 6`整体作为一个模式,match guard作用于
//模式整体,而不是单独的`6`
_ => println!("no"),
}

@绑定

@允许在创建存放值的变量时,同时测试值是否匹配模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Message{
Hello { id: i32},
}

let msg = Message::Hello { id: 5 };
match msg{
Message::Hello{ id: id_variable @ 3...7 } => {
//匹配结构体模式(值绑定`id_variable`)&&值在`3...7`范围
println!("Found an id in range: {}", id_variable)
},
Message::Hello{ id: 10...12 } => {
//此分支模式指定了值的范围,但是没有绑定值给变量`id`
//结构体匹配简略写法不能应用与此
println!("Found an id in another range")
}
Message::Hello{ id } => {
//此分支结构体匹配简略写法,值绑定于`id`
println!("Found some other id: {}", id)
},
}

闭包closures和函数指针

闭包是可以保存进变量或作为参数传递给其他函数的匿名函数, 可以在一个地方创建闭包,而在不同的上下文执行闭包。和函数 的区别在于,其可以捕获调用者作用域中的值,当然这会有性能 损失,如果不需要捕获调用者作用域中的值可以考虑使用函数

1
2
3
4
let closures = |param1, param2|{
...
expression
}
  • 闭包参数:调用者使用,
    • 创建闭包赋值给变量,再通过变量调用闭包
    • 创建闭包作为参数传递,其他函数调用
  • 捕获环境变量:创建闭包作为参数传递,直接使用周围环境变量

闭包类型推断和注解

闭包不要求像函数一样需要在参数和返回值上注明类型,函数需要 类型注解因为其是需要暴露给的显示接口的一部分,而闭包不用于 作为对外暴露的接口

  • 作为匿名函数直接使用,或者存储在变量中
  • 通常很短,使用场景上下文比较简单,编译器能够推断参数和 返回值类型

当然,闭包也可以添加注解增加明确性

1
2
3
4
5
fn  add_one_v1   {x: u32} -> u32 { x + 1 };
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1;
//闭包体只有一行可以省略`{}`

Rust会根据闭包出调用为每个参数和返回值推断类型,并将其锁定, 如果尝试对同一闭包使用不同类型的参数调用会报错

1
2
3
4
5
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
//此时已经锁定闭包参数、返回值类型
let n = example_closure(5);
//尝试使用`i32`类型调用闭包会报错

Fntrait bound

每个闭包实例有自己独有的匿名类型,即使两个闭包有相同的签名, 其类型依然不同。为了定义使用闭包的结构体、枚举、函数参数, (这些定义中都需要指定元素类型),需要使用泛型和trait bound

  • FnOnce:获取从周围环境捕获的变量的所有权,因此只能调用 一次,即Once的含义
  • Fn:获取从周围环境捕获的变量的不可变引用
  • FnMut:获取从周围环境捕获的变量的可变引用

所有的闭包都实现了以上3个trait中的一个,Rust根据闭包如何 使用环境中的变量推断其如何捕获环境,及实现对应的trait

1
2
3
4
5
6
struct Cacher<T>
where T: Fn(u32) -> u32{
//`T`的类型中包括`Fn`、参数、返回值三个限定
calculation: T,
value: Option<u32>,
}

Function Pointer fn

1
2
3
4
5
6
7
8
9
10
11
fn add_one(x: i32) -> i32{
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32{
//`f`是`fn`函数指针类型
f(arg) + f(arg)
}
fn main(){
let answer = do_twice(add_one, 5);
println!("the anwser is: {}", anwser);
}

函数指针类型实现了以上全部FnFnMutFnOnce三个 trait,所以总是可以在调用期望闭包作为参数的函数时传递函数 指针,因此倾向于使用泛型和闭包trait bound的函数,这样可以 同时使用闭包、函数指针作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let list_of_numbers = vec![1, 2, 3];
let list_of_Strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string())
//闭包作为参数传递
.collect();

let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string)
//函数作为参数传递
//使用了完全限定语法,因为存在多个`to_string`函数
//标准库为所有实现了`Display`的类型实现了此trait
.collect();

与不存在闭包的外部代码(如C语言,只有函数没有闭包)交互时, 只能使用函数作为参数,不能使用闭包。

move关键字

move关键字强制闭包获其捕获的环境变量的所有权,在将闭包 传递给新线程以便将数据移动到新线程时非常实用

1
2
3
4
5
6
7
fn main(){
let x = vec![1, 2, 3]
let equal_to_x = move |z| z == x;
println!("can't use x here: {:?}", x);
//此时`x`的所有权已经转移进闭包,不能在闭包外使用
let y = vec![1, 2, 3];
assert!(equal_to_x(y));

返回闭包

闭包表现为trait,没有确定的类型、大小,无法直接返回,也不 允许使用函数指针fn作为返回值类型,需要使用trait对象返回 闭包

1
2
3
4
fn return_closure() -> Box<Fn(i32) -> i32>{
//trait对象作为返回值
Box::new(|x| x + 1)
}

迭代器Iterator

迭代器负责遍历序列中的每一项和决定序列何时结束的逻辑。Rust 中迭代器时惰性的,直到调用方法”消费“迭代器之前都不会有效果

Iteratortrait

迭代器都实现了标准库中Iteratortrait

1
2
3
4
5
6
7
8
trait Iterator{
type Item;
//定义`Iterator`的关联类型
fn next(&mut self) -> Option<Self::Item);
//参数是`&mut self`,要求迭代器是`mut`类型
//`next`方法改变了迭代器中用来记录序列位置的状态
//methods with default implementation elided
}

next方法

nextIterator唯一要求被实现的方法,其返回迭代器中封装 在Some中的一项(消费迭代器中的一项),迭代器结束时, 返回None

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[test]
fn iterator_demostration(){
let v1 = vec![1, 2, 3];
let mut v_iter = v1.iter();
//注意`v_iter`声明为`mut`
//使用`for`循环时无需使`v1_iter`可变,`for`会获取其
//所有权并在后台使其可变

assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
//真的很难理解,rust中&integer是怎么比较的
assert_eq!(v1_iter.nett(), None));
}

消费适配器Comsuming Adaptors

Iteratortrait中定义,调用next方法,消耗迭代器

1
2
3
4
5
6
7
8
#[test]
fn iterator_sum(){
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

let total:i32 = v1_iter.sum();
//`sum`获取迭代器所有权,`v1_iter`不能继续使用
assert_eq!(total, 6);

迭代器适配器Iterator Adaptors

Iteratortrait中定义,将当前迭代器变为其他迭代器,同样是 惰性的,必须调用消费适配器以便获取迭代适配器的结果

1
2
3
4
5
6
let v1:Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
//这里因为没有调用消费适配器,其实没有做事
let v2:Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#[derive(PartialEq, Debug)]
struct Shoe{
size: u32,
style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, show_size: u32) -> Vec<Shoe>{
shoes.into_iter()
//获取vector所有权的迭代器
.filter(|s| s.size == show_size)
//这里使用闭包获取外部环境变量
.collect()
}

#[test]
fn filter_by_size(){
let shoes = vec![
Shoe{size: 10, style: String::from("sneaker")},
Shoe{size: 13, style: String::from("sandal")},
Shoe{size: 10, style: String::from("boot")},
];

let in_my_size = shoes_in_my_size(shoes, 10);

assert_eq!(
in_my_size,
vec![
Shoe{size: 10, style: String::from("sneaker")},
Shoe{size: 10, style: String::from("boot")},
]
);
}

实现Iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct Counter{
count: i32,
}
impl Counter{
fn new() -> Counter{
Counter{count: 0}
}
}

impl Iterator for Counter{
type Item = u32;
//迭代器将返回`u32`值集合

fn next(&mut self) -> Option<Self::Item>{
self.count += 1;

if self.count < 6{
Some(self.count)
}else{
None
}
}
}

#[test]
fn using_other_iterator_trait_methods(){
let sum: u32 = Counter::new().zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18, sum);

并发(并行)

多线程可以改善性能,但是也会增加复杂性

  • 竞争状态Race Conditions:多个线程以不一致的顺序访问资源
  • 死锁Dead Lock:线程互相等待资源释放,阻止继续运行
  • 只会在特定情况出现、无法稳定重现的bug

线程模型

  • 1:1模型:一个OS线程对应一个语言线程,语言调用操作系统 API创建线程,性能较好
  • M:N模型:语言有自己的线程实现,其提供的线程称为 绿色(green)线程,M个绿色线程对应N个OS线程,更好的 运行控制、更底的上下文切换成本

Rust为了更小的运行时(这里表示二进制文件中语言自身提供的 代码)考虑,标准库中只提供了1:1线程模式实现。可以通过一些 crate扩展M:N线程模式。

spawn创建新线程

std::thread::spawn接受一个闭包作为参数,返回JoinHandle 类型的句柄。作为spawn参数的闭包和一般的闭包有些不同, 线程直接独立执行,所以此时闭包捕获外部环境变量不能按照默认 的获取不可变引用,因为此时捕获的变量值可能已经被丢弃,必须 使用move关键字获取所有权,而一般的闭包是顺序执行的,没有 特殊需要可以直接获取不可变引用,而能够保证值不被丢弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::thread;
use std::time:Duration;
fn main(){
let handle = thread::spawn(|| {
//`thread::spawn`接受一个闭包作为参数,返回
//`JoinHandle`类型的值(不是引用)
for i in 1..10{
println!("number {} from spwaned thread!", i);
thread::sleep(Duration::from_millis(1));
}
);

for i in 1..5{
println!("number{} from main thread!", i);
thread::sleep(Duration::from_millis(1));
}

handle.join().wrap();
//`JoinHandle.join()`将阻塞直到其对应的线程结束
//如果调用`join`,spawn线程可能无法执行完毕
//因为主线程执行完,整个进行结束
//注意`join`调用的位置决定阻塞的位置

let v = vec![1, 2 , 3];
let handle = thread::spawn(move || {
//这里`move`关键字将捕获的外部环境中变量`v`所有权
//移入spawn线程,否则无法正常编译
prinln!("here's a vector: {:?}", v);
});
handle.join().unwrap();
}

消息传递

Rust中实现消息传递并发的主要工具是通道(channel)

mpsc::channel

mpsc:multiple producer single consumer,多个生产者,单个 消费者,即Rust标准库实现通道的方式允许多个产生值的发送端,但 只能有一个消费这些值的接收端。发送端或接收端任一被丢弃时, 意味着通道被关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
use std::thread;
use std::sync:mpsc;
use std::time:Duration;

fn main(){
let (tx, rx) = mpsc::channel();
//`tx`表示发送端,`rx`表示接收端

let tx1 = mpsc::Sender::clone(&tx);
//clone发送端创建多个生产者

thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];

for val in vals {
tx1.send(val).unwrap();
//`send`会获取参数所有权归接收者所有,避免
//值被其他线程丢弃、修改导致意外结果

//`send`返回`Result<T, E>`,如果接收端被丢弃
//将没有发送值的目标,将返回`Err<E>`
thread::sleep(Duration::from_secs(1));
}
});

thread::spawn(move || {d
let vals = vec![
String::from("move"),
String::from("messages"),
String::from("for"),
String::from("you"),
];

for val in vals{
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});

let received = rx.recv().unwrap();
//`recv`将阻塞直到接收到一个值,返回`Result<T, E>`
//通道发送端关闭时`recv`将返回`Err<E>`表明不会有新值

let received = rx.try_recv().unwrap();
//`try_recv`不阻塞,立刻返回`Result<T, E>`,`Err<E>`
//表示当前没有消息
//可以再循环中多次调用`try_recv`,有消息进行处理,
//否则进行其他工作直到下次调用`try_recv`

for received in rx{
//将接收端`rx`当作迭代器使用,返回接收到值
println!("Got: {}", received);
}
}

共享状态

(任何语言)通道都类似于单所有权,一旦值通过通道传送,将无法 再次使用,而共享内存类似于多所有权

Mutex<T>

互斥器mutex:mutual exclusion,任意时刻只允许一个线程访问 数据

  • 线程在访问数据之前需要获取互斥器的锁lock,lock是作为 互斥器一部分的数据结构,记录数据所有者的排他访问权
  • 处理完互斥器保护的数据之后,需要解锁,这样才能允许其他 线程获取数据

Mutex<T>类似于线程安全版本的RefCell<T>cell族), 提供了内部可变性,Mutex<T>有可能造成死锁,如一个操作需要 两个锁,两个线程各持一个互相等待。

Arc<T>原子引用计数atomically reference counted,则是 线程安全版本的Rc<T>,而线程安全带有性能惩罚,如非必要, 使用单线程版本Rc<T>性能更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use std::sync::{Mutex, Arc};
use std::thread;

fn main(){
let counter = Arc::new(Mutex::new(0));
//因为需要在多个线程内引用,所以需要使用多所有权
//数据结构,而`Rc`不是线程安全的,需要使用线程安全
//`Arc`
let mut handles = vec![];

for _ in 0..10{
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
//`lock`返回一个`MutexGuard`类型的智能指针,
//实现了`Deref`指向其内部数据,`Drop`当
//`MutexGuard`离开作用域时自动释放锁

//只有`lock`才能获取值,类型系统保证访问数据之前
//获取锁;而锁的释放自动发生,保证锁一定会释放

//这里发生了一次强制解引用多态,将`counter`
//解引用为`Mutex<T>`类型
*num += 1;
});

handles.push(handle);
}

for handle in handles{
handle.join().unwrap();
}
println!("Result: {}", counter.lock.unwrap());
}

Synctrait、Sendtrait

Rust的并发模型大部分属于标准库的实现,但是 std::marker::Sendstd::marker::Sync时内嵌于语言的

  • Send:表明类型所有权可以在线程间传递,几乎所有类型都是 Send的,Rc<T>是其中的一个例外,因为Rc<T>clone之后 在两个线程间可能同时更新引用计数,trait bound保证无法将 不安全的Rc<T>在线程间传递。任何全部由Send组成的类型 会自动标记为Send
  • Sync:表明类型可以安全的在多线程中拥有其值的引用,对于 任何类型,如果&TSend的,那么T就是Sync的。 Cell<T>系列不是Sync的,Mutex<T>是。基本类型是Sync 的,全部由Sync组成的类型也是Sync

SendSync是标记trait,不需要实现任何方法,全部是SendSync组成的类型就是SendSync,一般不需要手动实现 它们,而且手动实现这些标记trait涉及编写不安全代码

R语法

生存

配置

注释

  • R语言的注释和其他脚本语言类似,使用#注释行,但是R没有 多行注释语法
  • 可以使用if(FALSE)语句进行“注释”
    1
    2
    3
    if(FALSE) {
    "comments"
    }

工作路径

1
2
3
4
getwd()
# 返回当前工作路径,默认`~/Documents`
setwd(/path/to/dir)
# 设置当前工作路径

变量

  • R中有效的变量名称由字母、数字、点、下划线组成,变量名以 字母、后不跟数字的点开头
  • R中变量为动态类型,可多次更改变量数据类型
  • 变量无法被声明,在首次赋值时生成

赋值

1
2
3
4
5
6
7
8
9
10
var.1 = c(0, 1, 2, 3)
# 不常用
var.2 <- c("learn", "R")
# 常用方法
var .3 <<- c(3, 1, TRUE, 2+3i)
# 特殊赋值符,可以扩展变作用域为“整个”工作空间

c(TRUE, 1) -> var.4
# 右赋值符
c(TRUE, 2+3i) ->> var.5

搜索

ls函数可以搜索当前工作空间中所有可用变量

1
2
3
4
5
6
ls(
pattern = "var_pattern",
all.name = FALSE/TRUE
)
# `pattern`:使用模式匹配变量名
# `all.name`:开头变量默认隐藏,可通过设置参数展示

删除

rm函数可以删除变量

1
2
3
4
rm(var.3)

rm(list = ls())
# 删除所有变量

数据导入

edit

1
2
3
4
5
6
7
8
9
10
11
mydata <- data.frame(
age = numeric(0),
gender = character(0),
weight = numeric(0)
)
# `numeric(0)`类似的赋值语句创建指定模式但不含数据变量
mydata <- edit(mydata)
# `edit`会调用允许手动输入数据的文本编辑其
# `edit`在副本上操作,需要重新给变量
fix(mydata)
# `mydata <- edit(mydata)`等价写法,直接在原对象上操作

read.table

1
2
3
4
5
6
7
8
9
10
11
12
13
DF <- read.table(
file(/path/to/file),
header = TRUE/FALSE,
sep = " \t\n\r"/",",
row.names = c(),
col.names = c("V1", "V2", ...)/c(str),
na.strings = NULL/c(str)),
colClasses = NULL/c("numeric", "character", "NULL"),
quote = "'""/str,
skip = 0/int,
stringAsFactors = TRUE/FALSE,
test = str
)
  • 说明:从带分隔符的文本文件中导入数据

  • 参数

    • header:第一行是否包含变量名
    • sep:分隔符,默认数个空格、tab、回车、换行
    • row.names:指定行标记符
    • col.names:指定DF对象列名
    • na.strings:表示缺失值的字符串向量,其包含字符串 读取时转为NA
    • colClasses:设置DF对象每列数据模式
      • “NULL”表示跳过
      • 长度小于列数时开始循环
      • 读取大型文本可以提高速度
    • quote:字符串划定界限,默认"'
    • StringAsFactor:标记字符向量是否转换为factor
      • colClasses优先级更高
      • 设为FALSE可以提升读取速度
    • text:读取、处理的字符串,而不是file

常用函数

1
2
3
4
5
6
7
8
9
print()
# 浏览对象取值
str()
# 查看对象结构
ls()
# 管理对象
remove()
rm()
# 删除指定对象

数据模式(类型)

  • 数据模式是指R存储数据的方式
  • 即从存储角度对R数据对象划分
  • class函数就是返回数据模式(类型)

Logical

只需要1byte存储

  • TRUE/T
  • FALSE/F

Integer

占用2-4byte

  • 2L0L

Numeric

可进一步分

  • float占用4byte
  • double占用8byte

R中数值型数据默认为double

  • 12.34

Complex

  • comlplex3+2i

Character

R中'"对中的任何值视为字符串

  • '"必须在开头、结尾成对存在
  • '"结尾的字符串中,只能插入对方

paste

连接多个字符串

1
2
3
4
5
6
7
Chars = paste(
...,
# 要组合的任意数量变量
sep = " ",
# 分隔符
collapse = NULL)
# 消除两个字符串之间空格

format

将数字、字符串格式为特定样式

1
2
3
4
5
6
7
8
9
10
11
12
13
Chars = format(
x([num, chars],
# 向量
digits(int),
# 显示的总位数
nsmall(int),
# 小数点右边最小位数
scientific=FALSE/TRUE,
# `TRUE`则显示科学计数法
width(int),
# 在开始处填充空白来显示的最小宽度
justify = c("left", "right", "centre", "none"))
# 字符串显示位置

nchar

计算包括空格在内的字符串长度

1
2
3
int = nchar(
x(chars)
)

touppertolower

改变字符串大小写

1
2
3
4
5
6
chars = toupper(
x(chars)
)
chars = tolower(
x(chars)
)

substring

获取字符串子串

1
2
3
4
5
6
chars = substring(
x(chars),
first(int),
last(int)
)
# 包括头尾
  • R对象是指可以赋值给变量的任何事物,包括常量、数据结构、 函数
  • 对象都拥有某种模式,描述对象如何存储
  • 对象拥有某个“类”,向print这样的泛型函数表明如何处理 此对象

Raw

  • rawv <- charToRaw("Hello")(byte类型)

结构角度划分R对象

Vector

用于存储数值型、字符型、逻辑型数据的一维数组

  • 单个向量中的出数据必须拥有相同的类型、模式(数值型、字符 型、逻辑型)
  • R中没有标量,标量以单元素向量形式出现
1
2
3
4
5
6
7
8
9
10
apple <- c("red", "green", "yellow") 
apple[1]
# 访问单个元素,从1开始
apple[1: 3]
# 切片,闭区间
apple[7] = "seven"
# 将值赋给某个向量、矩阵、数组或列表中一个不存在的元素时
# R将自动扩展其以容纳新值,中间部分设为`NA`

is.vector(apple)

创建向量

1
2
3
4
5
6
7
8
9
10
11
12
rep(start: end, each=repeat_time)
# 元素重复
rep(start: end, times=repeat_time)
# 向量重复

seq(from=start, to=end, by=step)
# 指定步长
seq(from=start, to=end, length=len)
# 指定个数

vector(length=len)
# 元素为`FALSE`

访问向量元素

1
2
3
4
5
6
7
a[1]
a[1:2]
a[c(1,3)]
a[c(T, F, T)]

a[-c(1:2)]
# 负号不能用于逻辑向量

Matrix

二维数组:组织具有相同存储类型的一组变量

  • 每个元素都拥有相同的模式(数值型、字符型、逻辑型)

创建矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mtx <- matrix(
vector,
nrow(int)
ncol(int),
byrow = FALSE/TRUE,
dimnames = list(
c(row_names),
c(col_names)
)
)

is.matrix()

cbind()
# 将多个已有向量(列)合并为矩阵

矩阵信息

1
2
3
4
5
6
dim(mtx)
# 显示矩阵行、列
colnames(mtx)
colnames(mtx[, col_start: col_end])
rownames(mtx)
ronnames(mtx[row_start: row_end, ])

访问矩阵元素

1
2


Array

类似于矩阵,但是维度可以大于2

  • 其中元素也只能拥有一种模式
1
2
3
4
5
arr <- array(
vector,
dimensions(c(int)),
dimnames = c(dim_names)
)

Data.Frame

数据帧是表、二维数组类似结构

  • 不同的列可以包含不同的模式
  • 每列包含一个变量的值,每行包含来自每列的一组值
  • 数据帧的数据可是数字、因子、字符串类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
df <- data.frame(
col1(c()),
col2(c()),
...,
row.names = coln
)
# `row.names`:指定实例标识符,即index

emp.data <- data.frame(
emp_id = c(1:5),
emp_name = c("Rick","Dan","Michelle","Ryan","Gary"),
salary = c(623.3,515.2,611.0,729.0,843.25),
start_date = as.Date(c("2017-01-01", "2017-09-23",
"2017-11-15", "2017-05-11", "2018-03-27")),
stringsAsFactors = FALSE
)
# 创建DF

统计性质

1
2
3
4
5
str(emp.data)
# 可以获得DF结构

summary(emp.data)
# 获得DF的统计摘要、性质

筛、删、减

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
emp.data.cols <- data.frame(
emp.data$emp_name,
emp.data$salary)
# 列名称获取DF中特定列

emp.data.rows <- emp.data([1:2, ]
# 行切片获取特定行

emp.data.rows_2 <- emp.data[c(3, 5), c(2, 4)]
# 行、列list获取特定行、列

emp.data$dept <- c("IT", "Operations", "IT", "HR", "Finance")
# 新列名添加新列

emp.newdata <- data.frame(
emp_id = c(6: 8),
emp_name = c("Rasmi","Pranab","Tusar"),
salary = c(578.0,722.5,632.8),
start_date = as.Date(c("2013-05-21","2013-07-30","2014-06-17")),
dept = c("IT","Operations","Fianance"),
stringsAsFactors = FALSE
)
emp.finaldata <- rbind(emp.data, emp.newdata)
# `rbind`将新DF同原DFconcat,达到添加新行

拼、接

rbind

结合两个DF对象行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
city <- c("Tampa", "Seattle", "Hartford", "Denver")
state <- c("FL", "WA", "CT", "CO")
zipcode <- c(33602, 98104, 06161, 80294)

address <- cbind(city, state, zipcode)
# `cbind`连接多个向量创建DF

new.address <- data.frame(
city = c("Lowry", "Charlotte"),
state = c("CO", "FL"),
zipcode = C("80230", "33949"),
stringAsFactors = FALSE
)
# 使用`data.fram`创建DF

all.address <- rbind(
address,
new.address
)
# `rbind`结合两个DF的行
merge

根据两DF列进行merge

1
2
3
4
5
6
7
8
9
10
lirary(MASS)
# 加载数据集

merged.Pima <- merge(
x = Pima.te,
y = Pima.tr,
by.x = c("bp", "bmi"),
by.y = c("bp", "bmi")
)
# 根据DF某(些)列merge
meltcast
1
2
3
4
5
6
7
8
9
10
11
12
13
library(MASS)
library(reshape2)
melton.ships <- melt(
ships,
id = c("type", "year")
)
# `melt`将剩余列转换为`variable`、`value`标识

recasted.ship <- cast(
molten.ship,
type+year~variable,sum
)
# 和`melt`相反,以某些列为“轴”合并

绑定

attachdetach
1
2
3
4
5
6
7
8
9
10
11
12
13
attach(emp.data)
# `attach`可以将数据框加入R的搜索路径
# 之后R遇到变量名之后,将检查搜索路径的数据框
# 注意,如果之前环境中已经有df对象列同名变量,那么原始
# 对象优先,不会被覆盖

emp.data.cols.copy <- data.frame(
emp_name,
salary
)
# 否则需要之前一样使用`$`
detach(emp.data)
# 将数据框从搜索路径移除
with
1
2
3
4
5
6
7
8
9
10
with(emp.data, {
emp.data.cols.copy <<- data.frame(
emp_name,
salary
)
})
# `{}`中的语句都针对`emp.data`执行,如果只有一条语句,
# 花括号可以省略
# `with`语句内`<-`赋值仅在其作用于内生效,需要使用`<<-`
# 特殊赋值符保存至“全局”变量中

Factor

分类变量、有序变量在R中称为因子,其决定了数据的分析方式、 如何进行视觉呈现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fctr <- factor(
vector(c(factors)),
ordered = FALSE/TRUE,
levels = c(ordered_unique_factors),
labels = c(factor_labels)

# `ordered`:默认不是有序变量
# `levels`:指定factors的“排序”,确定映射的整数值,
# 对于分类变量也可以设置
# 没有在`levels`中显式指定的factor视为缺失
# `labels`:设置各factor labels,输出则按照labels输出
# 注意`labels`顺序必须和`levels`一致
# 对数值型factor尤其有用
)
  • factor以整形向量的形式存储类别值
    • 整数取值范围为1~kk为定性(分类、有序)变量中 唯一值个数
  • 同时一个由字符串(原始值)组成的内部向量将映射到这些整数
    • 分类变量:字符串映射的整数值由字母序决定
    • 有序变量:按字母序映射可能与逻辑顺序不一致,可以使用 参数levels指定顺序

List

一些对象、成分的有序集合

  • 允许整合若干(可能无关)的对象到单个对象下
  • 很多R函数结果运行结果以列表形式返回,由调用者决定使用 其中何种成分
1
2
3
4
5
6
7
8
9
10
11
12
13

l <- list(
[name1 =]object1,
[name2 =]object2,
...
)
# 可以给列表中的对象命名
# 命名成分`l$name1`也可以正常运行


list1 <- list(c(2, 5, 3), 21, 3, sin)
print(list1)
print(class(list1))

运算符

算术运算符

算术操作符作用与向量的每个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
v <- c(2, 5.5, 6)
t <- c(8, 3, 4)
print(v + t)
# 算术操作符作用与向量的每个元素
print(v - t)
print(v * t)
print(v/t)
print(v %% t)
# 向量求余
print(v %/% t)
# 求商
print(v ^ t)
# 指数运算

关系运算符

比较两个向量的相应元素,返回布尔值向量

1
2
3
4
5
6
7
8
9
v <- c(2, 5.5, 6, 9)
t <- c(8, 2.5, 14, 9)
print (v > t)
# 比较两个向量的相应元素,返回布尔值向量
print (v < t)
print (v == t)
print (v != t)
print (v <= t)
print (v >= t)

逻辑运算符

只适用于逻辑、数字、复杂类型向量,所有大于1的数字被认为是 逻辑值TRUE

1
2
3
4
5
6
7
8
9
10
v <- c(3, 1, TRUE, 2+3i)
t <- c(4, 1, False, 2+3i)
print(v & t)
# 比较两个向量相应元素,返回布尔值向量
print(v | t)
print(!v)

print(v && t)
# 只考虑向量的第一个元素,输出单个bool值元素向量
print(v || t)

其他运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t <- 2: 8
# 为向量按顺序创建一系列数字

v1 <- 8
v2 <- 12
print (v1 %in% t)
print (v2 %in% t)
# 标识元素是否属于向量

M = matrix(c(2, 6, 5, 1, 10, 4),
nrow = 2,
ncol = 3,
byrow = TRUE)
t = M %*% t(M)
# 矩阵相乘

R语句

条件

1
2
3
if
else
switch

循环

1
2
3
4
5
repeat
while
for
break
next

函数

1
2
3
4
5
func_name <- function(
arg_1,
arg_2,...){

}

R内置函数

1
2
3
print(seq(32, 44))
print(mean(25: 82))
print(sum(41: 68))

自定义参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
new.function_1 <- fucntion(){
# 无参数函数
for(i in 1: 5){
print(i ^ 2)
}
}
new.function_1()

new.function_2 <- function(a, b, c){
# 有参数函数
result <- a * b + c
print(result)
}
new.function_2(5, 3, 11)
# 按参数顺序调用函数
new.function_2(
a = 11,
b = 5,
c = 3)
# 按参数名称调用

new.function_3 <- function(a=3, b=6){
# 含有默认参数函数
result <- a * b
print(result)
}
new.function_3
# 无参数(使用默认参数)
new.function_3(9, 5)

函数功能延迟计算

1
2
3
4
5
6
7
new.function <- function(a, b){
print(a ^ 2)
print(a)
print(b)
}
new.function(6)
# 调用函数能部分执行成功,直到`print(b)`

R语言包是R函数、编译代码、样本数据的集合

  • 存储在R环境中名为library的目录下
  • 默认情况下,只有R安装时提供默认包可用,已安装的其他包 必须显式加载
1
2
3
4
5
6
7
8
.libPaths()
# 获取包含R包的库位置

library()
# 获取已安装所有软件包列表

search()
# 获取当前R环境中加载的所有包

安装包

加载包

1
2
3
library("pkg_name",
lib.loc=/path/to/library)
# `lib.loc`参数默认应该就是`.libPaths`,一般不用设置