├── Makefile ├── maketoc ├── README.md ├── 22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md ├── 16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md ├── 07_Zsh-开发指南(第七篇-数值计算).md ├── 17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md ├── 15_Zsh-开发指南(第十五篇-进程与作业控制).md ├── 04_Zsh-开发指南(第四篇-字符串处理之通配符).md ├── 06_Zsh-开发指南(第六篇-哈希表).md ├── 11_Zsh-开发指南(第十一篇-变量的进阶内容).md ├── 08_Zsh-开发指南(第八篇-变量修饰语).md ├── 12_Zsh-开发指南(第十二篇-[[-]]-的用法).md ├── 18_Zsh-开发指南(第十八篇-更多内置模块的用法).md ├── 21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md ├── 20_Zsh-开发指南(第二十篇-代码风格).md ├── 14_Zsh-开发指南(第十四篇-文件读写).md ├── 10_Zsh-开发指南(第十篇-文件查找和批量处理).md ├── 02_Zsh-开发指南(第二篇-字符串处理之常用操作).md ├── 09_Zsh-开发指南(第九篇-函数和脚本).md ├── 13_Zsh-开发指南(第十三篇-管道和重定向).md ├── 05_Zsh-开发指南(第五篇-数组).md ├── 01_Zsh-开发指南(第一篇-变量和语句).md ├── 03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md ├── 00_Zsh-开发指南(目录).md ├── 19_Zsh-开发指南(第十九篇-脚本实例讲解).md └── LICENSE /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @./maketoc > 00_Zsh-开发指南(目录).md 3 | @echo '# Zsh 开发指南' > README.md 4 | @echo >> README.md 5 | @echo '[目录](00_Zsh-开发指南(目录).md)' >> README.md 6 | @cat 00_Zsh-开发指南(目录).md | grep '^## ' | sed 's/^## /\n/g' >> README.md 7 | -------------------------------------------------------------------------------- /maketoc: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | for filename (*Zsh-开发指南(第*md) { 4 | local title=${${${filename#*(}%)*}//-/ } 5 | echo "## [$title]("$filename")" 6 | 7 | for i (${(f)"$(grep "^###" $filename)"}) { 8 | local level=${i%% *} 9 | local subtitle=${i#$level } 10 | level=${level/"####"/- } 11 | echo "$level [$subtitle]("$filename"#${${${(L)subtitle// /-}//、}//:})" 12 | } 13 | 14 | echo "\n---\n" 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zsh 开发指南 2 | 3 | [目录](00_Zsh-开发指南(目录).md) 4 | 5 | [第一篇 变量和语句](01_Zsh-开发指南(第一篇-变量和语句).md) 6 | 7 | [第二篇 字符串处理之常用操作](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md) 8 | 9 | [第三篇 字符串处理之转义字符和格式化输出](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md) 10 | 11 | [第四篇 字符串处理之通配符](04_Zsh-开发指南(第四篇-字符串处理之通配符).md) 12 | 13 | [第五篇 数组](05_Zsh-开发指南(第五篇-数组).md) 14 | 15 | [第六篇 哈希表](06_Zsh-开发指南(第六篇-哈希表).md) 16 | 17 | [第七篇 数值计算](07_Zsh-开发指南(第七篇-数值计算).md) 18 | 19 | [第八篇 变量修饰语](08_Zsh-开发指南(第八篇-变量修饰语).md) 20 | 21 | [第九篇 函数和脚本](09_Zsh-开发指南(第九篇-函数和脚本).md) 22 | 23 | [第十篇 文件查找和批量处理](10_Zsh-开发指南(第十篇-文件查找和批量处理).md) 24 | 25 | [第十一篇 变量的进阶内容](11_Zsh-开发指南(第十一篇-变量的进阶内容).md) 26 | 27 | [第十二篇 [[ ]] 的用法](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md) 28 | 29 | [第十三篇 管道和重定向](13_Zsh-开发指南(第十三篇-管道和重定向).md) 30 | 31 | [第十四篇 文件读写](14_Zsh-开发指南(第十四篇-文件读写).md) 32 | 33 | [第十五篇 进程与作业控制](15_Zsh-开发指南(第十五篇-进程与作业控制).md) 34 | 35 | [第十六篇 alias 和 eval 的用法](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md) 36 | 37 | [第十七篇 使用 socket 文件和 TCP 实现进程间通信](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md) 38 | 39 | [第十八篇 更多内置模块的用法](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md) 40 | 41 | [第十九篇 脚本实例讲解](19_Zsh-开发指南(第十九篇-脚本实例讲解).md) 42 | 43 | [第二十篇 代码风格](20_Zsh-开发指南(第二十篇-代码风格).md) 44 | 45 | [第二十一篇 测试方法以及编写可测试代码的方法](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md) 46 | 47 | [第二十二篇 Bash 和 zsh 用法简明对照表](22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md) 48 | -------------------------------------------------------------------------------- /22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 习惯写 bash 的开发者容易将 bash 下的用法用在 zsh 上,虽然多数情况并不会产生错误,但往往会多做很多不必要的工作,让脚本显得更臃肿或难以理解。 4 | 5 | ### Bash 和 zsh 用法简明对照表 6 | 7 | | Bash 用法 | Zsh 用法 | 说明 | 8 | | ---------------------- | --------------------- | ---------------------------------- | 9 | | `"$var"` | `$var` | 避免变量中有空格导致异常 | 10 | | `"$@"` | `$*` | 避免变量中有空格导致异常 | 11 | | `"${array[@]}"` | `$array` | 取数组所有元素,`@` 可改成 `*` | 12 | | `"${#array[@]}"` | `$#array` | 取数组中元素个数,`@` 可改成 `*` | 13 | | `"${array[n - 1]}"` | `$array[n]` | 取数组第 n 个元素,bash 从 0 开始,zsh 从 1 开始 | 14 | | `"$array"` | `$array[1]` | Bash 中的 `$array` 是取数组的第一个元素 | 15 | | `echo a*b` | `echo "a*b"` | Zsh 默认配置中,通配符如果匹配不到文件会报错 | 16 | | `if true; then :; fi` | `if true {}` | Zsh 中不需要使用 `:` 作为空语句 | 17 | | `[ "$var" == value ]` | `[[ $var == value ]]` | Zsh 中的 `[ ]` 里不支持 `==`,一律用 `[[ ]]` | 18 | | `ls \| tee file \| less` | `ls > file \| less` | Zsh 中不需要用 `tee` 即可实现相同功能 | 19 | 20 | ### 总结 21 | 22 | 本文简单列出了一些 zsh 中已经不再需要的 bash 用法,以及 zsh 和 bash 行为不一致的用法。待补充。 23 | -------------------------------------------------------------------------------- /16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | alias(别名)在 shell 中是非常常用的,它主要用于给命令起别名,简化输入。但主要用于交互场景,在脚本中基本用不到。eval 是一个非常强大的命令,它的功能是将字符串解析成代码再执行,但也会额外增加很多复杂性,非必要场景尽量少用。alias 和 eval 看起来好像没什么关系,但功能上有相似之处,所以放在一起讲。 4 | 5 | ### alias 6 | 7 | 最典型的例子是将 ls -l 简化成 ll: 8 | 9 | ``` 10 | % alias ll='ls -l' 11 | % ll 12 | total 0 13 | drwx------ 0 goreliu goreliu 512 Aug 31 13:55 tmux-1000 14 | drwxr-xr-x 0 goreliu goreliu 512 Aug 31 13:37 yaourt-tmp-goreliu 15 | ``` 16 | 17 | alias 的效果相当于直接将字符串替换过来,比较好理解。 18 | 19 | ``` 20 | # 直接运行 alias,会列出所有的 alias 21 | % alias 22 | ll='ls -l' 23 | lla='ls -F --color --time-style=long-iso -lA' 24 | ... 25 | ``` 26 | 27 | 这样的 alias 只有在行首出现时,才会被解析。但 zsh 中还有一种功能更强大的全局 alias,不在行首也能被解析: 28 | 29 | ``` 30 | % alias -g G='| grep' 31 | 32 | % ls G tmux 33 | tmux-1000 34 | ``` 35 | 36 | 但这样需要格外注意可能导致的副作用,比如我想创建一个名为 G 的文件: 37 | 38 | ``` 39 | % touch G 40 | touch: missing file operand 41 | Try 'touch --help' for more information. 42 | Usage: grep [OPTION]... PATTERN [FILE]... 43 | Try 'grep --help' for more information. 44 | ``` 45 | 46 | 结果 G 被替换了,只能在 G 两边加引号。 47 | 48 | 如果全局 alias 没用好,可能导致灾难性的后果,比如误删重要文件(像把某个全局 alias 传给 rm 后,恰好删除了 alias 字符串中的某些文件),所以需要执行权衡后再使用,并且用的时候要多加注意。 49 | 50 | ### eval 51 | 52 | eval 的功能是将字符串作为代码来执行。看上去好像很简单,但实际涉及很复杂的内容,主要是符号转义导致的语义问题。 53 | 54 | 在 bash 中,eval 的一个重要的使用场景是将变量的值当变量名,然后取它的变量值,类似于 c 语言中指向变量的指针: 55 | 56 | ``` 57 | % str1=str2 58 | % str2=abc 59 | % eval echo \$$str1 60 | abc 61 | ``` 62 | 63 | 注意这里有一个 \ 和两个 $,原因是第二个 $ 是和平时一样,正常取 str1 的值的,而第一个 $ 需要转义,因为它要在 eval 执行的过程中取 str2 的值,不能现在就展开。 64 | 65 | 这个用法很容易出问题,而且可读性很差。幸好 zsh 中无需这么用,有更好的办法: 66 | 67 | ``` 68 | % str1=str2 69 | % str2=abc 70 | % echo ${(P)str1} 71 | abc 72 | ``` 73 | 74 | (P) 专门用于这种场景,不需要再去转义 $。 75 | 76 | 此外 eval 有时也用来动态执行代码,比如一个脚本接受用户的输入,而这输入也是一段脚本代码,就可以用 eval 来运行它。但这种用法是极其危险的,因为脚本中可能有各种危险操作,而且 shell 的语法很灵活,很难通过静态扫描的方法判断是否有危险操作。不可靠的代码根本不应该去运行。即使一定要运行,也可以先写到文件里再运行,避免传过来的代码影响到自身的逻辑。 77 | 78 | 但也不是说 zsh 中就完全没有必要用 eval 了,在某些特别的场景(比如用于改造语法加语法糖)还是有用的。但如果要使用,就一定要注意它可能导致的副作用,利弊只能自己权衡了。eval 的具体用法,和 bash 中的基本没有区别,可以去网上搜索 bash eval 用法来了解,这里就不介绍了。 79 | 80 | ### 总结 81 | 82 | 本文简单介绍了 alias 的用法和 eval 的场景使用场景。alias 很简单,主要在 .zshrc 里使用。eval 很复杂,非必要场景尽量避免使用。 83 | -------------------------------------------------------------------------------- /07_Zsh-开发指南(第七篇-数值计算).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 数值计算并非 zsh 的强项,但应付一些简单的场景还是没问题的。并且 zsh 提供一个数值计算库,里边有一些比较常用的数学函数。 4 | 5 | ### 整数和浮点数类型 6 | 7 | Zsh 中通常不用指定变量类型,但也可以指定。对数值计算来说,区分整数和浮点数是很重要的,不指定变量类型会带来不方便。 8 | 9 | ``` 10 | # 整数 11 | % integer i=123 12 | # (t) 用于输出变量类型 13 | % echo ${(t)i} 14 | integer 15 | 16 | # 浮点数 17 | % float f=123.456 18 | % echo ${(t)f} 19 | float 20 | 21 | # 注意一旦指定了变量类型,类型就不会变了,除非再重新指定其他类型,或者用 unset 删除掉 22 | # 如果把浮点数赋值给整数变量,会取整 23 | % i=12.34 24 | % echo $i 25 | 12 26 | % a=-12.34 27 | % echo $a 28 | -12 29 | 30 | # 整数是 64 位的带符号整数(在 32 位系统下也是) 31 | % echo $((-2 ** 63)) $((2 ** 63 - 1)) 32 | -9223372036854775808 9223372036854775807 33 | 34 | # 浮点数是 64 位带符号浮点数(在 32 位系统下也是) 35 | % echo $((-1.79e-308)) $((1.79e308)) 36 | -1.79e-308 1.79e+308 37 | ``` 38 | 39 | ### 运算符 40 | 41 | 数值计算主要是在 `(( ))` 或者 `$(( ))` 中进行的,在 `$[ ]` 或者 `$var[ ]`(可用于数组索引的计算)中也能进行一部分,这里统一使用小括号。 42 | 43 | ``` 44 | % integer i=123 45 | % float f=123.456 46 | 47 | # $(( )) 会计算后返回数值 48 | % echo $((i*f)) 49 | 15185.088 50 | 51 | # (( )) 用于判断数值比较的结果 52 | % ((i < f && i + 1 > f)) && echo good 53 | 54 | # 在 (( )) 中也可以给变量赋值 55 | # (( )) 中的语法类似 c 语言,变量名前不需要 $,等号两边可以有空格 56 | % float result 57 | % ((result = i / f)) 58 | % echo $result 59 | 9.963063764e-01 60 | ``` 61 | 62 | 运算符列表: 63 | 64 | 运算符 | 功能 | 样例 65 | --- | --- | --- 66 | `+` `-` `*` `/` | 四则运算 | 1 + 2 * 3 / 4 67 | `**` | 乘方 | 3 ** 3.5 68 | `%` | 取余 | 5 % 3 69 | `++` `--` | 自增、自减 | i++(返回 i) ++i(返回 i + 1) 70 | `&` \| `^` | 按位与、按位或、按位异或 | 11 & 13 71 | `~` | 按位取反 | ~15 72 | `<<` `>>` | 按位左移、按位右移 | 1 << 3 73 | `<` `<=` `>` `>=` | 大小比较 | 2 <= 4 74 | `==` `!=` | 相等比较 | 2 != 4 75 | `&&` \|\| | 逻辑与、逻辑或 | 2 <= 4 && 1 != 3 \|\| 5 > 0 76 | `!` | 逻辑非 | ! 1 > 2 77 | `^^` | 逻辑异或(两个中只有一个为真) | 1 > 0 ^^ 1 < 0 78 | `=` | 赋值 | i = 10 79 | `+=` `-=` `*=` `/=` `%=` `**=` `&=` `^=` \|= `<<=` `>>=` `&&=` `^^=` \|\|= | 复合赋值| i += 10 80 | `( )` | 调整优先级 | (1 + 2 ) * 3 81 | `? :` | 三元运算符 | 1 > 2 ? 100 : 200 82 | `,` | 逗号运算符(只返回后者) | 40, 20 == 20 83 | 84 | 运算符的优先级和其他编程语言的差不多,不列出了,如果不确定可以加小括号。这部分内容和 c、java、javascript 等语言基本一致。 85 | 86 | ### 数学函数 87 | 88 | Zsh 包含了一个数学模块,如果需要使用数学函数,需要先加载 `zsh/mathfunc` 模块。 89 | 90 | ``` 91 | % zmodload -i zsh/mathfunc 92 | 93 | % echo $((sin(0) + ceil(14.4))) 94 | 15.0 95 | ``` 96 | 97 | 函数列表: 98 | 99 | 函数名 | 功能 100 | --- | --- 101 | `abs` | 求绝对值 102 | `ceil` | 向上取整 103 | `floor` | 向下取整 104 | `int` | 截断取整 105 | `float` | 转换成浮点数 106 | `sqrt` | 开平方 107 | `cbrt` | 开立方 108 | `log` | 自然对数 109 | `log10` | 常用对数 110 | `rand48` | 随机数 111 | 112 | 更多函数: 113 | 114 | `acos`、`acosh`、`asin`、`asinh`、`atan`、`atanh`、`cos`、`cosh`、`erf`、`erfc`、`exp`、 `expm1`、`fabs`、`gamma`、`j0`、`j1`、`lgamma`、`log1p`、`logb`、`sin`、`sinh`、`tan`、 `tanh`、`y0`、`y1`、`ilogb`、`signgam`、`copysign`、`fmod`、`hypot`、`nextafter`、`jn`、 `yn`、`ldexp`、`scalb` 115 | 116 | 117 | ### 参考 118 | 119 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 120 | -------------------------------------------------------------------------------- /17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 就像我之前提到的,zsh 脚本是可以直接使用 socket 文件(UNIX domain socket 所使用)或者 TCP 和其他进程通信的。如果进程都在本地,用 socket 文件效率更高些,并且不要占用端口,权限也更好控制。如果是在不同机器,可以使用 TCP。 4 | 5 | ### Socket 文件 6 | 7 | UNIX domain socket 是比管道更先进的进程通信方法,是全双工的方式,并且稳定性更好。但性能比管道差一些,不过一般性能瓶颈都不会出现在这里,不用考虑性能问题。而且在一个 socket 文件上可以建立多个连接,更容易管理。另外如果通信方式从 socket 文件改成 TCP,只需要修改很少的代码(建立和关闭连接的代码稍微改一下),而从管道改成 TCP 则要麻烦很多。 8 | 9 | 所以建议用 zsh 写进程交互脚本的话,直接使用 socket 文件,而不是命名管道(匿名管道就能满足需求的简单场景忽略不计)。 10 | 11 | Socket 文件的用法: 12 | 13 | ``` 14 | # 监听连接端 15 | # 首先要加载 socket 模块 16 | % zmodload zsh/net/socket 17 | 18 | % zsocket -l test.sock 19 | % listenfd=$REPLY 20 | # 此处阻塞等待连接 21 | % zsocket -a $listenfd 22 | # 连接建立完成 23 | % fd=$REPLY 24 | % echo $fd 25 | 5 26 | 27 | # 然后 $fd 就可读可写 28 | % cat <&$fd 29 | good 30 | ``` 31 | 32 | ``` 33 | # 发起连接端 34 | # 首先要加载 socket 模块 35 | % zmodload zsh/net/socket 36 | 37 | % zsocket test.sock 38 | # 连接建立完成 39 | % fd=$REPLY 40 | % echo $fd 41 | 4 42 | 43 | # 然后 $fd 就可读可写 44 | % echo good >&$fd 45 | ``` 46 | 47 | 连接建立后,怎么用就随意了。实际使用时,要判断 fd 看连接是否正常建立了。通常使用 socket 文件要比在网络环境使用 TCP 稳定性高很多,一般不会连接中断或者出其他异常。另外可以在 zsocket 后加 -v 参数,查看详细的信息(比如使用的 fd 号)。 48 | 49 | 关闭连接: 50 | 51 | ``` 52 | # 发起连接端 53 | # fd 是之前存放 fd 号的变量,不需要加 $ 54 | % exec {fd}>&- 55 | 56 | # 监听连接端 57 | % exec {listenfd}>&- 58 | % exec {fd}>&- 59 | # 删除 socket 文件即可,如果下次再使用会重新创建,该文件不能重复使用 60 | % rm test.sock 61 | ``` 62 | 63 | ### TCP 64 | 65 | 使用 TCP 连接的方式和使用 socket 文件基本一样。 66 | 67 | ``` 68 | # 监听连接端 69 | # 首先要加载 tcp 模块 70 | % zmodload zsh/net/tcp 71 | 72 | % ztcp -l 1234 73 | % listenfd=$REPLY 74 | # 此处阻塞等待连接 75 | % ztcp -a $listenfd 76 | # 连接建立完成 77 | % fd=$REPLY 78 | % echo $fd 79 | 3 80 | 81 | # 然后 $fd 就可读可写 82 | % cat <&$fd 83 | good 84 | ``` 85 | 86 | ``` 87 | # 发起连接端 88 | # 首先要加载 tcp 模块 89 | % zmodload zsh/net/tcp 90 | 91 | % ztcp 127.0.0.1 1234 92 | # 连接建立完成 93 | % fd=$REPLY 94 | % echo $fd 95 | 3 96 | 97 | # 然后 $fd 就可读可写 98 | % echo good >&$fd 99 | ``` 100 | 101 | 关闭连接: 102 | 103 | ``` 104 | # 发起连接端 105 | # fd 是之前存放 fd 号的变量 106 | % ztcp -c $fd 107 | 108 | # 监听连接端 109 | % ztcp -c $listenfd 110 | % ztcp -c $fd 111 | ``` 112 | 113 | ### 程序样例 114 | 115 | recv_tcp,监听指定端口,并输出发送过来的消息。使用方法:recv_tcp 端口 116 | 117 | ``` 118 | #!/bin/zsh 119 | 120 | zmodload zsh/net/tcp 121 | 122 | (($+1)) || { 123 | echo "Usage: ${0:t} port" 124 | exit 1 125 | } 126 | 127 | ztcp -l $1 128 | listenfd=$REPLY 129 | 130 | [[ $listenfd == <-> ]] || exit 1 131 | 132 | while ((1)) { 133 | ztcp -a $listenfd 134 | fd=$REPLY 135 | [[ $fd == <-> ]] || continue 136 | 137 | cat <&$fd 138 | ztcp -c $fd 139 | } 140 | ``` 141 | 142 | send_tcp,用来向指定机器的指定端口发一条消息。使用方法:send_tcp 机器名 端口 消息 (机器名可选,如果没有则发到本机,消息可以包含空格) 143 | 144 | ``` 145 | #!/bin/zsh 146 | 147 | zmodload zsh/net/tcp 148 | 149 | (($# >= 2)) || { 150 | echo "Usage: ${0:t} [hostname] port message" 151 | exit 1 152 | } 153 | 154 | if [[ $1 == <0-65535> ]] { 155 | ztcp 127.0.0.1 $1 156 | } else { 157 | ztcp $1 $2 158 | shift 159 | } 160 | 161 | fd=$REPLY 162 | [[ "$fd" == <-> ]] || exit 1 163 | 164 | echo ${*[2,-1]} >&$fd 165 | ztcp -c $fd 166 | ``` 167 | 168 | ### 总结 169 | 170 | 本文介绍了使用 socket 文件或者 TCP 来实现两个脚本之间通信的方法。 171 | -------------------------------------------------------------------------------- /15_Zsh-开发指南(第十五篇-进程与作业控制).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 通常情况 zsh 脚本是在一个进程中(并且单线程)执行的,但有时我们需要并行执行一些代码,因为现在的 CPU 基本都是多核的,这样可以加快运行速度。这就涉及到进程与作业控制。这里不讲进程的概念。 4 | 5 | ### 在子进程中执行代码 6 | 7 | 之前我们提到过,小括号中的代码是在子进程中执行的: 8 | 9 | ``` 10 | % (sleep 1000 && echo good) 11 | 12 | # 然后再另一个 zsh 里查看进程 13 | % pstree | grep sleep 14 | `-tmux: server-+-zsh---zsh---sleep 15 | ``` 16 | 17 | 里边有两个 zsh 进程。如果不加小括号的话: 18 | 19 | ``` 20 | % sleep 1000 && echo good 21 | 22 | # 然后再另一个 zsh 里查看进程 23 | % pstree | grep sleep 24 | `-tmux: server-+-zsh---sleep 25 | ``` 26 | 27 | 就只有一个 zsh 进程。这说明使用小括号时,里边的代码是在子进程(一个新的 zsh 进程)执行的。但需要注意的时,如果括号里只有一个命令(比如 sleep 1000),那么并不会再开一个子进程来执行了。 28 | 29 | 那么在子进程里执行代码有什么意义呢?如果像上边那样放着前台运行,是没有什么意义。但我们可以把它放后台运行。 30 | 31 | ### 在后台运行进程 32 | 33 | 首先我们先看下怎么把单个程序放后台运行。 34 | 35 | ``` 36 | % sleep 1000 & 37 | [1] 850 38 | ``` 39 | 40 | 在 sleep 1000 后边加一个 &,就会把它放后台运行。然后会输出一行内容,[1] 是进程的作业(job)号,850 是进程号(PID)。我们可以继续运行别的命令,不需要等待 sleep 结束了。 41 | 42 | jobs 命令可以查看当前在后台运行的所有作业: 43 | 44 | ``` 45 | % jobs 46 | [1] + running sleep 1000 47 | 48 | # -l 会输出进程号 49 | % jobs -l 50 | [1] + 850 running sleep 1000 51 | ``` 52 | 53 | fg 命令可以把后台的作业切换回前台: 54 | 55 | ``` 56 | # 然后会继续等待 sleep 运行 57 | % fg 58 | [1] + running sleep 1000 59 | ``` 60 | 61 | 如果进程已经运行起来了,我们想再把它放到后台,可以这样: 62 | 63 | ``` 64 | # 回车后按 ctrl + z 65 | % sleep 1000 66 | ^Z 67 | zsh: suspended sleep 1000 68 | # 这时可以运行 jobs 看一下,sleep 是处于挂起状态的 69 | % jobs 70 | [1] + suspended sleep 1000 71 | # 可以用 bg 让 sleep 恢复运行 72 | % bg 73 | [1] + continued sleep 1000 74 | # 这样 sleep 就运行在后台了 75 | % jobs 76 | [1] + running sleep 1000 77 | ``` 78 | 79 | 其实 jobs、fg、bg 这些命令并不常用,大概了解下用法即可。比如现在在用 vim 编辑文件,文件还没有保存,但我想退到终端运行个命令,然后再回到 vim。可以按 ctrl + z 让 vim 挂起,然后运行命令,最后再运行 fg 让 vim 恢复。但通常我们可以启动多个终端模拟器,或者开一个新终端模拟器标签,或者用 tmux,没必要在一个 shell 里这么折腾。 80 | 81 | ### 在脚本中使用后台进程执行代码 82 | 83 | 那么回答之前的场景,要在后台进程里执行 sleep 1000 && echo good: 84 | 85 | ``` 86 | % {sleep 1000 && echo aa} & 87 | ``` 88 | 89 | 这样大括号里的代码都会在后台进程里执行,脚本里可以继续写别的。如果做完了后需要再等大括号里边的代码运行。 90 | 91 | ``` 92 | #!/bin/zsh 93 | 94 | {sleep 5 && echo p1} & 95 | # $! 是上一个运行的后台进程的进程号 96 | pid=$! 97 | {sleep 10 && echo p2} & 98 | echo aaa 99 | # 要做的其他事情先做完 100 | sleep 2 101 | echo bbb 102 | # wait 加进程号用来等待进程结束,类似 fg,但脚本中不能用 fg 103 | wait $pid 104 | echo ccc 105 | ``` 106 | 107 | 结果: 108 | 109 | ``` 110 | % ./test.zsh 111 | aaa 112 | bbb 113 | p1 114 | ccc 115 | # p2 是脚本运行完过几秒才输出的 116 | % p2 117 | ``` 118 | 119 | 这样我们就可以同时操作多个进程来为自己服务了。而进程之间的通信,可以用命名管道或者普通文件来做,也可以使用 socket 文件(Zsh 中有 zsh/net/socket 模块,使用它可以通过 socket 文件来通信。管道是单向的,而 socket 双向的,更灵活一些,后续我们会了解它的用法),或者使用网络通信(如果脚本分布在不同的机器,zsh 中有 zsh/net/tcp 模块,这样无需外部命令就可进行 tcp 通信,后续也会讲到它)。 120 | 121 | ### 信号 122 | 123 | 运行中的进程可以接受信号然后对信号做出响应。kill 命令用来给进程发送信号。 124 | 125 | 15(SIGTERM)是最常用的信号,也是 kill 不加参数的默认信号,用于终止一个进程。kill num 即可终止进程号是 num 的进程。但 15 信号可以被进程捕获,然后并不退出。如果要强行杀掉一个进程,可以用 9 信号(SIGKILL),它是进程无法捕获的,但这样的话进程正在做的事情会突然中断,可能会有严重的影响,所以通常情况不要使用 9 信号杀进程。 126 | 127 | 在脚本中捕获信号: 128 | 129 | ``` 130 | #!/bin/zsh 131 | 132 | # SIGINT 是 2 信号,ctrl + c 会触发 133 | TRAPINT() { 134 | # 处理一些退出前的善后工作 135 | sleep 333 136 | } 137 | 138 | sleep 1000 139 | ``` 140 | 141 | 然后运行这个脚本,然后 ctrl + c,脚本没有退出,因为在执行 sleep 333,要再按一次才会退出。 142 | 143 | 在脚本中使用信号,通常是给其他进程发(主要是 15),而不是给自己发。在脚本中也很少需要捕获信号处理。信号相关的更多内容,以后可能会补充。 144 | 145 | ### 总结 146 | 147 | 本文大概讲了进程与作业控制相关内容,主要用于在脚本里使用多进程执行代码,而不是在终端里进行作业控制(因为很少需要这样做)。关于脚本中的多个进程如何配合的内容还需要继续完善。 148 | -------------------------------------------------------------------------------- /04_Zsh-开发指南(第四篇-字符串处理之通配符).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 这是字符串处理系列的第三篇文章。前两篇基本覆盖了字符串处理中的常用操作,但在字符串匹配方面,没有详细展开。 4 | 5 | 通配符(glob)是 shell 中的一个比较重要的概念,可以认为是正则表达式的简化版本。通配符在字符串匹配和文件名搜索等方面非常有用。本篇只讲它在字符串匹配上的用法。 6 | 7 | ### 通配符的基本用法 8 | 9 | 之前在讲字符串匹配判断时,通配符出现过,就是 `*"$str"*` 两边的星号。 10 | 11 | ``` 12 | % str1=abcd 13 | % str2=bc 14 | 15 | # 星号要在引号外边 16 | % [[ $str1 == *"$str2"* ]] && echo good 17 | good 18 | 19 | # 注意带通配符的字符串必须放在右边 20 | % [[ *"$str2"* == $str1 ]] && echo good 21 | 22 | ``` 23 | 24 | 星号是最常用的通配符,用于匹配任意数量(包括 0 个)的任意字符。 25 | 26 | ``` 27 | # 问号用于匹配一个任意字符 28 | % [[ abcd == ab?? ]] && echo good 29 | good 30 | 31 | # 中括号用于匹配出现在其中的单个字符 32 | % [[ abcd == abc[bcd] ]] && echo good 33 | good 34 | 35 | # 如果中括号里第一个字符是 ^,则匹配除了除了中括号里的单个字符 36 | % [[ abcd == abc[^de] ]] && echo good 37 | % [[ abcd == abc[^ce] ]] && echo good 38 | good 39 | 40 | # 中括号里可以指定字符的范围 41 | % [[ a4 == [a-b][2-5] ]] && echo good 42 | good 43 | 44 | # 可以指定多个字符范围,并且可以掺杂其他字符 45 | % [[ B4 == [a-cdddA-B][2-5] ]] && echo good 46 | good 47 | 48 | # 尖括号用于匹配一定范围的单个整数 49 | % [[ 123 == 12<3-4> ]] && echo good 50 | good 51 | 52 | # 可以匹配整个整数 53 | % [[ 123 == <100-200> ]] && echo good 54 | good 55 | 56 | # 可以没有上下界,默认的下界是 0,上界是正无穷 57 | % [[ 123 == <100-> && 123 == <-200> ]] && echo good 58 | good 59 | 60 | # 可以上下界都没有,那么会匹配任意正整数和 0 61 | # 这个可以用来判断字符串是否构成整数 62 | # [[ 123 == <-> ]] && echo good 63 | good 64 | 65 | # ( 1 | 2 | ... ) 用于同时判断多个条件,满足一个即可 66 | % [[ ab == (aa|ab) ]] && echo good 67 | good 68 | 69 | # 如果中括号里要用 - 或者 ^,放在最后即可,不需要转义 70 | % [[ -^3 == [a-c-][3^-][3^-] ]] && echo good 71 | good 72 | 73 | ``` 74 | 75 | 以上是通配符的基本用法,总结一下。 76 | 77 | | 通配符 | 含义 | | 78 | | ---------------- | ------------------------------ | ---- | 79 | | \* | 任意数量的任意字符 | | 80 | | ? | 任意一个字符 | | 81 | | [abcd] | abcd 中的任意一个字符 | | 82 | | [^abcd] | 除 abcd 外的任意一个字符 | | 83 | | [a-c] | a 和 c 之间的一个字符 | | 84 | | [a-cB-Dxyz] | a 和 c 之间、B 和 D 之间以及 xyz 中的一个字符 | | 85 | | <1-100> | 1 和 100 之间的整数 | | 86 | | <-50> | 0 和 50 之间的整数 | | 87 | | <100-> | 大于 100 的整数 | | 88 | | <-> | 任意正整数和 0 | | 89 | | ([a-c]\|<1-100>) | a 和 c 之间的一个字符或者 1 和 100 之间的整数 | | 90 | 91 | ### 加强版通配符 92 | 93 | Zsh 还支持加强版通配符,功能更多一些。如果使用加强版的通配符,需要先在代码里加上 `setopt EXTENDED_GLOB`。 94 | 95 | | 通配符 | 含义 | 匹配的样例 | 96 | | ----------- | -------------------------- | --------------- | 97 | | ^abc | 除了 abc 外的任意字符串 | aaa | 98 | | abc^abc | 以 abc 开头,但后边不是 abc 的字符串 | abcabd | 99 | | a*c~abc | 符合 a*c 但不是 abc 的字符串 | adc | 100 | | a# | 任意数量(包括 0)个 a | aaa | 101 | | b## | 一个或者多个 b | b | 102 | | (ab)## | 一个或者多个 ab | abab | 103 | | (#i)abc | 忽略大小写的 abc | AbC | 104 | | (#i)ab(#I)c | 忽略大小写的 ab 接着 c | ABc | 105 | | (#l)aBc | a 和 c 忽略大小写,但 B 必须大写 的 aBc | aBC | 106 | | (#a1)abc | 最多错(多或缺也算)一个字符的 abc | a2c 或 ab 或 abcd | 107 | 108 | 此外还有一些更高级的用法,暂时先略过。 109 | 110 | ### 总结 111 | 112 | 字符串的内容先告一段落,但之后的文章依然会不断地涉及字符串,因为数组和哈希表里的内容通常是字符串,处理目录文件时也涉及大量的字符串操作等等,届时会有新的字符串处理方法。此外,如果我发现新的处理字符串的方法或者技巧,也会更新这几篇文章。 113 | 114 | ### 参考 115 | 116 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 117 | -------------------------------------------------------------------------------- /06_Zsh-开发指南(第六篇-哈希表).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 哈希表是比数组更复杂的数据结构,在某些语言里被称作关联数组或者字典等等。简单说,哈希表用于存放指定键(key)对应的值(value),键和值的关系,就像字典中单词和释义的对应关系,通过单词可以快速找到释义,而不需要从头依次遍历匹配。准确地说,哈希表只是该功能的一种实现方式,也可以使用各种树或者其他数据结构来实现,不同的实现方式适合不同的场景,使用方法是一样的。但为了简化概念,统一使用哈希表这个名称。 4 | 5 | 6 | ### 哈希表定义 7 | 8 | 和其他变量类型不同,哈希表是需要提前声明的,因为哈希表的赋值语法和数组一样,如果不声明,是无法区分的。 9 | 10 | ``` 11 | % typeset -A hashmap 12 | # 或者用 local,二者功能是一样的 13 | % local -A hashmap 14 | 15 | # 赋值的语法和数组一样,但顺序依次是键、值、键、值 16 | % hashmap=(k1 v1 k2 v2) 17 | 18 | # 直接用 echo 只能输出值 19 | % echo $hashmap 20 | v1 v2 21 | 22 | # 使用 (kv) 同时输出键和值,(kv) 会把键和值都放到同一个数组里 23 | % echo ${(kv)hashmap} 24 | k1 v1 k2 v2 25 | 26 | # 哈希表的大小是键值对的数量 27 | % echo $#hashmap 28 | 2 29 | ``` 30 | 31 | ### 元素读写 32 | 33 | 读写哈希表的方法和数组类似,只是用于定位的数字变成了字符串。 34 | 35 | ``` 36 | # 可以声明和赋值写到一行 37 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 38 | % echo $hashmap[k2] 39 | v2 40 | 41 | % hashmap[k2]="V2" 42 | 43 | # 删除元素的方法和数组不同,引号不能省略 44 | % unset "hashmap[k1]" 45 | % echo ${(kv)hashmap} 46 | k2 V2 k3 v3 47 | ``` 48 | 49 | ### 哈希表拼接 50 | 51 | ``` 52 | # 追加元素的方法和数组一样 53 | % hashmap+=(k4 v4 k5 v5) 54 | % echo $hashmap 55 | V2 v3 v4 v5 56 | 57 | 58 | % local -A hashmap1 hashmap2 59 | % hashmap1=(k1 v1 k2 v2) 60 | % hashmap2=(k2 v222 k3 v3) 61 | 62 | # 拼接哈希表,要展开成数组再追加 63 | % hashmap1+=(${(kv)hashmap2}) 64 | # 如果键重复,会直接替换值,哈希表的键是不重复的 65 | % echo ${(kv)hashmap1} 66 | k1 v1 k2 v222 k3 v3 67 | ``` 68 | 69 | ### 哈希表遍历 70 | 71 | 用 `(kv)` `(k)` 等先将哈希表转化成数组,然后再遍历。 72 | 73 | ``` 74 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 75 | 76 | # 只遍历值 77 | % for i ($hashmap) { 78 | > echo $i 79 | > } 80 | v1 81 | v2 82 | v3 83 | 84 | # 只遍历键 85 | % for i (${(k)hashmap}) { 86 | > echo $i 87 | > } 88 | k1 89 | k2 90 | k3 91 | 92 | # 同时遍历键和值 93 | % for k v (${(kv)hashmap}) { 94 | > echo "$k -> $v" 95 | > } 96 | k1 -> v1 97 | k2 -> v2 98 | k3 -> v3 99 | ``` 100 | 101 | ### 元素查找 102 | 103 | 判断键是否存在。 104 | 105 | ``` 106 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 107 | % (($+hashmap[k1])) && echo good 108 | good 109 | % (($+hashmap[k4])) && echo good 110 | ``` 111 | 112 | 如果需要判断某个值是否存在,直接对值的数组判断即可。但这样做就体现不出哈希表的优势了。 113 | 114 | ``` 115 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 116 | # value 是值的数组,也可以用 local -a 强行声明为数组 117 | % value=($hashmap) 118 | 119 | % (( $value[(I)v1] )) && echo good 120 | good 121 | % (( $value[(I)v4] )) && echo good 122 | ``` 123 | 124 | ### 元素排序 125 | 126 | 对哈希表元素排序的方法,和数组类似,多了 `k` `v` 两个选项,其余的选项如 `o`(升序)、`O`(降序)、`n`(按数字大小)、`i`(忽略大小写)等通用,不再一一举例。 127 | 128 | ``` 129 | % local -A hashmap=(aa 33 cc 11 bb 22) 130 | 131 | # 只对值排序 132 | % echo ${(o)hashmap} 133 | 11 22 33 134 | 135 | # 只对键排序 136 | % echo ${(ok)hashmap} 137 | aa bb cc 138 | 139 | # 键值放在一起排序 140 | % echo ${(okv)hashmap} 141 | 11 22 33 aa bb cc 142 | ``` 143 | 144 | ### 从字符串、文件构造哈希表 145 | 146 | 因为哈希表可以从数组构造,所以从字符串、文件构造哈希表,和数组的操作是一样的,不再一一举例。 147 | 148 | ``` 149 | % str="k1 v1 k2 v2 k3 v3" 150 | % local -A hashmap=(${=str}) 151 | % echo $hashmap 152 | v1 v2 v3 153 | ``` 154 | 155 | ### 对哈希表中的每个元素统一处理 156 | 157 | 对哈希表中的每个元素统一处理,和对数组的操作是类似的,多了 `k` `v` 两个选项用于指定是对键处理还是对值处理,可以一起处理。不再一一举例。 158 | 159 | ``` 160 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 161 | % print ${(U)hashmap} 162 | V1 V2 V3 163 | 164 | % print ${(Uk)hashmap} 165 | K1 K2 K3 166 | 167 | % print ${(Ukv)hashmap} 168 | K1 V1 K2 V2 K3 V3 169 | ``` 170 | 171 | `:#` 也可以在哈希表上用。 172 | 173 | ``` 174 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 175 | 176 | # 排除匹配到的值 177 | % echo ${hashmap:#v1} 178 | v2 v3 179 | 180 | # 只输出匹配到的键 181 | % echo ${(Mk)hashmap:#k[1-2]} 182 | k1 k2 183 | ``` 184 | 185 | ### 总结 186 | 187 | 本篇简单讲了哈希表的基本用法。篇幅不长,但因为哈希表的操作和数组类似,很多操作数组的方法都可以用作哈希表上,而且可以把键或者值单独作为数组处理,所以操作哈希表更为复杂一些。 188 | 189 | 另外还有一些更进阶的处理数组和哈希表方法,之后会讲到。 190 | -------------------------------------------------------------------------------- /11_Zsh-开发指南(第十一篇-变量的进阶内容).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 之前我们已经依次讲过 zsh 下的五种变量(字符串、数组、哈希表、整数、浮点数)的基本用法。但变量的使用方面,还有一些比较进阶的内容,这对一些比较特别的场景很有帮助。 4 | 5 | ### typeset 命令 6 | 7 | typeset 命令用于对变量进行详细的设置。我们之前在哈希表那篇见过它。typeset -A 可以用来定义哈希表。 8 | 9 | ``` 10 | % typeset -A hashmap=(aa bb cc dd) 11 | ``` 12 | 13 | 但我们后续都使用 local,因为 local 的功能和 hashmap 是一样的(除了不能用 -f 和 -g,这两个选项不常用),并且更短更容易输入。这里提到 typeset 命令,因为这个名称很好地反映了它的功能。但知道了这个后,我们可以继续使用 local 命令,毕竟它们是一样的。 14 | 15 | typeset 命令有很多选项,可以作用在变量上,起到各种各样的效果。 16 | 17 | ### 强制字符串内容为小写或者大写 18 | 19 | ``` 20 | # 强制字符串内容为小写 21 | % local -l str=abcABC && echo $str 22 | abcabc 23 | 24 | # 强制字符串内容为大写 25 | % local -u str=abcABC && echo $str 26 | ABCABC 27 | ``` 28 | 29 | ### 设置变量为环境变量 30 | 31 | ``` 32 | % local -x str=abc 33 | # 通常使用 export,功能一样 34 | % export str=abc 35 | ``` 36 | 37 | 环境变量可以被子进程读取。 38 | 39 | ### 设置变量为只读变量 40 | 41 | ``` 42 | % local -r str1=abc 43 | # 通常使用 readonly,功能一样 44 | % readonly str2=abc 45 | 46 | % str1=bcd 47 | zsh: read-only variable: str1 48 | % str2=bcd 49 | zsh: read-only variable: str2 50 | ``` 51 | 52 | ### 设置数组不包含重复元素 53 | 54 | ``` 55 | % local -U array=(aa bb aa cc) && echo $array 56 | aa bb cc 57 | ``` 58 | 59 | ### 设置整数的位数 60 | 61 | ``` 62 | # 如果位数不够,输出内容会用 0 补全 63 | % local -Z 3 i=5 && echo $i 64 | 005 65 | 66 | # 如果超出范围会被截断 67 | % local -Z 3 i=1234 && echo $i 68 | 234 69 | ``` 70 | 71 | ### 进制转换 72 | 73 | 设置整数为其他进制显示: 74 | 75 | ``` 76 | % local -i 16 i=255 77 | % echo $i 78 | 16#FF 79 | ``` 80 | 81 | 可以设置 2 到 36 之间任意进制。设置几进制显示,并不影响计算,只是显示格式不同。 82 | 83 | 用 [#n] num 也可以显示十进制数为 n 进制: 84 | 85 | ``` 86 | % echo $(([#16] 255)) 87 | 16#FF 88 | ``` 89 | 90 | 可以用 n#num 来显示 n 进制整数为十进制: 91 | 92 | ``` 93 | % echo $((16#ff)) 94 | 255 95 | ``` 96 | 97 | 我们可以定义一系列函数来快捷地转换进制,不需要使用 bc 等外部命令: 98 | 99 | ``` 100 | 0x() { 101 | echo $((16#$1)) 102 | } 103 | 104 | 0o() { 105 | echo $((8#$1)) 106 | } 107 | 108 | 0b() { 109 | echo $((2#$1)) 110 | } 111 | 112 | p16() { 113 | echo $(([#16] $1)) 114 | } 115 | 116 | p8() { 117 | echo $(([#8] $1)) 118 | } 119 | 120 | p2() { 121 | echo $(([#2] $1)) 122 | } 123 | 124 | 125 | # 其他进制转十进制 126 | % 0x ff 127 | 255 128 | % 0b 1101 129 | 13 130 | 131 | # 十进制转其他进制 132 | % p16 1234 133 | 16#4D2 134 | ``` 135 | 136 | ### 同时对多个变量赋相同的值 137 | 138 | ``` 139 | % local {i,j,k}=123 140 | % echo $i $j $k 141 | 123 123 123 142 | ``` 143 | 144 | ### 绑定字符串和数组 145 | 146 | ``` 147 | % local -T DIR dir 148 | % dir=(/a /b/c /b/d /e/f) 149 | % echo $DIR 150 | /a:/b/c:/b/d:/e/f 151 | 152 | # 删除 dir 后,DIR 也会被删除(反之亦然) 153 | % unset dir 154 | % echo $+DIR 155 | 0 156 | ``` 157 | 158 | Linux 下经常需要处理带分隔符冒号的字符串(比如 $PATH)。如果只修改其中某一个字段,比较麻烦。local -T 可以把字符串绑定到数组上,这样直接修改数组,字符串内容也会同步变化(反之亦然)。其实在 zsh 中,$PATH 字符串就是和 $path 数组绑定的,可以直接通过修改 $path 来达到修改 $PATH 的目的,这在某些场景会方便很多。 159 | 160 | ### 显示变量的定义方式 161 | 162 | ``` 163 | % array=(aa bb cc) 164 | % local -p array 165 | typeset -a array=(aa bb cc) 166 | 167 | % array+=(dd) 168 | % local -p array 169 | typeset -a array=(aa bb cc dd) 170 | ``` 171 | 172 | ### 什么地方该加双引号 173 | 174 | 用过 bash 的读者大概会对里边的双引号印象比较深刻,很多地方不加双引号都会出错,为了避免出错,很多人每个变量左右都加上双引号,麻烦不说,代码看起来也比较乱。 175 | 176 | 其实 zsh 中已经没有那些问题了,变量两边无需加双引号,不会出现莫名其妙的错误。但有些地方还是需要加双引号的。 177 | 178 | **需要加双引号的场景:** 179 | 180 | 1. 像这样的包含字符或者特殊符号的字符串 `"aa bb \t \n *"` 出现在代码中时,两边要加双引号,这个基本不需要说明。 181 | 2. 在用 `$()` 调用命令时,如果希望结果按一个字符串处理,需要加上双引号,`"$()"`,不然的话,如果命令结果中有空格,`$()` 会被展开成多个字符串。 182 | 3. 如果想将数组当单个字符串处理,需要加双引号,`array=(a b); print -l "$array"`。 183 | 4. 其他的原本不是单个字符串的东西,需要转成单个字符串的场景,要加双引号。 184 | 185 | **其余情况通常都不需要加双引号,典型的情况:** 186 | 187 | 1. 任何情况下,字符串变量的两边都不需要加双引号,无论里边的内容多么特殊,或者变量存不存在,都没有关系,如 `$str`。 188 | 2. 如果不转换类型(比如数组转成字符串),任何变量的两边都不需要加双引号。 189 | 3. `$1` `$2` `$*` 这些参数(其实它们也都是单个字符串),都不需要加双引号,无论内容是什么,或者参数是否存在。 190 | 191 | 以上的 7 种情况几乎覆盖了所有场景,如果有没覆盖到的,试一下即可(让里边的内容包含空格、换行和其他特殊字符等等,看看结果是否符合预期)。 192 | 193 | ### 总结 194 | 195 | 本文简单介绍了一些比较使用的 typeset(或者 local)命令的用法,typeset 命令还有很多其他参数,但一般很少用,以后我也会继续更新。 196 | 197 | ### 参考 198 | 199 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 200 | 201 | http://www.linux-mag.com/id/1079/ 202 | 203 | 204 | ### 更新历史 205 | 206 | 20170831:新增“什么地方该加双引号” 207 | -------------------------------------------------------------------------------- /08_Zsh-开发指南(第八篇-变量修饰语).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 我们已经了解了字符串、数组、哈希表、整数、浮点数的基本用法,但应付某些复杂的场景依然力不从心。 4 | 5 | 变量修饰语是 zsh 中有一个很独特的概念,对变量进行操作,功能上和函数类似,但用起来更方便,在一行代码里实现复杂功能主要靠它了。而代价是可读性更差,怎么用就要自己权衡了。它也是 zsh 最有特色的部分之一。变量修饰语主要应用于数组和哈希表,但也有一小部分可以应用于字符串(整数和浮点数也会被当成字符串处理)。 6 | 7 | ### 变量修饰语的格式 8 | 9 | 其实前边的文章中,变量修饰语已经出现过,但当时没有详细说明。 10 | 11 | 比如在大小写转换的例子中。 12 | 13 | ``` 14 | % str="ABCDE abcde" 15 | 16 | # 转成大写,(U) 和 :u 两种用法效果一样 17 | % echo ${(U)str} --- ${str:u} 18 | ABCDE ABCDE --- ABCDE ABCDE 19 | 20 | # 转成小写,(L) 和 :l 两种用法效果一样 21 | % echo ${(L)str} --- ${str:l} 22 | abcde abcde --- abcde abcde 23 | ``` 24 | 25 | 这里的 `(U)`、`:l` 等等都是变量修饰语。变量修饰语主要有两种格式。 26 | 27 | ``` 28 | ${(x)var} 29 | ${var:x} 30 | ``` 31 | 32 | 其中 var 是变量名,x 是 一个或多个字母,不同字母的功能不同。第二行的冒号也可能是其他符号。${var} 和 $var 基本相同,大括号用于避免变量名中的字符和后边的字符粘连,通常情况是不需要加大括号的。但如果使用变量修饰语,大括号就必不可少(其实第二种格式中,大括号可以省略,但考虑可读性和错误提示等因素,还是加上比较好)。 33 | 34 | 变量修饰语可以嵌套使用。因为加了修饰语的变量依然是变量,可以和正常的变量一样处理。 35 | 36 | ``` 37 | % str=abc 38 | % echo ${(U)str} 39 | ABC 40 | % echo ${(C)${(U)str}} 41 | Abc 42 | 43 | % echo ${${a:u}:l} 44 | abc 45 | 46 | # 可以两种风格嵌套在一起 47 | % echo ${(C)${a:u}} 48 | Abc 49 | ``` 50 | 51 | 这里要注意 $ 之后全程不能有空格,否则会有语法错误。也就是说不能通过加空格来避免因为字符挤在一起造成的可读性变差。但熟悉了格式后,就可以比较容易识别出代码的功能。比较复杂的逻辑可以换行继续写,而没必要一定嵌套使用。 52 | 53 | 知道了变量修饰语的用法后,重要的就是都有哪些可以使用的变量修饰语了。 54 | 55 | ### 变量默认值 56 | 57 | 和变量默认值(读取变量时如果变量为空或者不存在,使用的默认值)相关的操作,变量可以是任何类型的。 58 | 59 | ``` 60 | % var=123 61 | 62 | # 如果变量有值,就输出变量值 63 | % echo ${var:-abc} 64 | 123 65 | 66 | # 如果变量没有值(变量不存在,为空字符串、空数组、空哈希表等),输出 abc 67 | % echo ${varr:-abc} 68 | abc 69 | 70 | 71 | % var="" 72 | # 和 :- 类似,但只有变量不存在时才替换成默认值 73 | % echo ${var-abc} 74 | % echo ${varr-abc} 75 | abc 76 | 77 | 78 | % var="" 79 | # 和 :- 类似,但如果变量没有值,则赋值为 abc 80 | % echo ${var:=abc} 81 | abc 82 | % echo $var 83 | abc 84 | 85 | 86 | % var=abc 87 | # 不管 var 有没有值,都赋值为 123 88 | % echo ${var::=123} 89 | 123 90 | % echo $var 91 | 123 92 | 93 | 94 | % var="" 95 | # 如果 var 没有值,直接报错 96 | % echo ${var:?error} 97 | zsh: var: error 98 | 99 | 100 | % var=abc 101 | # 如果 var 有值,输出 123 102 | % echo ${var:+123} 103 | % echo ${varr:+123} 104 | 105 | ``` 106 | 107 | ### 数组拼接成字符串 108 | 109 | ``` 110 | % array=(aa bb cc dd) 111 | 112 | # 用换行符拼接 113 | % echo ${(F)array} 114 | aa 115 | bb 116 | cc 117 | dd 118 | 119 | # 用空格拼接 120 | % str=$array 121 | % echo $str 122 | aa bb cc dd 123 | 124 | # 使用其他字符或字符串拼接 125 | % echo ${(j:-=:)array} 126 | aa-=bb-=cc-=dd 127 | ``` 128 | 129 | ### 字符串切分成数组 130 | 131 | ``` 132 | % str=a##b##c##d 133 | 134 | % array=(${(s:##:)str}) 135 | % print -l $array 136 | a 137 | b 138 | c 139 | d 140 | ``` 141 | 142 | ### 输出变量类型 143 | 144 | ``` 145 | # 注意如果不加 integer 或者 float,都为字符串,但计算时会自动转换类型 146 | % integer i=1 147 | % float f=1.2 148 | % str=abc 149 | % array=(a b c) 150 | % local -A hashmap=(k1 v1 k2 v2) 151 | 152 | % echo ${(t)i} ${(t)f} ${(t)str} ${(t)array} ${(t)hashmap} 153 | integer float scalar array association 154 | ``` 155 | 156 | ### 字符串、数组或哈希表嵌套取值 157 | 158 | 可以嵌套多层。 159 | 160 | ``` 161 | % str=abcde 162 | % echo ${${str[3,5]}[3]} 163 | e 164 | 165 | % array=(aa bb cc dd) 166 | % echo ${${array[2,3]}[2]} 167 | cc 168 | # 如果只剩一个元素了,就取字符串的字符 169 | % echo ${${array[2]}[2]} 170 | b 171 | 172 | % local -A hashmap=(k1 v1 k2 v2 k3 v3) 173 | % echo ${${hashmap[k1]}[2]} 174 | 1 175 | ``` 176 | 177 | ### 字符串内容作为变量名再取值 178 | 179 | 不需要再通过繁琐的 eval 来做这个。 180 | 181 | ``` 182 | % var=abc 183 | % abc=123 184 | 185 | % echo ${(P)var} 186 | 123 187 | ``` 188 | 189 | ### 对齐或截断数组中的字符串 190 | 191 | ``` 192 | % array=(abc bcde cdefg defghi) 193 | 194 | # 只取每个字符串的最后两个字符 195 | % echo ${(l:2:)array} 196 | bc de fg hi 197 | 198 | # 用空格补全字符串并且右对齐 199 | % print -l ${(l:7:)array} 200 | abc 201 | bcde 202 | cdefg 203 | defghi 204 | 205 | # 用指定字符补全 206 | % print -l ${(l:7::0:)array} 207 | 0000abc 208 | 000bcde 209 | 00cdefg 210 | 0defghi 211 | 212 | # 用指定字符补全,第二个字符只用一次 213 | % print -l ${(l:7::0::1:)array} 214 | 0001abc 215 | 001bcde 216 | 01cdefg 217 | 1defghi 218 | 219 | # 左对齐 220 | % print -l ${(r:7::0::1:)array} 221 | abc1000 222 | bcde100 223 | cdefg10 224 | defghi1 225 | ``` 226 | 227 | ### 总结 228 | 229 | 文中只介绍了几个比较常用的变量修饰语,还有一些没有提及,可能后续再补充。 230 | 231 | ### 参考 232 | 233 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 234 | -------------------------------------------------------------------------------- /12_Zsh-开发指南(第十二篇-[[-]]-的用法).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | [[ ]] 是我们比较熟悉的符号了,从第一篇开始我们就一直在用,但我一直没有详细介绍它的用法,只用到了它的一小部分功能。本文详细介绍 [[ ]] 的用法。 4 | 5 | ### 比较字符串 6 | 7 | [[ ]] 最常用的功能之一是比较字符串,这也是我们一直在用的功能。 8 | 9 | ``` 10 | # 匹配 11 | % [[ abc == abc ]] && echo good 12 | good 13 | 14 | # = 和 == 是一样的,最好统一使用一种 15 | % [[ abc = abc ]] && echo good 16 | good 17 | 18 | # 不匹配 19 | % [[ abc != abd ]] && echo good 20 | good 21 | 22 | # 正则表达式匹配 23 | % [[ abc =~ a.c ]] && echo good 24 | good 25 | 26 | # 前者字符序比后者小 27 | % [[ abc < bcd ]] && echo good 28 | good 29 | 30 | # 前者字符序比后者大 31 | % [[ cde > bcd ]] && echo good 32 | good 33 | 34 | # 没有 >= 和 <= 35 | % [[ cde >= bcd ]] && echo good 36 | zsh: parse error near `bcd' 37 | ``` 38 | 39 | 除了在里边用等号、不等号之类比较外,还可以判断字符串是否为空: 40 | 41 | ``` 42 | % str=abc 43 | # 判断字符串内容长度是否大于 0,等同于 (($#str)) 44 | % [[ -n $str ]] && echo good 45 | good 46 | 47 | % str="" 48 | # 判断字符串是否为空,等同于 ((! $#str)) 49 | % [[ -z $str ]] && echo good 50 | good 51 | ``` 52 | 53 | 但这两种用法,我们都有更方便的其他实现方法,没有必要用它们。 54 | 55 | ### 判断文件 56 | 57 | [[ ]] 另一类很重要的功能是判断文件,比如判断某一个文件是否存在、是否是目录、是否可读等等。 58 | 59 | 判断 /bin/zsh 文件是否存在: 60 | 61 | ``` 62 | % [[ -e /bin/zsh ]] && echo good 63 | good 64 | % [[ -e /bin/zshh ]] && echo good 65 | 66 | ``` 67 | 68 | -e 可以替换成如下的选项,用法是一致的: 69 | 70 | | 选项 | 符合条件的文件 | 71 | | ---- | -------------------------------- | 72 | | -b | 块设备文件 | 73 | | -c | 字符设备文件 | 74 | | -d | 目录 | 75 | | -e | 存在的任何文件 | 76 | | -f | 普通文件,含符号链接,不含目录、设备文件、socket、FIFO | 77 | | -g | 设置了 setgid 的文件 | 78 | | -h | 符号链接 | 79 | | -k | 设置了粘滞位(sticky bit)的文件 | 80 | | -p | FIFO 文件 | 81 | | -r | 对当前进程可读的文件 | 82 | | -s | 非空文件 | 83 | | -u | 设置了 setuid 的文件 | 84 | | -x | 对当前进程可执行的文件 | 85 | | -w | 对当前进程可写的文件 | 86 | | -L | 符号链接(同 -h) | 87 | | -O | 被当前进程的用户拥有的文件 | 88 | | -G | 被当前进程的用户组拥有的文件 | 89 | | -S | socket 文件 | 90 | | -N | atime 和 mtime 一样的文件 | 91 | 92 | 还有一个比较特殊的 -t 选项: 93 | 94 | ``` 95 | # $$ 是当前的进程 id 96 | % ls /proc/$$/fd 97 | 0 1 10 11 2 98 | % [[ -t 10 ]] && echo good 99 | good 100 | % [[ -t 3 ]] && echo good 101 | 102 | ``` 103 | 104 | -t 后要接数字(如果不是,相当于 0),判断当前进程是否打开了对应的 fd(进程默认会打开 0、1、2 这三个 fd,分别对应标准输入、标准输出和错误输出,此外每打开一个文件、管道或者网络连接,都会对应一个 fd,关掉后对应 fd 会消失)。 105 | 106 | ### 比较文件 107 | 108 | 除了判断单个文件是否符合条件外,[[ ]] 还可以用来比较两个文件。 109 | 110 | ``` 111 | # file1 比 file2 新 112 | % [[ file1 -nt file2 ]] 113 | 114 | # file1 比 file2 旧 115 | % [[ file1 -ot file2 ]] 116 | 117 | # file1 和 file2 是否对应同一个文件(路径相同或者互为硬连接) 118 | % [[ file1 -ef file2 ]] 119 | ``` 120 | 121 | ### 比较数值 122 | 123 | [[ ]] 也可以用来比较数值,注意不是用等号、大于号、小于号等等比较,有一系列专门的符号。通常我们没必要用 [[ ]] 来比较数值,用 (( )) 更方便一些。 124 | 125 | ``` 126 | # -eq 是判断两个数值是否相等 127 | % [[ 12 -eq 12 ]] && echo good 128 | good 129 | ``` 130 | 131 | -eq 可以替换成下列符号,用法一样: 132 | 133 | | 符号 | 含义 | 134 | | ---- | ---- | 135 | | -eq | 相等 | 136 | | -ne | 不相等 | 137 | | -lt | < | 138 | | -gt | > | 139 | | -le | <= | 140 | | -ge | >= | 141 | 142 | ### 组合使用 143 | 144 | ``` 145 | # && 是逻辑与 146 | % [[ a == a && b == b ]] && echo good 147 | good 148 | 149 | # || 是逻辑或 150 | % [[ a == a || a == b ]] && echo good 151 | good 152 | 153 | # ! 是逻辑非 154 | % [[ ! a == b ]] && echo good 155 | good 156 | 157 | # 可以一起用,! 优先级最高,其次 &&,再次 || 158 | % [[ ! a == b && b == a || b == b ]] && echo good 159 | good 160 | 161 | # 如果不确定优先级,可以加小括号 162 | % [[ ((! a == b) && b == a) || b == b ]] && echo good 163 | good 164 | ``` 165 | 166 | 需要注意一下空格,[[ ]] 内侧和内容之间需要空格隔开,== 两边也需要空格。如果是在 zsh 中直接敲入,! 后边也要加一个空格,不然会被解析成历史命令。 167 | 168 | ### [ ] 符号 169 | 170 | 除了 [[ ]] 符号,[ ] 符号(它是古老的 test 命令化身)也可以用来判断字符串、文件、数值等等,但功能没有 [[ ]] 全,只支持上边列的一部分功能(不支持 ==、=~、>、<、(、) ,并且逻辑与或的语法不一样,不能调整优先级,用起来很不方便),通常没有必要使用 [ ](如需使用,可以 man test 查看用法)。 171 | 172 | ### 总结 173 | 174 | 本文详细介绍了 [[ ]] 的用法,基本覆盖全面了。 175 | 176 | ### 参考 177 | 178 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 179 | -------------------------------------------------------------------------------- /18_Zsh-开发指南(第十八篇-更多内置模块的用法).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 除了 zsh/mathfunc、zsh/net/socket、zsh/net/tcp,zsh 还内置了一些其他的内置模块。本文简单讲几个比较常用的模块。 4 | 5 | ### 模块的使用方法 6 | 7 | ``` 8 | # 使用 zmodload 加模块名来加载模块 9 | % zmodload zsh/mathfunc 10 | 11 | # 如果不加参数,可以查看现在已经加载了的模块 12 | % zmodload 13 | zsh/complete 14 | zsh/complist 15 | zsh/computil 16 | zsh/main 17 | zsh/mathfunc 18 | zsh/parameter 19 | zsh/stat 20 | zsh/zle 21 | zsh/zutil 22 | 23 | # 加 -u 参数可以卸载模块 24 | % zmodload -u zsh/mathfunc 25 | 26 | # 还有其他参数,可以补全查看帮助,不详细介绍了 27 | % zmodload - 28 | -- option -- 29 | -A -- create module aliases 30 | -F -- handle features 31 | -I -- define infix condition names 32 | -L -- output in the form of calls to zmodload 33 | -P -- array param for features 34 | -R -- remove module aliases 35 | -a -- autoload module 36 | -b -- autoload module for builtins 37 | -c -- autoload module for condition codes 38 | -d -- list or specify module dependencies 39 | -e -- test if modules are loaded 40 | -f -- autoload module for math functions 41 | -i -- suppress error if command would do nothing 42 | -l -- list features 43 | -m -- treat feature arguments as patterns 44 | -p -- autoload module for parameters 45 | -u -- unload module 46 | ``` 47 | 48 | ### 日期时间相关模块 49 | 50 | 我们知道使用 date 命令可以查看当前时间,也可以用来做日期时间的格式转换。但如果脚本里需要频繁地读取或者处理时间(比如打日志的时候,每一行加一个时间戳),那么调用 date 命令的资源消耗就太大了。Zsh 的 zsh/datetime 模块提供和 date 命令类似的功能。 51 | 52 | ``` 53 | % zmodload zsh/datetime 54 | 55 | # 输出当前时间戳(从 1970 年年初到现在的秒数),和 date +%s 一样 56 | % echo $EPOCHSECONDS 57 | 1504231297 58 | 59 | # 输出高精度的当前时间戳,浮点数 60 | % echo $EPOCHREALTIME 61 | 1504231373.9913284779 62 | 63 | # 输出当前时间戳的秒和纳秒部分,是一个数组 64 | # 可以用 epochtime[1] 和 epochtime[2] 分别读取 65 | % echo $epochtime 66 | 1504231468 503125900 67 | 68 | # 安装指定格式输出当前时间,和 date +%... 效果一样 69 | # 格式字符串可以 man date 或者 man strftime 查看 70 | % strftime "%Y-%m-%d %H:%M:%S (%u)" $EPOCHSECONDS 71 | 2017-09-01 10:06:47 (5) 72 | 73 | # 如果加了 -s str 参数,将指定格式的时间存入 str 变量而不输出 74 | % strftime -s str "%Y-%m-%d %H:%M:%S (%u)" $EPOCHSECONDS 75 | % echo $str 76 | 2017-09-01 10:10:58 (5) 77 | 78 | # 如果加了 -r 参数,从指定的时间字符串反解出时间戳,之前操作的逆操作 79 | # 也可以同时加 -s 参数来讲结果存入变量 80 | % strftime -r "%Y-%m-%d %H:%M:%S (%u)" "2017-09-01 10:10:58 (5)" 81 | 1504231858 82 | ``` 83 | 84 | 这基本覆盖了 date 的常用功能,而运行速度比 date 命令快很多。 85 | 86 | ### 读写 gdbm 数据库 87 | 88 | 有时我们的脚本需要将某些数据持久化到本地文件,但像哈希表之类的数据,如果存放到普通文件里,载入和保存的资源消耗都比较大,而且如果脚本突然异常退出,数据会丢失。而且某些时候,我们可能需要操作一个巨大的哈希表,并不能全部将它载入到内存中。那么我们可以使用 gdbm 数据库文件。 89 | 90 | Gdbm 是一个很轻量的 Key-Value 数据库,可以认为它就像一个保存在文件里的哈希表。Zsh 的 zsh/db/gdbm 模块可以很方便地读写 gdbm 数据库文件。 91 | 92 | ``` 93 | % zmodload zsh/db/gdbm 94 | 95 | # 声明数据库文件对应的哈希表 96 | % local -A sampledb 97 | # 创建数据库文件,文件名是 sample.gdbm,对应 sampledb 哈希表 98 | # 如果该文件已经存在,则会继续使用该文件 99 | % ztie -d db/gdbm -f sample.gdbm sampledb 100 | 101 | # 然后正常使用 sampledb 哈希表即可,数据会同步写入到数据库文件中 102 | % sampledb[k1]=v1 103 | % sampledb+=(k2 v2 k3 v3) 104 | % echo ${(kv)sampledb} 105 | k1 v1 k2 v2 k3 v3 106 | 107 | # 获取数据库文件路径 108 | % zgdbmpath sampledb 109 | % echo $REPLY 110 | /home/goreliu/sample.gdbm 111 | 112 | # 释放数据库文件 113 | % zuntie -u sampledb 114 | 115 | 116 | # 也可以用只读的方式加载数据库文件 117 | % ztie -r -d db/gdbm -f sample.gdbm sampledb 118 | # 但这样的话,需要用 zuntie -u 释放数据库文件 119 | % zuntie -u sampledb 120 | ``` 121 | 122 | 如果数据量比较大,或者有比较特别的需求,要先了解下 gdbm 是否符合自己的场景再使用。 123 | 124 | ### 调度命令 125 | 126 | 有时我们需要在未来的某个时刻运行某一个命令。虽然也可以 sleep 然后运行,但这样要多占两个进程,而且不好控制(比如要取消运行其中的某一个)。Zsh 的 zsh/sched 模块用于调度命令的运行。 127 | 128 | ``` 129 | % zmodload zsh/sched 130 | 131 | # 5 秒后运行 ls 命令 132 | % sched +5 ls 133 | # 可以随便做些别的 134 | % date 135 | Fri Sep 1 10:36:16 DST 2017 136 | # 五秒后,ls 命令被运行 137 | git sample.gdbm tmp 138 | 139 | # 不加参数可以查看已有的待运行命令 140 | % sched 141 | 1 Fri Sep 1 21:16:05 date 142 | 2 Fri Sep 1 21:16:30 date 143 | 3 Fri Sep 1 21:17:12 date 144 | 145 | # -n 可以去除第 n 个待运行命令 146 | % sched -2 147 | % sched 148 | 1 Fri Sep 1 21:16:05 date 149 | 2 Fri Sep 1 21:17:12 date 150 | ``` 151 | 152 | ### 底层的文件读写命令 153 | 154 | 有时我们可能需要更精细地操作文件,zsh 提供了一个 zsh/system 模块,里边包含一些底层的文件读写命令(对应 open、read、write 等系统调用)。使用这些函数,可以更精细地控制文件的读写,比如控制每次读写的数据量、从中间位置读写、上文件锁等等。这些命令的用法比较复杂,参数也比较多,这里就不列出了。如果需要使用,可以 man zshmodules 然后搜索 zsh/system 查看文档。 155 | 156 | 函数列表:sysopen、sysread、sysseek、syswrite、zsystem flock、systell、syserror 157 | 158 | ### 其他模块 159 | 160 | 其余的在脚本编写方面可能用的上的模块还有: 161 | 162 | zsh/pcre(使用 pcre 正则表达式库,默认使用的是 POSIX regex 库) 163 | 164 | zsh/stat(内部的 stat 命令,可用于取代 stat 命令) 165 | 166 | zsh/zftp(内置的 ftp 客户端) 167 | 168 | zsh/zprof(Zsh 脚本的性能追踪工具) 169 | 170 | zsh/zpty(操作 pty 的命令) 171 | 172 | zsh/zselect(select 系统调用的封装) 173 | 174 | 可以用 man zshmodules 查看。 175 | 176 | ### 自己编写模块 177 | 178 | 如果因为性能等因素,要自己写 zsh 模块来调用,也是比较方便的。Zsh 的源码中 Src/Modules 是模块目录,里边有一个实例模块 example(example.c 和 example.mdd 文件)。可以参考代码编写自己的模块,难度并不是很大。 179 | 180 | ### 总结 181 | 182 | 本文介绍了几个比较常用的 zsh 内置模块,以后可能继续补充更多模块的用法。 183 | -------------------------------------------------------------------------------- /21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 在正式的场景,代码写完后都是需要测试的,shell 脚本也不例外。但 shell 脚本的特性导致测试方法和其他语言有所不同。 4 | 5 | ### 单元测试 6 | 7 | 作为一种重要的测试方法,单元测试在很多种编程语言程序测试中起到举重轻重的作用。但不幸的是,单元测试基本不适用于 shell 脚本。并不是说 shell 脚本不能被单元测试,而是说单元测试能测试出来的问题很少,投入却很大。为了让 shell 脚本能被单元测试,50 行的代码很可能要改写成 100 多行甚至更多行。更重要的是 shell 脚本严重依赖外部环境,多数问题需要对脚本整体进行功能测试才能发现,而不是对单个函数进行单元测试。对单元测试的精力投入很可能会减少在功能测试的精力投入。 8 | 9 | 所以不建议推行 shell 脚本的单元测试,这不仅会让开发者很痛苦,也很难减少问题的出现几率,甚至有可能适得其反。 10 | 11 | ### 单个脚本的功能测试 12 | 13 | Shell 脚本的最小测试粒度是单个脚本。必须保证单个脚本是容易测试的,不能多个脚本耦合太紧密而难以对其中某一个进行单独测试。 14 | 15 | 有主体逻辑的脚本依赖的外部环境必须是容易模拟的。比如需要从数据库中读取数据,对数据进行处理,然后写入到文件中,这些功能不能在同一个脚本中完成。因为数据库这个外部环境不容易模拟,会导致测试困难。需要把读写数据库的功能独立成单独的脚本,功能尽量简单,测试该脚本时只需要关心数据是否正常读取了出来,格式是否被正确转换等等,而不需要关心处理数据的具体逻辑。处理数据的主体逻辑代码要独立成一个(或者多个)脚本,测试该脚本时,无需准备数据库环境,直接用另一个脚本或者数据文件取代读取数据库的脚本,提供测试数据。如果文件写入的环境复杂(比如文件或者目录结构复杂,或者要写入到分布式文件系统等等),也需要将文件写入的脚本独立出来以便更易于测试。 16 | 17 | 对有主体逻辑的脚本进行功能测试,不能手动进行,必须写测试脚本,可以自动运行。每次脚本改动后进行回归测试。项目稳定后,可以在每次提交代码后自动运行测试脚本。测试脚本必须覆盖正常和异常情况,不能只覆盖正常情况。异常情况的多少,要根据脚本的复杂度而定。 18 | 19 | 有复杂外部依赖的脚本,功能必须单一,逻辑尽量简单,代码尽量稳定,不经常改动。比如读写数据库、启停进程、复杂的目录文件操作等有复杂外部依赖的脚本,功能必须单一,只与一个特定的外部依赖交互,提供尽量和外部依赖无关的中间数据,尽量不包含和外部环境无关的逻辑。该类脚本要容易模拟,以便在测试其他部分时不再需要依赖外部环境。 20 | 21 | 对于有复杂外部依赖的脚本,可以写脚本自动测试,也可以手动测试,测试时需要包含正常和异常的情况,不能只测试正常情况。 22 | 23 | ### 功能测试示例 24 | 25 | **需要写脚本完成如下功能:** 26 | 27 | 如果 process1 和 process2 两个进程都存在,以 process2 进程 cwd 目录中的 `data/output.txt` 为输入,做一些比较复杂的处理,然后输出到 process1 进程 cwd 目录中的 `data/input.txt` 文件(如果该文件已存在,则不处理),处理完后,删除之前的 `data/output.txt`。 28 | 29 | **分析:** 30 | 31 | process1 和 process2 两个进程都是复杂的外部依赖,不能在主体逻辑脚本里直接依赖它们,所以要把检查进程是否存在的逻辑独立成单独的脚本。输入和输出文件的路径依赖进程路径,为了测试方便,也要把获取文件路径的逻辑独立成单独的脚本。 32 | 33 | **脚本功能实现:** 34 | 35 | 检查进程是否存在和获取进程 cwd 目录的 util.zsh 脚本: 36 | 37 | ``` 38 | #!/bin/zsh 39 | 40 | check_process() { 41 | pidof $1 42 | } 43 | 44 | get_process_cwd() { 45 | readlink /proc/$1/cwd 46 | } 47 | ``` 48 | 49 | 50 | 主体逻辑脚本 main.zsh: 51 | 52 | ``` 53 | #!/bin/zsh 54 | 55 | # 有错误即退出,可以省掉很多错误处理的代码 56 | set -e 57 | 58 | # 切换到脚本当前目录 59 | cd ${0:h} 60 | 61 | # 加载依赖的脚本 62 | source ./util.zsh 63 | 64 | # 检查进程是否存在 65 | local process1_pid=$(check_process process1) 66 | local process2_pid=$(check_process process2) 67 | 68 | # 这里的 input 和 output 是相对脚本来说的 69 | local input_file=$(get_process_cwd $process2_pid)/data/output.txt 70 | local output_file=$(get_process_cwd $process1_pid)/data/input.txt 71 | 72 | # 如果输入文件不存在,直接退出 73 | [[ -e $input_file ]] || { 74 | echo $input_file not found. 75 | exit 1 76 | } 77 | 78 | # 如果输出文件已存在,也直接退出 79 | [[ -e $output_file ]] && { 80 | echo $output_file already exists. 81 | exit 0 82 | } 83 | 84 | # 处理 $input_file 内容 85 | # 省略 86 | 87 | # 将结果输出到 $output_file 88 | # 省略 89 | ``` 90 | 91 | **功能测试方法:** 92 | 93 | util.zsh 里的两个函数功能过于简单,无需测试。 94 | 95 | 测试 main.zsh 时,需要构造一系列测试用的 util.zsh,用于模拟各种情况: 96 | 97 | ``` 98 | # 进程存在的情况 99 | check_process() { 100 | echo $$ 101 | } 102 | 103 | # 进程不存在的情况 104 | check_process() { 105 | return 1 106 | } 107 | 108 | # 进程 process1 存在而 process2 不存在的情况 109 | check_process() { 110 | [[ $1 == process1 ]] && echo 1234 && return 111 | [[ $1 == process2 ]] && return 1 112 | } 113 | 114 | # 输出了进程号,但实际进程不存在的情况 115 | check_process() { 116 | echo 0 117 | } 118 | 119 | # 其他情况 120 | # 省略 121 | 122 | 123 | # 路径存在的情况 124 | get_process_cwd() { 125 | [[ $1 == process1 ]] && echo /path/to/cwd1 && return 126 | [[ $1 == process2 ]] && echo /path/to/cwd2 && return 127 | } 128 | 129 | # 路径不存在的情况 130 | get_process_cwd() { 131 | return 1 132 | } 133 | 134 | # 输出了路径,但路径实际不存在的情况 135 | get_process_cwd() { 136 | echo /wrong/path 137 | } 138 | 139 | # 其他情况 140 | # 省略 141 | ``` 142 | 143 | 然后组合这些情况,写测试脚本判断 main.zsh 的处理是否符合预期。 144 | 145 | 其中一个测试脚本样例: 146 | 147 | util_test1.zsh 内容: 148 | 149 | ``` 150 | #!/bin/zsh 151 | 152 | # 进程存在 153 | check_process() { 154 | echo $$ 155 | } 156 | 157 | # 直接返回正确的路径 158 | get_process_cwd() { 159 | [[ $1 == process1 ]] && echo /path/to/cwd1 && return 160 | [[ $1 == process2 ]] && echo /path/to/cwd2 && return 161 | } 162 | ``` 163 | 164 | test.zsh 内容: 165 | 166 | ``` 167 | #!/bin/zsh 168 | 169 | # 用于测试的函数,可以独立成单独脚本以便复用 170 | assert_ok() { 171 | (($1 == 0)) || { 172 | echo Error, retcode: $1 173 | exit 1 174 | } 175 | } 176 | 177 | check_output_file() { 178 | # 检查输出文件是否符合预期 179 | # 省略 180 | } 181 | 182 | # 应用 util_test1.zsh 183 | ln -sf util_test1.zsh util.zsh 184 | 185 | # 运行脚本 186 | ./main.zsh 187 | 188 | # 检查返回值是否正常 189 | assert_ok $? 190 | 191 | # 检查输出文件是否符合预期 192 | check_output_file /path/to/output/file 193 | 194 | # 其他检查 195 | # 省略 196 | 197 | # 应用 util_test2.zsh 198 | ln -sf util_test2.zsh util.zsh 199 | 200 | # 省略 201 | ``` 202 | 203 | ### 集成测试 204 | 205 | 测试完每个脚本的功能后,需要将各个脚本以及其他程序整合起来测试互相调用过程是否正常。如果功能比较复杂,需要分批整合,测试各个逻辑单元是否能正常工作。在这部分测试中,和外部环境交互的脚本如果逻辑较为简单,可以不参与,用模拟脚本替代。可以手动测试或自动测试。同样不能只测试正常情况。 206 | 207 | ### 系统测试 208 | 209 | 将所有相关组件整合起来,测试整个系统或者子系统的功能。模拟脚本不能参与系统测试,必须使用真实的外部环境。系统测试通常需要手动进行,可以用自动化测试系统来辅助。需要覆盖尽可能多的情况,不能只测试系统的正常功能。 210 | 211 | ### 总结 212 | 213 | 本文简单介绍了 shell 脚本的测试方法,以及编写可测试代码的方法。 214 | -------------------------------------------------------------------------------- /20_Zsh-开发指南(第二十篇-代码风格).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 因为 shell 脚本语法比较灵活,写 shell 脚本的开发者熟悉的编程语言也有较大差异,大家很容易写出风格迥异的代码出来。如果只有自己一个人用还好,如果是大家合作开发同一个项目,代码风格不同就会造成不小的麻烦。所以约定一个代码风格是很有必要的。 4 | 5 | 本文中的代码风格约定只是我的个人建议,可以根据自己的需求或者喜好来调整。本文的代码风格约定,在一定程度上也适用于 bash。 6 | 7 | 注意需要有丰富 shell 编程经验的人制定和维护代码风格约定,不然很容易无法执行或者流于形式而解决不了实际问题。代码风格约定不只需要约定代码怎么写,而且要说明为什么要这么写,不然容易因为难以服众而无法推广。 8 | 9 | ### 缩进 10 | 11 | - 统一使用 4 个空格来缩进。 12 | 13 | 原因: 14 | 15 | 1. 要用空格而不是 tab。因为在终端上 `cat` `less` `diff` 等命令都将 tab 显示成 8 个空格的宽度,有些命令是不可配置的(即使可配置,要让所有机器配置同步也是件麻烦的事情)。如果自己在编辑器上配置 tab 为 4 个或者 2 个空格,那么就会和 `cat` `less` 等命令的显示方法不一致,会导致很多麻烦。 16 | 2. 8 个空格太长,缩进几次就会导致行太长,而 shell 脚本每行不宜过长。 17 | 3. 2 个空格的话,如果缩进比较频繁,看起来比较费劲。另外如果写代码时不小心多了或者少了一个空格,在某些场景,不看逻辑的话,就无法确定是多个一个还是少了一个,更容易导致他人错误的修改,或者代码越改越乱。 18 | 4. 对于 4 个空格也可能导致缩进层数多时行太长的问题,通过修改逻辑减少缩进层数或者折行的方法,而不是减少缩进的空格数量来解决。 19 | 20 | ### 每行代码最多字符数 21 | 22 | - 非特殊场景,每行代码不超过 100 个字符。 23 | 24 | 原因: 25 | 26 | 1. 代码过长,阅读起来不方便,用 `diff` 之类工具对代码进行分析处理也不方便,所以需要约定最长字符数。 27 | 2. 经典的 80 个字符的约定,是受当时的输出设备限制而产生的标准,而现在的屏幕基本都是宽屏的,终端模拟器也都是可调大小的(而不是固定的 80x24)没必要削足适履迎合陈旧的标准,浪费屏幕空间。而且如果使用 80 个字符的约定,很容易遇到需要折行的情况,反而会导致可读性下降。 28 | 3. 如果一行超过了 100 个字符,通常说明逻辑太多,需要分行或者折行。 29 | 4. 某些特殊场景,比如显示一个 ASCII 字符组成的图片,会有一行超过 100 个字符的需求,所有不能严格执行每行必须不超过 100 个字符的约定。如果分行或者折行会不可避免地导致代码可读性下降,那么优先考虑可读性。 30 | 31 | ### 折行 32 | 33 | - 在前一行尾部加一个空格和 `\` 折行,折行后缩进一层(4 个空格)。 34 | - 如果缩进的是一个文本块,可以使用对齐缩进,也可以使用 4 个空格的固定缩进。 35 | - 如果是在 `aa && bb || cc`、`[[ ]]` 或者 `(( ))` 中折行,`&&` `||` 放在下一行的行首。 36 | 37 | 原因: 38 | 39 | 1. 折行的缩进和普通的缩进都是为了体现代码的递进关系,没必要区分对待(比如折行缩进两层)。 40 | 2. 如果为了看起来美观,使用对齐缩进而不是固定缩进。那么因为每个人的审美不同,很容易产生不同的缩进方法,从而产生不必要的麻烦。但对文本块来说比较特殊,因为通常对齐缩进不会产生争议。 41 | 3. `&&` 和 `||` 在逻辑上属于后半个语句,在自然语言中也是这样,比如 `明天我去公园或者去逛街`,如果需要拆成两个子句,那么会是 `明天我去公园,或者去逛街`,而不是 `明天我去公园或者,去逛街`。对代码来说也是一样。而且把 `&&` 或 `||` 放在行首更容易对齐,看起来更舒服。 42 | 43 | ### 空格 44 | 45 | - 在缩进和对齐之外的场景,不允许出现逻辑上不必要的连续多个空格。 46 | - `+` `&&` `|` 等双元运算符左右要加一个空格。 47 | - `!` `~`等一元运算符和作用对象之间不加空格。 48 | - `( )` 和 `(( ))` `{ }` 内侧不加空格,`[[ ]]` 因为语法需要,内侧加一个空格。 49 | - `;` 之前不加空格,之后加一个空格。 50 | - 定义函数时(以及在 `(( ))` 中调用函数时),函数名和 `(` 之间不加空格。 51 | - `if ` `while` 等关键字和后边的内容之间加一个空格。 52 | - `if [[ ]] {` 等场景中,`{` 和前边的内容之间加一个空格。 53 | - 变量和 `[ ]` 之间不加空格,用 `[ ]` 取数组或者哈希表值时,`[ ]` 内侧不加空格。 54 | - `>` `<` 等重定向符号和文件或者文件描述符之间不加空格。 55 | 56 | 原因: 57 | 58 | 1. 适量地添加空格可以让代码更清晰易读。 59 | 2. 这些约定基本属于很多编程语言代码风格中约定成俗的习惯,符合多数人的审美。 60 | 61 | ### 空行 62 | 63 | - 非特殊场景,不允许出现超过两个连续空行。 64 | - `#!/bin/zsh` 后加一个空行。 65 | - `if ` `while` 等语句块之后加一个空行。 66 | - 定义函数后加一个空行。 67 | - 逻辑关系不强的两行(或者两块)代码之间,根据逻辑关系强弱(自行判断),加一个或两个空行。 68 | 69 | 原因: 70 | 71 | 1. 适量添加空格,可以让代码逻辑按照空行分隔,提高可读性。 72 | 2. 因为添加空行的方法涉及诸多因素,很难详细约定,主要靠开发者自行判断。 73 | 74 | ### 括号 75 | 76 | - 在判断条件的场景,不使用 `[ ]`,用 `[[ ]]` 代替。 77 | - 在数值计算的场景,使用 `$(( ))` 而不是 `$[ ]`。 78 | 79 | 原因: 80 | 81 | 1. 在判断条件的场景,`[ ]` 的功能没有 `[[ ]]` 丰富,而且二者的用法存在差异,混合使用容易出问题。 82 | 2. 在数值比较或者计算的场景,`$[ ]` 的功能没有 `$(( ))` 丰富,混合使用容易出问题。 83 | 3. `[ ]` 在各种地方功能不一致,非必要场景尽量避免使用。 84 | 85 | ### 常量 86 | 87 | - 字符串常量中如果没有特殊符号,两端可以不加引号,也可以加引号。 88 | - 使用数值时,两端不加引号。 89 | 90 | 原因: 91 | 92 | 1. 如果任何字符串常量两端都加引号,容易让代码中充斥着引号,影响可读性。并且如果不小心误删引号,容易导致难以定位错误。 93 | 2. shell 脚本和很多其他编程语言不同,处理字符串的逻辑占很大部分,每个字符串常量两边都加引号的话,会增加很多额外工作量。 94 | 95 | ### 变量 96 | 97 | - 用 `$var` 取变量值时,两边不加双引号,除非需要将非字符串变量转换成字符串。 98 | - 在非必须场景,不需要加 `${var}` 中的大括号。 99 | - 变量使用前要明确指明是局部变量(用 `local` 定义)还是全局变量(用 `typeset -g` 定义)。 100 | - 能用局部变量的地方全部使用局部变量(用 `local` 定义)。 101 | - 变量名中的单词可以使用下划线分隔或者驼峰风格,在不影响可读性的情况也可以使用全小写字母,但在同一个文件中要一致。 102 | 103 | 原因: 104 | 105 | 1. 和 bash 不同,zsh 在使用 `$var` 读取变量内容时,不用因为变量不存在、值为空、包含特殊符号而产生各种逻辑错误,所以无需在两端加双引号。 106 | 2. 用 `$var` 读变量是很多编程语言都有的用法,而 `${var}` 几乎是 shell 中特有的用法,并且输入更麻烦,没必要推广这种用法。而且因为不加大括号导致变量名粘连而出错的情况,编写代码时即可识别出来,和外部输入无关,不需要为了避免不存在的问题而输入很多额外的大括号。 107 | 3. 如果不指明变量是全局变量还是局部变量,默认是全局变量,有时候很难简单地判断一个变量是作为全局变量还是局部变量使用的,这样会给脚本的维护者带来很多麻烦。 108 | 4. 如果能使用局部变量的地方使用全局变量,更容易出现全局变量重名而互相影响导致错误的情况。这种错误是很难排查的(因为不会产生语法错误,容易让人怀疑是代码逻辑的问题,而不去检查是否有全局变量重名的情况),往往会浪费开发或者测试人员大量的时间。 109 | 5. 不同编程语言的开发者对变量名的风格偏好不同,不宜规定统一风格。 110 | 111 | ### 引号 112 | 113 | - 字符串常量两端可以添加双引号或者单引号,但同一个文件中风格要一致。 114 | 115 | 原因: 116 | 117 | 1. 双引号和单引号的功能不同,混合使用是不可避免的。 118 | 2. 在双引号和单引号都适用的场景,统一使用一种引号,可以让代码更整洁易读。 119 | 3. 编程语言背景不同的开发者,对单双引号的偏好不同,不宜强行规定默认使用的引号。 120 | 121 | ### 函数 122 | 123 | - 可以使用 `name()` 或者 `function name()` 定义函数,但同一个文件中风格要一致。 124 | 125 | 原因: 126 | 127 | 1. 如果约定统一使用 `name()` 定义函数,那么没有照顾 JavaScript 等编程语言开发者的习惯,而且 `function` 关键字有助于代码的搜索。 128 | 2. 如果约定统一使用 `function name()` 定义函数,需要额外输入 9 个字符,而意义有限,投入比产出要大。 129 | 130 | ### 脚本行数 131 | 132 | - 非特殊场景,单个脚本文件不超过 1000 行。 133 | 134 | 原因: 135 | 136 | 1. 因为 shell 脚本的特性,单个脚本文件过长容易导致各种问题(比如全局变量互相影响)。1000 行代码对于多数场景都够用了。 137 | 2. 如果写的是安装脚本之类需要分发的脚本,那么分发单个文件要比分发多个文件(需要打包解包等额外工作)容易很多,这种场景可能需要写长脚本。所以不宜强行规定单个脚本文件最大行数。 138 | 139 | ### 语句风格 140 | 141 | - 条件、循环、选择等语句,可以使用本系列教程中的风格,也可以使用 POSIX shell 风格,但同一个文件的风格要一致。 142 | 143 | 原因: 144 | 145 | 1. 本系列教程的中语句风格简洁易懂,并且和 c、Java、JavaScript 等语言的语句风格相近。 146 | 2. 从 bash 迁移过来的开发者习惯使用 POSIX shell 风格语句,需要兼顾。 147 | 148 | 本系列教程语句风格实例: 149 | 150 | ``` 151 | if [[ ... ]] { 152 | } elif ((...)) { 153 | } else { 154 | } 155 | 156 | case $i { 157 | (a) 158 | ... 159 | ;; 160 | 161 | (*) 162 | ... 163 | ;; 164 | } 165 | ``` 166 | 167 | POSIX shell 语句风格实例: 168 | 169 | ``` 170 | if [[ ... ]]; then 171 | elif ((...)); then 172 | else 173 | fi 174 | 175 | case $i in 176 | (a) 177 | ... 178 | ;; 179 | 180 | (*) 181 | ... 182 | ;; 183 | esac 184 | ``` 185 | 186 | ### 总结 187 | 188 | 本文介绍了我建议的 zsh 代码风格,可以适当参考。 189 | -------------------------------------------------------------------------------- /14_Zsh-开发指南(第十四篇-文件读写).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 之前我们也偶尔接触过读写文件的方法,本篇会系统讲读写文件的各种方法。 4 | 5 | ### 写文件 6 | 7 | 写文件要比读文件简单一些,最常用的用法是使用 > 直接将命令的输出重定向到文件。如果文件存在,内容会被覆盖;如果文件不存在,会被创建。 8 | 9 | ``` 10 | % echo abc > test.txt 11 | ``` 12 | 13 | 如果不想覆盖之前的文件内容,可以追加写入: 14 | 15 | ``` 16 | % echo abc >> test.txt 17 | ``` 18 | 19 | 这样如果文件存在,内容会被追加写入进去;如果文件不存在,也会被创建。 20 | 21 | #### 创建文件 22 | 23 | 有时我们只想先创建个文件,等以后需要的时候再写入。 24 | 25 | touch 命令用于创建文件(普通文件): 26 | 27 | ``` 28 | % touch test1.txt test2.txt 29 | 30 | # 或者用 echo 输出重定向,效果和 touch 一样 31 | # 加 -n 是因为不加的话 echo 会输出一个换行符 32 | % echo -n >>test1.txt >>test2.txt 33 | 34 | # 或者使用输入重定向 35 | % >>test1.txt >>test2.txt >test2{1..1000}.txt 56 | #!/bin/zsh 57 | 58 | >>test3{1..1000}.txt test.txt 78 | 79 | # 使用输入重定向 80 | % >test.txt test.txt 130 | ``` 131 | 132 | 可以直接把数组写入到文件,每行一个元素: 133 | 134 | ``` 135 | % array=(aa bb cc) 136 | 137 | % print -l $array > test.txt 138 | ``` 139 | 140 | 如果是将一段内容比较固定的字符串写入到文件,可以这样: 141 | 142 | ``` 143 | # 在脚本中也是如此,第二行以后的行首 > 代表换行,非输入内容 144 | # < test.txt < aa 148 | > bb 149 | > cc dd 150 | > ee 151 | > EOF 152 | 153 | % cat test.txt 154 | aa 155 | bb 156 | cc dd 157 | ee 158 | ``` 159 | 160 | #### 用 mapfile 读写文件 161 | 162 | 如果不喜欢使用重定向符号,还可以用哈希表来操作文件。Zsh 有一个 zsh/mapfile 模块,用起来很方便: 163 | 164 | ``` 165 | % zmodload zsh/mapfile 166 | 167 | # 这样就可以创建文件并写入内容,如果文件存在则会被覆盖 168 | % mapfile[test.txt]="ab cd" 169 | % cat test.txt 170 | ab cd 171 | 172 | # 判断文件是否存在 173 | % (($+mapfile[test.txt])) && echo good 174 | good 175 | 176 | # 读取文件 177 | % echo $mapfile[test.txt] 178 | ab cd 179 | 180 | # 删除文件 181 | % unset "mapfile[test.txt]" 182 | 183 | # 遍历文件 184 | % for i (${(k)mapfile}) { 185 | > echo $i 186 | > } 187 | test1.txt 188 | test2.txt 189 | ``` 190 | 191 | #### 从文件中间位置写入 192 | 193 | 有时我们需要从一个文件的中间位置(比如从第 100 的字符或者第三行开始)继续写入,覆盖之后的内容。Zsh 并不直接提供这样的方法,但我们可以迂回实现,先用 truncate 命令把文件截断,然后追加写。如果文件后边的内容还需要保留,可以在截断之前先读取进来(见下文读文件部分的例子),最后再写回去。 194 | 195 | ``` 196 | % echo 1234567890 > test.txt 197 | # 只保留前 5 个字符 198 | % truncate -s 5 test.txt 199 | % cat test.txt 200 | 12345 201 | % echo abcde >> test.txt 202 | % cat test.txt 203 | 12345abcde 204 | ``` 205 | 206 | ### 读文件 207 | 208 | #### 读取整个文件 209 | 210 | 读取整个文件比较容易: 211 | 212 | ``` 213 | % str=$( echo $i 228 | > } test.txt 276 | # 先全部读进来 277 | % str=$(> test.txt 282 | # 将后半部分文件追加回去 283 | % echo -n $str[6,-1] >> test.txt 284 | % cat test.txt 285 | 12345abcde67890 286 | ``` 287 | 288 | 但如果比较比较大的话,就不能将整个文件全部读进来,可以先在循环里用 read -k num 一次读固定数量的字符,然后写入一个中间文件,然后再 truncate 原文件,插入内容。最后再 cat 中间文件 >> 原文件 追加原来的后半部分内容即可。 289 | 290 | 另外这种从文件中间写入或者读取内容的场景,都可以使用 dd 命令实现,可以自行搜索 dd 命令的用法。 291 | 292 | ### 总结 293 | 294 | 本文比较详细地介绍了各种读写文件的方法,基本可以覆盖常用的场景。 295 | -------------------------------------------------------------------------------- /10_Zsh-开发指南(第十篇-文件查找和批量处理).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 寻找满足特定条件的文件路径,简称文件查找,是 shell 脚本的常见任务,因为条件复杂多样,这样的任务并不轻松。很多人使用 find 命令来做,但 find 只能覆盖一部分功能,其他的要自己进一步处理,而且 find 并不好用,和脚本其他部分配合也比较麻烦,容易出错。用 zsh 的话,基本不需要 find 命令,借助 zsh 自身的功能便足以应付多数场景,而且语法更优雅简洁不易出错。 4 | 5 | ### 简单例子 6 | 7 | 列出 /usr/bin 目录下以 zsh 开头的文件。 8 | 9 | ``` 10 | # 加 -l 为了换行显示更易读,如果需要操作这些文件,将 print -l 换成其他命令即可 11 | % print -l /usr/bin/zsh* 12 | /usr/bin/zsh 13 | /usr/bin/zsh-5.4.1 14 | /usr/bin/zshdb 15 | ``` 16 | 17 | 有人可能会说用 ls /usr/bin/zsh* 就行。如果用 ls 的话,就平添了不少额外工作,因为 zsh* 已经匹配一次文件路径,结果出来了,传给 ls 后,ls 又去 stat 了一下那些文件,而这完全是多余工作,如果文件列表长的话,要多消耗不少时间。很多看起来理所当然的 shell 用法都存在类似这样的问题。所以打命令或者写脚本时,不能看结果正确就可以了,要知其所以然。如果嫌 print -l 太长,alias 个 pl 就可以了,print -l 非常常用。 18 | 19 | 删除 /tmp 下所有的形如 abc1234.tmp(前边字母后边数字,个数不限,但至少有一个字母一个数字)的文件,包括子目录里的。 20 | 21 | ``` 22 | % setopt EXTENDED_GLOB 23 | # /**/ 是递归搜索文件 24 | # ## 是前边的内容至少重复一次,<-> 是任何整数或 0 25 | % rm -v /tmp/**/[a-zA-Z]##<->.tmp 26 | removed '/tmp/yaourt-tmp-goreliu/abc123.tmp' 27 | ``` 28 | 29 | setopt EXTENDED_GLOB 是启用扩展的通配符支持,本文后续的内容默认该选项已开启,不然通配符功能太弱,建议写到 .zshrc 里边。通配符的内容之前已经讲过,可以当手册参考。 30 | 31 | 两个小例子热完身后开始进入正题。 32 | 33 | ### 按文件属性查找 34 | 35 | 除了匹配文件路径外,很多时候我们还需要按文件属性查找,比如根据文件类型、权限、大小、修改时间等等。这里需要使用一个新东西,通配符修饰语。 36 | 37 | 先举个例子看看它的样子。列出当前目录及子目录中的所有普通文件(即 ls -l 结果中第一位是 - 的文件,非目录、符号链接、设备文件、socket、FIFO 等等)。 38 | 39 | ``` 40 | % print -l **/*(.) 41 | a.txt 42 | b/htm 43 | ``` 44 | 45 | 这里比之前的例子多了个末尾的小括号,里边有一个点。这个小括号及里边的内容便是通配符修饰语,专门用于按文件属性来匹配文件。点(.)代表普通文件。 46 | 47 | 更多例子: 48 | 49 | ``` 50 | # 列出当前目录下的非空目录,F 是空的意思 51 | % print -l *(/F) 52 | 53 | # 列出当前目录下的空目录,^ 是取反 54 | % print -l *(/^F) 55 | 56 | # 列出当前目录下的符号链接文件和可执行的普通文件,多种文件类型用逗号隔开 57 | % print -l *(@,.x) 58 | 59 | # 列出符合 0644 权限的普通文件 60 | % print -l *(.f0644) 61 | ``` 62 | 63 | 那么我们来看下都有哪些可用的通配符修饰语,然后再举更复杂的例子。 64 | 65 | ### 通配符修饰语列表 66 | 67 | 名称 | 含义 | 使用样例或补充说明 | 68 | -- | -- | -- | 69 | / | 目录 | | 70 | F | 非空 | /F(空目录) /^F(非空目录) | 71 | . | 普通文件 | | 72 | @ | 符号链接 | | 73 | = | socket 文件 | | 74 | p | FIFO 文件 | | 75 | \* | 可执行的普通文件 | | 76 | % | 设备文件 | | 77 | %b | 块设备文件 | | 78 | %c | 字符设备文件 | | 79 | r | 文件拥有着有读权限 | | 80 | w | 文件拥有着有写权限 | | 81 | x | 文件拥有着有执行权限 | | 82 | A | 文件拥有组用户有读权限 | | 83 | I | 文件拥有组用户有写权限 | | 84 | E | 文件拥有组用户有执行权限 | | 85 | R | 任何用户都有读权限 | | 86 | W | 任何用户都有写权限 | | 87 | X | 任何用户都有执行权限 | | 88 | s | 设置了 setuid 的文件 | | 89 | S | 设置了 setgid 的文件 | | 90 | t | 设置了粘滞位(sticky bit)的文件 | | 91 | f | 符合指定的权限 | f0644 f4755 f700 | 92 | e | | 暂无 | 93 | \+ | | 暂无 | 94 | d | 指定设备号 | | 95 | l | 硬连接个数 | l-2(小于 2) l+3(大于 3) 96 | U | 当前用户拥有 | | 97 | G | 当前用户所在组拥有 | | 98 | u | 指定用户 id 拥有 | u1000 | 99 | g | 指定用户组 id 拥有 | g1000 | 100 | a | 指定文件的 atime | 下文有说明 | 101 | m | 指定文件的 mtime | 下文有说明 | 102 | c | 指定文件的 ctime | 下文有说明 | 103 | L | 指定文件大小 | 下文有说明 | 104 | ^ | 取反 | /^F | 105 | \- | | 暂无 | 106 | M | | 暂无 | 107 | T | | 暂无 | 108 | N | 如果没匹配到,返回空而不报错 | | 109 | D | 包含隐藏文件(. 开头) | | 110 | n | 按数值大小排序 | 下文有说明 | 111 | o | 递增排序 | 下文有说明 | 112 | O | 递减排序 | 下文有说明 | 113 | [n] | 只取前 n 个文件 | .[5]| 114 | [n1,n2] | 取第 n1 到 n2 个文件 | /[5,10] | 115 | :X | | 暂无 | 116 | 117 | ### 更复杂的用法 118 | 119 | #### 按文件时间查找文件 120 | 121 | ``` 122 | # 列出最近一天修改过内容的文件 123 | % print -l *(.m-1) 124 | 125 | # 列出最近一个月没有读取过的文件 126 | % print -l *(.aM+1) 127 | ``` 128 | 129 | m 后边可加单位,如果没有单位,默认是天。其他单位:M(月)、w(周)、h(小时)、m(分钟)、s(秒)。+ 是指定时间之前,- 是指定时间之内。 130 | 131 | a 是最后访问时间(atime),但注意如果分区挂载时指定了 noatime 或者 realtime(可以查看 /proc/mount 确认),那么 atime 并不是真正的最后访问时间。m 是最后修改时间(mtime),这里指内容修改,而不包括文件属性(如权限)的修改。c 是最后状态修改时间(ctime),如果文件内容没有修改,而文件属性发生变化,这个时间会更新。如果不能理解请在网上搜索相关文章。 132 | 133 | #### 按文件大小查找文件 134 | 135 | ``` 136 | # 列出当前目录下小于 2k 的文件 137 | % print -l *(.Lk-2) 138 | 139 | # 列出当前目录下大于 1m 的文件 140 | % print -l *(.Lm+1) 141 | 142 | # 注意这样只能找到空文件,因为以 m 为单位的话,文件只能是 0 m 或者 1 m,不能 0.5 m 143 | # 所以比 1 小就是 0 m,是空文件 144 | % print -l *(.Lm-1) 145 | ``` 146 | 147 | 默认的单位是字节,还可以使用 k、m 和 p(512 字节的块),也可以使用大写的 K、M、P,含义一样。 148 | 149 | #### 文件排序 150 | 151 | ``` 152 | # 按文件名排序,同一目录下的文件和目录名会一起排,而不是先排目录再排文件 153 | % print -l **/*(.on) 154 | bb.txt 155 | cc/aa.txt 156 | cc/dd.txt 157 | zz.txt 158 | 159 | # 按文件的目录深度逆序排,d 是从深往浅排,O 是逆序 160 | % print -l **/*(.Od) 161 | zz.txt 162 | bb.txt 163 | cc/dd.txt 164 | cc/aa.txt 165 | 166 | # 先按文件名排序,然后再按大小排序,这样大小相同的文件依然是按文件名排的 167 | % print -l **/*(.onoL) 168 | bb.txt 169 | cc/aa.txt 170 | cc/dd.txt 171 | cc.txt 172 | ``` 173 | 174 | 像第三个例子那样,可以排多次。 175 | 176 | 可供排序的因素:n(文件名,如果不指定排序选项,默认按文件名排,即 on)、L(大小)、l(硬连接数)、a(atime)、m(mtime)、c(ctime)、d(所在目录深度,从深到浅排)。 177 | 178 | #### 组合使用 179 | 180 | 现在我们大概了解了都有哪些可供使用的通配符修饰语,单个使用已经没有什么问题了。但如果同时使用多个,就涉及到怎么组合在一起的问题。 181 | 182 | 类型和类型之间要用逗号隔开,如果不指定类型,代表所有类型都可以,逗号前后的内容互不干扰(取反 ^ 操作只影响到逗号之前内容)。同一个类型可以同时加多个选项,依次添加即可。 183 | 184 | ``` 185 | # 当前目录下的两天内修改过的目录 186 | # 加上小于 3 m 的普通文件从小到大排 187 | # 再加上所有的符号链接文件(包括隐藏文件) 188 | % print -l *(/m-2,.Lm-3oL,@D) 189 | ``` 190 | 191 | ### 文件批量重命名 192 | 193 | 对文件进行批量重命名,是一个比较常见的场景。Zsh 中有一个非常方便的命令 zmv,它可以让批量重命名变得很简单。 194 | 195 | ``` 196 | # 使用前需要先加载进来 197 | % autoload -U zmv 198 | 199 | # 将所有 txt 文件扩展名改成 conf 200 | # 参数要用单引号扩起来,$1 代表第一个参数中括号中的内容 201 | % zmv '(*).txt' '$1.conf' 202 | 203 | # 如果加了 -W 参数,zmv 会自动识别文件名中需要保留的部分 204 | % zmv -W '*.txt' '*.conf' 205 | 206 | # 调整文件名各部分的前后顺序 207 | % zmv '(*).(*).txt' '$2.$1.txt' 208 | # 加 -n 预览而不实际运行 209 | % zmv -n '(*).(*).txt' '$2.$1.txt' 210 | mv -- a.b.txt b.a.txt 211 | 212 | # 0 1 2 ... 前添加 0,以便和 10 11 12 ... 宽度一致 213 | % zmv '([0-9]).(*)' '0$1.$2' 214 | # 去掉开头的一个 0 215 | % zmv '(0)(*)' '$2' 216 | 217 | # 文件整理到目录 218 | % zmv '(*) - (*) - (*).txt' '$1/$2 - $3.txt' 219 | 220 | # 转换大小写 221 | % zmv '(*).txt' '${(U)1}.txt' 222 | % zmv '(*).txt' '${(L)1}.txt' 223 | ``` 224 | 225 | ### 不展开通配符 226 | 227 | 有时我们不想展开通配符,比如我写了一个计算的函数叫做 calc: 228 | 229 | ``` 230 | calc() { 231 | zmodload zsh/mathfunc 232 | echo $(($*)) 233 | } 234 | 235 | % calc 12+12 236 | 24 237 | ``` 238 | 239 | 但如果我想计算 12 * 12: 240 | 241 | ``` 242 | % calc 12*12 243 | zsh: no matches found: 12*12 244 | ``` 245 | 246 | 如果不加引号的话,星号会被作为通配符使用,然后去找符合 12*12 的文件名,没找到所有报错了。但我并不想找文件。 247 | 248 | noglob 命令可以禁止展开后边内容的通配符,这样就不需要加引号了。 249 | 250 | ``` 251 | % noglob calc 12*12 252 | 144 253 | ``` 254 | 255 | 然后可以写个 alias: 256 | 257 | ``` 258 | % alias js="noglob calc" 259 | % js 12*12 260 | 144 261 | ``` 262 | 263 | 这样就可以更方便地使用计算器了。 264 | 265 | ### 总结 266 | 267 | 本文介绍了文件查找中的通配符修饰语的用法,并且列出来大多数常用的通配符修饰语,还有一小部分更复杂或者更少用的暂时空缺,以后可能会补上。这些通配符修饰语没有必要全部记下来,熟悉常用的,其余的等用的时候再查询即可。 268 | 269 | ### 参考 270 | 271 | http://www.bash2zsh.com/zsh_refcard/refcard.pdf 272 | 273 | http://blog.sina.com.cn/s/blog_687bd5d50101epna.html 274 | 275 | ### 更新历史 276 | 277 | 2017.08.31:增加“不展开通配符”和“文件批量重命名”。 278 | -------------------------------------------------------------------------------- /02_Zsh-开发指南(第二篇-字符串处理之常用操作).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 字符串处理是 shell 脚本的重点部分,因为 shell 脚本主要的工作是和文件或者其他程序打交道,数据格式通常是文本,而处理没有统一格式的文本文件出奇地复杂,shell 命令中也有很多都是处理文本的。用 bash 处理文本的话,因为自身的功能有限,经常需要调用像 `awk`、`sed`、`grep`、`cat`、`cut`、`comm`、`dirname`、`basename`、`expr`、`sort`、`uniq`、`head`、`tail`、`tac`、`tr`、`wc` 这样命令,不留神脚本就成了命令大聚会。命令用法各异,有的很简单(比如 `cut`、`tr`、`wc`),看一眼 man 就会用;有的很复杂(比如 `awk`、`sed`、`grep`),用了好多年基本也只会用很少一部分功能。互相配合也容易出现各种各样的问题(比如要命的空格和换行符问题),难以调试,调用命令的开销也很大。而用好了 zsh 的话,可以大幅减少这些命令的使用(并不能完全避免,因为某些场景确实比较适合用这样的命令处理,比如处理一个大文本文件),并且大幅提升脚本的性能(主要因为减少了进程启动的开销,比如一次简单的字符串替换,调用外部命令实现比内部实现的时间要多好几个数量级)。 4 | 5 | 但也因此 zsh 的字符串处理功能很复杂,可以说 zsh 的字符串处理功能,要比绝大多数编程语言自带的字符串函数库或者类库要强大(在不依赖外部命令的情况)。同时各种用法也比较怪异,很多时候简洁性和可读性是有矛盾的,很难兼顾。而 shell 的使用场景决定简洁性是不能被牺牲掉的,即使用 Python 这样比较简洁的语言来处理字符串,很多时候也只能写出冗长的代码,而 zsh 经常可以一行搞定(可能有人想到了 Perl,Perl 在处理文本方面确实有比较明显的优势,但使用 Perl 的话也要承担更多的成本),如果再加上适当地使用外部命令,基本可以应付大多数字符串处理场景。因为字符串处理的内容比较丰富,我会分多篇文章写。本篇只涉及最基础和常用的字符串操作,包括字符串的拼接、切片、截断、查找、遍历、替换、匹配、大小写转换、分隔等等。 6 | 7 | 字符串定义和简单比较,我已经在前一篇文章提过了,现在直接进入正题。 8 | 9 | ### 字符串长度 10 | 11 | ``` 12 | % str=abcde 13 | % echo $#str 14 | 5 15 | 16 | # 读取函数或者脚本的第一个参数的长度 17 | % echo $#1 18 | ``` 19 | 20 | ### 字符串拼接 21 | 22 | ``` 23 | % str1=abc 24 | % str2=def 25 | 26 | % str2+=$str1 27 | % echo $str2 28 | defabc 29 | 30 | % str3=$str1$str2 31 | abcdefabc 32 | ``` 33 | 34 | ### 字符串切片 35 | 36 | 字符串切片之前也提过,这里简单复习一下。逗号前后不能有空格。字符位置是从 1 开始算起的。 37 | 38 | ``` 39 | % str=abcdef 40 | % echo $str[2,4] 41 | bcd 42 | % echo $str[2,-1] 43 | bcdef 44 | 45 | # $1 是文件或者函数的第一个参数 46 | echo ${1[2,4]} 47 | ``` 48 | 49 | 字符串切片还有另一种风格的方法,即 bash 风格,功能大同小异。通常没有必要用这个,而且因为字符位置是从 0 开始算,容易混淆。 50 | 51 | ``` 52 | % str=abcdef 53 | % echo ${str:1:3} 54 | bcd 55 | % echo ${str:1:-1} 56 | bcde 57 | ``` 58 | 59 | ### 字符串截断 60 | 61 | ``` 62 | % str=abcdeabcde 63 | 64 | # 删除左端匹配到的内容,最小匹配 65 | % echo ${str#*b} 66 | cdeabcde 67 | 68 | # 删除右端匹配到的内容,最小匹配 69 | % echo ${str%d*} 70 | abcdeabc 71 | 72 | # 删除左端匹配到的内容,最大匹配 73 | % echo ${str##*b} 74 | cde 75 | 76 | # 删除右端匹配到的内容 77 | % echo ${str%%d*} 78 | abc 79 | ``` 80 | 81 | ### 字符串查找 82 | 83 | 子字符串定位。 84 | 85 | ``` 86 | % str=abcdef 87 | 88 | # 这里用的是 i 的大写,不是 L 的小写 89 | % echo $str[(I)cd] 90 | 3 91 | 92 | # I 是从右往左找,如果找不到则为 0, 方便用来判断 93 | % (($str[(I)cd])) && echo good 94 | good 95 | 96 | # 找不到则为 0 97 | % echo $str[(I)cdd] 98 | 0 99 | 100 | # 也可以使用小 i,小 i 是从左往右找,找不到则返回数组大小 + 1 101 | % echo $str[(i)cd] 102 | 3 103 | 104 | % echo $str[(i)cdd] 105 | 7 106 | ``` 107 | 108 | ### 遍历字符 109 | 110 | ``` 111 | % str=abcd 112 | 113 | % for i ({1..$#str}) { 114 | > echo $str[i] 115 | >} 116 | a 117 | b 118 | c 119 | d 120 | ``` 121 | 122 | ### 字符串替换 123 | 124 | 按内容替换和删除字符。 125 | ``` 126 | % str=abcabc 127 | 128 | # 只替换找到的第一个 129 | % echo ${str/bc/ef} 130 | aefabc 131 | 132 | # 删除匹配到的第一个 133 | % echo ${str/bc} 134 | aabc 135 | 136 | # 替换所有找到的 137 | % echo ${str//bc/ef} 138 | aefaef 139 | 140 | # 删除匹配到的所有的 141 | % echo ${str//bc} 142 | aa 143 | 144 | 145 | % str=abcABCabcABCabc 146 | 147 | # /# 只从字符串开头开始匹配,${str/#abc} 也同理 148 | % echo ${str/#abc/123} 149 | 123ABCabcABCabc 150 | 151 | # /% 只从字符串结尾开始匹配,echo ${str/%abc} 也同理 152 | % echo ${str/%abc/123} 153 | abcABCabcABC123 154 | 155 | 156 | % str=abc 157 | # 如果匹配到了则输出空字符串 158 | % echo ${str:#ab*} 159 | 160 | # 如果匹配不到,则输出原字符串 161 | % echo ${str:#ab} 162 | abc 163 | 164 | # 加 (M) 后效果反转 165 | % echo ${(M)str:#ab} 166 | 167 | ``` 168 | 169 | 按位置删除字符。 170 | 171 | ``` 172 | %str=abcdef 173 | 174 | # 删除指定位置字符 175 | % str[1]= 176 | % echo $str 177 | bcdef 178 | 179 | # 可以删除多个 180 | % str[2,4]= 181 | % echo $str 182 | bf 183 | ``` 184 | 185 | 按位置替换字符。 186 | 187 | ``` 188 | % str=abcdefg 189 | 190 | # 一对一地替换 191 | % str[2]=1 192 | % echo $str 193 | a1cdefg 194 | 195 | # 可以多对多(也包括一对多和多对一)地替换字符,两边的字符数量不需要一致。 196 | # 把第二、三个字符替换成 2345 197 | % str[2,3]=2345 198 | % echo $str 199 | a2345defg 200 | ``` 201 | 202 | ### 判断字符串变量是否存在 203 | 204 | 如果用 `[[ "$strxx" == "" ]]` ,那无法区分变量是没有定义还是内容为空,在某些情况是需要区分二者的。 205 | 206 | ``` 207 | % (($+strxx)) && echo good 208 | 209 | % strxx="" 210 | % (($+strxx)) && echo good 211 | good 212 | ``` 213 | 214 | `(($+var))` 的用法也可以用来判断其他类型的变量,如果变量存在则返回真(0),否则返回假(1)。 215 | 216 | ### 字符串匹配判断 217 | 218 | 判断是否包含字符串。 219 | 220 | ``` 221 | % str1=abcd 222 | % str2=bc 223 | 224 | % [[ $str1 == *$str2* ]] && echo good 225 | good 226 | ``` 227 | 228 | 正则表达式匹配。 229 | 230 | ``` 231 | % str=abc55def 232 | 233 | # 少量字符串的话,尽量不要用 grep 234 | # 本文不讲正则表达式格式相关内容 235 | # 另外 zsh 有专门的正则表达式模块 236 | % [[ $str =~ "c[0-9]{2}\de" ]] && echo a 237 | a 238 | ``` 239 | 240 | ### 大小写转换 241 | 242 | ``` 243 | % str="ABCDE abcde" 244 | 245 | # 转成大写,(U) 和 :u 两种用法效果一样 246 | % echo ${(U)str} --- ${str:u} 247 | ABCDE ABCDE --- ABCDE ABCDE 248 | 249 | # 转成小写,(L) 和 :l 两种用法效果一样 250 | % echo ${(L)str} --- ${str:l} 251 | abcde abcde --- abcde abcde 252 | 253 | # 转成首字母大写 254 | % echo ${(C)str} 255 | Abcde Abcde 256 | ``` 257 | 258 | ### 目录文件名截取 259 | 260 | ``` 261 | % filepath=/a/b/c.x 262 | 263 | # :h 是取目录名,即最后一个 / 之前的部分,如果没有 / 则为 . 264 | % echo ${filepath:h} 265 | /a/b 266 | 267 | # :t 是取文件名,即最后一个 / 之后的部分,如果没有 / 则为字符串本身 268 | % echo ${filepath:t} 269 | c.x 270 | 271 | # :e 是取文件扩展名,即文件名中最后一个点之后的部分,如果没有点则为空 272 | % echo ${filepath:e} 273 | x 274 | 275 | # :r 是去掉末尾扩展名的路径 276 | % echo ${filepath:r} 277 | /a/b/c 278 | ``` 279 | 280 | ### 字符串分隔 281 | 282 | ``` 283 | # 使用空格作为分隔符,多个空格也只算一个分隔符 284 | % str='aa bb cc dd' 285 | % echo ${str[(w)2]} 286 | bb 287 | % echo ${str[(w)3]} 288 | cc 289 | 290 | # 指定分隔符 291 | % str='aa--bb--cc' 292 | # 如果分隔符是 : 就用别的字符作为左右界,比如 ws.:. 293 | % echo ${str[(ws:--:)3]} 294 | cc 295 | ``` 296 | 297 | ### 多行字符串 298 | 299 | 字符串定义可以跨行。 300 | 301 | ``` 302 | % str="line1 303 | > line2" 304 | % echo $str 305 | line1 306 | line2 307 | ``` 308 | 309 | ### 读取文件内容到字符串 310 | 311 | ``` 312 | # 比用 str=$(cat filename) 性能好很多 313 | str=$( /dev/pts/1 44 | l-wx------ 1 goreliu goreliu 0 2017-08-30 21:15 1 -> pipe:[2803] 45 | lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 2 -> /dev/pts/1 46 | 47 | # 查看 wc 进程打开的 fd 48 | % ls -l /proc/$(pidof wc)/fd 49 | total 0 50 | lr-x------ 1 goreliu goreliu 0 2017-08-30 21:16 0 -> pipe:[2803] 51 | lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 1 -> /dev/pts/1 52 | lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 2 -> /dev/pts/1 53 | ``` 54 | 55 | cat 命令的效果是等待用户输入,等用户输入一行,它就把这行再输出来,直到用户按 ctrl - d。所以 cat | wc -l 也会等待用户输入。 56 | 57 | 我们看下 fd 的指向,/dev/ps1/1 是指向伪终端设备文件的,进程就是通过这个来读取用户的输入和输出自己的内容。0 是标准输入(即用户输入端),1 是标准输出(即正常情况的输出端),2 是错误输出(即异常情况的输出端)。但是 cat 的输出端指向了 一个管道,并且 wc 的 输入端指向了一个相同的管道,这代表两个进程的输入输出端是通过管道连接的。这种管道是匿名管道,即只在内核中存在,是没有对应的文件路径的。 58 | 59 | ### 重定向 60 | 61 | 重定向,指的便是 fd 的重定向,管道也是重定向的一种方法。但用得更多的是将进程的 fd 重定向到文件。 62 | 63 | 一个最简单的例子是输出内容到文件。 64 | 65 | ``` 66 | % echo abce > test.txt 67 | % cat test.txt 68 | abce 69 | ``` 70 | 71 | 因为这个用法太常见了,大家可能习以为常了。我们依然来看下更多的细节。 72 | 73 | ``` 74 | % cat > test.txt 75 | 76 | # 在另一个 zsh 中运行 77 | % ls -l /proc/$(pidof cat)/fd 78 | total 0 79 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:43 0 -> /dev/pts/1 80 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:43 1 -> /tmp/test.txt 81 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:43 2 -> /dev/pts/1 82 | ``` 83 | 84 | 可以看到标准输出已经指向 test.txt 文件了。 85 | 86 | 除了标准输出可以重定向,标准输入(fd 0),错误输出(fd 2)也都可以。 87 | 88 | ``` 89 | % touch 0.txt 1.txt 2.txt 90 | % sleep 1000 <0.txt >1.txt 2>2.txt 91 | 92 | # 在另一个 zsh 中运行 93 | % ls -l /proc/$(pidof sleep)/fd 94 | total 0 95 | lr-x------ 1 goreliu goreliu 0 Aug 30 21:46 0 -> /tmp/0.txt 96 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:46 1 -> /tmp/1.txt 97 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:46 2 -> /tmp/2.txt 98 | ``` 99 | 100 | <0.txt 是重定向标准输入,2>2.txt 是重定向错误输出,>1.txt(即 1>1.txt)是重定向到标准输出。然后我们看到 3 个文件已经各就各位,全部被重定向了。但因为 sleep 并不去读写任何东西,重定向它的输入输出没有什么意义。 101 | 102 | ### 更多重定向的用法 103 | 104 | 一个 fd 只能重定向到一个文件,一一对应。但在 zsh 中,我们可以把一个 fd 对应到多个文件。 105 | 106 | ``` 107 | % cat >0.txt >1.txt >2.txt 108 | ``` 109 | 110 | 输入完成后,3 个文件的内容都更新了,这是怎么回事呢? 111 | 112 | 其实是 zsh 进程做了中介。 113 | 114 | ``` 115 | % pstree -p | grep cat 116 | `-tmux: server(1172)-+-zsh(1173)---cat(1307)---zsh(1308) 117 | 118 | % ls -l /proc/1307/fd 119 | total 0 120 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:57 0 -> /dev/pts/1 121 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:57 1 -> pipe:[2975] 122 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:57 2 -> /dev/pts/1 123 | 124 | % ls -l /proc/1308/fd 125 | total 0 126 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 12 -> /tmp/0.txt 127 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 13 -> /tmp/1.txt 128 | lr-x------ 1 goreliu goreliu 0 Aug 30 21:58 14 -> pipe:[2975] 129 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 15 -> /tmp/2.txt 130 | ``` 131 | 132 | 可以看到 cat 的标准输出是重定向到管道了,管道对面是 zsh 进程,然后 zsh 打开了那三个文件。实际将内容写入文件的是 zsh,而不是 cat。但不管是谁写入的,这个用法很方便。 133 | 134 | 标准输入、错误输出也可以重定向多个文件。 135 | 136 | ``` 137 | % echo good >0.txt >1.txt >2.txt 138 | 139 | % cat <0.txt <1.txt <2.txt 140 | good 141 | good 142 | good 143 | ``` 144 | 145 | 给 cat 的标准输出重定向 3 个文件,它将 3 个文件的内容全部读取了出来。 146 | 147 | 除了能同时重定向 fd 到多个文件外,还可以同时重定向到管道和文件。 148 | 149 | ``` 150 | # 敲完 a b c 后 ctrl -d 退出 151 | % cat >0.txt >1.txt | wc -l 152 | a 153 | b 154 | c 155 | 3 156 | 157 | % cat 0.txt 1.txt 158 | a 159 | b 160 | c 161 | a 162 | b 163 | c 164 | ``` 165 | 166 | 可以看到输入的内容写入了文件,并且通过管道传给了 wc -l,不用说,这又是 zsh 在做背后工作,将数据分发给了文件和管道。所以在 zsh 中是不需要使用 tee 命令的。 167 | 168 | ### 命名管道 169 | 170 | 除了匿名管道,我们还可以使用命名管道,这样更容易控制。命名管道所使用的文件即 FIFO(First Input First Output,先入先出)文件。 171 | 172 | ``` 173 | # mkfifo 用来创建 FIFO 文件 174 | % mkfifo fifo 175 | % ls -l 176 | prw-r--r-- 1 goreliu goreliu 0 2017-08-30 21:29 fifo| 177 | 178 | # cat 写入 fifo 179 | % cat > fifo 180 | 181 | # 打开另一个 zsh,运行 wc -l 读取 fifo 182 | % wc -l < fifo 183 | ``` 184 | 185 | 然后在 cat 那边输入一些内容,按 ctrl - d 退出,wc 这边就会统计输入的行数。 186 | 187 | 在输入完成之前,我们也可以看一下 cat 和 wc 两个进程的 fd 指向哪里: 188 | 189 | ``` 190 | % ls -l /proc/$(pidof cat)/fd 191 | total 0 192 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:35 0 -> /dev/pts/2 193 | l-wx------ 1 goreliu goreliu 0 Aug 30 21:35 1 -> /tmp/fifo 194 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:35 2 -> /dev/pts/2 195 | 196 | % ls -l /proc/$(pidof wc)/fd 197 | total 0 198 | lr-x------ 1 goreliu goreliu 0 Aug 30 21:34 0 -> /tmp/fifo 199 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:34 1 -> /dev/pts/1 200 | lrwx------ 1 goreliu goreliu 0 Aug 30 21:34 2 -> /dev/pts/1 201 | ``` 202 | 203 | 可以看到之前的匿名管道已经变成了我们刚刚创建的 fifo 文件,其他的并无不同。 204 | 205 | ### exec 命令的用法 206 | 207 | 说起重定向,就不得不提 exec 命令。exec 命令主要用于启动新进程替换当前进程以及对 fd 做一些操作。 208 | 209 | 用 exec 启动新进程: 210 | 211 | ``` 212 | % exec cat 213 | ``` 214 | 215 | 看上去效果和直接运行 cat 差不多。但如果运行 ctrl + d 退出 cat,终端模拟器就关闭了,因为在运行 exec cat 的时候,zsh 进程将已经被 cat 取代了,回不去了。 216 | 217 | 但在脚本中很少直接这样使用 exec,更多情况是用它来操作 fd: 218 | 219 | ``` 220 | # 将当前 zsh 的错误输出重定向到 test.txt 221 | % exec 2>test.txt 222 | # 随意敲入一个不存在的命令,错误提示不出现了 223 | % fdsafds 224 | # 错误提示被重定向到 test.txt 里 225 | % cat test.txt 226 | zsh: command not found: fdsafds 227 | ``` 228 | 229 | 更多用法: 230 | 231 | | 用法 | 功能 | 232 | | ----------- | ----------------------------- | 233 | | n>filename | 重定向 fd n 的输出到 filename 文件 | 234 | | nfilename | 同时重定向 fd n 的输入输出为 filename 文件 | 236 | | n>&m | 重定向 fd n 的输出到 fd m | 237 | | n<&m | 重定向 fd n 的输入为 fd m | 238 | | n>&- | 关闭 fd n 的输出 | 239 | | n<&- | 关闭 fd n 的输入 | 240 | 241 | 更多例子: 242 | 243 | ``` 244 | # 把错误输出关闭,这样错误内容就不再显示 245 | % exec 2>&- 246 | % fsdafdsa 247 | 248 | % exec 3>test.txt 249 | % echo good >&3 250 | % exec 3>&- 251 | # 关闭后无法再输出 252 | % echo good >&3 253 | zsh: 3: bad file descriptor 254 | 255 | % exec 3>test.txt 256 | # 将 fd 4 的输出重定向到 fd 3 257 | % exec 4>&3 258 | % echo abcd >&4 259 | # 输出内容到 fd 4,test.txt 内容更新了 260 | % cat test.txt 261 | abcd 262 | ``` 263 | 264 | 通常情况我们用 exec 主要为了重定向输出和关闭输出,比较少操作输入。 265 | 266 | ### 总结 267 | 268 | 本文讲了管道和重定向的基本概念和各种用法。Zsh 中的重定向还是非常灵活好用的,之后的文章会详细讲在实际场景中怎样使用。 269 | 270 | ### 参考 271 | 272 | http://adelphos.blog.51cto.com/2363901/1601563 273 | 274 | ### 更新历史 275 | 276 | 20170901:增加“exec 命令的用法”。 -------------------------------------------------------------------------------- /05_Zsh-开发指南(第五篇-数组).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 了解完结构比较简单的字符串后,我们来看更复杂一些的数组。其实字符串在 zsh 中也可以当字符数组操作,但很少有需要把字符串当数组来处理的场景。本篇中主要讲的是字符串数组,复杂度要比单个字符串高一些。 4 | 5 | 在实际的脚本编写中,较少需要处理单个的字符串。往往需要处理从各个地方过来的大量文本,不可避免会用到数组。用好数组,会让文本处理工作事半功倍。 6 | 7 | 本篇只涉及数组的基础用法。 8 | 9 | ### 数组定义 10 | 11 | 数组可以直接赋值使用,不需要提前声明。等号和小括号之间不能有空格,小括号中的元素以空格隔开。 12 | 13 | ``` 14 | % array=(a bc ccc dddd) 15 | # 用 $array 即可访问数组全部元素,输出时元素以空格分隔 16 | % echo $array 17 | a bc ccc dddd 18 | 19 | # 使用 print -l 可以每行输出一个元素 20 | % print -l $array 21 | a 22 | bc 23 | ccc 24 | dddd 25 | 26 | # 输出数组中的元素个数,用法和取字符串长度一样 27 | % echo $#array 28 | 4 29 | 30 | # 包含带空格的字符串 31 | % array=(a "bc ccc" dddd) 32 | % print -l $array 33 | a 34 | bc ccc 35 | dddd 36 | 37 | # 可以换行赋值,但如果行中间有空格,依然需要加引号 38 | % array=( 39 | > a 40 | > bb 41 | > "c c c" 42 | > dddd 43 | > ) 44 | ``` 45 | 46 | ### 元素读写 47 | 48 | ``` 49 | % array=(a bc ccc dddd) 50 | 51 | # 用法和取字符串的第几个字符一样,从 1 开始算 52 | % echo $array[3] 53 | ccc 54 | # -1 依然是最后一个元素,-2 是倒数第二个,以此类推 55 | % echo $array[-1] 56 | dddd 57 | 58 | % array[3]=CCC 59 | 60 | # 如果赋值的内容是一个空的小括号,则删除该元素 61 | % array[2]=() 62 | 63 | % print -l $array 64 | a 65 | CCC 66 | dddd 67 | 68 | # 用 += 为数组添加一个新元素 69 | % array+=eeeee 70 | % print -l $array 71 | a 72 | CCC 73 | dddd 74 | eeeee 75 | 76 | # 用 unset 可以删除整个数组 77 | % unset array 78 | 79 | # array 变量变成未定义状态 80 | % echo $+array 81 | 0 82 | ``` 83 | 84 | ### 数组拼接 85 | 86 | ``` 87 | % array1=(a b c d) 88 | % array2=(1 2 3 4) 89 | 90 | # 用 += 拼接数组 91 | % array1+=(e f g) 92 | % echo $array1 93 | a b c d e f g 94 | 95 | # 拼接另一个数组,小括号不可以省略,否则 array1 会被转成一个字符串 96 | % array2+=($array1) 97 | % echo $#array2 98 | 11 99 | 100 | # 去掉小扩号后,array1 被转成了一个字符串 101 | % array2+=$array1 102 | % echo $#array2 103 | 12 104 | % echo $array2[12] 105 | a b c d e f g 106 | 107 | 108 | # 字符串可以直接拼接数组而转化成数组 109 | % str=abcd 110 | % str+=(1234) 111 | 112 | % echo $#str 113 | 2 114 | ``` 115 | 116 | ### 数组遍历 117 | 118 | ``` 119 | % array1=(a bb ccc dddd) 120 | % array2=(1 2 3) 121 | 122 | # 用 for 可以直接遍历数组,小括号不可省略 123 | % for i ($array1) { 124 | > echo $i 125 | > } 126 | a 127 | bb 128 | ccc 129 | dddd 130 | 131 | # 小括号里可以放多个数组,依次遍历 132 | % for i ($array1 $array2) { 133 | > echo $i 134 | > } 135 | a 136 | bb 137 | ccc 138 | dddd 139 | 1 140 | 2 141 | 3 142 | ``` 143 | 144 | ### 数组切片 145 | 146 | 数组切片和字符串切片操作方法完全相同。 147 | 148 | ``` 149 | % array=(a bb ccc dddd) 150 | 151 | % echo $array[2,3] 152 | bb ccc 153 | 154 | # 依然可以多对多地替换元素 155 | % array[3,-1]=(1 2 3 4) 156 | % echo $array 157 | a bb 1 2 3 4 158 | 159 | # 也可以使用另一种语法,不建议使用 160 | % echo ${array:0:3} 161 | a bb 1 162 | ``` 163 | 164 | ### 元素查找 165 | 166 | 数组的元素查找方法,和字符串的子字符串查找语法一样。 167 | 168 | ``` 169 | % array=(a bb ccc dddd ccc) 170 | 171 | # 用小 i 输出从左到右第一次匹配到的元素位置 172 | % echo $array[(i)ccc] 173 | 3 174 | 175 | # 如果找不到,返回数组大小 + 1 176 | % echo $array[(i)xxx] 177 | 6 178 | 179 | # 用大 I 输出从右到左第一次匹配到的元素位置 180 | % echo $array[(I)ccc] 181 | 5 182 | 183 | # 如果找不到,返回 0 184 | % echo $array[(I)xxx] 185 | 0 186 | 187 | # 可以用大 I 判断是否存在元素 188 | % (($array[(I)dddd])) && echo good 189 | good 190 | 191 | % (($array[(I)xxx])) && echo good 192 | 193 | 194 | % array=(aaa bbb aab bbc) 195 | # n:2: 从指定的位置开始查找 196 | % echo ${array[(in:2:)aa*]} 197 | 3 198 | ``` 199 | 200 | ### 元素排序 201 | 202 | ``` 203 | % array=(aa CCC b DD e 000 AA 3 aa 22) 204 | 205 | # 用小写字母 o 升序排列,从小到大 206 | % echo ${(o)array} 207 | 000 22 3 aa aa AA b CCC DD e 208 | 209 | # 用大写字母 O 降序排列,从大到小 210 | % echo ${(O)array} 211 | e DD CCC b AA aa aa 3 22 000 212 | 213 | # 加 i 的话大小写不敏感 214 | % echo ${(oi)array} 215 | 000 22 3 aa AA aa b CCC DD e 216 | 217 | 218 | % array=(cc aaa b 12 115 90) 219 | # 加 n 的话按数字大小顺序排 220 | % echo ${(on)array} 221 | 12 90 115 aaa b cc 222 | 223 | # Oa 用于反转数组元素的排列顺序 224 | % echo ${(Oa)array} 225 | 90 115 12 b aaa cc 226 | ``` 227 | 228 | ### 去除重复元素 229 | 230 | ``` 231 | % array=(ddd a bb a ccc bb ddd) 232 | 233 | % echo ${(u)array} 234 | ddd a bb ccc 235 | ``` 236 | 237 | ### 使用连续字符或者数值构造数组 238 | 239 | ``` 240 | # 大括号中的逗号分隔的字符串会被展开 241 | % array=(aa{bb,cc,11}) && echo $array 242 | aabb aacc aa11 243 | 244 | # .. 会将前后的数组连续展开 245 | % array=(aa{1..3}) && echo $array 246 | aa1 aa2 aa3 247 | 248 | # 第二个 .. 后的数字是展开的间隔 249 | % array=(aa{15..19..2}) && echo $array 250 | aa15 aa17 aa19 251 | 252 | # 也可以从大到小展开 253 | % array=(aa{19..15..2}) && echo $array 254 | aa19 aa17 aa15 255 | 256 | # 可以添加一个或多个前导 0 257 | % array=(aa{01..03}) && echo $array 258 | aa01 aa02 aa03 259 | 260 | # 单个字母也可以像数值那样展开,多个字母不行 261 | % array=(aa{a..c}) && echo $array 262 | aaa aab aac 263 | 264 | # 字母是按 ASCII 码的顺序展开的 265 | % array=(aa{Y..c}) && echo $array 266 | aaY aaZ aa[ aa\ aa] aa^ aa_ aa` aaa aab aac 267 | 268 | 269 | # 这些用法都可以用在 for 循环里边 270 | % for i (aa{a..c}) { 271 | > echo $i 272 | > } 273 | aaa 274 | aab 275 | aac 276 | ``` 277 | 278 | ### 从字符串构造数组 279 | 280 | ``` 281 | % str="a bb ccc dddd" 282 | 283 | # ${=str} 可以将 str 内容按空格切分成数组 284 | % array=(${=str}) 285 | % print -l $array[2,3] 286 | bb 287 | ccc 288 | 289 | 290 | % str="a:bb:ccc:dddd" 291 | # 如果是其他分隔符,可以设置 IFS 环境变量指定 292 | % IFS=: 293 | % array=(${=str}) 294 | % print -l $array[2,3] 295 | bb 296 | ccc 297 | 298 | 299 | % str="a\nbb\nccc\ndddd" 300 | # 如果是其他分隔符,也可以用 (s:x:) 指定 301 | % array=(${(s:\n:)str}) 302 | % print -l $array[2,3] 303 | bb 304 | ccc 305 | 306 | 307 | % str="a##bb##ccc##dddd" 308 | # 分隔符可以是多个字符 309 | % array=(${(s:##:)str}) 310 | % print -l $array[2,3] 311 | bb 312 | ccc 313 | 314 | 315 | % str="a:bb:ccc:dddd" 316 | # 如果分隔符是 :,可以 (s.:.) 317 | % array=(${(s.:.)str}) 318 | % print -l $array[2,3] 319 | bb 320 | ccc 321 | ``` 322 | 323 | ### 从文件构造数组 324 | 325 | `test.txt` 内容。 326 | 327 | ``` 328 | a 329 | bb 330 | ccc 331 | dddd 332 | ``` 333 | 334 | 每行一个元素。 335 | 336 | ``` 337 | # f 的功能是将字符串以换行符分隔成数组 338 | # 双引号不可省略,不然会变成一个字符串,引号也可以加在 ${ } 上 339 | % array=(${(f)"$(` 代表此行是换行后的输入内容,以 `#` 开头的为注释(非 root 用户的命令提示符,本系列文章不需要 root 用户),其余的是命令的输出内容。另外某些地方会贴成段的 zsh 代码,那样就省略开头的 `%`,比较容易分辨。 76 | 77 | 一个样例: 78 | 79 | ``` 80 | # 前两行是输入内容,第三行是输出内容 81 | % echo "Hello \ 82 | > World" 83 | Hello World 84 | ``` 85 | 86 | 本系列文章使用的 zsh 版本是 5.4.1(写这篇文章时的最新版本),代码在老版本中可能运行不了或者结果有出入,尽量使用最新版本。 87 | 88 | 下面直接进入正题。 89 | 90 | ### 变量 91 | 92 | 接触一门新的编程语言,运行完 Hello World 后,首先要了解的基本就是如何定义和使用变量了。有了变量后可以比较变量内容,进而可以接触条件、循环、分支等语句,继而了解函数的用法,更高级的数据结构的使用,更多库函数,等等。这样就大概了解了一门面向过程的语言的基本用法,剩下的可以等到用的时候再查手册。 93 | 94 | 所以这一篇讲最基本的变量和语句。 95 | 96 | zsh 有 5 种变量:整数、浮点数(bash 不支持)、字符串、数组、哈希表(或者叫关联数组或者字典,本系列文章统一使用“哈希表”这一名词),另外还有一些其他语言少有的东西,比如 alias(但主要是交互时使用,编程时基本用不到)。此篇只涉及整数、浮点数、字符串,并且不涉及数值计算和字符串处理等内容。 97 | 98 | #### 变量定义 99 | 100 | Zsh 的变量多数情况不需要提前声明或者指定类型,可以直接赋值和使用(但哈希表是一个例外)。 101 | 102 | ``` 103 | # 等号两端不能有空格 104 | % num1=123 105 | % num2=123.456 106 | % str1=abcde 107 | # 如果字符串中包含空格等特殊字符,需要加引号 108 | % str2='abc def' 109 | # 也可以用双引号,但和单引号有区别,比如双引号里可以使用变量,而单引号不可以 110 | % str3="abc def $num1" 111 | # 在字符串中可以使用转义字符,单双引号均可 112 | % str4="abc\tdef\ng" 113 | 114 | # 输出变量,也可以使用 print 115 | % echo $str1 116 | abcde 117 | 118 | # 简单的数值计算 119 | % num3=$(($num1 + $num2)) 120 | # (( 中的变量名可以不用 $ 121 | % num3=$((num1 + num2)) 122 | 123 | # 简单的字符串操作 124 | % str=abcdef 125 | # 2 和 4 都是字符在数组的位置,从 1 开始数,逗号两边不能有空格 126 | % echo $str[2,4] 127 | bcd 128 | # -1 是最后一个字符 129 | % echo $str[4,-1] 130 | def 131 | ``` 132 | 133 | #### 变量比较 134 | 135 | ``` 136 | # 比较数值 137 | % num=123 138 | # (( )) 用于数值比较等操作,如果为真返回 0,否则返回 1 139 | # && 后边的语句在前边的语句为真时才执行 140 | # 注意这里只能使用双等号来比较 141 | % ((num == 123)) && echo good 142 | good 143 | # (( 里边可以使用与(&&)或(||)非(!)操作符,同 c 系列语言 144 | % ((num == 1 || num == 2)) && echo good 145 | 146 | # 比较字符串 147 | % str=abc 148 | # 比较字符串要用 [[,内侧要有空格,[[ 的具体用法之后会讲到 149 | # 这里双等号可以替换成单等号,可以根据自己的习惯选用 150 | # 本系列文章统一使用双等号,因为和 (( )) 一致,并且使用双等号的常用编程语言更多些 151 | # $str 两侧不需要加双引号,即使 str 未定义或者 $str 中含空格和特殊符号 152 | % [[ $str == abc ]] && echo good 153 | good 154 | # 可以和空字符串 "" 比较,未定义的字符串和空字符串比较结果为真 155 | # [[ 里也可以用 && || ! 156 | % [[ $str == "" || $str == 123 ]] && echo good 157 | ``` 158 | 159 | ### 语句 160 | 161 | 稍微了解下简单变量的使用后,快速进入语句部分。 162 | 163 | zsh 支持多种风格的语法,包括经典的 posix shell (bash 的语法和它类似,但有一些扩展,可以归为一类)的,以及 csh 风格的等等。但 posix shell 的语法并不好用,我们没必要一定使用这个。我只选用一种我认为最方便简洁的语法,没有 `fi`、`then`、`do`、`done`、`esac`、`in` 等的关键字(虽然其中某些关键字其他编程语言也有,但基本用法都各异,而且容易混淆),也不需要多余的分号。如果不确定语法是否符合预期,可以定义一个函数然后使用 `which` 查看,内容会被转化成原始(posix shell 风格)的样子。熟悉 bash 并且喜欢使用 bash 语法的读者可以跳过这部分内容,语法的不同并不影响后续内容的阅读,继续使用 bash 风格语法写 zsh 也是没有问题的。 164 | 165 | #### 条件语句 166 | 167 | ``` 168 | # 格式 169 | if [[ ]] { 170 | } elif { 171 | } else { 172 | } 173 | ``` 174 | 175 | 大括号也可以另起一行,本系列文章统一使用这种风格,缩进为 4 个空格。注意 `elif` 不可写作 `else if`。 176 | 177 | `[[ ]]` 用于比较字符串、判断文件等,功能比较复杂多样,这里先使用最基础的用法。注意尽量不要用 `[[ ]]` 比较数值,因为不留神的话,数值会被转化成字符串来比较,没有任何错误提示,但结果可能不符合预期,导致不必要的麻烦。 178 | 179 | ``` 180 | # 样例 181 | if [[ "$str" == "name" || "$str" == "value" ]] { 182 | echo "$str" 183 | } 184 | ``` 185 | 186 | `(( ))` 用于比较数值,里边可以调用各种数值相关的函数,格式类似 c 语言,变量前的 `$` 可省略。 187 | 188 | ``` 189 | # 格式 190 | if (( )) { 191 | } 192 | ``` 193 | 194 | ``` 195 | # 样例 196 | if ((num > 3 && num + 3 < 10)) { 197 | echo $num 198 | } 199 | ``` 200 | 201 | `{ }` 用于在当前 shell 运行命令并且判断运行结果。 202 | 203 | ``` 204 | # 格式 205 | if { } { 206 | } 207 | ``` 208 | 209 | ``` 210 | # 样例 211 | if {grep sd1 /etc/fstab} { 212 | echo good 213 | } 214 | ``` 215 | 216 | `( )` 用于在子 shell 运行命令并且判断运行结果,用法和 {} 类似,不再举例。 217 | ``` 218 | # 格式 219 | if ( ) { 220 | } 221 | ``` 222 | 223 | 这几种括号可以一起使用,这样可以同时判断字符串、数值、文件、命令结果等等。最好不要混合使用 `&&` `||`,会导致可读性变差和容易出错。 224 | 225 | ``` 226 | # 格式 227 | if [[ ]] && (( )) && { } { 228 | } 229 | ``` 230 | 231 | #### 循环语句 232 | 233 | ``` 234 | # 格式 235 | while [[ ]] { 236 | break/continue 237 | } 238 | ``` 239 | 240 | 和 `if` 一样,这里的 `[[ ]]` 可以替换成其他几种括号,功能也是一样的,不再依次举例。`break` 用于结束循环,`continue` 用于直接进入下一次循环。所有的循环语句中都可以使用 `break` 和 `continue`,下边不再赘述。 241 | 242 | ``` 243 | # 样例 死循环 244 | while ((1)) { 245 | echo good 246 | } 247 | ``` 248 | 249 | `until` 和 `while` 相反,不满足条件时运行,一旦满足则停止,其他的用法和 `while` 相同,不再举例。 250 | 251 | ``` 252 | # 格式 253 | until [[ ]] { 254 | } 255 | ``` 256 | 257 | `for` 循环主要用于枚举,这里的括号是 `for` 的特有用法,不是在子 shell 执行。括号内是字符串(可放多个,空格隔开)、数组(可放多个)或者哈希表(可放多个,哈希表是枚举值而不是键)。`i` 是用于枚举内容的变量名,变量名随意。 258 | 259 | ``` 260 | # 格式 261 | for i ( ) { 262 | } 263 | ``` 264 | 265 | ``` 266 | # 样例 267 | for i (aa bb cc) { 268 | echo $i 269 | } 270 | 271 | # 枚举当前目录的 txt 文件 272 | for i (*.txt) { 273 | echo $i 274 | } 275 | 276 | # 枚举数组 277 | array=(aa bb cc) 278 | for i ($array) { 279 | echo $i 280 | } 281 | ``` 282 | 283 | 经典的 c 风格 `for` 循环。 284 | 285 | ``` 286 | # 格式 287 | for (( ; ; )) { 288 | } 289 | ``` 290 | 291 | ``` 292 | # 样例 293 | for ((i=0; i < 10; i++)) { 294 | echo $i 295 | } 296 | ``` 297 | 298 | 这个样例只是举例,实际上多数情况不需要使用这种 `for` 循环,可以这样。 299 | 300 | ``` 301 | # 样例,{1..10} 可以生成一个 1 到 10 的数组 302 | for i ({1..10}) { 303 | echo $i 304 | } 305 | ``` 306 | 307 | `repeat` 语句用于循环固定次数,`n` 是一个整数或者内容为整数的变量。 308 | 309 | ``` 310 | # 格式 311 | repeat n { 312 | } 313 | ``` 314 | 315 | ``` 316 | # 样例 317 | repeat 5 { 318 | echo good 319 | } 320 | ``` 321 | 322 | #### 分支语句 323 | 324 | 分支逻辑用 `if` 也可以实现,但 `case` 更适合这种场景,并且功能更强大。 325 | 326 | ``` 327 | # 格式 + 样例 328 | case $i { 329 | (a) 330 | echo 1 331 | ;; 332 | 333 | (b) 334 | echo 2 335 | # 继续执行下一个 336 | ;& 337 | 338 | (c) 339 | echo 3 340 | # 继续向下匹配 341 | ;| 342 | 343 | (c) 344 | echo 33 345 | ;; 346 | 347 | (d) 348 | echo 4 349 | ;; 350 | 351 | (*) 352 | echo other 353 | ;; 354 | } 355 | ``` 356 | 357 | `;;` 代表结束 `case` 语句,`;&` 代表继续执行紧接着的下一个匹配的语句(不再进行匹配),`;|` 代表继续往下匹配看是否有满足条件的分支。 358 | 359 | #### 用户输入选择语句 360 | 361 | `select` 语句是用于根据用户的选择决定分支的语句,语法和 `for` 语句差不多,如果不 `break`,会循环让用户选择。 362 | 363 | ``` 364 | # 格式 365 | select i ( ) { 366 | } 367 | ``` 368 | 369 | ``` 370 | # 样例 371 | select i (aa bb cc) { 372 | echo $i 373 | } 374 | ``` 375 | 376 | 输出是这样的。 377 | 378 | ``` 379 | 1) aa 2) bb 3) cc 380 | ?# 381 | ``` 382 | 383 | 按上边的数字加回车来选择。 384 | 385 | #### 异常处理语句 386 | 387 | ``` 388 | # 格式 389 | { 390 | 语句 1 391 | } always { 392 | 语句 2 393 | } 394 | ``` 395 | 396 | 如果语句 1 执行出错,则执行语句 2。 397 | 398 | #### 简化的条件语句 399 | 400 | `if` 语句的简化版,在只有一个分支的情况下更简洁,功能和 `if` 语句类似,不赘述。 401 | 402 | ``` 403 | 格式: 404 | [[ ]] || { 405 | } 406 | 407 | [[ ]] && { 408 | } 409 | ``` 410 | 411 | 最好不要连续混合使用 `&&` `||`,比如。 412 | 413 | ``` 414 | aa && bb || cc && dd 415 | ``` 416 | 417 | 容易导致逻辑错误或者误解,可以用 `{ }` 把语句包含起来。 418 | 419 | ``` 420 | aa && { bb || { cc && dd } } 421 | ``` 422 | 423 | 比较复杂的判断还是用 `if` 可读写更好,`&&` `||` 通常只适用于简单的场景。 424 | 425 | ### 总结 426 | 427 | 本篇简单介绍了变量和语句的使用方法。变量部分只涉及了最基础常用的部分,后续文章会详细介绍。语句部分已经覆盖了所有需要使用的语句,实际上这些语句都不只有这一种语法,但本系列文章统一使用这个语法。但涉及到的几种括号的用法比较复杂,之后的文章也会详细介绍。 428 | -------------------------------------------------------------------------------- /03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 上一篇讲了 zsh 的常用字符串操作,这篇开始讲更为琐碎的转义字符和格式化输出相关内容。包括转义字符、引号、`print`、`printf` 的使用等等。其中很多内容没有必要记忆,作为手册参考即可。 4 | 5 | ### 转义字符 6 | 7 | 转义字符是很多编程语言中都有的概念,它主要解决某些字符因为没有对应键盘按键无法直接输出、字符本身有特殊含义(比如 `\`、`"`)或者显示不直观(比如难以区别多个空格和一个 tab)等问题。 8 | 9 | 最常用的转义字符是 `\n`(换行)、`\r`(回车)、`\t`(tab)。 10 | 11 | 直接用 `echo`、`print` 或者 `printf` 内置命令都可以正常输出转义字符,但包括转义字符的字符串需要用引号(单双引号都可以)扩起来。 12 | 13 | ``` 14 | % echo 'Hello\n\tWorld' 15 | Hello 16 | World 17 | ``` 18 | 19 | 常用转义字符对照表,不常用的可以去查 ASCII 码表,然后使用 `\xnn`(如 `\x14`)。 20 | 21 | | 转义字符 | 含义 | ASCII 码值(十六进制) | 22 | | ----- | ------ | -------------- | 23 | | \n | 换行 | 0a | 24 | | \r | 回车 | 0d | 25 | | \t | tab | 09 | 26 | | \\\\ | \ | 5c | 27 | | \\` | ` | 60 | 28 | | \\xnn | 取决于 nn | nn | 29 | 30 | 可以用 `hexdump` 命令查看字符的 ASCII 码值。 31 | 32 | ``` 33 | % echo ab= | hexdump -C 34 | 00000000 61 62 3d 0a |ab=.| 35 | 00000004 36 | ``` 37 | 38 | 还有一些字符是可选转义(通常有特殊含义的字符都是如此)的,比如空格、`"`、`'`、`*`、`~`、`$`、`&`、`(`、`)`、`[`、`]`、`{`、`}`、`;`、`?` 等等,即如果在引号里边则无需转义(即使转义也不出错,转义方法都说前边加一个 `\`),但如果在引号外边则需要转义。谨慎起见,包含半角符号的字符串全部用引号包含即可,可以避免不必要的麻烦。 39 | 40 | 可以这样检查一个字符在空格外是否需要转义,输出的字符中前边带 `\` 的都是需要的。 41 | 42 | ``` 43 | % str='~!@#$%^&*()_+-={}|[]:;<>?,./"' 44 | # -r 选项代表忽略字符串中的转义符合 45 | # ${(q)str} 功能是为字符串中的特殊符号添加转义符号 46 | % print -r ${(q)str} 47 | \~\!@\#\$%\^\&\*\(\)_+-=\{\}\|\[\]:\;\<\>\?,./\" 48 | ``` 49 | 50 | ### 单引号 51 | 52 | 单引号的左右主要是为了避免字符串里的特殊字符起作用。在单引号中,只有一个字符需要转义,转义符号 `\` 。所以如果字符串里包含特殊符号时,最好使用单引号包含起来,避免不必要的麻烦。如果字符串需要包含单引号,可以使用这几种方法。 53 | 54 | ``` 55 | # 用双引号包含 56 | % echo "a'b" 57 | a'b 58 | 59 | # 用转义符号 60 | % echo a\'b 61 | a'b 62 | 63 | # 同时使用单引号和转义符号,用于包含单引号和其他特殊符号的场景 64 | % echo 'a"\'\''b*?' 65 | a"\'b*? 66 | ``` 67 | 68 | ### 双引号 69 | 70 | 双引号的作用类似单引号,但没有单引号那么严格,有些特殊字符在双引号里可以继续起作用。 71 | 72 | ``` 73 | # 以使用变量 74 | % str=abc 75 | % echo "$str" 76 | abc 77 | 78 | # 可以使用 $( ) 运行命令 79 | % echo "$(ls)" 80 | git 81 | tmp 82 | 83 | # 可以使用 ` ` 运行命令,不建议在脚本里使用 ` ` 84 | % echo "`date`" 85 | Mon Aug 28 09:49:11 CST 2017 86 | 87 | # 可以使用 $(( )) 计算数值 88 | % echo "$((1 + 2))" 89 | 3 90 | 91 | # 可以使用 $[ ] 计算数值 92 | % echo "$[1 + 2]" 93 | 3 94 | ``` 95 | 96 | 简单说,`$` 加各种东西的用法在双引号里都是可以正常使用的,而其他特殊符号(比如 `*`、`?`、`>`)的功能通常不可用。 97 | 98 | ### 反引号 99 | 100 | 反引号是用来运行命令的,它会返回命令结果,以便保存到变量等等。 101 | 102 | ``` 103 | % str=`ls` 104 | % echo $str 105 | git 106 | tmp 107 | 108 | # 完全可以用 $( ) 取代 109 | % str=$(ls) 110 | % echo $str 111 | git 112 | tmp 113 | ``` 114 | 115 | 反引号的功能和 `$( )` 功能基本一样,但 `$( )` 可以嵌套,而反引号不可以,而且反引号看起来更费事,某些字体中的反引号和单引号差别不大。所以在脚本里不建议使用反引号。 116 | 117 | ### print 命令用法 118 | 119 | `print` 是类似 `echo` 的内部命令(`echo` 命令很简单,不作介绍),但功能比 `echo` 强大很多。完全可以使用 `print` 代替 `echo`。 120 | 121 | 不加参数的 `print` 和 `echo` 的功能基本一样,但如果字符串里包含转义字符,某些情况可能不一致。如果需要输出转义字符,尽量统一使用 `print`,避免不一致导致的麻烦。 122 | 123 | ``` 124 | % print 'Line\tone\n\Line\ttwo' 125 | Line one 126 | Line two 127 | 128 | # echo 的输出和 print 不一致 129 | % echo 'Line\tone\n\Line\ttwo' 130 | Line one 131 | \Line two 132 | ``` 133 | 134 | `print` 有很多参数,在 zsh 里输入 `print -` 然后按 tab 即可查看选项帮助(如果没有效果,需要配置 `~/.zshrc` 里的补全选项,网上有很多现成的配置)。 135 | 136 | ``` 137 | # - 后直接按 tab,C 是补全上去的 138 | % print -C 139 | -- option -- 140 | -C -- print arguments in specified number of columns 141 | -D -- substitute any arguments which are named directories using ~ notation 142 | -N -- print arguments separated and terminated by nulls 143 | ... 144 | ``` 145 | 146 | ### print 命令选项功能介绍 147 | 148 | 这里以常用程度的顺序依次介绍所有的选项,另外文末有“`print` 选项列表”方便查询。 149 | 150 | `-l` 用于分行输出字符串: 151 | 152 | ``` 153 | # 每个字符串一行,字符串列表是用空格隔开的 154 | % print -l aa bb 155 | aa 156 | bb 157 | 158 | # 也可以接数组,数组相关的内容之后会讲到 159 | # 命令后的多个字符串都可以用数组取代,效果是相同的 160 | % array=(aa bb) 161 | % print -l $array 162 | aa 163 | bb 164 | ``` 165 | 166 | `-n` 用于不在输出内容的末尾自动添加换行符(`echo` 命令也有这个用法): 167 | 168 | ``` 169 | % print abc 170 | abc 171 | # 下面输出 abc 后的 % 高亮显示,代表这一行末尾没有换行符 172 | % print -n abc 173 | abc% 174 | ``` 175 | 176 | `-m` 用于只输出匹配到的字符串: 177 | 178 | ``` 179 | % print -m "aa*" aabb abc aac 180 | aabb aac 181 | ``` 182 | 183 | `-o/-O/-i` 用于对字符串排序: 184 | 185 | ``` 186 | # print -o 对字符串升序排列 187 | % print -o a d c 1 b g 3 s 188 | 1 3 a b c d g s 189 | 190 | # print -O 对字符串降序排列 191 | % print -O a d c 1 b g 3 s 192 | s g d c b a 3 1 193 | 194 | # 加 -i 参数后,对大小写不敏感 195 | % print -oi A B C a c A B C 196 | A a A B B C c C 197 | 198 | # 不加 -i 的话小写排在大写的前面 199 | % print -o A B C a c A B C 200 | a A A B B c C C 201 | ``` 202 | 203 | `-r` 用于不对字符串进行转义。`print` 默认是会对转义字符进行转义的,加 `-r` 后会原样输出: 204 | 205 | ``` 206 | % print -r '\n' 207 | \n 208 | ``` 209 | 210 | `-c` 用于将字符串按列输出。如果对自动决定的列数不满意,可以用 `-C` 指定列数: 211 | 212 | ``` 213 | % print -c a bbbbb ccc ddddd ee ffffff gg hhhhhh ii jj kk 214 | a ccc ee gg ii kk 215 | bbbbb ddddd ffffff hhhhhh jj 216 | ``` 217 | 218 | `-C` 用于按指定列数输出字符串: 219 | 220 | ``` 221 | # 从上到下 222 | % print -C 3 a bb ccc dddd ee f 223 | a ccc ee 224 | bb dddd f 225 | 226 | % print -C 3 a bb ccc dddd ee f g 227 | a dddd g 228 | bb ee 229 | ccc f 230 | 231 | # 加 -a 后,改成从左向右 232 | % print -a -C 3 a bb ccc dddd ee f g 233 | a bb ccc 234 | dddd ee f 235 | g 236 | ``` 237 | 238 | `-D` 用于将符合条件的路径名转化成带 ~ 的格式(~ 是家目录): 239 | 240 | ``` 241 | % print -D /home/goreliu/git 242 | ~/git 243 | 244 | # mine 是这样定义的 hash -d mine='/mnt/c/mine' 245 | % print -D /mnt/c/mine 246 | ~mine 247 | ``` 248 | 249 | `-N` 用于将输出的字符串以 `\x00`(null)分隔,而不是空格。这样可能方便处理包含空格的字符串,`xargs` 等命令也可以接受以 `\x00` 分隔的字符串: 250 | 251 | ``` 252 | % print -N aa bb cc 253 | aabbcc% 254 | 255 | % print -N aa bb cc | hexdump -C 256 | 00000000 61 61 00 62 62 00 63 63 00 |aa.bb.cc.| 257 | 00000009 258 | ``` 259 | 260 | `-x` 用于将行首的 tab 替换成空格。`-x` 是将行首的 tab 展开成空格,`-x` 后的参数是一个 tab 对应的空格数: 261 | 262 | ``` 263 | % print -x 2 '\t\tabc' | hexdump -C 264 | 00000000 20 20 20 20 61 62 63 0a | abc.| 265 | 00000008 266 | 267 | % print -x 4 '\t\tabc' | hexdump -C 268 | 00000000 20 20 20 20 20 20 20 20 61 62 63 0a | abc.| 269 | 0000000c 270 | ``` 271 | 272 | `-X` 用于将所有的 tab 补全成空格。注意不是简单地替换成空格。比如每行有一个 tab,`-X 8`,那么如果 tab 前(到行首或者上一个 tab)有 5 个字符,就补全 3 个空格,凑够 8,这么做是为了对齐每一列的。但如果前边有 8 个或者 8 个以上字符,那么依然是一个 tab 替换成 8 个字符,因为 tab 不能凭空消失,一定要转成至少一个空格才行。如果没理解就自己多试试找规律吧。 273 | 274 | ``` 275 | % print -X 2 'ab\t\tabc' | hexdump -C 276 | 00000000 61 62 20 20 20 20 61 62 63 0a |ab abc.| 277 | 0000000a 278 | 279 | % print -X 4 'ab\t\tabc' | hexdump -C 280 | 00000000 61 62 20 20 20 20 20 20 61 62 63 0a |ab abc.| 281 | 0000000c 282 | ``` 283 | 284 | `-u` 用于指定文件描述符(fd)输出。`print` 默认输出到 fd 1,即 stdout,可以指定成其他 fd(2 是 stderr,其他的可以运行 `ls -l /proc/$$/fd` 查看。 285 | 286 | ``` 287 | % print -u 2 good 288 | good 289 | 290 | # 和重定向输出效果一样 291 | % print good >&2 292 | ``` 293 | 294 | `-v` 用于把输出内容保存到变量: 295 | 296 | ``` 297 | # 和 str="$(print aa bb cc)" 效果一样 298 | % print -v str aa bb cc 299 | % echo $str 300 | aa bb cc 301 | ``` 302 | 303 | `-s/-S` 用于把字符串保存到历史记录: 304 | 305 | ``` 306 | % print -s ls -a 307 | % history | tail -n 1 308 | 2222 ls -a 309 | 310 | # -S 也类似,但需要用引号把命令引起来 311 | % print -S "ls -a" 312 | % history | tail -n 1 313 | 2339 ls -a 314 | ``` 315 | 316 | `-z` 用于把字符串输出到命令行编辑区: 317 | 318 | ``` 319 | # _是光标位置 320 | % print -z aa bb cc 321 | % aa bb cc_ 322 | ``` 323 | 324 | `-f` 用于按指定格式化字符串输出,同 `printf`,用法见“`printf` 命令用法”。 325 | 326 | `-P` 用于输出带颜色和特殊样式的字符串,见“输出带颜色和特殊样式的字符串”。 327 | 328 | `-b` 用于辨认出 bindkey 中的转义字符串,bindkey 是 Zle 的快捷键配置内容,写脚本用不到,不作介绍。 329 | 330 | `-R` 用于模拟 `echo` 命令,只支持 `-n` 和 `-e` 选项,通常用不到。 331 | 332 | ### printf 命令用法 333 | 334 | `printf` 命令很像 c 语言的 `printf` 函数,用于输出格式化后的字符串: 335 | 336 | ``` 337 | # 末尾输出高亮的 % 代表该行末尾没有换行符 338 | # printf 不会在输出末尾自动添加换行符 339 | # 为了避免误解,之后的例子省略该 % 符号 340 | % printf ":%d %f:" 12 34.56 341 | :12 34.560000:% 342 | ``` 343 | 344 | `printf` 的第一个参数是格式化字符串,在 zsh 里输入 `printf %` 后按 tab,可以看到所有支持的用法。下面只举几个比较常用的例子: 345 | 346 | ``` 347 | # 整数 浮点数 字符串 348 | % printf "%d %f %s" 12 12.34 abcd 349 | 12 12.340000 abcd% 350 | 351 | # 取小数点后 1 位 352 | % printf "%.1f" 12.34 353 | 12.3 354 | 355 | # 科学计数法输出浮点数 356 | % printf "%e" 12.34 357 | 1.234000e+01 358 | 359 | # 将十进制数字转成十六进制输出 360 | % printf "%x" 12 361 | c 362 | 363 | # 补齐空格或者补齐 0 364 | % printf "%5d\n%05d" 12 12 365 | 12 366 | 00012 367 | ``` 368 | 369 | 我把完整的格式贴在这里,方便搜索: 370 | 371 | ``` 372 | -- print format specifier -- 373 | -- leave one space in front of positive number from signed conversion 374 | - -- left adjust result 375 | . -- precision 376 | ' -- thousand separators 377 | * -- field width in next argument 378 | # -- alternate form 379 | % -- a percent sign 380 | + -- always place sign before a number from signed conversion 381 | 0 -- zero pad to length 382 | b -- as %s but interpret escape sequences in argument 383 | c -- print the first character of the argument 384 | E e -- double number in scientific notation 385 | f -- double number 386 | G g -- double number as %f or %e depending on size 387 | i d -- signed decimal number or with leading " numeric value of following character 388 | n -- store number of printed bytes in parameter specified by argument 389 | o -- unsigned octal number 390 | q -- as %s but shell quote result 391 | s -- print the argument as a string 392 | u -- unsigned decimal number 393 | X x -- unsigned hexadecimal number, letters capitalized as x 394 | ``` 395 | 396 | ### 输出带颜色和特殊样式的字符串 397 | 398 | 用 zsh 的 `print -P` 可以方便地输出带颜色和特殊样式的字符串,不用再和 `\033[41;36;1m` 之类莫名其妙的字符串打交道了。 399 | 400 | ``` 401 | # %B 加粗 %b 取消加粗 402 | # %F{red} 前景色 %f 取消前景色 403 | # %K{red} 背景色 %k 取消背景色 404 | # %U 下滑线 %u 取消下滑线 405 | # %S 反色 %s 取消反色 406 | # 407 | # black or 0 red or 1 408 | # green or 2 yellow or 3 409 | # blue or 4 magenta or 5 410 | # cyan or 6 white or 7 411 | 412 | # 显示加粗的红色 abc 413 | % print -P '%B%F{red}abc' 414 | abc 415 | 416 | # 没覆盖到的功能可以用原始的转义符号,可读性比较差 417 | # 4[0-7] 背景色 418 | # 3[0-7] 前景色 419 | # 0m 正常 1m 加粗 2m 变灰 3m 斜体 4m 下滑钱 5m 闪烁 6m 快速闪烁 7m 反色 420 | 421 | # 显示闪烁的红底绿字 abc 422 | % print "\033[41;32;5mabc\033[0m" 423 | abc 424 | ``` 425 | 426 | ### print 选项列表 427 | 428 | 为了方便查询,我把 `print` 的选项列表放在这里: 429 | 430 | | 选项 | 功能 | 参数 | 431 | | ---- | ------------------------- | --------- | 432 | | -C | 按列输出 | 列数 | 433 | | -D | 替换路径成带 `~` 的版本 | 无 | 434 | | -N | 使用 `\x00` 作为字符串的间隔 | 无 | 435 | | -O | 降序排列 | 无 | 436 | | -P | 输出颜色和特殊样式 | 无 | 437 | | -R | 模拟 `echo` 命令 | 无 | 438 | | -S | 放命令放入历史命令文件(要加引号) | 无 | 439 | | -X | 替换所有 tab 为空格 | tab 对应空格数 | 440 | | -a | 和 `-c`/`-C` 一起使用时,改为从左到右 | 无 | 441 | | -b | 识别出 bindkey 转义字符串 | 无 | 442 | | -c | 按列输出(自动决定列数) | 无 | 443 | | -f | 同 `printf` | 无 | 444 | | -i | 和 `-o`/`-O` 一起用时,大小写不敏感排序 | 无 | 445 | | -l | 使用换行符作为字符串分隔符 | 无 | 446 | | -m | 只输出匹配的字符串 | 匹配模式字符串 | 447 | | -n | 不自动添加最后的换行符 | 无 | 448 | | -o | 升序排列 | 无 | 449 | | -r | 不处理转义字符 | 无 | 450 | | -s | 放命令放入历史命令文件(不加引号) | 无 | 451 | | -u | 指定 fd 输出 | fd 号 | 452 | | -v | 把内容保存到变量 | 变量名 | 453 | | -x | 替换行首的 tab 为空格 | tab 对应空格数 | 454 | | -z | 把内容放置到命令行编辑区 | 无 | 455 | 456 | ### 参考 457 | 458 | http://zsh.sourceforge.net/Guide/zshguide05.html 459 | -------------------------------------------------------------------------------- /00_Zsh-开发指南(目录).md: -------------------------------------------------------------------------------- 1 | ## [第一篇 变量和语句](01_Zsh-开发指南(第一篇-变量和语句).md) 2 | ### [导读](01_Zsh-开发指南(第一篇-变量和语句).md#导读) 3 | ### [为什么用 zsh 写脚本](01_Zsh-开发指南(第一篇-变量和语句).md#为什么用-zsh-写脚本) 4 | ### [Zsh 脚本样例](01_Zsh-开发指南(第一篇-变量和语句).md#zsh-脚本样例) 5 | ### [为什么要使用 shell 脚本语言](01_Zsh-开发指南(第一篇-变量和语句).md#为什么要使用-shell-脚本语言) 6 | ### [格式约定](01_Zsh-开发指南(第一篇-变量和语句).md#格式约定) 7 | ### [变量](01_Zsh-开发指南(第一篇-变量和语句).md#变量) 8 | - [变量定义](01_Zsh-开发指南(第一篇-变量和语句).md#变量定义) 9 | - [变量比较](01_Zsh-开发指南(第一篇-变量和语句).md#变量比较) 10 | ### [语句](01_Zsh-开发指南(第一篇-变量和语句).md#语句) 11 | - [条件语句](01_Zsh-开发指南(第一篇-变量和语句).md#条件语句) 12 | - [循环语句](01_Zsh-开发指南(第一篇-变量和语句).md#循环语句) 13 | - [分支语句](01_Zsh-开发指南(第一篇-变量和语句).md#分支语句) 14 | - [用户输入选择语句](01_Zsh-开发指南(第一篇-变量和语句).md#用户输入选择语句) 15 | - [异常处理语句](01_Zsh-开发指南(第一篇-变量和语句).md#异常处理语句) 16 | - [简化的条件语句](01_Zsh-开发指南(第一篇-变量和语句).md#简化的条件语句) 17 | ### [总结](01_Zsh-开发指南(第一篇-变量和语句).md#总结) 18 | 19 | --- 20 | 21 | ## [第二篇 字符串处理之常用操作](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md) 22 | ### [导读](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#导读) 23 | ### [字符串长度](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串长度) 24 | ### [字符串拼接](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串拼接) 25 | ### [字符串切片](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串切片) 26 | ### [字符串截断](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串截断) 27 | ### [字符串查找](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串查找) 28 | ### [遍历字符](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#遍历字符) 29 | ### [字符串替换](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串替换) 30 | ### [判断字符串变量是否存在](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#判断字符串变量是否存在) 31 | ### [字符串匹配判断](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串匹配判断) 32 | ### [大小写转换](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#大小写转换) 33 | ### [目录文件名截取](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#目录文件名截取) 34 | ### [字符串分隔](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#字符串分隔) 35 | ### [多行字符串](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#多行字符串) 36 | ### [读取文件内容到字符串](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#读取文件内容到字符串) 37 | ### [读取进程输出到字符串](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#读取进程输出到字符串) 38 | ### [参考](02_Zsh-开发指南(第二篇-字符串处理之常用操作).md#参考) 39 | 40 | --- 41 | 42 | ## [第三篇 字符串处理之转义字符和格式化输出](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md) 43 | ### [导读](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#导读) 44 | ### [转义字符](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#转义字符) 45 | ### [单引号](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#单引号) 46 | ### [双引号](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#双引号) 47 | ### [反引号](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#反引号) 48 | ### [print 命令用法](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#print-命令用法) 49 | ### [print 命令选项功能介绍](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#print-命令选项功能介绍) 50 | ### [printf 命令用法](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#printf-命令用法) 51 | ### [输出带颜色和特殊样式的字符串](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#输出带颜色和特殊样式的字符串) 52 | ### [print 选项列表](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#print-选项列表) 53 | ### [参考](03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md#参考) 54 | 55 | --- 56 | 57 | ## [第四篇 字符串处理之通配符](04_Zsh-开发指南(第四篇-字符串处理之通配符).md) 58 | ### [导读](04_Zsh-开发指南(第四篇-字符串处理之通配符).md#导读) 59 | ### [通配符的基本用法](04_Zsh-开发指南(第四篇-字符串处理之通配符).md#通配符的基本用法) 60 | ### [加强版通配符](04_Zsh-开发指南(第四篇-字符串处理之通配符).md#加强版通配符) 61 | ### [总结](04_Zsh-开发指南(第四篇-字符串处理之通配符).md#总结) 62 | ### [参考](04_Zsh-开发指南(第四篇-字符串处理之通配符).md#参考) 63 | 64 | --- 65 | 66 | ## [第五篇 数组](05_Zsh-开发指南(第五篇-数组).md) 67 | ### [导读](05_Zsh-开发指南(第五篇-数组).md#导读) 68 | ### [数组定义](05_Zsh-开发指南(第五篇-数组).md#数组定义) 69 | ### [元素读写](05_Zsh-开发指南(第五篇-数组).md#元素读写) 70 | ### [数组拼接](05_Zsh-开发指南(第五篇-数组).md#数组拼接) 71 | ### [数组遍历](05_Zsh-开发指南(第五篇-数组).md#数组遍历) 72 | ### [数组切片](05_Zsh-开发指南(第五篇-数组).md#数组切片) 73 | ### [元素查找](05_Zsh-开发指南(第五篇-数组).md#元素查找) 74 | ### [元素排序](05_Zsh-开发指南(第五篇-数组).md#元素排序) 75 | ### [去除重复元素](05_Zsh-开发指南(第五篇-数组).md#去除重复元素) 76 | ### [使用连续字符或者数值构造数组](05_Zsh-开发指南(第五篇-数组).md#使用连续字符或者数值构造数组) 77 | ### [从字符串构造数组](05_Zsh-开发指南(第五篇-数组).md#从字符串构造数组) 78 | ### [从文件构造数组](05_Zsh-开发指南(第五篇-数组).md#从文件构造数组) 79 | ### [从文件列表构造数组](05_Zsh-开发指南(第五篇-数组).md#从文件列表构造数组) 80 | ### [数组交集差集](05_Zsh-开发指南(第五篇-数组).md#数组交集差集) 81 | ### [数组交叉合并](05_Zsh-开发指南(第五篇-数组).md#数组交叉合并) 82 | ### [对数组中的字符串进行统一的处理](05_Zsh-开发指南(第五篇-数组).md#对数组中的字符串进行统一的处理) 83 | ### [总结](05_Zsh-开发指南(第五篇-数组).md#总结) 84 | ### [参考](05_Zsh-开发指南(第五篇-数组).md#参考) 85 | ### [更新历史](05_Zsh-开发指南(第五篇-数组).md#更新历史) 86 | 87 | --- 88 | 89 | ## [第六篇 哈希表](06_Zsh-开发指南(第六篇-哈希表).md) 90 | ### [导读](06_Zsh-开发指南(第六篇-哈希表).md#导读) 91 | ### [哈希表定义](06_Zsh-开发指南(第六篇-哈希表).md#哈希表定义) 92 | ### [元素读写](06_Zsh-开发指南(第六篇-哈希表).md#元素读写) 93 | ### [哈希表拼接](06_Zsh-开发指南(第六篇-哈希表).md#哈希表拼接) 94 | ### [哈希表遍历](06_Zsh-开发指南(第六篇-哈希表).md#哈希表遍历) 95 | ### [元素查找](06_Zsh-开发指南(第六篇-哈希表).md#元素查找) 96 | ### [元素排序](06_Zsh-开发指南(第六篇-哈希表).md#元素排序) 97 | ### [从字符串、文件构造哈希表](06_Zsh-开发指南(第六篇-哈希表).md#从字符串文件构造哈希表) 98 | ### [对哈希表中的每个元素统一处理](06_Zsh-开发指南(第六篇-哈希表).md#对哈希表中的每个元素统一处理) 99 | ### [总结](06_Zsh-开发指南(第六篇-哈希表).md#总结) 100 | 101 | --- 102 | 103 | ## [第七篇 数值计算](07_Zsh-开发指南(第七篇-数值计算).md) 104 | ### [导读](07_Zsh-开发指南(第七篇-数值计算).md#导读) 105 | ### [整数和浮点数类型](07_Zsh-开发指南(第七篇-数值计算).md#整数和浮点数类型) 106 | ### [运算符](07_Zsh-开发指南(第七篇-数值计算).md#运算符) 107 | ### [数学函数](07_Zsh-开发指南(第七篇-数值计算).md#数学函数) 108 | ### [参考](07_Zsh-开发指南(第七篇-数值计算).md#参考) 109 | 110 | --- 111 | 112 | ## [第八篇 变量修饰语](08_Zsh-开发指南(第八篇-变量修饰语).md) 113 | ### [导读](08_Zsh-开发指南(第八篇-变量修饰语).md#导读) 114 | ### [变量修饰语的格式](08_Zsh-开发指南(第八篇-变量修饰语).md#变量修饰语的格式) 115 | ### [变量默认值](08_Zsh-开发指南(第八篇-变量修饰语).md#变量默认值) 116 | ### [数组拼接成字符串](08_Zsh-开发指南(第八篇-变量修饰语).md#数组拼接成字符串) 117 | ### [字符串切分成数组](08_Zsh-开发指南(第八篇-变量修饰语).md#字符串切分成数组) 118 | ### [输出变量类型](08_Zsh-开发指南(第八篇-变量修饰语).md#输出变量类型) 119 | ### [字符串、数组或哈希表嵌套取值](08_Zsh-开发指南(第八篇-变量修饰语).md#字符串数组或哈希表嵌套取值) 120 | ### [字符串内容作为变量名再取值](08_Zsh-开发指南(第八篇-变量修饰语).md#字符串内容作为变量名再取值) 121 | ### [对齐或截断数组中的字符串](08_Zsh-开发指南(第八篇-变量修饰语).md#对齐或截断数组中的字符串) 122 | ### [总结](08_Zsh-开发指南(第八篇-变量修饰语).md#总结) 123 | ### [参考](08_Zsh-开发指南(第八篇-变量修饰语).md#参考) 124 | 125 | --- 126 | 127 | ## [第九篇 函数和脚本](09_Zsh-开发指南(第九篇-函数和脚本).md) 128 | ### [导读](09_Zsh-开发指南(第九篇-函数和脚本).md#导读) 129 | ### [函数定义](09_Zsh-开发指南(第九篇-函数和脚本).md#函数定义) 130 | ### [参数处理](09_Zsh-开发指南(第九篇-函数和脚本).md#参数处理) 131 | ### [函数嵌套](09_Zsh-开发指南(第九篇-函数和脚本).md#函数嵌套) 132 | ### [返回值](09_Zsh-开发指南(第九篇-函数和脚本).md#返回值) 133 | ### [局部变量](09_Zsh-开发指南(第九篇-函数和脚本).md#局部变量) 134 | ### [脚本](09_Zsh-开发指南(第九篇-函数和脚本).md#脚本) 135 | ### [exit 命令](09_Zsh-开发指南(第九篇-函数和脚本).md#exit-命令) 136 | ### [用 getopts 命令处理命令行选项](09_Zsh-开发指南(第九篇-函数和脚本).md#用-getopts-命令处理命令行选项) 137 | ### [总结](09_Zsh-开发指南(第九篇-函数和脚本).md#总结) 138 | ### [参考](09_Zsh-开发指南(第九篇-函数和脚本).md#参考) 139 | ### [更新历史](09_Zsh-开发指南(第九篇-函数和脚本).md#更新历史) 140 | 141 | --- 142 | 143 | ## [第十篇 文件查找和批量处理](10_Zsh-开发指南(第十篇-文件查找和批量处理).md) 144 | ### [导读](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#导读) 145 | ### [简单例子](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#简单例子) 146 | ### [按文件属性查找](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#按文件属性查找) 147 | ### [通配符修饰语列表](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#通配符修饰语列表) 148 | ### [更复杂的用法](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#更复杂的用法) 149 | - [按文件时间查找文件](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#按文件时间查找文件) 150 | - [按文件大小查找文件](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#按文件大小查找文件) 151 | - [文件排序](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#文件排序) 152 | - [组合使用](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#组合使用) 153 | ### [文件批量重命名](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#文件批量重命名) 154 | ### [不展开通配符](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#不展开通配符) 155 | ### [总结](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#总结) 156 | ### [参考](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#参考) 157 | ### [更新历史](10_Zsh-开发指南(第十篇-文件查找和批量处理).md#更新历史) 158 | 159 | --- 160 | 161 | ## [第十一篇 变量的进阶内容](11_Zsh-开发指南(第十一篇-变量的进阶内容).md) 162 | ### [导读](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#导读) 163 | ### [typeset 命令](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#typeset-命令) 164 | ### [强制字符串内容为小写或者大写](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#强制字符串内容为小写或者大写) 165 | ### [设置变量为环境变量](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#设置变量为环境变量) 166 | ### [设置变量为只读变量](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#设置变量为只读变量) 167 | ### [设置数组不包含重复元素](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#设置数组不包含重复元素) 168 | ### [设置整数的位数](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#设置整数的位数) 169 | ### [进制转换](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#进制转换) 170 | ### [同时对多个变量赋相同的值](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#同时对多个变量赋相同的值) 171 | ### [绑定字符串和数组](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#绑定字符串和数组) 172 | ### [显示变量的定义方式](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#显示变量的定义方式) 173 | ### [什么地方该加双引号](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#什么地方该加双引号) 174 | ### [总结](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#总结) 175 | ### [参考](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#参考) 176 | ### [更新历史](11_Zsh-开发指南(第十一篇-变量的进阶内容).md#更新历史) 177 | 178 | --- 179 | 180 | ## [第十二篇 [[ ]] 的用法](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md) 181 | ### [导读](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#导读) 182 | ### [比较字符串](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#比较字符串) 183 | ### [判断文件](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#判断文件) 184 | ### [比较文件](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#比较文件) 185 | ### [比较数值](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#比较数值) 186 | ### [组合使用](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#组合使用) 187 | ### [[ ] 符号](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#[-]-符号) 188 | ### [总结](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#总结) 189 | ### [参考](12_Zsh-开发指南(第十二篇-[[-]]-的用法).md#参考) 190 | 191 | --- 192 | 193 | ## [第十三篇 管道和重定向](13_Zsh-开发指南(第十三篇-管道和重定向).md) 194 | ### [导读](13_Zsh-开发指南(第十三篇-管道和重定向).md#导读) 195 | ### [管道](13_Zsh-开发指南(第十三篇-管道和重定向).md#管道) 196 | ### [关于管道的更多细节](13_Zsh-开发指南(第十三篇-管道和重定向).md#关于管道的更多细节) 197 | ### [重定向](13_Zsh-开发指南(第十三篇-管道和重定向).md#重定向) 198 | ### [更多重定向的用法](13_Zsh-开发指南(第十三篇-管道和重定向).md#更多重定向的用法) 199 | ### [命名管道](13_Zsh-开发指南(第十三篇-管道和重定向).md#命名管道) 200 | ### [exec 命令的用法](13_Zsh-开发指南(第十三篇-管道和重定向).md#exec-命令的用法) 201 | ### [总结](13_Zsh-开发指南(第十三篇-管道和重定向).md#总结) 202 | ### [参考](13_Zsh-开发指南(第十三篇-管道和重定向).md#参考) 203 | ### [更新历史](13_Zsh-开发指南(第十三篇-管道和重定向).md#更新历史) 204 | 205 | --- 206 | 207 | ## [第十四篇 文件读写](14_Zsh-开发指南(第十四篇-文件读写).md) 208 | ### [导读](14_Zsh-开发指南(第十四篇-文件读写).md#导读) 209 | ### [写文件](14_Zsh-开发指南(第十四篇-文件读写).md#写文件) 210 | - [创建文件](14_Zsh-开发指南(第十四篇-文件读写).md#创建文件) 211 | - [清空文件](14_Zsh-开发指南(第十四篇-文件读写).md#清空文件) 212 | - [删除文件](14_Zsh-开发指南(第十四篇-文件读写).md#删除文件) 213 | - [多行文本写入](14_Zsh-开发指南(第十四篇-文件读写).md#多行文本写入) 214 | - [用 mapfile 读写文件](14_Zsh-开发指南(第十四篇-文件读写).md#用-mapfile-读写文件) 215 | - [从文件中间位置写入](14_Zsh-开发指南(第十四篇-文件读写).md#从文件中间位置写入) 216 | ### [读文件](14_Zsh-开发指南(第十四篇-文件读写).md#读文件) 217 | - [读取整个文件](14_Zsh-开发指南(第十四篇-文件读写).md#读取整个文件) 218 | - [按行遍历文件](14_Zsh-开发指南(第十四篇-文件读写).md#按行遍历文件) 219 | - [读取指定行](14_Zsh-开发指南(第十四篇-文件读写).md#读取指定行) 220 | - [读取文件到数组](14_Zsh-开发指南(第十四篇-文件读写).md#读取文件到数组) 221 | - [读取指定数量的字符](14_Zsh-开发指南(第十四篇-文件读写).md#读取指定数量的字符) 222 | - [向文件中间插入内容](14_Zsh-开发指南(第十四篇-文件读写).md#向文件中间插入内容) 223 | ### [总结](14_Zsh-开发指南(第十四篇-文件读写).md#总结) 224 | 225 | --- 226 | 227 | ## [第十五篇 进程与作业控制](15_Zsh-开发指南(第十五篇-进程与作业控制).md) 228 | ### [导读](15_Zsh-开发指南(第十五篇-进程与作业控制).md#导读) 229 | ### [在子进程中执行代码](15_Zsh-开发指南(第十五篇-进程与作业控制).md#在子进程中执行代码) 230 | ### [在后台运行进程](15_Zsh-开发指南(第十五篇-进程与作业控制).md#在后台运行进程) 231 | ### [在脚本中使用后台进程执行代码](15_Zsh-开发指南(第十五篇-进程与作业控制).md#在脚本中使用后台进程执行代码) 232 | ### [信号](15_Zsh-开发指南(第十五篇-进程与作业控制).md#信号) 233 | ### [总结](15_Zsh-开发指南(第十五篇-进程与作业控制).md#总结) 234 | 235 | --- 236 | 237 | ## [第十六篇 alias 和 eval 的用法](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md) 238 | ### [导读](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md#导读) 239 | ### [alias](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md#alias) 240 | ### [eval](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md#eval) 241 | ### [总结](16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md#总结) 242 | 243 | --- 244 | 245 | ## [第十七篇 使用 socket 文件和 TCP 实现进程间通信](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md) 246 | ### [导读](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md#导读) 247 | ### [Socket 文件](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md#socket-文件) 248 | ### [TCP](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md#tcp) 249 | ### [程序样例](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md#程序样例) 250 | ### [总结](17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md#总结) 251 | 252 | --- 253 | 254 | ## [第十八篇 更多内置模块的用法](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md) 255 | ### [导读](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#导读) 256 | ### [模块的使用方法](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#模块的使用方法) 257 | ### [日期时间相关模块](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#日期时间相关模块) 258 | ### [读写 gdbm 数据库](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#读写-gdbm-数据库) 259 | ### [调度命令](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#调度命令) 260 | ### [底层的文件读写命令](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#底层的文件读写命令) 261 | ### [其他模块](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#其他模块) 262 | ### [自己编写模块](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#自己编写模块) 263 | ### [总结](18_Zsh-开发指南(第十八篇-更多内置模块的用法).md#总结) 264 | 265 | --- 266 | 267 | ## [第十九篇 脚本实例讲解](19_Zsh-开发指南(第十九篇-脚本实例讲解).md) 268 | ### [导读](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#导读) 269 | ### [实例一:复制一个目录的目录结构](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例一复制一个目录的目录结构) 270 | ### [实例二:寻找不配对的文件](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例二寻找不配对的文件) 271 | ### [实例三:用 sed 批量重命名文件](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例三用-sed-批量重命名文件) 272 | ### [实例四:根据文件的 md5 删除重复文件](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例四根据文件的-md5-删除重复文件) 273 | ### [实例五:转换 100 以内的汉字数字为阿拉伯数字](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例五转换-100-以内的汉字数字为阿拉伯数字) 274 | ### [实例六:为带中文汉字数字的文件名重命名成以对应数字开头](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例六为带中文汉字数字的文件名重命名成以对应数字开头) 275 | ### [实例七:统一压缩解压工具](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例七统一压缩解压工具) 276 | ### [实例八:方便并发运行命令的工具](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例八方便并发运行命令的工具) 277 | ### [实例九:批量转换图片格式](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#实例九批量转换图片格式) 278 | ### [总结](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#总结) 279 | ### [更新历史](19_Zsh-开发指南(第十九篇-脚本实例讲解).md#更新历史) 280 | 281 | --- 282 | 283 | ## [第二十篇 代码风格](20_Zsh-开发指南(第二十篇-代码风格).md) 284 | ### [导读](20_Zsh-开发指南(第二十篇-代码风格).md#导读) 285 | ### [缩进](20_Zsh-开发指南(第二十篇-代码风格).md#缩进) 286 | ### [每行代码最多字符数](20_Zsh-开发指南(第二十篇-代码风格).md#每行代码最多字符数) 287 | ### [折行](20_Zsh-开发指南(第二十篇-代码风格).md#折行) 288 | ### [空格](20_Zsh-开发指南(第二十篇-代码风格).md#空格) 289 | ### [空行](20_Zsh-开发指南(第二十篇-代码风格).md#空行) 290 | ### [括号](20_Zsh-开发指南(第二十篇-代码风格).md#括号) 291 | ### [常量](20_Zsh-开发指南(第二十篇-代码风格).md#常量) 292 | ### [变量](20_Zsh-开发指南(第二十篇-代码风格).md#变量) 293 | ### [引号](20_Zsh-开发指南(第二十篇-代码风格).md#引号) 294 | ### [函数](20_Zsh-开发指南(第二十篇-代码风格).md#函数) 295 | ### [脚本行数](20_Zsh-开发指南(第二十篇-代码风格).md#脚本行数) 296 | ### [语句风格](20_Zsh-开发指南(第二十篇-代码风格).md#语句风格) 297 | ### [总结](20_Zsh-开发指南(第二十篇-代码风格).md#总结) 298 | 299 | --- 300 | 301 | ## [第二十一篇 测试方法以及编写可测试代码的方法](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md) 302 | ### [导读](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#导读) 303 | ### [单元测试](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#单元测试) 304 | ### [单个脚本的功能测试](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#单个脚本的功能测试) 305 | ### [功能测试示例](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#功能测试示例) 306 | ### [集成测试](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#集成测试) 307 | ### [系统测试](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#系统测试) 308 | ### [总结](21_Zsh-开发指南(第二十一篇-测试方法以及编写可测试代码的方法).md#总结) 309 | 310 | --- 311 | 312 | ## [第二十二篇 Bash 和 zsh 用法简明对照表](22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md) 313 | ### [导读](22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md#导读) 314 | ### [Bash 和 zsh 用法简明对照表](22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md#bash-和-zsh-用法简明对照表) 315 | ### [总结](22_Zsh-开发指南(第二十二篇-Bash-和-zsh-用法简明对照表).md#总结) 316 | 317 | --- 318 | 319 | -------------------------------------------------------------------------------- /19_Zsh-开发指南(第十九篇-脚本实例讲解).md: -------------------------------------------------------------------------------- 1 | ### 导读 2 | 3 | 本文将讲解一些比较简单的 zsh 脚本实例。 4 | 5 | ### 实例一:复制一个目录的目录结构 6 | 7 | 功能: 8 | 9 | 将一个目录及它下边的所有目录复制到另一个目录中(即创建同名目录),但不复制目录下的其他类型文件。 10 | 11 | 例子: 12 | 13 | ``` 14 | src 的目录结构: 15 | 16 | src 17 | ├── a 18 | ├── b 19 | │   ├── 1.txt 20 | │   └── 2 21 | │   └── 3.txt 22 | ├── c.txt 23 | ├── d 24 | ├── e f 25 | │   └── g 26 | │   └── 4.txt 27 | └── g h -> e f 28 | 29 | 要构造一个 dst 目录,只包含 src 下的目录,内容如下: 30 | 31 | dst 32 | └── src 33 | ├── a 34 | ├── b 35 | │   └── 2 36 | ├── d 37 | └── e f 38 | └── g 39 | ``` 40 | 41 | 思路: 42 | 43 | 1. 首先需要先将 src 目录下的目录名筛选出来,可以用 `**/*(/)` 匹配。 44 | 2. 然后用 `mkdir -p` 在 dst 目录中创建对应的目录。 45 | 46 | ``` 47 | # 参数 1:src 目录 48 | # 参数 2:待创建的 dst 目录 49 | 50 | #!/bin/zsh 51 | 52 | for i ($1/**/*(/)) { 53 | # -p 参数是递归创建目录,这样不用考虑目录的创建顺序 54 | mkdir -p $2/$i 55 | } 56 | ``` 57 | 58 | ### 实例二:寻找不配对的文件 59 | 60 | 功能: 61 | 62 | 需要当前目录下有一些 .txt 和 .txt.md5sum 的文件,需要寻找出没有对应的 .md5sum 文件的 .txt 文件。(实际的场景是寻找已经下载完成的文件,未下载完的文件都对应某个带后缀的文件。) 63 | 64 | 例子: 65 | 66 | ``` 67 | 当前目录的所有文件: 68 | 69 | aa.txt 70 | bb.txt 71 | bb.txt.md5sum 72 | cc dd.txt 73 | cc dd.txt.md5sum 74 | ee ff.txt.md5sum 75 | gg.txt 76 | hh ii.txt 77 | 78 | 需要找出没有对应 .md5sum 的 .txt 文件: 79 | aa.txt 80 | gg.txt 81 | hh ii.txt 82 | ``` 83 | 84 | 思路: 85 | 86 | 1. 找到所有 .md5sum 文件,然后把文件名中的 .md5sum 去掉,即为那些需要排除的 .txt 文件(a)。 87 | 2. 所有的文件,排除掉 .m5sum 文件,再排除掉 a,即结果。 88 | 89 | 实现: 90 | 91 | ``` 92 | #!/bin/zsh 93 | 94 | all_files=(*) 95 | bad_files=(*.md5sum) 96 | bad_files+=(${bad_files/.md5sum}) 97 | 98 | # 数组差集操作 99 | echo ${all_files:|bad_files} 100 | ``` 101 | 102 | ### 实例三:用 sed 批量重命名文件 103 | 104 | 功能: 105 | 106 | 用形如 sed 命令的用法批量重命名文件。 107 | 108 | 例子: 109 | 110 | ``` 111 | # 实现 renamex 命令,接受的第一个参数为 sed 的主体参数,其余参数是文件列表 112 | # 效果是根据 sed 对文件名的修改重命名这些文件 113 | 114 | % tree 115 | . 116 | ├── aaa_aaa.txt 117 | ├── aaa.txt 118 | ├── ccc.txt 119 | └── xxx 120 | ├── aaa bbb.txt 121 | └── bbb ccc.txt 122 | 123 | % renamex s/aaa/bbb/g **/* 124 | 'aaa_aaa.txt' -> 'bbb_bbb.txt' 125 | 'aaa.txt' -> 'bbb.txt' 126 | 'xxx/aaa bbb.txt' -> 'xxx/bbb bbb.txt' 127 | 128 | % tree 129 | . 130 | ├── bbb_bbb.txt 131 | ├── bbb.txt 132 | ├── ccc.txt 133 | └── xxx 134 | ├── bbb bbb.txt 135 | └── bbb ccc.txt 136 | ``` 137 | 138 | 思路: 139 | 140 | 1. 要找出所有的文件名,然后用 sed 替换成新文件名。 141 | 2. 如果文件名有变化,用 mv 命令移动 142 | 143 | 实现: 144 | 145 | ``` 146 | #!/bin/zsh 147 | 148 | (($+2)) || { 149 | echo 'Usage: renamex s/aaa/bbb/g *.txt' 150 | return 151 | } 152 | 153 | for name ($*[2,-1]) { 154 | local new_name="$(echo $name | sed $1)" 155 | [[ $name == $new_name ]] && continue 156 | mv -v $name $new_name 157 | } 158 | ``` 159 | 160 | ### 实例四:根据文件的 md5 删除重复文件 161 | 162 | 功能: 163 | 164 | 删除当前目录以及子目录下所有的重复文件(根据 md5 判断,不是很严谨)。 165 | 166 | 思路: 167 | 168 | 1. 用 md5sum 命令计算所有文件的 md5。 169 | 2. 使用哈希表判断 md5 是否重复,删除哈希表里已经有 md5 的后续文件。 170 | 171 | 实现: 172 | 173 | ``` 174 | #!/bin/zsh 175 | 176 | # D 是包含以 . 开头的隐藏文件 177 | local files=("${(f)$(md5sum **/*(.D))}") 178 | local files_to_delete=() 179 | local -A md5s 180 | 181 | for i ($files) { 182 | # 取前 32 位,即 md5 的长度 183 | local md5=$i[1,32] 184 | 185 | if (($+md5s[$md5])) { 186 | # 取 35 位之后的内容,即文件路径,md5 后边有两个空格 187 | files_to_delete+=($i[35,-1]) 188 | } else { 189 | md5s[$md5]=1 190 | } 191 | } 192 | 193 | (($#files_to_delete)) && rm -v $files_to_delete 194 | ``` 195 | 196 | ### 实例五:转换 100 以内的汉字数字为阿拉伯数字 197 | 198 | 功能: 199 | 200 | 转换 100 以内的汉字数字为阿拉伯数字,如六十八转换成 68。 201 | 202 | 思路: 203 | 204 | 1. 建一个哈希表存放汉字与数字的对应关系。 205 | 2. 比较麻烦的是“十”,在不同的位置,转换成的数字不同,需要分别处理。 206 | 207 | 实现: 208 | 209 | ``` 210 | #!/bin/zsh 211 | 212 | local -A table=( 213 | 零 0 214 | 一 1 215 | 二 2 216 | 三 3 217 | 四 4 218 | 五 5 219 | 六 6 220 | 七 7 221 | 八 8 222 | 九 9 223 | ) 224 | 225 | local result 226 | 227 | if [[ $1 == 十 ]] { 228 | result=一零 229 | } elif [[ $1 == 十* ]] { 230 | result=${1/十/一} 231 | } elif [[ $1 == *十 ]] { 232 | result=${1/十/零} 233 | } elif [[ $1 == *十* ]] { 234 | result=${1/十} 235 | } else { 236 | result=$1 237 | } 238 | 239 | for i ({1..$#result}) { 240 | result[i]=$table[$result[i]] 241 | 242 | if [[ -z $result[i] ]] { 243 | echo error 244 | return 1 245 | } 246 | } 247 | 248 | echo $result 249 | 250 | 运行结果: 251 | 252 | % ./convert 一 253 | 1 254 | % ./convert 十 255 | 10 256 | % ./convert 十五 257 | 15 258 | % ./convert 二十 259 | 20 260 | % ./convert 五十六 261 | 56 262 | % ./convert 一百 263 | error 264 | ``` 265 | 266 | ### 实例六:为带中文汉字数字的文件名重命名成以对应数字开头 267 | 268 | 功能: 269 | 270 | 见下边例子。 271 | 272 | 例子: 273 | 274 | ``` 275 | 当前目录有如下文件: 276 | 277 | Zsh-开发指南(第一篇-变量和语句).md 278 | Zsh-开发指南(第七篇-数值计算).md 279 | Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md 280 | Zsh-开发指南(第九篇-函数和脚本).md 281 | Zsh-开发指南(第二篇-字符串处理之常用操作).md 282 | Zsh-开发指南(第五篇-数组).md 283 | Zsh-开发指南(第八篇-变量修饰语).md 284 | Zsh-开发指南(第六篇-哈希表).md 285 | Zsh-开发指南(第十一篇-变量的进阶内容).md 286 | Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md 287 | Zsh-开发指南(第十三篇-管道和重定向).md 288 | Zsh-开发指南(第十九篇-脚本实例讲解).md 289 | Zsh-开发指南(第十二篇-[[-]]-的用法).md 290 | Zsh-开发指南(第十五篇-进程与作业控制).md 291 | Zsh-开发指南(第十八篇-更多内置模块的用法).md 292 | Zsh-开发指南(第十六篇-alias-和-eval-的用法).md 293 | Zsh-开发指南(第十四篇-文件读写).md 294 | Zsh-开发指南(第十篇-文件查找和批量处理).md 295 | Zsh-开发指南(第四篇-字符串处理之通配符).md 296 | 297 | 需要重命名成这样: 298 | 299 | 01_Zsh-开发指南(第一篇-变量和语句).md 300 | 02_Zsh-开发指南(第二篇-字符串处理之常用操作).md 301 | 03_Zsh-开发指南(第三篇-字符串处理之转义字符和格式化输出).md 302 | 04_Zsh-开发指南(第四篇-字符串处理之通配符).md 303 | 05_Zsh-开发指南(第五篇-数组).md 304 | 06_Zsh-开发指南(第六篇-哈希表).md 305 | 07_Zsh-开发指南(第七篇-数值计算).md 306 | 08_Zsh-开发指南(第八篇-变量修饰语).md 307 | 09_Zsh-开发指南(第九篇-函数和脚本).md 308 | 10_Zsh-开发指南(第十篇-文件查找和批量处理).md 309 | 11_Zsh-开发指南(第十一篇-变量的进阶内容).md 310 | 12_Zsh-开发指南(第十二篇-[[-]]-的用法).md 311 | 13_Zsh-开发指南(第十三篇-管道和重定向).md 312 | 14_Zsh-开发指南(第十四篇-文件读写).md 313 | 15_Zsh-开发指南(第十五篇-进程与作业控制).md 314 | 16_Zsh-开发指南(第十六篇-alias-和-eval-的用法).md 315 | 17_Zsh-开发指南(第十七篇-使用-socket-文件和-TCP-实现进程间通信).md 316 | 18_Zsh-开发指南(第十八篇-更多内置模块的用法).md 317 | 19_Zsh-开发指南(第十九篇-脚本实例讲解).md 318 | ``` 319 | 320 | 思路: 321 | 322 | 1. 首先需要写将汉字数字转成阿拉伯数字的函数。 323 | 2. 然后需要从文件名中截取汉字数字,然后转成阿拉伯数字。 324 | 3. 拼接文件名,然后移动文件。 325 | 326 | 实现: 327 | 328 | ``` 329 | #!/bin/zsh 330 | 331 | # 转换数字的逻辑和上一个实例一样 332 | 333 | local -A table=( 334 | 零 0 335 | 一 1 336 | 二 2 337 | 三 3 338 | 四 4 339 | 五 5 340 | 六 6 341 | 七 7 342 | 八 8 343 | 九 9 344 | ) 345 | 346 | convert() { 347 | local result 348 | 349 | if [[ $1 == 十 ]] { 350 | result=一零 351 | } elif [[ $1 == 十* ]] { 352 | result=${1/十/一} 353 | } elif [[ $1 == *十 ]] { 354 | result=${1/十/零} 355 | } elif [[ $1 == *十* ]] { 356 | result=${1/十} 357 | } else { 358 | result=$1 359 | } 360 | 361 | for i ({1..$#result}) { 362 | result[i]=$table[$result[i]] 363 | 364 | if [[ -z $result[i] ]] { 365 | echo error 366 | return 1 367 | } 368 | } 369 | 370 | echo $result 371 | } 372 | 373 | for i (Zsh*.md) { 374 | # -Z 2 是为了在前边补全一个 0 375 | # 把文件名“第”之前和“篇”之后的全部去除 376 | local -Z 2 num=$(convert ${${i#*第}%篇*}) 377 | mv -v $i ${num}_$i 378 | } 379 | ``` 380 | 381 | ### 实例七:统一压缩解压工具 382 | 383 | 功能: 384 | 385 | Linux 下常用的压缩、归档格式众多,参数各异,写一个用法统一的压缩解压工具,用于创建、解压 `.zip` `.7z` `.tar` `.tgz` `.tbz2` `.txz` `.tar.gz` `.tar.bz2` `.tar.xz` `.cpio` `.ar` `.gz` `.bz2` `.xz` 等文件。(类似 `atool`,但 `atool` 很久没更新了,一些新的格式不支持,没法定制。而且是用 `perl` 写的,很难看懂。所以还是决定自己写一个,只覆盖 `atool` 的一部分常用功能。) 386 | 387 | 例子: 388 | 389 | ``` 390 | # a 用于创建压缩文件 391 | % a a.tgz dir1 file1 file2 392 | dir1/ 393 | file1 394 | file2 395 | 396 | # al 用于列出压缩文件中的文件列表 397 | % al a.tgz 398 | drwxr-xr-x goreliu/goreliu 0 2017-09-13 11:23 dir1/ 399 | -rw-r--r-- goreliu/goreliu 3 2017-09-13 11:23 file1 400 | -rw-r--r-- goreliu/goreliu 3 2017-09-13 11:23 file2 401 | 402 | # x 用于解压文件 403 | % x a.tgz 404 | dir1/ 405 | file1 406 | file2 407 | a.tgz -> a 408 | 409 | # 如果解压后的文件名或目录名中当前目录下已经存在,则解压到随机目录 410 | % x a.tgz 411 | dir1/ 412 | file1 413 | file2 414 | a.tgz -> /tmp/test/x-c4I 415 | ``` 416 | 417 | 思路: 418 | 419 | 1. 压缩文件时,根据传入的文件名判断压缩文件的格式。 420 | 2. 解压和查看压缩文件内容时,根据传入的文件名和 `file` 命令结果判断压缩文件的格式。 421 | 3. 为了复用代码,多个命令整合到一个文件,然后 `ln -s` 成多个命令。 422 | 423 | 实现: 424 | 425 | ``` 426 | #!/bin/zsh 427 | 428 | get_type_by_name() { 429 | case $1 { 430 | (*.zip|*.7z|*.jar) 431 | echo 7z 432 | ;; 433 | 434 | (*.rar|*.iso) 435 | echo 7z_r 436 | ;; 437 | 438 | (*.tar|*.tgz|*.txz|*.tbz2|*.tar.*) 439 | echo tar 440 | ;; 441 | 442 | (*.cpio) 443 | echo cpio 444 | ;; 445 | 446 | (*.cpio.*) 447 | echo cpio_r 448 | ;; 449 | 450 | (*.gz) 451 | echo gz 452 | ;; 453 | 454 | (*.xz) 455 | echo xz 456 | ;; 457 | 458 | (*.bz2) 459 | echo bz2 460 | ;; 461 | 462 | (*.lzma) 463 | echo lzma 464 | ;; 465 | 466 | (*.lz4) 467 | echo lz4 468 | ;; 469 | 470 | (*.ar) 471 | echo ar 472 | ;; 473 | 474 | (*) 475 | return 1 476 | ;; 477 | } 478 | } 479 | 480 | get_type_by_file() { 481 | case $(file -bz $1) { 482 | (Zip *|7-zip *) 483 | echo 7z 484 | ;; 485 | 486 | (RAR *) 487 | echo 7z_r 488 | ;; 489 | 490 | (POSIX tar *|tar archive) 491 | echo tar 492 | ;; 493 | 494 | (*cpio archive*) 495 | echo cpio 496 | ;; 497 | 498 | (*gzip *) 499 | echo gz 500 | ;; 501 | 502 | (*XZ *) 503 | echo xz 504 | ;; 505 | 506 | (*bzip2 *) 507 | echo bz2 508 | ;; 509 | 510 | (*LZMA *) 511 | echo lzma 512 | ;; 513 | 514 | (*LZ4 *) 515 | echo lz4 516 | ;; 517 | 518 | (current ar archive) 519 | echo ar 520 | ;; 521 | 522 | (*) 523 | return 1 524 | ;; 525 | } 526 | } 527 | 528 | 529 | (($+commands[tar])) || alias tar=bsdtar 530 | (($+commands[cpio])) || alias cpio=bsdcpio 531 | 532 | case ${0:t} { 533 | (a) 534 | 535 | (($#* >= 2)) || { 536 | echo Usage: $0 target files/dirs 537 | return 1 538 | } 539 | 540 | case $(get_type_by_name $1) { 541 | (7z) 542 | 7z a $1 $*[2,-1] 543 | ;; 544 | 545 | (tar) 546 | tar -cavf $1 $*[2,-1] 547 | ;; 548 | 549 | (cpio) 550 | find $*[2,-1] -print0 | cpio -H newc -0ov > $1 551 | ;; 552 | 553 | (gz) 554 | gzip -cv $*[2,-1] > $1 555 | ;; 556 | 557 | (xz) 558 | xz -cv $*[2,-1] > $1 559 | ;; 560 | 561 | (bz2) 562 | bzip2 -cv $*[2,-1] > $1 563 | ;; 564 | 565 | (lzma) 566 | lzma -cv $*[2,-1] > $1 567 | ;; 568 | 569 | (lz4) 570 | lz4 -cv $2 > $1 571 | ;; 572 | 573 | (ar) 574 | ar rv $1 $*[2,-1] 575 | ;; 576 | 577 | (*) 578 | echo $1: error 579 | return 1 580 | ;; 581 | } 582 | ;; 583 | 584 | (al) 585 | 586 | (($#* >= 1)) || { 587 | echo Usage: $0 files 588 | return 1 589 | } 590 | 591 | for i ($*) { 592 | case $(get_type_by_name $i || get_type_by_file $i) { 593 | (7z|7z_r) 594 | 7z l $i 595 | ;; 596 | 597 | (tar) 598 | tar -tavf $i 599 | ;; 600 | 601 | (cpio|cpio_r) 602 | cpio -itv < $i 603 | ;; 604 | 605 | (gz) 606 | zcat $i 607 | ;; 608 | 609 | (xz) 610 | xzcat $i 611 | ;; 612 | 613 | (bz2) 614 | bzcat $i 615 | ;; 616 | 617 | (lzma) 618 | lzcat $i 619 | ;; 620 | 621 | (lz4) 622 | lz4cat $i 623 | ;; 624 | 625 | (ar) 626 | ar tv $i 627 | ;; 628 | 629 | (*) 630 | echo $i: error 631 | ;; 632 | } 633 | } 634 | ;; 635 | 636 | (x) 637 | 638 | (($#* >= 1)) || { 639 | echo Usage: $0 files 640 | return 1 641 | } 642 | 643 | for i ($*) { 644 | local outdir=${i%.*} 645 | 646 | [[ $outdir == *.tar ]] && { 647 | outdir=$outdir[1, -5] 648 | } 649 | 650 | if [[ -e $outdir ]] { 651 | outdir="$(mktemp -d -p $PWD x-XXX)" 652 | } else { 653 | mkdir $outdir 654 | } 655 | 656 | case $(get_type_by_name $i || get_type_by_file $i) { 657 | (7z|7z_r) 658 | 7z x $i -o$outdir 659 | ;; 660 | 661 | (tar) 662 | tar -xavf $i -C $outdir 663 | ;; 664 | 665 | (cpio|cpio_r) 666 | local file_path=$i 667 | [[ $i != /* ]] && file_path=$PWD/$i 668 | cd $outdir && cpio -iv < $file_path && cd .. 669 | ;; 670 | 671 | (gz) 672 | zcat $i > $outdir/$i[1,-4] 673 | ;; 674 | 675 | (xz) 676 | xzcat $i > $outdir/$i[1,-4] 677 | ;; 678 | 679 | (bz2) 680 | bzcat $i > $outdir/$i[1,-5] 681 | ;; 682 | 683 | (lzma) 684 | lzcat $i > $outdir/$i[1,-6] 685 | ;; 686 | 687 | (lz4) 688 | lz4cat $i > $outdir/$i[1,-5] 689 | ;; 690 | 691 | (ar) 692 | local file_path=$i 693 | [[ $i != /* ]] && file_path=$PWD/$i 694 | cd $outdir && ar x $file_path && cd .. 695 | ;; 696 | 697 | (*) 698 | echo $i: error 699 | ;; 700 | } 701 | 702 | local files=$(ls -A $outdir) 703 | 704 | if [[ -z $files ]] { 705 | rmdir $outdir 706 | } elif [[ -e $outdir/$files && ! -e $files ]] { 707 | mv -v $outdir/$files . && rmdir $outdir 708 | echo $i " -> " $files 709 | } else { 710 | echo $i " -> " $outdir 711 | } 712 | } 713 | ;; 714 | 715 | (*) 716 | echo error 717 | return 1 718 | ;; 719 | } 720 | ``` 721 | 722 | ### 实例八:方便并发运行命令的工具 723 | 724 | 功能: 725 | 726 | 我们经常会遇到在循环里批量处理文件的场景(比如将所有 jpg 图片转换成 png 图片),那么就会遇到一个麻烦:如果在前台处理文件,那同一时间只能处理一个,效率太低;如果在后台处理文件,那么瞬间就会启动很多个进程,占用大量资源,系统难以承受。我们希望的是在同一时间最多同时处理固定数量(比如 10 个)的文件,如果已经达到了这个数量,那么就先等一会,直到有退出的进程后再继续。`parallel` 命令中在一定程度上能满足这个需求,但用起来太麻烦。 727 | 728 | 例子: 729 | 730 | ``` 731 | # rr 是一个函数(可放在 .zshrc 中),直接 rr 加命令即可使用 732 | # 命令中支持变量、重定向等等,格式上和直接输入命令没有区别(不支持 alias) 733 | % rr sleep 5 734 | [4] 5031 735 | % rr sleep 5 736 | [5] 5032 737 | 738 | # 如果不加参数,则显示当前运行的进程数、最大进程并发数和运行中进程的进程号 739 | # 默认最大进程并发数是 10 740 | % rr 741 | running/max: 2/10 742 | pid: 5031 5032 743 | # 5 秒之后,运行结束 744 | % rr 745 | running/max: 0/10 746 | 747 | 748 | # 用 -j 来指定最大进程并发数,指定一次即可,如需修改可再次指定 749 | # 可以只调整最大进程并发数而不运行命令 750 | % rr -j2 sleep 10 751 | [4] 5035 752 | % rr sleep 10 753 | [5] 5036 754 | 755 | # 超过了最大进程并发数,等待,并且每一秒检查一次是否有进程退出 756 | # 如果有进程退出,则继续在后台运行当前命令 757 | % rr sleep 10 758 | running/max: 2/2, wait 1s ... 759 | pid: 5035 5036 760 | running/max: 2/2, wait 1s ... 761 | pid: 5035 5036 762 | [4] - done $* 763 | [4] 5039 764 | 765 | 766 | # 实际使用场景,批量将 jpg 图片转换成 png 图片,gm 是 graphicsmagick 中的命令 767 | # 转换图片格式比较耗时,顺序执行的话需要很久 768 | % for i (*.jpg) { rr gm convert $i ${i/jpg/png} } 769 | [4] 5055 770 | [5] 5056 771 | [6] 5057 772 | [7] 5058 773 | [8] 5059 774 | [9] 5060 775 | [10] 5061 776 | [11] 5062 777 | [12] 5063 778 | [13] 5064 779 | running/max: 10/10, wait 1s ... 780 | pid: 5060 5061 5062 5063 5064 5055 5056 5057 5058 5059 781 | running/max: 10/10, wait 1s ... 782 | pid: 5060 5061 5062 5063 5064 5055 5056 5057 5058 5059 783 | [11] done $* 784 | [5] done $* 785 | [5] 5067 786 | [12] done $* 787 | [11] 5068 788 | [6] done $* 789 | [6] 5069 790 | [12] 5070 791 | running/max: 10/10, wait 1s ... 792 | pid: 5070 5060 5061 5064 5055 5067 5068 5069 5058 5059 793 | [13] - done $* 794 | [4] done $* 795 | [4] 5072 796 | [13] 5073 797 | running/max: 10/10, wait 1s ... 798 | pid: 5070 5060 5072 5061 5073 5067 5068 5069 5058 5059 799 | [5] done $* 800 | [6] done $* 801 | [5] 5075 802 | [6] 5076 803 | running/max: 10/10, wait 1s ... 804 | pid: 5070 5060 5072 5061 5073 5075 5076 5068 5058 5059 805 | ... 806 | ``` 807 | 808 | 思路: 809 | 810 | 1. 需要在全局变量里记录最大进程并发数和当前运行的进程(哈希表)。 811 | 2. 每运行一个进程,将对应的进程号放入哈希表中。 812 | 3. 如果当前运行进程数达到最大进程并发数,则循环检查哈希表里的进程是否退出。 813 | 814 | 实现: 815 | 816 | ``` 817 | rr() { 818 | (($+max_process)) || typeset -g max_process=10 819 | (($+running_process)) || typeset -gA running_process=() 820 | 821 | [[ $1 == -j<1-> ]] && { 822 | max_process=${1[3,-1]} 823 | shift 824 | } 825 | 826 | (($# == 0)) && { 827 | for i (${(k)running_process}) { 828 | [[ -e /proc/$i ]] || unset "running_process[$i]" 829 | } 830 | 831 | echo "running/max: $#running_process/$max_process" 832 | (($#running_process > 0)) && echo "pid: ${(k)running_process}" 833 | return 834 | } 835 | 836 | while ((1)) { 837 | local running_process_num=$#running_process 838 | 839 | if (($running_process_num < max_process)) { 840 | $* & 841 | running_process[$!]=1 842 | return 843 | } 844 | 845 | for i (${(k)running_process}) { 846 | [[ -e /proc/$i ]] || unset "running_process[$i]" 847 | } 848 | 849 | (($#running_process == $running_process_num)) && { 850 | echo "running/max: $running_process_num/$max_process, wait 1s ..." 851 | echo "pid: ${(k)running_process}" 852 | sleep 1 853 | } 854 | } 855 | } 856 | ``` 857 | 858 | 使用 inotifywait 的版本(无需循环 sleep 等待): 859 | 860 | ``` 861 | rr() { 862 | (($+max_process)) || typeset -gi max_process=10 863 | (($+running_process)) || typeset -gA running_process=() 864 | 865 | while {getopts j:h arg} { 866 | case $arg { 867 | (j) 868 | ((OPTARG > 0)) && max_process=$OPTARG 869 | ;; 870 | 871 | (h) 872 | echo "Usage: $0 [-j max_process] [cmd] [args]" 873 | return 874 | ;; 875 | } 876 | } 877 | 878 | shift $((OPTIND - 1)) 879 | 880 | (($# == 0)) && { 881 | for i (${(k)running_process}) { 882 | [[ -e $i ]] || unset "running_process[$i]" 883 | } 884 | 885 | echo "running/max: $#running_process/$max_process" 886 | (($#running_process > 0)) && echo "pids:" ${${(k)running_process/\/proc\/}/\/exe} 887 | return 0 888 | } 889 | 890 | while ((1)) { 891 | local running_process_num=$#running_process 892 | 893 | if (($running_process_num < max_process)) { 894 | $* & 895 | running_process[/proc/$!/exe]=1 896 | return 897 | } 898 | 899 | for i (${(k)running_process}) { 900 | [[ -e $i ]] || unset "running_process[$i]" 901 | } 902 | 903 | (($#running_process == $running_process_num)) && { 904 | echo "wait $running_process_num pids:" ${${(k)running_process/\/proc\/}/\/exe} 905 | inotifywait -q ${(k)running_process} 906 | } 907 | } 908 | } 909 | ``` 910 | 911 | ### 实例九:批量转换图片格式 912 | 913 | 功能: 914 | 915 | 将当前目录及子目录的所有常见图片格式转换成 jpg 格式(jpg 格式也要转换一遍,可以减少文件体积),然后删除原图片。需要用 5 个并发进程来处理。注意避免仅扩展名不同的文件互相覆盖的情况。 916 | 917 | 例子: 918 | 919 | ``` 920 | % tree 921 | . 922 | ├── mine 923 | │   ├── 信.txt 924 | │   ├── 第一封信.jpg 925 | │   └── 第二封信.JPG 926 | ├── 搞笑 927 | │   ├── 卖萌.GIF 928 | │   ├── 猫吃鱼.gif 929 | │   └── 猫抢东西吃.gif 930 | └── 素材 931 | ├── 104 按键模板.jpg 932 | ├── 104 按键模板.psd 933 | ├── ahk 934 | │   ├── ahk_bg.jpg 935 | │   ├── ahk_home_logo.jpg 936 | │   ├── ahk_home_logo.txt 937 | │   ├── ahk_home_qr.jpg 938 | │   ├── ahk_home_qr_small.jpg 939 | │   └── ahk_logo.png 940 | ├── stp_fc_cw_png_pk 941 | │   ├── HD.PNG 942 | │   ├── newimage.png 943 | │   ├── nshd.PNG 944 | │   └── std.png 945 | ├── 地球.jpg 946 | ├── 星系.JPEG 947 | ├── 木纹 背景.GIF 948 | ├── 木纹 背景.jpeg 949 | └── 木纹 背景.jpg 950 | 951 | 5 directories, 23 files 952 | 953 | % alltojpg 954 | running/max: 0/5 955 | running: 5, wait 1.0000000000s ... 956 | pid: 5953 5954 5955 5956 5957 957 | running: 5, wait 1.0000000000s ... 958 | pid: 5965 5966 5967 5968 5959 959 | 960 | % tree 961 | . 962 | ├── mine 963 | │   ├── 信.txt 964 | │   ├── 第一封信.jpg 965 | │   └── 第二封信.jpg 966 | ├── 搞笑 967 | │   ├── 卖萌_g.jpg 968 | │   ├── 猫吃鱼_g.jpg 969 | │   └── 猫抢东西吃_g.jpg 970 | └── 素材 971 | ├── 104 按键模板.jpg 972 | ├── 104 按键模板.psd 973 | ├── ahk 974 | │   ├── ahk_bg.jpg 975 | │   ├── ahk_home_logo.jpg 976 | │   ├── ahk_home_logo.txt 977 | │   ├── ahk_home_qr.jpg 978 | │   ├── ahk_home_qr_small.jpg 979 | │   └── ahk_logo_p.jpg 980 | ├── stp_fc_cw_png_pk 981 | │   ├── HD_p.jpg 982 | │   ├── newimage_p.jpg 983 | │   ├── nshd_p.jpg 984 | │   └── std_p.jpg 985 | ├── 地球.jpg 986 | ├── 星系_e.jpg 987 | ├── 木纹 背景_e.jpg 988 | ├── 木纹 背景_g.jpg 989 | └── 木纹 背景.jpg 990 | 991 | 5 directories, 23 files 992 | ``` 993 | 994 | 思路: 995 | 996 | 1. 并发运行命令的方法见上一个实例。 997 | 2. 转换图片格式用 `gm convert` 命令(graphicsmagick 中)或者 `convert` 命令(imagemagick 中)。 998 | 3. 常见的图片文件扩展名有 `jpg` `jpeg` `png` `gif`,另外可能是大写的扩展名。 999 | 4. 为了避免类似 `a.gif` 覆盖 `a.jpg` 的情况,为不同的文件格式添加不同后缀,这样可以无需检查是否有同名文件,加快速度。 1000 | 1001 | 实现: 1002 | 1003 | ``` 1004 | #!/bin/zsh 1005 | 1006 | # rr 是上一个实例中的代码 1007 | rr() { 1008 | (($+max_process)) || typeset -gi max_process=10 1009 | (($+running_process)) || typeset -gA running_process=() 1010 | 1011 | while {getopts j:h arg} { 1012 | case $arg { 1013 | (j) 1014 | ((OPTARG > 0)) && max_process=$OPTARG 1015 | ;; 1016 | 1017 | (h) 1018 | echo "Usage: $0 [-j max_process] [cmd] [args]" 1019 | return 1020 | ;; 1021 | } 1022 | } 1023 | 1024 | shift $((OPTIND - 1)) 1025 | 1026 | (($# == 0)) && { 1027 | for i (${(k)running_process}) { 1028 | [[ -e $i ]] || unset "running_process[$i]" 1029 | } 1030 | 1031 | echo "running/max: $#running_process/$max_process" 1032 | (($#running_process > 0)) && echo "pids:" ${${(k)running_process/\/proc\/}/\/exe} 1033 | return 0 1034 | } 1035 | 1036 | while ((1)) { 1037 | local running_process_num=$#running_process 1038 | 1039 | if (($running_process_num < max_process)) { 1040 | $* & 1041 | running_process[/proc/$!/exe]=1 1042 | return 1043 | } 1044 | 1045 | for i (${(k)running_process}) { 1046 | [[ -e $i ]] || unset "running_process[$i]" 1047 | } 1048 | 1049 | (($#running_process == $running_process_num)) && { 1050 | echo "wait $running_process_num pids:" ${${(k)running_process/\/proc\/}/\/exe} 1051 | inotifywait -q ${(k)running_process} 1052 | } 1053 | } 1054 | } 1055 | 1056 | 1057 | # JPG 作为中间扩展名 1058 | rename .JPG .jpg **/*.JPG 1059 | 1060 | # 设置进程并发数为 5 1061 | rr -j5 1062 | 1063 | for i (**/*.(jpg|png|PNG|jpeg|JPEG|gif|GIF)) { 1064 | rr gm convert $i $i.JPG 1065 | } 1066 | 1067 | # 等所有操作结束 1068 | wait 1069 | 1070 | # 删除原文件 1071 | rm **/*.(jpg|png|PNG|jpeg|JPEG|gif|GIF) 1072 | 1073 | # 避免覆盖同名文件 1074 | rename .jpg.JPG .jpg **/*.JPG 1075 | rename .png.JPG _p.jpg **/*.JPG 1076 | rename .PNG.JPG _p.jpg **/*.JPG 1077 | rename .jpeg.JPG _e.jpg **/*.JPG 1078 | rename .JPEG.JPG _e.jpg **/*.JPG 1079 | rename .gif.JPG _g.jpg **/*.JPG 1080 | rename .GIF.JPG _g.jpg **/*.JPG 1081 | ``` 1082 | 1083 | ### 总结 1084 | 1085 | 本文讲解了几个比较实用的 zsh 脚本,后续可能会补充更多个。 1086 | 1087 | ### 更新历史 1088 | 1089 | 2017.09.13:新增“实例七”、“实例八”和“实例九”。 1090 | 1091 | 2017.10.09:“示例八”和“示例九”中,新增使用 inotifywait 的 rr 函数。 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------