├── images └── console_codes_imgs │ ├── 2019061911202157.png │ ├── 2019061913581248.png │ ├── 2019061914121546.png │ ├── 2019061914124782.png │ ├── 201906191419546.png │ ├── 2019061914310225.png │ ├── 2019061914383268.png │ ├── 20190619111931351.png │ ├── 20190619134438396.png │ ├── 20190619134533628.png │ ├── 20190619134657313.gif │ ├── 20190619134709590.png │ ├── 20190619134719632.png │ ├── 20190619134739654.png │ ├── 20190619135720596.png │ ├── 20190619135736113.png │ ├── 20190619135828236.png │ ├── 20190619135843795.gif │ ├── 20190619135900464.png │ ├── 20190619135931669.png │ ├── 20190619141052508.png │ ├── 20190619141101784.png │ ├── 20190619141135101.png │ ├── 20190619141152927.png │ ├── 20190619141224273.png │ ├── 20190619141237138.png │ ├── 20190619141257476.png │ ├── 20190619141310443.png │ ├── 20190619141319563.png │ ├── 20190619141329662.png │ ├── 20190619141342103.png │ ├── 20190619141353126.png │ ├── 20190619141404538.png │ ├── 20190619141416743.png │ ├── 20190619141425171.png │ ├── 20190619141715955.png │ ├── 20190619141729838.png │ ├── 20190619141813132.png │ ├── 20190619141818655.png │ ├── 20190619141826855.png │ ├── 20190619141843323.png │ ├── 20190619141851119.png │ ├── 20190619141859165.png │ ├── 20190619141906860.png │ ├── 20190619141940807.png │ ├── 20190619141948164.png │ ├── 20190619142001372.png │ ├── 20190619142009959.png │ ├── 20190619142015180.png │ ├── 20190619142021305.png │ ├── 20190619142042919.png │ ├── 20190619142347178.png │ ├── 20190619142534957.png │ ├── 20190619142540367.png │ ├── 20190619142629366.png │ ├── 20190619142741691.png │ ├── 20190619142746703.png │ ├── 20190619142956913.png │ ├── 20190619143007175.png │ └── 20190619143725445.png ├── manuscript ├── chapter14.txt ├── chapter0.txt ├── Book.txt ├── chapter12.txt ├── chapter6.txt ├── chapter9.txt ├── chapter13.txt ├── chapter15.txt ├── chapter5.txt ├── chapter11.txt ├── chapter16.txt ├── chapter17.txt ├── chapter7.txt ├── chapter3.txt ├── chapter10.txt ├── chapter8.txt ├── chapter2.txt ├── chapter18.txt ├── chapter4.txt ├── chapter19.txt └── chapter1.txt ├── mapfile命令.md ├── 进度条.md ├── LICENSE.md ├── declare命令.md ├── 百分号编码字符串.md ├── 字符串大小写转换.md ├── CONTRIBUTING-Zh_CN.md ├── 删除字符串中的所有的空白并用空格分割单词.md ├── 在字符串中匹配正则表达式.md ├── CONTRIBUTING.md ├── 指定分隔符拆分字符串.md ├── 删除重复的数组元素.md ├── 删除字符串前后空格.md ├── test.sh ├── bash终端中输出字体格式详解.md ├── README.md.bak └── README.md /images/console_codes_imgs/2019061911202157.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061911202157.png -------------------------------------------------------------------------------- /images/console_codes_imgs/2019061913581248.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061913581248.png -------------------------------------------------------------------------------- /images/console_codes_imgs/2019061914121546.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061914121546.png -------------------------------------------------------------------------------- /images/console_codes_imgs/2019061914124782.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061914124782.png -------------------------------------------------------------------------------- /images/console_codes_imgs/201906191419546.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/201906191419546.png -------------------------------------------------------------------------------- /images/console_codes_imgs/2019061914310225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061914310225.png -------------------------------------------------------------------------------- /images/console_codes_imgs/2019061914383268.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/2019061914383268.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619111931351.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619111931351.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134438396.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134438396.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134533628.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134533628.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134657313.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134657313.gif -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134709590.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134709590.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134719632.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134719632.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619134739654.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619134739654.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135720596.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135720596.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135736113.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135736113.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135828236.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135828236.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135843795.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135843795.gif -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135900464.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135900464.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619135931669.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619135931669.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141052508.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141052508.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141101784.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141101784.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141135101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141135101.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141152927.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141152927.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141224273.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141224273.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141237138.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141237138.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141257476.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141257476.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141310443.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141310443.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141319563.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141319563.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141329662.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141329662.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141342103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141342103.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141353126.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141353126.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141404538.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141404538.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141416743.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141416743.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141425171.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141425171.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141715955.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141715955.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141729838.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141729838.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141813132.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141813132.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141818655.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141818655.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141826855.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141826855.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141843323.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141843323.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141851119.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141851119.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141859165.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141859165.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141906860.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141906860.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141940807.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141940807.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619141948164.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619141948164.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142001372.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142001372.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142009959.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142009959.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142015180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142015180.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142021305.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142021305.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142042919.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142042919.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142347178.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142347178.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142534957.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142534957.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142540367.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142540367.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142629366.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142629366.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142741691.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142741691.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142746703.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142746703.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619142956913.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619142956913.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619143007175.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619143007175.png -------------------------------------------------------------------------------- /images/console_codes_imgs/20190619143725445.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/A-BenMao/pure-bash-bible-zh_CN/HEAD/images/console_codes_imgs/20190619143725445.png -------------------------------------------------------------------------------- /manuscript/chapter14.txt: -------------------------------------------------------------------------------- 1 | #性能 2 | 3 | ##禁用Unicode码 4 | 5 | 如果不需要unicode,则可以禁用它以提高性能。结果可能会有所不同,但是[neofetch](https://github.com/dylanaraps/neofetch) 和其他程序有明显改善。 6 | 7 | ```shell 8 | # 禁用 unicode. 9 | LC_ALL=C 10 | LANG=C 11 | ``` 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /manuscript/chapter0.txt: -------------------------------------------------------------------------------- 1 | #前言 2 | 3 | 纯`bash`脚本替代外部流程和程序的集合。 `bash`脚本语言远比大部分人了解到的更强大,大多数任务都可以在不依赖外部程序的情况下由`bash`独立完成。 4 | 5 | 在`bash`中调用外部进程是昂贵的,过度使用会导致效率明显的下降。 使用内置方法编写的脚本和程序(*在适合的地方*)将更快,依赖性更小,并能对脚本本身有更好的理解。 6 | 7 | 本书的目的是为大家用`bash`编写程序和脚本过程中遇到问题时提供了一种思路。 示例展示了将这些解决方案合并到代码中的函数格式。 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | chapter0.txt 2 | chapter1.txt 3 | chapter2.txt 4 | chapter3.txt 5 | chapter4.txt 6 | chapter5.txt 7 | chapter6.txt 8 | chapter7.txt 9 | chapter8.txt 10 | chapter9.txt 11 | chapter10.txt 12 | chapter11.txt 13 | chapter12.txt 14 | chapter13.txt 15 | chapter14.txt 16 | chapter15.txt 17 | chapter16.txt 18 | chapter17.txt 19 | chapter18.txt 20 | chapter19.txt 21 | -------------------------------------------------------------------------------- /manuscript/chapter12.txt: -------------------------------------------------------------------------------- 1 | #算术运算符-1 2 | 3 | ##用更简单语法设置变量 4 | 5 | ```shell 6 | # 简单的数学运算 7 | ((var=1+2)) 8 | 9 | # 递减/递增变量 10 | ((var++)) 11 | ((var--)) 12 | ((var+=1)) 13 | ((var-=1)) 14 | 15 | # 使用变量 16 | ((var=var2*arr[2])) 17 | ``` 18 | 19 | ##三元测试 20 | 21 | ```shell 22 | # 如果var2大于var,则将var的值设置为var2,否则设置为var. 23 | # var: 要设置的变量. 24 | # var2>var: 条件测试. 25 | # ?var2: 如果条件测试成功时生效. 26 | # :var: 条件测试失败时生效. 27 | ((var=var2>var?var2:var)) 28 | ``` 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /manuscript/chapter6.txt: -------------------------------------------------------------------------------- 1 | #变量 2 | 3 | ##分配和访问一个变量 4 | 5 | ```shell 6 | $ hello_world="value" 7 | 8 | # 创建一个变量名. 9 | $ var="world" 10 | $ ref="hello_$var" 11 | 12 | # 打印存储为 'hello_$var' 变量名称的值. 13 | $ printf '%s\n' "${!ref}" 14 | value 15 | ``` 16 | 17 | 或者, 在 `bash` 4.3+以上版本: 18 | 19 | ```shell 20 | $ hello_world="value" 21 | $ var="world" 22 | 23 | # 声明一个名称引用. 24 | $ declare -n ref=hello_$var 25 | 26 | $ printf '%s\n' "$ref" 27 | value 28 | ``` 29 | 30 | ##根据另一个变量命名变量 31 | 32 | ```shell 33 | $ var="world" 34 | $ declare "hello_$var=value" 35 | $ printf '%s\n' "$hello_world" 36 | value 37 | ``` 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /manuscript/chapter9.txt: -------------------------------------------------------------------------------- 1 | #花括号展开 2 | 3 | ##范围 4 | 5 | ```shell 6 | # 符号: {..} 7 | 8 | # 打印 1-100 之间的数字. 9 | echo {1..100} 10 | 11 | # 打印一个范围内的浮点数. 12 | echo 1.{1..9} 13 | 14 | # 打印a-z间的字符. 15 | echo {a..z} 16 | echo {A..Z} 17 | 18 | # 嵌套. 19 | echo {A..Z}{0..9} 20 | 21 | # 打印用零填充的数字. 22 | # 警告: bash 4+ 23 | echo {01..100} 24 | 25 | # 更改步长. 26 | # 符号: {....} 27 | # 警告: bash 4+ 28 | echo {1..10..2} # 每次增加2. 29 | ``` 30 | 31 | ##字符串列表 32 | 33 | ```shell 34 | echo {apples,oranges,pears,grapes} 35 | 36 | # 示例用法: 37 | # 删除~/Downloads/目录下的 Movies, Music 和 ISOS 文件夹 . 38 | rm -rf ~/Downloads/{Movies,Music,ISOS} 39 | ``` 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /manuscript/chapter13.txt: -------------------------------------------------------------------------------- 1 | #trap命令 2 | 3 | Traps允许脚本在各种信号上执行代码。在 [pxltrm](https://github.com/dylanaraps/pxltrm) (*用bash编写的像素艺术编辑器*) traps 用于在窗口大小调整时重绘用户界面。另一个用例是在脚本退出时清理临时文件。 4 | 5 | Traps应该在脚本开头附近添加,以便捕获任何早期错误. 6 | 7 | **NOTE:** 有关信号的完整列表,请参阅 `trap -l`. 8 | 9 | 10 | ##在脚本退出时执行操作 11 | 12 | ```shell 13 | # 脚本退出时清除屏幕。 14 | trap 'printf \\e[2J\\e[H\\e[m' EXIT 15 | ``` 16 | 17 | ##忽略终端中断(CTRL+C,SIGINT) 18 | 19 | ```shell 20 | trap '' INT 21 | ``` 22 | 23 | ##对窗口调整大小时做出反应 24 | 25 | ```shell 26 | # 在窗口调整大小时调用函数. 27 | trap 'code_here' SIGWINCH 28 | ``` 29 | 30 | ##在命令之前执行某些操作 31 | 32 | ```shell 33 | trap 'code_here' DEBUG 34 | ``` 35 | 36 | ##在shell函数或源文件完成执行时执行某些操作 37 | 38 | ```shell 39 | trap 'code_here' RETURN 40 | ``` 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /manuscript/chapter15.txt: -------------------------------------------------------------------------------- 1 | #已过时的语法 2 | 3 | ##释伴声明 4 | 5 | 使用 `#!/usr/bin/env bash` 而不是 `#!/bin/bash`. 6 | 7 | - 前者搜索用户的 `PATH` 以查找 `bash` 二进制文件. 8 | - 后者假设它始终安装在 `/bin/` 目录,可能导致问题. 9 | 10 | ```shell 11 | # 正确的方式: 12 | 13 | #!/usr/bin/env bash 14 | 15 | # 错误的方式: 16 | 17 | #!/bin/bash 18 | ``` 19 | 20 | ##命令替换 21 | 22 | 使用 `$()`而不是 `` ` ` ``. 23 | 24 | ```shell 25 | # 正确的方式. 26 | var="$(command)" 27 | 28 | # 错误的方式. 29 | var=`command` 30 | 31 | # $() 很容易嵌套,而``不能. 32 | var="$(command "$(command)")" 33 | ``` 34 | 35 | ##声明函数 36 | 37 | 不要使用`function`关键字,它会降低与旧版本`bash`的兼容性. 38 | 39 | ```shell 40 | # 正确的方式. 41 | do_something() { 42 | # ... 43 | } 44 | 45 | # 错误的方式. 46 | function do_something() { 47 | # ... 48 | } 49 | ``` 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /manuscript/chapter5.txt: -------------------------------------------------------------------------------- 1 | #文件路径 2 | 3 | ##获取文件路径的目录名 4 | 5 | 替代 `dirname` 命令. 6 | 7 | **示例函数:** 8 | 9 | ```sh 10 | dirname() { 11 | # 用法: dirname "path" 12 | printf '%s\n' "${1%/*}/" 13 | } 14 | ``` 15 | 16 | **示例用法:** 17 | 18 | ```shell 19 | $ dirname ~/Pictures/Wallpapers/1.jpg 20 | /home/black/Pictures/Wallpapers/ 21 | 22 | $ dirname ~/Pictures/Downloads/ 23 | /home/black/Pictures/ 24 | ``` 25 | 26 | ##获取文件路径的基本名称 27 | 28 | 29 | 替代 `basename` 命令. 30 | 31 | **示例函数:** 32 | 33 | ```sh 34 | basename() { 35 | # Usage: basename "path" 36 | : "${1%/}" 37 | printf '%s\n' "${_##*/}" 38 | } 39 | ``` 40 | 41 | **示例用法:** 42 | 43 | ```shell 44 | $ basename ~/Pictures/Wallpapers/1.jpg 45 | 1.jpg 46 | 47 | $ basename ~/Pictures/Downloads/ 48 | Downloads 49 | ``` 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /mapfile命令.md: -------------------------------------------------------------------------------- 1 | 2 | # mapfile # 3 | 4 | bash提供了两个内置命令:readarray和mapfile,它们是同义词。它们的作用是从标准输入读取一行行的数据,然后每一行都赋值给一个数组的各元素。显然,在shell编程中更常用的是从文件、从管道读取,不过也可以从文件描述符中读取数据。 5 | 6 | 7 | > 语法 8 | 9 | mapfile [OPTIONS] ARRAY 10 | readarray [OPTIONS] ARRAY 11 | 12 | ``` 13 | 其中options: 14 | -O INDEX :指定从哪个索引号开始存储数据,默认存储数据的起始索引号为0 15 | -n count :最多只拷贝多少行到数组中,如果count=0,则拷贝所有行 16 | -s count :忽略前count行不读取 17 | -c NUM :每读取NUM行就调用一次"-C callback"选项指定的callback程序 18 | -C callback:每读取"-c NUM"选项指定的NUM行就执行一次callback回调程序 19 | -d string :指定读取数据时的行分隔符,默认是换行符 20 | -t :移除尾随行分隔符,默认是换行符 21 | -u fd :指定从文件描述符fd而非标准输入中读取数据 22 | ``` 23 | 24 | - 如果不指定ARRAY参数,则默认使用数组MAPFILE 25 | 26 | - 如果不指定"-O"选项,则在存储数据之前先清空数组(如果该数组已存在) 27 | 28 | - 给定了"-C callback"却没有给定"-c NUM"时,则默认为每5000行调用一次回调程序 29 | 30 | - 回调程序是在读取给定行数之后,赋值到数组元素之前执行的。所以流程为:"读NUM行-->callback-->赋值" 31 | 32 | - 每次调用回调函数时,都将调用callback之前的最后一行数据及其对应的索引号作为回调程序的参数。例如-c 3 -C callback,则会将索引号2和第3行内容,索引号5和第6行内容作为callback程序的参数 33 | 34 | - "-t"去除行尾分隔符,一般来说都是换行符。 -------------------------------------------------------------------------------- /进度条.md: -------------------------------------------------------------------------------- 1 | #
进度条
# 2 | 3 | # 进度条函数 4 | bar() { 5 | # 用法: bar 1 10 6 | # ^----- 已经完成的百分比 (0-100). 7 | # ^--- 字符总长度. 8 | ((elapsed=$1*$2/100)) 9 | 10 | # 创建空格表示的进度条 11 | printf -v prog "%${elapsed}s" 12 | printf -v total "%$(($2-elapsed))s" 13 | 14 | printf '%s\r' "[${prog// /-}${total}]" 15 | } 16 | 17 | # 进度条使用示例 18 | for ((i=0;i<=100;i++)); do 19 | # 纯粹的暂停动作 (为了本例可以更好的演示). 20 | (:;:) && (:;:) && (:;:) && (:;:) && (:;:) 21 | 22 | # Print the bar. 23 | bar "$i" "10" 24 | done 25 | 26 | printf '\n' 27 | 28 | 29 | 30 | 31 | 32 | ---------- 33 | 34 | **语法说明:** 35 | 36 | 37 | > ((elapsed=$1*$2/100)) 38 | 39 | - (( ))的作用是重定义括号中的变量值,此处是计算已经完成的占总字符的长度; 40 | 41 | > printf -v prog "%${elapsed}s" 42 | 43 | - `"%${elapsed}s"`此处%`%ns`表示指定输出字符串长度,如果不够,空格补全,次数会输出指定个数的空格; 44 | - `printf -v`把输出作为一个变量,使用`-v var`格式。 45 | 46 | > '%s\r' 47 | 48 | - '\r' 回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖; 49 | - '\n' 换行,换到下一行的行首。 50 | 51 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Dylan Araps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /manuscript/chapter11.txt: -------------------------------------------------------------------------------- 1 | 2 | #算术运算符 3 | 4 | ##指派 5 | 6 | | 操作符 | 它有什么作用? | 7 | | --------- | ---------------- | 8 | | `=` | 初始化或更改变量的值。 9 | 10 | ##四则运算 11 | 12 | | 操作符 | 它有什么作用? | 13 | | --------- | ---------------- | 14 | | `+` | 加 15 | | `-` | 减 16 | | `*` | 乘 17 | | `/` | 除 18 | | `**` | 幂运算 19 | | `%` | 求模 20 | | `+=` | 先加后赋值 (*`x += y`等同于`x = x + y`*) 21 | | `-=` | 先减后赋值 (*`x -= y`等同于`x = x - y`*) 22 | | `*=` | 先乘后赋值 (*`x *= y`等同于`x = x * y`*) 23 | | `/=` | 先除后赋值 (*`x /= y`等同于`x = x / y`*) 24 | | `%=` | 先取模后赋值 (*`x %= y`等同于`x = x % y`*) 25 | 26 | ##位运算 27 | 28 | | 操作符 | 它有什么作用? | 29 | | --------- | ---------------- | 30 | | `<<` | 按位左移 31 | | `<<=` | 按位左移后赋值 32 | | `>>` | 按位右移 33 | | `>>=` | 按位右移后赋值 34 | | `&` | 按位与操作 35 | | `&=` | 按位与操作后赋值 36 | | `\|` | 按位或操作 37 | | `\|=` | 按位或操作赋值 38 | | `~` | 按位非操作 39 | | `^` | 按位异或操作 40 | | `^=` | 按位异或操作后赋值 41 | 42 | ##逻辑运算 43 | 44 | | 操作符 | 它有什么作用? | 45 | | --------- | ---------------- | 46 | | `!` | NOT 47 | | `&&` | AND 48 | | `\|\|` | OR 49 | 50 | ##复杂运算 51 | 52 | | 操作符 | 它有什么作用? | 例子 | 53 | | --------- | ---------------- | ------- | 54 | | `,` | 逗号分隔符 | `((a=1,b=2,c=3))` 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /manuscript/chapter16.txt: -------------------------------------------------------------------------------- 1 | #内部变量 2 | 3 | ##获取`bash`二进制文件的位置 4 | 5 | ```shell 6 | "$BASH" 7 | ``` 8 | 9 | ##获取当前运行`bash`命令的版本 10 | 11 | ```shell 12 | # 作为字符串. 13 | "$BASH_VERSION" 14 | 15 | # 作为数组. 16 | "${BASH_VERSINFO[@]}" 17 | ``` 18 | 19 | ##打开用户默认的文本编辑器 20 | 21 | ```shell 22 | "$EDITOR" "$file" 23 | 24 | # NOTE: 这个变量可能是空的,设置一个失败调用值. 25 | "${EDITOR:-vi}" "$file" 26 | ``` 27 | 28 | ##获取当前函数的名称 29 | 30 | ```shell 31 | # 当前函数. 32 | "${FUNCNAME[0]}" 33 | 34 | # 父函数. 35 | "${FUNCNAME[1]}" 36 | 37 | # 等等. 38 | "${FUNCNAME[2]}" 39 | "${FUNCNAME[3]}" 40 | 41 | # 包括父类的所有函数 42 | "${FUNCNAME[@]}" 43 | ``` 44 | 45 | ##获取系统的主机名 46 | 47 | ```shell 48 | "$HOSTNAME" 49 | 50 | # NOTE: 这个变量可能是空的. 51 | # (可选):将失败调用设置为hostname命令 52 | "${HOSTNAME:-$(hostname)}" 53 | ``` 54 | 55 | ##获取操作系统的架构(32位或64位) 56 | 57 | ```shell 58 | "$HOSTTYPE" 59 | ``` 60 | 61 | ##获取操作系统/内核的名称 62 | 63 | 这可用于条件判断不同的操作系统,而无需调用`uname`。 64 | 65 | ```shell 66 | "$OSTYPE" 67 | ``` 68 | 69 | ##获取当前的工作目录 70 | 71 | 这是内置`pwd`的替代方案。 72 | 73 | ```shell 74 | "$PWD" 75 | ``` 76 | 77 | ##获取脚本运行的秒数 78 | 79 | ```shell 80 | "$SECONDS" 81 | ``` 82 | 83 | ##获取伪随机整数 84 | 85 | 每次使用`$RANDOM`时, 返回`0` and `32767`之间的不同整数。 此变量不应用于与安全性相关的任何内容(包括加密密钥等)。 86 | 87 | 88 | ```shell 89 | "$RANDOM" 90 | ``` 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /declare命令.md: -------------------------------------------------------------------------------- 1 | 2 | # declare # 3 | 4 | declare为shell指令,在第一种语法中可用来声明变量并设置变量的属性([rix]即为变量的属性),在第二种语法中可用来显示shell函数。若不加上任何参数,则会显示全部的shell变量与函数。 5 | 6 | 7 | > 语法 8 | 9 | declare [+/-][rxi][变量名称=设置值] 或 declare -f/-F 10 | 11 | 12 | > 参数说明 13 | 14 | - +/- ”-“可用来指定变量的属性,”+”则是取消变量所设的属性; 15 | 16 | - -f 列出之前由用户在脚本中定义的函数名称和函数体; 17 | 18 | - -r [name[=value]] 或 readonly name:将变量定义为只读(不可修改和删除); 19 | 20 | - -x name[=value] 或 export name[=value]:将变量设置为环境变量,可供shell以外的程序来使用; 21 | 22 | - -i 将变量定义为整数型(求值结果仅为整数,否则显示为0); 23 | 24 | - -a name:声明变量为普通数组; 25 | 26 | - -A name:声明变量为关联数组(支持索引下标为字符串); 27 | 28 | - -F 仅显示函数名(格式为declare -f function_name); 29 | 30 | - -g name:在shell函数中可创建全局变量; 31 | 32 | - -p [name]:显示指定变量的属性和值。 33 | 34 | 35 | ### 改变变量属性 ### 36 | 37 | # declare -i ef //声明整数型变量 38 | # ef=1 //变量赋值(整数值) 39 | # echo $ef //显示变量内容 40 | 1 41 | # ef="wer" //变量赋值(文本值) 42 | # echo $ef 43 | 0 44 | # declare +i ef //取消变量属性 45 | # ef="wer" 46 | # echo $ef 47 | wer 48 | 49 | ### 列出脚本中的函数 ### 50 | 51 | [root@ ~]# cat aa 52 | aaa(){ 53 | dsfasdf 54 | } 55 | bbb(){ 56 | dasfasdf 57 | } 58 | 59 | declare -F 60 | printf '==============\n' 61 | declare -f 62 | 63 | [root@ ~]# ./aa 64 | declare -f aaa 65 | declare -f bbb 66 | ============== 67 | aaa () 68 | { 69 | dsfasdf 70 | } 71 | bbb () 72 | { 73 | dasfasdf 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /manuscript/chapter17.txt: -------------------------------------------------------------------------------- 1 | #有关终端的信息 2 | 3 | ##获取终端的总行列数(*来自脚本*) 4 | 5 | 在纯bash中编写脚本和`stty`/`tput`无法调用时,这很方便。 6 | 7 | **示例函数:** 8 | 9 | ```sh 10 | get_term_size() { 11 | # 用法: get_term_size 12 | 13 | # (:;:) 是一个短暂暂停,以确保变量立即导出 14 | shopt -s checkwinsize; (:;:) 15 | printf '%s\n' "$LINES $COLUMNS" 16 | } 17 | ``` 18 | 19 | **示例用法:** 20 | 21 | ```shell 22 | # 输出: 行数 列数 23 | $ get_term_size 24 | 15 55 25 | ``` 26 | 27 | ##获取终端的像素大小 28 | 29 | **警告**: 这在某些终端仿真器中不起作用。 30 | 31 | **示例函数:** 32 | 33 | ```sh 34 | get_window_size() { 35 | # 用法: get_window_size 36 | printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}" 37 | IFS=';t' read -d t -t 0.05 -sra term_size 38 | printf '%s\n' "${term_size[1]}x${term_size[2]}" 39 | } 40 | ``` 41 | 42 | **示例用法:** 43 | 44 | ```shell 45 | # 输出: 长度x高度 46 | $ get_window_size 47 | 1200x800 48 | 49 | # 输出 (失败): 50 | $ get_window_size 51 | x 52 | ``` 53 | 54 | ##获取当前光标位置 55 | 56 | 用纯bash创建TUI时,是很有用的。 57 | TUI是指文本用户界面(Text-based User Interface),通过文本实现交互窗口展示内容,定位光标和鼠标实现用户交互。 58 | 59 | **示例函数:** 60 | 61 | ```sh 62 | get_cursor_pos() { 63 | # 用法: get_cursor_pos 64 | IFS='[;' read -p $'\e[6n' -d R -rs _ y x _ 65 | printf '%s\n' "$x $y" 66 | } 67 | ``` 68 | 69 | **示例用法:** 70 | 71 | ```shell 72 | # Output: X Y 73 | $ get_cursor_pos 74 | 1 8 75 | ``` 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /百分号编码字符串.md: -------------------------------------------------------------------------------- 1 | #
百分号编码字符串
# 2 | 3 | # 百分号编码字符串 4 | urlencode() { 5 | # 用法: urlencode "string" 6 | local LC_ALL=C 7 | for (( i = 0; i < ${#1}; i++ )); do 8 | : "${1:i:1}" 9 | case "$_" in 10 | [a-zA-Z0-9.~_-]) 11 | printf '%s' "$_" 12 | ;; 13 | 14 | *) 15 | printf '%%%02X' "'$_" 16 | ;; 17 | esac 18 | done 19 | printf '\n' 20 | } 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ---------- 29 | 30 | **语法说明:** 31 | 32 | 33 | > local LC_ALL=C 34 | 35 | - 去除所有本地化的设置,设置语言环境为POSIX【C】,让命令能正确执行; 36 | - 使用local,就相当于在函数开始的时候定义变量,在函数返回之前做unset,只不过local用起来更简单明了。 37 | 38 | > ${#1}  "${1:i:1}" 39 | 40 | - `${#1}`返回第一个参数的长度,即有多少个字符; 41 | - `"${1:i:1}"` 每次提取一个字符 42 | 43 | 44 | > %%%02x 45 | 46 | - %%%02x 可以分开为两部分"%%"和"%02X"; 47 | - 两个%%是输出一个'%',这里第一个%是转义符; 48 | - %02x中的%x是把数字输出为16进制的格式,%02x是保证输出至少占两个字符的位置,如果不够两位的话前面补0 49 | 50 | 51 | ---------- 52 | 53 | **bash中的case语句** 54 | 55 | 56 | case ${VAR} in 57 | pattern1) 58 | commands1 59 | ;; 60 | pattern2) 61 | commands2 62 | ;; 63 | esac 64 | 65 | 66 | pattern表示通配符表达式,注意,与正则表达式有区别 67 | 比如:通配符如果加上双引号后就不是按通配符处理,而是按文本处理。 68 | -------------------------------------------------------------------------------- /字符串大小写转换.md: -------------------------------------------------------------------------------- 1 | #
字符串大小写转换
# 2 | 3 | # 将字符串转换为小写 4 | lower() { 5 | # Usage: lower "string" 6 | printf '%s\n' "${1,,}" 7 | } 8 | 9 | 10 | # 将字符串转换为大写 11 | upper() { 12 | # Usage: upper "string" 13 | printf '%s\n' "${1^^}" 14 | } 15 | 16 | # 反转字符串大小写 17 | reverse_case() { 18 | # Usage: reverse_case "string" 19 | printf '%s\n' "${1~~}" 20 | } 21 | 22 | 23 | 24 | 25 | 26 | ---------- 27 | 28 | **bash中的大小写转换:** 29 | 30 | 31 | ${PARAMETER^} 32 | 33 | ${PARAMETER^^} 34 | 35 | ${PARAMETER,} 36 | 37 | ${PARAMETER,,} 38 | 39 | ${PARAMETER~} 40 | 41 | ${PARAMETER~~} 42 | 43 | 44 | 这些扩展操作符修改参数文本中字母的大小写(以`"aaBB Cd"`为例)。 45 | 46 | - `^`运算符将第一个字符修改为大写;(`"AaBB Cd"`) 47 | - `,`运算符将第一个字符修改为小写;(`"aaBB Cd"`) 48 | - 使用双重格式(`^^`和`,,`)时,将转换所有字符;(`"AABB CD"`)(`"aabb cd"`) 49 | - `〜`反转字符串中每个单词的第一个字母;(`"AaBB cd"`) 50 | - 同时`~~`反转所有字符.(`"AAbb cD"`) 51 | 52 | 53 | ***对数组的处理*** 54 | 对于数组而言,大小写转换作用于每个展开的元素,例如: 55 | 56 | 57 | 定义: array=(This is some Text) 58 | 59 | echo "${array[@],}" 60 | ⇒ this is some text 61 | 62 | echo "${array[@],,}" 63 | ⇒ this is some text 64 | 65 | echo "${array[@]^}" 66 | ⇒ This Is Some Text 67 | 68 | echo "${array[@]^^}" 69 | ⇒ THIS IS SOME TEXT 70 | 71 | echo "${array[2]^^}" 72 | ⇒ SOME 73 | 74 | -------------------------------------------------------------------------------- /CONTRIBUTING-Zh_CN.md: -------------------------------------------------------------------------------- 1 | # 编写Bible 2 | 3 | 4 | 5 | * [往Bible增加代码.](#往Bible增加代码) 6 | * [代码块的特殊含义.](#代码块的特殊含义) 7 | * [编写测试脚本](#编写测试脚本) 8 | * [运行测试](#运行测试) 9 | 10 | 11 | 12 | ## 往Bible增加代码. 13 | 14 | - 代码必须仅仅使用 `bash` 内建函数或功能. 15 | - 如果代码执行不能始终正常工作,强烈建议增加失败回调以允许程序正常回退到外部程序. 16 | - 失败回调距离: `${HOSTNAME:-$(hostname)}` 17 | - 如果可能的话,将代码封装在函数中. 18 | - 这样做可以方便测试程序的编写. 19 | - 这将同样方便 `shellcheck` 正确的扫描检测. 20 | - 另外一个好处是展示一个工作用例. 21 | - 编写一些用例. 22 | - 显示一些输入和输出. 23 | 24 | 25 | ## 代码块的特殊含义. 26 | 27 | 在函数上标记 `sh` 表示可以被扫描和单元测试. 28 | 29 | ```sh 30 | # Shellcheck 将会扫描分析,测试脚本将会获取到这个函数. 31 | func() { 32 | # Usage: func "arg" 33 | : 34 | } 35 | ``` 36 | 37 | 在代码上标记 `shell` 表示这段代码将被忽略. 38 | 39 | ```shell 40 | # Shorter file creation syntax. 41 | :>file 42 | ``` 43 | 44 | ## 编写测试脚本 45 | 46 | 可在此处查看测试文件: https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/test.sh 47 | 48 | 测试单元举例说明: 49 | 50 | ```sh 51 | test_upper() { 52 | result="$(upper "HeLlO")" 53 | assert_equals "$result" "HELLO" 54 | } 55 | ``` 56 | 57 | 步骤: 58 | 59 | 1. 编写测试脚本. 60 | - 名字命名为 `test_func_name` 61 | - 将函数输出存储在变量中 (`$result` or `${result[@]}`). 62 | - 调用 `assert_equals` 测试变量和预期输出之间的相等性。 63 | 2. 测试脚本将自动执行它. :+1: 64 | 65 | 66 | ## 运行测试 67 | 68 | 运行 `test.sh` 的同时也将在代码上执行`shellcheck`. 69 | 70 | ```sh 71 | cd pure-bash-bible 72 | ./test.sh 73 | ``` 74 | -------------------------------------------------------------------------------- /manuscript/chapter7.txt: -------------------------------------------------------------------------------- 1 | #转义字符 2 | 3 | 与流行的看法相反, 使用原始的转义字符并不会出现问题. 使用`tput`抽象相同的ANSI序列,就像手动打印一样. 更糟糕的是,`tput`实际上并不是便携式的. 有许多`tput`变体,每个变体都有不同的命令和语法 (*尝试运行 `tput setaf 3` 在 FreeBSD 系统里*). 4 | 5 | ##文本颜色 6 | 7 | **NOTE:** 需要RGB值的序列仅适用于真彩色终端仿真器. 8 | 9 | | 序列 | 它将做什么? | 值 | 10 | | -------- | ---------------- | ----- | 11 | | `\e[38;5;m` | 设置文本前景色. | `0-255` 12 | | `\e[48;5;m` | 设置文本背景颜色. | `0-255` 13 | | `\e[38;2;;;m` | 将文本前景色设置为RGB颜色. | `R`, `G`, `B` 14 | | `\e[48;2;;;m` | 将文本背景颜色设置为RGB颜色. | `R`, `G`, `B` 15 | 16 | ##文本属性 17 | 18 | | 序列 | 它将做什么? | 19 | | -------- | ---------------- | 20 | | `\e[m` | 重置文本格式和颜色. 21 | | `\e[1m` | 粗体. | 22 | | `\e[2m` | 微弱的文字. | 23 | | `\e[3m` | 斜体文字. | 24 | | `\e[4m` | 下划线文字. | 25 | | `\e[5m` | 慢速闪烁. | 26 | | `\e[7m` | 交换前景色和背景色. | 27 | 28 | 29 | ##移动光标 30 | 31 | | 序列 | 它将做什么? | 值 | 32 | | -------- | ---------------- | ----- | 33 | | `\e[;H` | 将光标移动到绝对位置. | `line`, `column` 34 | | `\e[H` | 将光标移动到原位 (`0,0`). | 35 | | `\e[A` | 将光标向上移动N行. | `num` 36 | | `\e[B` | 将光标向下移动N行. | `num` 37 | | `\e[C` | 将光标向右移动N列. | `num` 38 | | `\e[D` | 将光标向左移动N列. | `num` 39 | | `\e[s` | 保存光标位置. | 40 | | `\e[u` | 恢复光标位置. | 41 | 42 | 43 | ##删除文本 44 | 45 | | 序列 | 它将做什么? | 46 | | -------- | ---------------- | 47 | | `\e[K` | 从光标位置删除到行尾. 48 | | `\e[1K` | 从光标位置删除到行首. 49 | | `\e[2K` | 擦除整个当前行. 50 | | `\e[J` | 从当前行删除到屏幕底部. 51 | | `\e[1J` | 从当前行删除到屏幕顶部. 52 | | `\e[2J` | 清除屏幕. 53 | | `\e[2J\e[H` | 清除屏幕并将光标移动到 `0,0`. 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /删除字符串中的所有的空白并用空格分割单词.md: -------------------------------------------------------------------------------- 1 | #
删除字符串中的所有的空白并用空格分割单词
# 2 | 3 | # sh 4 | trim_all() { 5 | # Usage: trim_all " example string " 6 | set -f 7 | set -- $* 8 | printf '%s\n' "$*" 9 | set +f 10 | } 11 | 12 | 13 | 14 | 15 | ---------- 16 | 17 | **语法说明:** 18 | 19 | 使用范例: `trim_all "   aaa  bbb  ccc  "` 20 | > set -f 21 | 22 | - set命令使用,-f表示取消通配符(目前测试发现去掉此句也无影响,暂时不知道作者这么写的初衷是啥); 23 | - +f 表示恢复通配符 24 | 25 | 26 | > set - - $* 27 | 28 | - 同样参考man bash中Special Parameters一节内容即可; 29 | - `--` (double bash)其实是bash内置命令,表示选项的结束,其后的任何参数都被视为文件名和参数; 30 | - `$ *`相当于`$1c$2c...`,其中c是IFS变量值的第一个字符,如果未设置IFS,则参数由空格分隔; 31 | - 即,原输入参数 "   aaa  bbb  ccc  " 在词句执行后会被解释成"aaa bbb ccc" 32 | 33 | 34 | ---------- 35 | 36 | ## **Linux脚本中的$#、$0、$1、$@、$*、$$、$?** ## 37 | 38 | 假设我们定义了一个命令的参数为:`" 11  22  33" "44   55"` 39 | 40 | 41 | - 我们可以用 **$ + 一个符号** 获取不同的参数值: 42 | 43 |
44 | - **$*** 45 |  以一个单字符串显示所有向脚本传递的参数(强调整体),即:`"11 22 33 44 55"`即当成一个整体输出,每一个变量参数之间以空格隔开; 46 | 47 | - **$@** 48 |  传给脚本的所有参数的列表(强调独立),即:`"11" "22" "33" "44" "55"`即每一个变量参数是独立的,可以使用for循环进行迭代输出 ; 49 | 50 | `$*` 和 `$@` 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。 51 | 但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。 52 | 53 | 54 | - **$#** 55 |  添加到Shell的参数个数,此处是2个(因为双引号内被认为是一个参数) 56 | 57 | - **$$** 58 |  脚本运行的当前进程ID号 59 | 60 | - **$?** 61 |  显示最后命令的退出状态,0表示没有错误,其他表示有错误 62 | 63 | - **$!** 64 |  Shell最后运行的后台Process的PID 65 | 66 | - **$0** 67 |  脚本本身的名字 68 | 69 | - **$1($2,$3…)** 70 |  传递给该shell脚本的第N个参数 71 | 72 |
73 | -------------------------------------------------------------------------------- /manuscript/chapter3.txt: -------------------------------------------------------------------------------- 1 | #循环 2 | 3 | ##循环生成范围内的数字 4 | 5 | 替代`seq`. 6 | 7 | ```shell 8 | # Loop from 0-100 (no variable support). 9 | for i in {0..100}; do 10 | printf '%s\n' "$i" 11 | done 12 | ``` 13 | 14 | ##循环遍历可变数字范围 15 | 16 | 替代 `seq`. 17 | 18 | ```shell 19 | # Loop from 0-VAR. 20 | VAR=50 21 | for ((i=0;i<=VAR;i++)); do 22 | printf '%s\n' "$i" 23 | done 24 | ``` 25 | 26 | ##循环数组 27 | 28 | ```shell 29 | arr=(apples oranges tomatoes) 30 | 31 | # Just elements. 32 | for element in "${arr[@]}"; do 33 | printf '%s\n' "$element" 34 | done 35 | ``` 36 | 37 | ##循环输出带索引的数组 38 | 39 | ```shell 40 | arr=(apples oranges tomatoes) 41 | 42 | # 元素和索引. 43 | for i in "${!arr[@]}"; do 44 | printf '%s %s\n' "$i ${arr[i]}" 45 | done 46 | 47 | # 替代方法. 48 | for ((i=0;i<${#arr[@]};i++)); do 49 | printf '%s %s\n' "$i ${arr[i]}" 50 | done 51 | ``` 52 | 53 | ##循环遍历文件的内容 54 | 55 | ```shell 56 | while read -r line; do 57 | printf '%s\n' "$line" 58 | done < "file" 59 | ``` 60 | 61 | ##循环遍历文件和目录 62 | 63 | 64 | 不要 `ls`. 65 | 66 | ```shell 67 | # 遍历当前目录下的文件和目录. 68 | for file in *; do 69 | printf '%s\n' "$file" 70 | done 71 | 72 | # 遍历目录中的png图片. 73 | for file in ~/Pictures/*.png; do 74 | printf '%s\n' "$file" 75 | done 76 | 77 | # 迭代输出目录. 78 | for dir in ~/Downloads/*/; do 79 | printf '%s\n' "$dir" 80 | done 81 | 82 | # 支持扩展. 83 | for file in /path/to/parentdir/{file1,file2,subdir/file3}; do 84 | printf '%s\n' "$file" 85 | done 86 | 87 | # 递归迭代,输出子目录下的所有文件. 88 | # 89 | shopt -s globstar 90 | for file in ~/Pictures/**/*; do 91 | printf '%s\n' "$file" 92 | done 93 | shopt -u globstar 94 | ``` 95 | globstar是Bash 4.0才引入的选项,当设置启用globstar(shopt -s globstar)时,两个星号意为对通配符进行展开就可以匹配任何当前目录(包括子目录)以及其的文件;若不启用globstar(shopt -u globstar),两个星号通配符的作用和一个星号通配符是相同的。 96 | 97 | 98 | -------------------------------------------------------------------------------- /manuscript/chapter10.txt: -------------------------------------------------------------------------------- 1 | 2 | #条件表达式 3 | 4 | ##文件判断 5 | 6 | | 表达式 | 值 | 它有什么作用? | 7 | | ---------- | ------ | ---------------- | 8 | | `-a` | `file` | 文件存在 9 | | `-b` | `file` | 文件存在并且是块特殊文件. 10 | | `-c` | `file` | 文件存在并且是字符特殊文件. 11 | | `-d` | `file` | 文件存在且是目录. 12 | | `-e` | `file` | 文件存在. 13 | | `-f` | `file` | 文件存在且是常规文件. 14 | | `-g` | `file` | 文件存在且其set-group-id位已设置. 15 | | `-h` | `file` | 文件存在并且是符号链接. 16 | | `-k` | `file` | 文件存在且其sticky-bit已设置 17 | | `-p` | `file` | 文件存在并且是命名管道 (*FIFO*). 18 | | `-r` | `file` | 文件存在且可读. 19 | | `-s` | `file` | 文件存在且其大小大于零. 20 | | `-t` | `fd` | 文件描述符是打开的并且引用到一个终端. 21 | | `-u` | `file` | 文件存在且其set-user-id位已设置. 22 | | `-w` | `file` | 文件存在且可写. 23 | | `-x` | `file` | 文件存在且可执行. 24 | | `-G` | `file` | 文件存在且拥有者是一个有效组ID. 25 | | `-L` | `file` | 文件存在并且是符号链接. 26 | | `-N` | `file` | 文件存在且自上次读取后已被修改. 27 | | `-O` | `file` | 文件存在并且拥有者是一个有效用户ID. 28 | | `-S` | `file` | 文件存在且是套接字. 29 | 30 | ##文件比较 31 | 32 | | 表达式 | 它有什么作用? | 33 | | ---------- | ---------------- | 34 | | `file -ef file2` | 是否两个文件都引用相同的inode和设备编号. 35 | | `file -nt file2` | 是否 `file` 比 `file2`更新 (*使用修改时间*) 或者 `file` 存在而 `file2` 不存在. 36 | | `file -ot file2` | 是否 `file` 比 `file2`更老 (*使用修改时间*) 或者 `file2` 存在而 `file` 不存在. 37 | 38 | ##条件变量 39 | 40 | | 表达式 | 值 | 它有什么作用? | 41 | | ---------- | ----- | ---------------- | 42 | | `-o` | `opt` | 是否启用了shell选项. 43 | | `-v` | `var` | 是否变量具有指定的值. 44 | | `-R` | `var` | 是否变量是一个名称引用. 45 | | `-z` | `var` | 是否字符串的长度为零. 46 | | `-n` | `var` | 是否字符串的长度不为零. 47 | 48 | ##比较变量 49 | 50 | | 表达式 | 它有什么作用? | 51 | | ---------- | ---------------- | 52 | | `var = var2` | 等于. 53 | | `var == var2` | 等于 (*同义词 `=`*). 54 | | `var != var2` | 不等于. 55 | | `var < var2` | 小于 (*以ASCII字母顺序排列.*) 56 | | `var > var2` | 大于 (*以ASCII字母顺序排列.*) 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /在字符串中匹配正则表达式.md: -------------------------------------------------------------------------------- 1 | #
在字符串中匹配正则表达式
# 2 | 3 | # sh 4 | regex() { 5 | # Usage: regex "string" "regex" 6 | [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}" 7 | } 8 | 9 | 10 | 11 | 12 | ---------- 13 | 14 | **语法说明:** 15 | 16 | 使用范例: `regex "   aaa  bbb  ccc  " "\s{2}(.{3})"` 17 | 18 | 19 | > [[ $1 =~ $2 ]] 20 | 21 | - 在bash3.0之后,bash内置了regexp ,`=~`就是match匹配; 22 | - `=~`左边为源字符串,右边为pattern,表示以 pattern 对该操作符左边的展开内容进行匹配; 23 | - 需要注意的是:**当使用单引号或双引号将 pattern 括起来的时候,括起来的 pattern 将会变为一般字符而非正则表达式元字符** 24 | 即:`[[ "   aaa  bbb  ccc  " =~ "\s{2}(.{3})" ]]`不会匹配到任何内容,但由于代码里用的是`$2`变量,则不存在这种问题。 25 | 26 | 27 | > "${BASH_REMATCH[1]}" 28 | 29 | - bash会将匹配到的结果保存到 BASH_REMATCH 数组中,BASH_REMATCH[0] 表示匹配到的所有内容,BASH_REMATCH[1]–BASH_REMATCH[n] 依次存放匹配到的各个子组 30 | 31 | ---------- 32 | 33 | ## **bash中(),{},(()),[],[[]]的区别** ## 34 | 35 | 1. () 36 | 一个命令组合,相当于一个命令组(括号中的名字在**子shell中运行**) 37 | [root@abenmao ~]# I=123;(echo $I;I=xyz;echo $I;);echo $I 38 | 123 39 | xyz 40 | 123 41 | 42 | 2. {} 43 | 也是一个命令组合,不过与`()`不同的是,`{}`中的命令是在**当前shell执行** 44 | [root@abenmao ~]# I=123;{ echo $I;I=xyz;echo $I; };echo $I 45 | 123 46 | xyz 47 | xyz 48 | 另外需要注意的是,`{}`是一个keyword,所以在命令与两边的`{`需用空格隔离,同时使用";"表示命令结束。 49 | 50 | 3. [] 51 | `[]`主要用作条件判断,判断对象包括文件类型和赋值比较, 52 | 需要特别注意的是,`[`是一个builtin内置命令,故在其中的变量引用或常量,需使用双引号或者单引号括起来, 53 | 否则会出现单词分割[(Word-Splitting)](https://en.wikipedia.org/wiki/Text_segmentation)现象。 54 | 55 | 常见的比较测试如下: 56 | 57 | - 数字测试:-eq -ne -lt -le -gt -ge 58 | 59 | - 文件测试:-r、-l、-w、-x、-f、-d、-s、-nt、-ot 60 | 61 | - 字符串测试:=、!=、-n、-z、\>、\< 62 | 63 | - 逻辑测试:-a、-o、! 64 | 65 | 4. (()) 66 | 支持四则运算,等同于let功能 67 | 68 | 5. [[]] 69 | - `[[]]`类似于`[]`,用于比较功能,但是支持正则表达式; 70 | - `[[]]`为一个keyword,括号与表达式中间必须要有空格进行隔离; 71 | - `[[]]`的逻辑运算符为"&&"、"||",支持[逻辑短路](https://zh.wikipedia.org/wiki/%E7%9F%AD%E8%B7%AF%E6%B1%82%E5%80%BC)。 72 | 73 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Writing the Bible 2 | 3 | 4 | 5 | * [Adding Code to the Bible.](#adding-code-to-the-bible) 6 | * [Special meanings for code blocks.](#special-meanings-for-code-blocks) 7 | * [Writing tests](#writing-tests) 8 | * [Running the tests](#running-the-tests) 9 | 10 | 11 | 12 | ## Adding Code to the Bible. 13 | 14 | - The code must use only `bash` built-ins. 15 | - A fallback to an external program is allowed if the code doesn't 16 | always work. 17 | - Example Fallback: `${HOSTNAME:-$(hostname)}` 18 | - If possible, wrap the code in a function. 19 | - This allows tests to be written. 20 | - It also allows `shellcheck` to properly lint it. 21 | - An added bonus is showing a working use-case. 22 | - Write some examples. 23 | - Show some input and the modified output. 24 | 25 | 26 | ## Special meanings for code blocks. 27 | 28 | Use `sh` for functions that should be linted and unit tested. 29 | 30 | ```sh 31 | # Shellcheck will lint this and the test script will source this. 32 | func() { 33 | # Usage: func "arg" 34 | : 35 | } 36 | ``` 37 | 38 | Use `shell` for code that should be ignored. 39 | 40 | ```shell 41 | # Shorter file creation syntax. 42 | :>file 43 | ``` 44 | 45 | ## Writing tests 46 | 47 | The test file is viewable here: https://github.com/dylanaraps/pure-bash-bible/blob/master/test.sh 48 | 49 | Example test: 50 | 51 | ```sh 52 | test_upper() { 53 | result="$(upper "HeLlO")" 54 | assert_equals "$result" "HELLO" 55 | } 56 | ``` 57 | 58 | Steps: 59 | 60 | 1. Write the test. 61 | - Naming is `test_func_name` 62 | - Store the function output in a variable (`$result` or `${result[@]}`). 63 | - Use `assert_equals` to test equality between the variable and the 64 | expected output. 65 | 2. The test script will automatically execute it. :+1: 66 | 67 | 68 | ## Running the tests 69 | 70 | Running `test.sh` also runs `shellcheck` on the code. 71 | 72 | ```sh 73 | cd pure-bash-bible 74 | ./test.sh 75 | ``` 76 | -------------------------------------------------------------------------------- /指定分隔符拆分字符串.md: -------------------------------------------------------------------------------- 1 | #
指定分隔符拆分字符串
# 2 | 3 | # sh 4 | split() { 5 | # Usage: split "string" "delimiter" 6 | IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" 7 | printf '%s\n' "${arr[@]}" 8 | } 9 | 10 | 11 | 12 | 13 | ---------- 14 | 15 | **语法说明:** 16 | 17 | 使用范例: `split "aa--bb--cc--dd--n n" "--"` 18 | 19 | > IFS=$'\n' 20 | 21 | - 此处是设置内部字段分隔符(IFS); 22 | - 环境变量IFS,称为内部字段分隔符(internal field separator),定义了bash shell用作字段分隔符的一系列字符。 23 | 默认情况下,bash shell会将下列字符当做字段分隔符: 24 | - 空格 25 | - 制表符 26 | - 换行符 27 | - 这里的作用是确保换行符是IFS,防止字符串中的空格被当做分隔符。 28 | 29 | 30 | > "${1//$2/$'\n'}" 31 | 32 | - 此句是在第一个参数中用`\n`换行符替换所有匹配第二个参数字段,详见[`${ } 的一些特异功能`](./删除字符串前后空格.md) 33 | - `"aa--bb--cc--dd--n n" `就变成了`"aa\nbb\ncc\ndd\nn n"`(注意:空格没有被替换) 34 | 35 | > <<< 36 | 37 | - `<<<`被称作Here String,是[here document](https://zh.wikipedia.org/zh-hans/Here%E6%96%87%E6%A1%A3)的一种定制形式; 38 | - 格式是:COMMAND <<<$WORD,$WORD将被扩展并且被送入COMMAND的stdin中; 39 | 40 | > read -d "" -ra arr 41 | 42 | 此句就是将字符串根据IFS进行分割,并将分割后的字段赋值给数组`arr` 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ---------- 52 | 53 | ## **环境变量IFS** ## 54 | 55 | 56 | 那么下面三个IFS的区别是什么呢? 57 | 58 | - `IFS='\n'` // 将字符\和字符n作为IFS的换行符。 59 | - `IFS=$'\n'` // 真正的使用换行符做为字段分隔符。 60 | - `IFS='\n':;"` // 这个赋值会将反斜杠、n、冒号、分号和双引号作为字段分隔符。 61 | 62 | 63 | 64 | 65 | ---------- 66 | 67 | ## **read命令** ## 68 | 69 | 70 | - read命令用于从标准输入中读取输入单行,并将读取的单行根据IFS变量分裂成多个字段,并将分割后的字段分别赋值给指定的变量列表。 71 | - 如果没有指定任何存储变量,则分割后的所有字段都存储在特定变量REPLY中。 72 | - 选项说明 73 | ``` 74 | -a:将分裂后的字段依次存储到指定的数组中,存储的起始位置从数组的index=0开始。 75 | -d:指定读取行的结束符号。默认结束符号为换行符。 76 | -n:限制读取N个字符就自动结束读取,如果没有读满N个字符就按下回车或遇到换行符,则也会结束读取。 77 | -N:严格要求读满N个字符才自动结束读取,即使中途按下了回车或遇到了换行符也不结束。其中换行符或回车算一个字符。 78 | -p:给出提示符。默认不支持"\n"换行,要换行需要特殊处理,见下文示例。例如,"-p 请输入密码:" 79 | -r:禁止反斜线的转义功能。这意味着"\"会变成文本的一部分。 80 | -s:静默模式。输入的内容不会回显在屏幕上。 81 | -t:给出超时时间,在达到超时时间时,read退出并返回错误。也就是说不会读取任何内容,即使已经输入了一部分。 82 | ``` 83 | 84 | 85 | 86 | ---------- 87 | -------------------------------------------------------------------------------- /manuscript/chapter8.txt: -------------------------------------------------------------------------------- 1 | #参数拓展 2 | 3 | ##间接 4 | 5 | | 参数 | 它将做什么? | 6 | | --------- | ---------------- | 7 | | `${!VAR}` | 根据`VAR`的值访问一个变量. 8 | | `${!VAR*}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. | 9 | | `${!VAR@}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. 如果是双引号,则每个变量名称都会扩展为单独的单词. | 10 | 11 | 12 | ##替换 13 | 14 | | 参数 | 它将做什么? | 15 | | --------- | ---------------- | 16 | | `${VAR#PATTERN}` | 删除第一次匹配的模式及其左边的字符. | 17 | | `${VAR##PATTERN}` | 删除最后一次匹配的模式及其左边的字符. | 18 | | `${VAR%PATTERN}` | 删除最后一次匹配的模式及其右边的字符. | 19 | | `${VAR%%PATTERN}` | 删除第一次匹配的模式及其右边的字符. | 20 | | `${VAR/PATTERN/REPLACE}` | 替换第一次匹配的字符. 21 | | `${VAR//PATTERN/REPLACE}` | 替换所有匹配的字符. 22 | | `${VAR/PATTERN}` | 删除第一次匹配的字符. 23 | | `${VAR//PATTERN}` | 删除所有匹配的字符. 24 | 25 | ##长度 26 | 27 | | 参数 | 它将做什么? | 28 | | --------- | ---------------- | 29 | | `${#VAR}` | 字符变量的长度. 30 | | `${#ARR[@]}` | 数组的长度. 31 | 32 | ##扩展 33 | 34 | | 参数 | 它将做什么? | 版本要求 | 35 | | --------- | ---------------- | 36 | | `${VAR:OFFSET}` | 从变量中删除第OFFSET个字符及之前的字符. 37 | | `${VAR:OFFSET:LENGTH}` | 获得从`OFFSET`字符之后`LENGTH`个字符的字符串.
(`${VAR:10:10}`: 获得从第10个字符到第20个字符的字符串) 38 | | `${VAR:: OFFSET}` | 从变量中获取前`OFFSET`个字符. 39 | | `${VAR:: -OFFSET}` | 从变量中移除前`OFFSET`个字符. 40 | | `${VAR: -OFFSET}` | 从变量中获取最后`OFFSET`个字符. 41 | | `${VAR:OFFSET:-OFFSET}` | 删除前`OFFSET`个字符以及最后`OFFSET`个字符. | `bash 4.2+` | 42 | 43 | ##改变大小写 44 | 45 | | 参数 | 它将做什么? | 版本要求 | 46 | | --------- | ---------------- | ------ | 47 | | `${VAR^}` | 大写第一个字符. | `bash 4+` | 48 | | `${VAR^^}` | 大写所有字符. | `bash 4+` | 49 | | `${VAR,}` | 小写第一个字符. | `bash 4+` | 50 | | `${VAR,,}` | 小写所有字符. | `bash 4+` | 51 | | `${VAR~}` | 反转第一个字符. | `bash 4+` | 52 | | `${VAR~~}` | 反转所有字符. | `bash 4+` | 53 | 54 | 55 | ##默认值 56 | 57 | | 参数 | 它将做什么? | 58 | | --------- | ---------------- | 59 | | `${VAR:-STRING}` | 如果 `VAR` 为空或未设置,使用 `STRING` 作为它的值. 60 | | `${VAR-STRING}` | 如果 `VAR` 未设置, 使用 `STRING` 作为它的值. 61 | | `${VAR:=STRING}` | 如果 `VAR` 为空或未设置, 设置 `VAR` 的值为 `STRING`. 62 | | `${VAR=STRING}` | 如果 `VAR` 未设置, 设置 `VAR` 的值为 `STRING`. 63 | | `${VAR:+STRING}` | 如果 `VAR` 不为空, 使用 `STRING` 作为它的值. 64 | | `${VAR+STRING}` | 如果 `VAR` 已设置, 使用 `STRING` 作为它的值. 65 | | `${VAR:?STRING}` | 如果为空或未设置,则显示一个错误. 66 | | `${VAR?STRING}` | 如果未设置则显示一个错误. 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /manuscript/chapter2.txt: -------------------------------------------------------------------------------- 1 | #数组 2 | 3 | ##反转数组 4 | 5 | 启用`extdebug`允许访问`BASH_ARGV`数组,该数组反向存储当前函数的参数 6 | 7 | **示例函数:** 8 | 9 | ```sh 10 | reverse_array() { 11 | # Usage: reverse_array "array" 12 | shopt -s extdebug 13 | f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@" 14 | shopt -u extdebug 15 | } 16 | ``` 17 | 18 | **示例用法:** 19 | 20 | ```shell 21 | $ reverse_array 1 2 3 4 5 22 | 5 23 | 4 24 | 3 25 | 2 26 | 1 27 | 28 | $ arr=(red blue green) 29 | $ reverse_array "${arr[@]}" 30 | green 31 | blue 32 | red 33 | ``` 34 | 35 | ##删除重复的数组元素 36 | 37 | 创建临时关联数组。设置关联数组值并发生重复赋值时,bash会覆盖该键。这允许我们有效地删除数组重复。 38 | 39 | **警告:** 版本要求 `bash` 4+ 40 | 41 | **示例函数:** 42 | 43 | ```sh 44 | remove_array_dups() { 45 | # Usage: remove_array_dups "array" 46 | declare -A tmp_array 47 | 48 | for i in "$@"; do 49 | [[ $i ]] && IFS=" " tmp_array["${i:- }"]=1 50 | done 51 | 52 | printf '%s\n' "${!tmp_array[@]}" 53 | } 54 | ``` 55 | 56 | **示例用法:** 57 | 58 | ```shell 59 | $ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 60 | 1 61 | 2 62 | 3 63 | 4 64 | 5 65 | 66 | $ arr=(red red green blue blue) 67 | $ remove_array_dups "${arr[@]}" 68 | red 69 | green 70 | blue 71 | ``` 72 | 73 | ##随机返回一个数组元素 74 | 75 | **示例函数:** 76 | 77 | ```sh 78 | random_array_element() { 79 | # Usage: random_array_element "array" 80 | local arr=("$@") 81 | printf '%s\n' "${arr[RANDOM % $#]}" 82 | } 83 | ``` 84 | bash的SHELL参数RANDOM可以生成0-32767的随机数 85 | 想设定从1到N的随机数范围的话,可以使用: 86 | $ ( ( (RANDOM % n) + 1 )) 87 | 88 | **示例用法:** 89 | 90 | ```shell 91 | $ array=(red green blue yellow brown) 92 | $ random_array_element "${array[@]}" 93 | yellow 94 | 95 | # Multiple arguments can also be passed. 96 | $ random_array_element 1 2 3 4 5 6 7 97 | 3 98 | ``` 99 | 100 | ##循环迭代一个数组 101 | 102 | 每次`printf`调用时,都会打印下一个数组元素。当打印到达最后一个数组元素时,它再次从第一个元素开始。 103 | 104 | ```sh 105 | arr=(a b c d) 106 | 107 | cycle() { 108 | printf '%s ' "${arr[${i:=0}]}" 109 | ((i=i>=${#arr[@]}-1?0:++i)) 110 | } 111 | ``` 112 | 113 | 114 | ##在两个值之间转换 115 | 116 | 这与上面的工作方式相同,这只是一个不同的用例。 117 | 118 | 119 | ```sh 120 | arr=(true false) 121 | 122 | cycle() { 123 | printf '%s ' "${arr[${i:=0}]}" 124 | ((i=i>=${#arr[@]}-1?0:++i)) 125 | } 126 | ``` 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /删除重复的数组元素.md: -------------------------------------------------------------------------------- 1 | #
删除重复的数组元素
# 2 | 3 | # 删除重复的数组元素 4 | remove_array_dups() { 5 | # Usage: remove_array_dups "array" 6 | declare -A tmp_array 7 | 8 | for i in "$@"; do 9 | [[ $i ]] && IFS=" " tmp_array["${i:- }"]=1 10 | done 11 | 12 | printf '%s\n' "${!tmp_array[@]}" 13 | } 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ---------- 22 | 23 | **语法说明:** 24 | 25 | 26 | > declare -A tmp_array 27 | 28 | - 声明语句将一个变量声明为关联数组; 29 | 30 | 31 | > tmp_array["${i:- }"]=1 32 | 33 | - `"${i:- }" 如果i为空则返回空格,如果i不为空,则返回i; 34 | - `tmp_array["$i"]=1` 将关联数组以i作下标的值赋为1,如果之前已经存在,则会重复赋值,变相的过滤了重复元素。 35 | 36 | 37 | > ${!tmp_array[@]} 38 | 39 | - 使用`${!数组名[@或者*]}`获取数组的索引列表 40 | 41 | ---------- 42 | 43 | # bash中的关联数组 # 44 | 45 | 46 | 47 | - Bash从4.0的版本开始就可以使用关联数组了 48 | 49 | - 关联数组的下标可以采用非整型类型,类似于其他语言中的字典key-value类型,其中,key为下标,value为对应的元素的值,key唯一,value可以不唯一。 50 | 51 | 52 | - 使用关联数组前应该先声明 `declare -A array` 53 | 54 | 55 | > 关联数组用法如下: 56 | 57 | array["jim"]=158 58 | array["amy"]=168 59 | 60 | > 或者直接使用内嵌“索引-值”列表法: 61 | 62 | array=(["jim"]=158 ["amy"]=168) 63 | 64 | 65 | > 遍历array: 66 | 67 | for key in ${!array[*]} 68 | do 69 | echo $key 70 | done 71 | 72 | > 使用下标遍历array: 73 | 74 | 75 | for i in "${!array[@]}" 76 | do 77 | echo ${array[$i]} 78 | done 79 | 80 | > 其他语法如下: 81 | 82 | 83 | echo ${!array[*]} #取关联数组所有键 84 | echo ${!array[@]} #取关联数组所有键 85 | echo ${array[*]} #取关联数组所有值 86 | echo ${array[@]} #取关联数组所有值 87 | echo ${#array[*]} #取关联数组长度 88 | echo ${#array[@]} #取关联数组长度 89 | 90 | 91 | 92 | - 在为关联数组赋值时,不加"$",而是直接 array["fe"]=189 ,在取值的时候,要加"$",如 $array["fe"] 则得到的是189 93 | 94 | - 清空数组元素 unset array[“fe”] ,但是这样清空后,array中仍有“fe”这个key,只是其对应的值被清空了 95 | 96 | 97 | - 清空整个数组 unset array,但是这样清空后,array的key是没有了,但是整个array也不能再用了,如下所示: 98 | 99 | 100 | declare -A array 101 | array["jim"]=158 102 | unset array 103 | array["jim"]=189 104 | 105 | 则会出错:语法错误,也就是在unset array之后,该array不再是关联数组,所以key不可以用非整型来做索引 106 | 107 | 108 | 109 | - 判断某一个key是否在该关联数组中,如下所示: 110 | 111 | 112 | function isInCountResult(){ 113 | for key in ${!count_result[*]} 114 | do 115 | if [ "$1" = "$key" ] 116 | then 117 | return 0 118 | fi 119 | done 120 | return 1 121 | } 122 | -------------------------------------------------------------------------------- /manuscript/chapter18.txt: -------------------------------------------------------------------------------- 1 | #转换 2 | 3 | ##将十六进制颜色转换为RGB 4 | 5 | **示例函数:** 6 | 7 | ```sh 8 | hex_to_rgb() { 9 | # Usage: hex_to_rgb "#FFFFFF" 10 | # hex_to_rgb "000000" 11 | : "${1/\#}" 12 | ((r=16#${_:0:2},g=16#${_:2:2},b=16#${_:4:2})) 13 | printf '%s\n' "$r $g $b" 14 | } 15 | ``` 16 | 17 | **示例用法:** 18 | 19 | ```shell 20 | $ hex_to_rgb "#FFFFFF" 21 | 255 255 255 22 | ``` 23 | 24 | 25 | ##将RGB颜色转换为十六进制 26 | 27 | **示例函数:** 28 | 29 | ```sh 30 | rgb_to_hex() { 31 | # Usage: rgb_to_hex "r" "g" "b" 32 | printf '#%02x%02x%02x\n' "$1" "$2" "$3" 33 | } 34 | ``` 35 | 36 | **示例用法:** 37 | 38 | ```shell 39 | $ rgb_to_hex "255" "255" "255" 40 | #FFFFFF 41 | ``` 42 | 43 | 44 | # 代码高尔夫 45 | 46 | [CODE GOLF](https://en.wikipedia.org/wiki/Code_golf),看看谁写的代码最短! 47 | 48 | 49 | ##更短的`for`循环语法 50 | 51 | ```shell 52 | # Tiny C风格. 53 | for((;i++<10;)){ echo "$i";} 54 | 55 | # 未记载的方法. 56 | for i in {1..10};{ echo "$i";} 57 | 58 | # 扩展. 59 | for i in {1..10}; do echo "$i"; done 60 | 61 | # C语言风格. 62 | for((i=0;i<=10;i++)); do echo "$i"; done 63 | ``` 64 | 65 | ##更短的无限循环 66 | 67 | ```shell 68 | # 普通方法 69 | while :; do echo hi; done 70 | 71 | # 更短的方式 72 | for((;;)){ echo hi;} 73 | ``` 74 | 75 | ##更短的函数声明 76 | 77 | ```shell 78 | # 普通方法 79 | f(){ echo hi;} 80 | 81 | # 用于子shell 82 | f()(echo hi) 83 | 84 | # 用于四则运算 85 | # 这可以被用来分配整数值。 86 | # Example: f a=1 87 | # f a++ 88 | f()(($1)) 89 | 90 | # 用作测试,循环等 91 | # NOTE: ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’ 也可以使用. 92 | f()if true; then echo "$1"; fi 93 | f()for i in "$@"; do echo "$i"; done 94 | ``` 95 | 96 | ##更短的`if`语法 97 | 98 | ```shell 99 | # 一行 100 | # Note: 当第一段是正确时执行第三段 101 | # Note: 此处利用了逻辑运算符的短路规则 102 | [[ $var == hello ]] && echo hi || echo bye 103 | [[ $var == hello ]] && { echo hi; echo there; } || echo bye 104 | 105 | # 多行(没有else,单条语句) 106 | # Note: 退出状态可能与if语句不同 107 | [[ $var == hello ]] && 108 | echo hi 109 | 110 | # 多行 (没有 else) 111 | [[ $var == hello ]] && { 112 | echo hi 113 | # ... 114 | } 115 | ``` 116 | 117 | ##用`case`语句来更简单的设置变量 118 | 119 | 内置的`:`可以用来避免在case语句中重复的实用`variable =`。 120 | `$ _`变量存储最后一个命令的最后一个参数。 121 | `:`总会成功,所以它可以用来存储变量值。 122 | 123 | ```shell 124 | case "$OSTYPE" in 125 | "darwin"*) 126 | : "MacOS" 127 | ;; 128 | 129 | "linux"*) 130 | : "Linux" 131 | ;; 132 | 133 | *"bsd"* | "dragonfly" | "bitrig") 134 | : "BSD" 135 | ;; 136 | 137 | "cygwin" | "msys" | "win32") 138 | : "Windows" 139 | ;; 140 | 141 | *) 142 | printf '%s\n' "Unknown OS detected, aborting..." >&2 143 | exit 1 144 | ;; 145 | esac 146 | 147 | # 最后,获取变量值. 148 | os="$_" 149 | ``` 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /manuscript/chapter4.txt: -------------------------------------------------------------------------------- 1 | #文本处理 2 | 3 | **警告:** `bash`在小于`<4.4`的版本中不能正确处理二进制数据. 4 | 5 | ##读取文件到一个字符串中 6 | 7 | 替代 `cat` 命令. 8 | 9 | ```shell 10 | file_data="$(<"file")" 11 | ``` 12 | 13 | ##读取文件到一个数组中 (*按行读取*) 14 | 15 | 替代 `cat` 命令. 16 | 17 | ```shell 18 | # Bash <4 19 | IFS=$'\n' read -d "" -ra file_data < "file" 20 | 21 | # Bash 4+ 22 | mapfile -t file_data < "file" 23 | ``` 24 | 25 | ##获取文件的前N行 26 | 27 | 替代 `head` 命令. 28 | 29 | **警告:** 版本要求 `bash` 4+ 30 | 31 | **示例函数:** 32 | 33 | ```sh 34 | head() { 35 | # Usage: head "n" "file" 36 | mapfile -tn "$1" line < "$2" 37 | printf '%s\n' "${line[@]}" 38 | } 39 | ``` 40 | 41 | **示例用法:** 42 | 43 | ```shell 44 | $ head 2 ~/.bashrc 45 | # Prompt 46 | PS1='➜ ' 47 | 48 | $ head 1 ~/.bashrc 49 | # Prompt 50 | ``` 51 | 52 | ##获取文件的最后N行 53 | 54 | 替代 `tail` 命令. 55 | 56 | **警告:** 版本要求 `bash` 4+ 57 | 58 | **示例函数:** 59 | 60 | ```sh 61 | tail() { 62 | # Usage: tail "n" "file" 63 | mapfile -tn 0 line < "$2" 64 | printf '%s\n' "${line[@]: -$1}" 65 | } 66 | ``` 67 | 68 | **示例用法:** 69 | 70 | ```shell 71 | $ tail 2 ~/.bashrc 72 | # Enable tmux. 73 | # [[ -z "$TMUX" ]] && exec tmux 74 | 75 | $ tail 1 ~/.bashrc 76 | # [[ -z "$TMUX" ]] && exec tmux 77 | ``` 78 | 79 | ##获取文件中的行数 80 | 81 | 82 | 替代 `wc -l`. 83 | 84 | **示例函数 (bash 4):** 85 | 86 | ```sh 87 | lines() { 88 | # Usage: lines "file" 89 | mapfile -tn 0 lines < "$1" 90 | printf '%s\n' "${#lines[@]}" 91 | } 92 | ``` 93 | 94 | **示例函数 (bash 3):** 95 | 96 | 这个方法比`mapfile`方法使用更少的内存,并且在`bash` 3中工作,但对于更大的文件来说它更慢。 97 | 98 | ```sh 99 | lines_loop() { 100 | # Usage: lines_loop "file" 101 | count=0 102 | while IFS= read -r _; do 103 | ((count++)) 104 | done < "$1" 105 | printf '%s\n' "$count" 106 | } 107 | ``` 108 | 109 | **示例用法:** 110 | 111 | ```shell 112 | $ lines ~/.bashrc 113 | 48 114 | 115 | $ lines_loop ~/.bashrc 116 | 48 117 | ``` 118 | 119 | ##计算目录中的文件或目录 120 | 121 | 122 | 这是通过将glob的输出传递给函数然后计算参数的数量来实现的。 123 | 124 | 125 | 126 | **示例函数:** 127 | 128 | ```sh 129 | count() { 130 | # Usage: count /path/to/dir/* 131 | # count /path/to/dir/*/ 132 | printf '%s\n' "$#" 133 | } 134 | ``` 135 | 136 | **示例用法:** 137 | 138 | ```shell 139 | # Count all files in dir. 140 | $ count ~/Downloads/* 141 | 232 142 | 143 | # Count all dirs in dir. 144 | $ count ~/Downloads/*/ 145 | 45 146 | 147 | # Count all jpg files in dir. 148 | $ count ~/Pictures/*.jpg 149 | 64 150 | ``` 151 | 152 | ##创建一个空文件 153 | 154 | 替代 `touch`. 155 | 156 | ```shell 157 | # 简短的方式. 158 | >file 159 | 160 | # 更长的替代品: 161 | :>file 162 | echo -n >file 163 | printf '' >file 164 | ``` 165 | 166 | ##提取两个标记之间的行 167 | 168 | **示例函数:** 169 | 170 | ```sh 171 | extract() { 172 | # 用法: extract file "opening marker" "closing marker" 173 | while IFS=$'\n' read -r line; do 174 | [[ $extract && $line != "$3" ]] && 175 | printf '%s\n' "$line" 176 | 177 | [[ $line == "$2" ]] && extract=1 178 | [[ $line == "$3" ]] && extract= 179 | done < "$1" 180 | } 181 | ``` 182 | 183 | **示例用法:** 184 | 185 | ```shell 186 | # 从MarkDown文件中提取代码块. 187 | $ extract ~/projects/pure-bash/README.md '```sh' '```' 188 | # Output here... 189 | ``` 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /删除字符串前后空格.md: -------------------------------------------------------------------------------- 1 | #
删除字符串前后空格
# 2 | 3 | # sh 4 | trim_string() { 5 | # Usage: trim_string " example string " 6 | : "${1#"${1%%[![:space:]]*}"}" 7 | : "${_%"${_##*[![:space:]]}"}" 8 | printf '%s\n' "$_" 9 | } 10 | 11 | 12 | 13 | 14 | ---------- 15 | 16 | **语法说明:** 17 | 18 | 使用范例: `trim_string "   aaa bbb  "` 19 | > : "${1#"${1%%[![:space:]]*}"}" 20 | 21 | - 此处的1代表输入的第一个参数,即`"   aaa bbb  "`; 22 | - 在Bash里边`:`是一个占位符; 23 | - `[:space:]` 匹配一个包括换行符,回车等在内的所有空白符; 24 | - `[![:space:]]` 表示不包含空白符; 25 | 26 | 如果!紧跟在一对方括号的左方括号之后,则表示不包括在方括号中列出的字符: 27 | 例如:file[! 1-4].c  表示以file开头,第5个字符不是1-4之间数字的.c文件名 28 | - `${1%%[![:space:]]*}` 表示去掉第一个参数中第一个不是空白符的字符及其右边的字符串,也就是说会返回字符串开头的所有空格`"   "`; 29 | - 然后这行代码就变成了`${1#"   "}`,表示去掉第一个参数第一次匹配的`"   "`及其左边的字符串; 30 | - 上述过程就完成了删除字符串开头空格的功能了。 31 | 32 | 33 | > : "${_%"${_##*[![:space:]]}"}" 34 | 35 | - _ 是一个shell参数,它扩展为上一个命令的最后一个参数。这里,就表示上一行命令的输出`"aaa bbb  "`; 36 | - 剩余语法逻辑与第一句类似,此处不再赘述。 37 | 38 | 39 | ---------- 40 | 41 | ## **${ } 的一些特异功能** ## 42 | 43 | 假设我们定义了一个变量为:`file=/dir1/dir2/dir3/my.file.txt` 44 | 45 | 46 | - 我们可以用 **${ }** 分别替换获得不同的值: 47 | 48 |
49 | **${file#*/}** :去掉第一条 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt 50 | 51 | **${file##*/}** :去掉最后一条 / 及其左边的字符串:my.file.txt 52 | 53 | **${file#*.}** :去掉第一个 . 及其左边的字符串:file.txt 54 | 55 | **${file##*.}** :去掉最后一个 . 及其左边的字符串:txt 56 | 57 | **${file%/*}** :去掉最后条 / 及其右边的字符串:/dir1/dir2/dir3 58 | 59 | **${file%%/*}** :去掉第一条 / 及其右边的字符串:(空值) 60 | 61 | **${file%.*}** :去掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file 62 | 63 | **${file%%.*}** :去掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my 64 | 65 | **${file:0:5}**:提取最左边的 5 个字节:/dir1 66 | 67 | **${file:5:5}**:提取第 5 个字节右边的连续 5 个字节:/dir2 68 |
69 | 70 | 71 |   **记忆的方法为:** 72 | 73 |
74 | - `#` 是去掉左边(在键盘上 # 在 $ 之左边); 75 | 76 | - `%` 是去掉右边(在键盘上 % 在 $ 之右边); 77 | 78 | - 单一符号是匹配第一次(从左往右匹配,去掉第一组匹配上的); 79 | 80 | - 两个符号是匹配最后一次(从左往右,去掉最后一组匹配上的)。 81 |
82 | 83 | 84 | - 我们也可以对变量值里的字符串作替换: 85 | 86 |
87 | **${file/dir/path}** :将第一个 dir 提换为 path:/path1/dir2/dir3/my.file.txt 88 | 89 | **${file//dir/path}** :将全部 dir 提换为 path:/path1/path2/path3/my.file.txt 90 | 91 | **echo ${MYSTRING//conservative/}**  <=等价于=> **echo ${MYSTRING//conservative}** 92 | 相当于指定替换为空,即删除 93 |
94 | 95 | 96 | 97 | - 利用 ${ } 还可针对不同的变量状态赋值(没设定、空值、非空值)(unset 与 null 及 non-null) 98 | 99 |
100 | **${file-my.file.txt}** :假如 $file 没有设定,则使用 my.file.txt 作传回值。(空值及非空值时不作处理) 101 | 102 | **${file:-my.file.txt}** :假如 $file 没有设定或为空值,则使用my.file.txt 作传回值。 (非空值时不作处理) 103 | 104 | **${file+my.file.txt}** :假如 $file 设为空值或非空值,均使用my.file.txt作传回值。(没设定时不作处理) 105 | 106 | **${file:+my.file.txt}** :若 $file 为非空值,则使用 my.file.txt 作传回值。(没设定及空值时不作处理) 107 | 108 | **${file=my.file.txt}** :若 $file 没设定,则使用 my.file.txt 作传回值,同时将 $file 赋值为 my.file.txt 。 (空值及非空值时不作处理) 109 | 110 | **${file:=my.file.txt} **:若$file没设定或为空值,则使用my.file.txt作传回值,同时将 $file 赋值为my.file.txt 。(非空值时不作处理) 111 | 112 | **${file?my.file.txt}** :若 $file 没设定,则将 my.file.txt 输出至STDERR。 (空值及非空值时不作处理) 113 | 114 | **${file:?my.file.txt}** :若 $file 没设定或为空值,则将 my.file.txt 输出至 STDERR。 (非空值时不作处理) 115 | 116 |
117 | 118 | 119 | - 此外,${#var} 可计算出变量值的长度: 120 | 121 |
122 | **${#file}** 可得到 27 ,因为 /dir1/dir2/dir3/my.file.txt 刚好是 27 个字节... 123 |
124 | 125 | -------------------------------------------------------------------------------- /manuscript/chapter19.txt: -------------------------------------------------------------------------------- 1 | #其他 2 | 3 | ##使用`read`作为`sleep`命令的替代品 4 | 5 | 令人惊讶的是,`sleep`是一个外部命令而不是`bash`内置的。 6 | 7 | **警告:** 要求`bash`版本 4+ 8 | 9 | **示例函数:** 10 | 11 | ```sh 12 | read_sleep() { 13 | # 用法: sleep 1 14 | # sleep 0.2 15 | read -rt "$1" <> <(:) || : 16 | } 17 | ``` 18 | 19 | **示例用法:** 20 | 21 | ```shell 22 | read_sleep 1 23 | read_sleep 0.1 24 | read_sleep 30 25 | ``` 26 | 27 | 对于性能要求较高的情况下,打开和关闭过多的文件描述符是不实用的,对于`read`的所有调用,文件描述符的分配只能进行一次:: 28 | 29 | (请参阅最原始的功能实现 https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever) 30 | 31 | ```shell 32 | exec {sleep_fd}<> <(:) 33 | while some_quick_test; do 34 | # equivalent of sleep 0.001 35 | read -t 0.001 -u $sleep_fd 36 | done 37 | ``` 38 | 39 | ##检查一个命令是否在用户的PATH中 40 | 41 | ```shell 42 | # 有3种方法可以使用,任何一种都正确。 43 | type -p executable_name &>/dev/null 44 | hash executable_name &>/dev/null 45 | command -v executable_name &>/dev/null 46 | 47 | # 用作检测. 48 | if type -p executable_name &>/dev/null; then 49 | # Program is in PATH. 50 | fi 51 | 52 | # 反向检测. 53 | if ! type -p executable_name &>/dev/null; then 54 | # Program is not in PATH. 55 | fi 56 | 57 | # 示例(如果未安装程序,则提前退出). 58 | if ! type -p convert &>/dev/null; then 59 | printf '%s\n' "error: convert is not installed, exiting..." 60 | exit 1 61 | fi 62 | ``` 63 | 64 | ##使用`strftime`获取当前日期 65 | 66 | Bash的`printf`有一个内置的获取日期的方法,可用来代替`date`命令。 67 | 68 | **警告:** 要求`bash`版本 4+ 69 | 70 | **示例函数:** 71 | 72 | ```sh 73 | date() { 74 | # 用法: date "format" 75 | printf "%($1)T\\n" "-1" 76 | } 77 | ``` 78 | - 了解时间格式: ['man strftime'](http://www.man7.org/linux/man-pages/man3/strftime.3.html) . 79 | 80 | **示例用法:** 81 | 82 | ```shell 83 | # 使用上述函数. 84 | $ date "%a %d %b - %l:%M %p" 85 | Fri 15 Jun - 10:00 AM 86 | 87 | # 直接使用printf. 88 | $ printf '%(%a %d %b - %l:%M %p)T\n' "-1" 89 | Fri 15 Jun - 10:00 AM 90 | 91 | # 使用printf分配变量. 92 | $ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1' 93 | $ printf '%s\n' "$date" 94 | Fri 15 Jun - 10:00 AM 95 | ``` 96 | 97 | ##获取当前用户的用户名 98 | 99 | **警告:** 要求`bash`版本 4.4+ 100 | 101 | ```shell 102 | $ : \\u 103 | # Expand the parameter as if it were a prompt string. 104 | $ printf '%s\n' "${_@P}" 105 | black 106 | ``` 107 | 108 | ##生成一个V4版本的UUID 109 | 110 | **警告**: 生成的值不具有加密安全性。 111 | 112 | **示例函数:** 113 | 114 | ```sh 115 | uuid() { 116 | # 用法: uuid 117 | C="89ab" 118 | 119 | for ((N=0;N<16;++N)); do 120 | B="$((RANDOM%256))" 121 | 122 | case "$N" in 123 | 6) printf '4%x' "$((B%16))" ;; 124 | 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;; 125 | 126 | 3|5|7|9) 127 | printf '%02x-' "$B" 128 | ;; 129 | 130 | *) 131 | printf '%02x' "$B" 132 | ;; 133 | esac 134 | done 135 | 136 | printf '\n' 137 | } 138 | ``` 139 | 140 | **示例用法:** 141 | 142 | ```shell 143 | $ uuid 144 | d5b6c731-1310-4c24-9fe3-55d556d44374 145 | ``` 146 | 147 | ##进度条 148 | 149 | 这是一种绘制进度条的简单方法,无需在函数本身中使用for循环。 150 | 151 | **示例函数:** 152 | 153 | ```sh 154 | bar() { 155 | # 用法: bar 1 10 156 | # ^----- 已经完成的百分比 (0-100). 157 | # ^--- 字符总长度. 158 | ((elapsed=$1*$2/100)) 159 | 160 | # 创建空格表示的进度条 161 | printf -v prog "%${elapsed}s" 162 | printf -v total "%$(($2-elapsed))s" 163 | 164 | printf '%s\r' "[${prog// /-}${total}]" 165 | } 166 | ``` 167 | 168 | **示例用法:** 169 | 170 | ```shell 171 | for ((i=0;i<=100;i++)); do 172 | # 纯粹的暂停动作 (为了本例可以更好的演示). 173 | (:;:) && (:;:) && (:;:) && (:;:) && (:;:) 174 | 175 | # Print the bar. 176 | bar "$i" "10" 177 | done 178 | 179 | printf '\n' 180 | ``` 181 | 182 | ##获取脚本中的函数列表 183 | 184 | ```sh 185 | get_functions() { 186 | # Usage: get_functions 187 | IFS=$'\n' read -d "" -ra functions < <(declare -F) 188 | printf '%s\n' "${functions[@]//declare -f }" 189 | } 190 | ``` 191 | 192 | ##绕过shell别名 193 | 194 | ```shell 195 | # alias 196 | ls 197 | 198 | # command 199 | # shellcheck disable=SC1001 200 | \ls 201 | ``` 202 | 203 | ##绕过shell函数 204 | 205 | ```shell 206 | # function 207 | ls 208 | 209 | # command 210 | command ls 211 | ``` 212 | 213 | - command命令 调用指定的指令并执行,命令执行时不查询shell函数。command命令只能够执行shell内部的命令。 214 | 215 | ##在后台运行命令 216 | 217 | 这将运行给定命令并使其保持后台运行,即使终端或SSH连接中断后也是如此。但是会忽略所有输出。 218 | 219 | 220 | ```sh 221 | bkr() { 222 | (nohup "$@" &>/dev/null &) 223 | } 224 | 225 | bkr ./some_script.sh 226 | ``` 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck source=/dev/null 3 | # 4 | # Tests for the Pure Bash Bible. 5 | 6 | test_trim_string() { 7 | result="$(trim_string " Hello, World ")" 8 | assert_equals "$result" "Hello, World" 9 | } 10 | 11 | test_trim_all() { 12 | result="$(trim_all " Hello, World ")" 13 | assert_equals "$result" "Hello, World" 14 | } 15 | 16 | test_regex() { 17 | result="$(regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$')" 18 | assert_equals "$result" "#FFFFFF" 19 | } 20 | 21 | test_lower() { 22 | result="$(lower "HeLlO")" 23 | assert_equals "$result" "hello" 24 | } 25 | 26 | test_upper() { 27 | result="$(upper "HeLlO")" 28 | assert_equals "$result" "HELLO" 29 | } 30 | 31 | test_reverse_case() { 32 | result="$(reverse_case "HeLlO")" 33 | assert_equals "$result" "hElLo" 34 | } 35 | 36 | test_trim_quotes() { 37 | result="$(trim_quotes "\"te'st' 'str'ing\"")" 38 | assert_equals "$result" "test string" 39 | } 40 | 41 | test_strip_all() { 42 | result="$(strip_all "The Quick Brown Fox" "[aeiou]")" 43 | assert_equals "$result" "Th Qck Brwn Fx" 44 | } 45 | 46 | test_strip() { 47 | result="$(strip "The Quick Brown Fox" "[aeiou]")" 48 | assert_equals "$result" "Th Quick Brown Fox" 49 | } 50 | 51 | test_lstrip() { 52 | result="$(lstrip "!:IHello" "!:I")" 53 | assert_equals "$result" "Hello" 54 | } 55 | 56 | test_rstrip() { 57 | result="$(rstrip "Hello!:I" "!:I")" 58 | assert_equals "$result" "Hello" 59 | } 60 | 61 | test_urlencode() { 62 | result="$(urlencode "https://github.com/dylanaraps/pure-bash-bible")" 63 | assert_equals "$result" "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible" 64 | } 65 | 66 | test_urldecode() { 67 | result="$(urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible")" 68 | assert_equals "$result" "https://github.com/dylanaraps/pure-bash-bible" 69 | } 70 | 71 | test_reverse_array() { 72 | IFS=$'\n' read -d "" -ra result < <(reverse_array 1 2 3 4 5) 73 | assert_equals "${result[*]}" "5 4 3 2 1" 74 | } 75 | 76 | test_remove_array_dups() { 77 | IFS=$'\n' read -d "" -ra result < <(remove_array_dups 1 1 2 2 3 3 4 5) 78 | assert_equals "${result[*]}" "1 2 3 4 5" 79 | } 80 | 81 | test_cycle() { 82 | # shellcheck disable=2034 83 | arr=(a b c d) 84 | result="$(cycle; cycle; cycle)" 85 | assert_equals "$result" "a b c " 86 | } 87 | 88 | test_head() { 89 | printf '%s\n%s\n\n\n' "hello" "world" > test_file 90 | result="$(head 2 test_file)" 91 | assert_equals "$result" $'hello\nworld' 92 | } 93 | 94 | test_tail() { 95 | printf '\n\n\n%s\n%s\n' "hello" "world" > test_file 96 | result="$(tail 2 test_file)" 97 | assert_equals "$result" $'hello\nworld' 98 | } 99 | 100 | test_lines() { 101 | printf '\n\n\n\n\n\n\n\n' > test_file 102 | result="$(lines test_file)" 103 | assert_equals "$result" "8" 104 | } 105 | 106 | test_lines_loop() { 107 | printf '\n\n\n\n\n\n\n\n' > test_file 108 | result="$(lines_loop test_file)" 109 | assert_equals "$result" "8" 110 | } 111 | 112 | test_count() { 113 | result="$(count ./{README.m,LICENSE.m,.travis.ym}*)" 114 | assert_equals "$result" "3" 115 | } 116 | 117 | test_dirname() { 118 | result="$(dirname "/home/black/Pictures/Wallpapers/1.jpg")" 119 | assert_equals "$result" "/home/black/Pictures/Wallpapers/" 120 | } 121 | 122 | test_basename() { 123 | result="$(basename "/home/black/Pictures/Wallpapers/1.jpg")" 124 | assert_equals "$result" "1.jpg" 125 | } 126 | 127 | test_hex_to_rgb() { 128 | result="$(hex_to_rgb "#FFFFFF")" 129 | assert_equals "$result" "255 255 255" 130 | 131 | result="$(hex_to_rgb "000000")" 132 | assert_equals "$result" "0 0 0" 133 | } 134 | 135 | test_rgb_to_hex() { 136 | result="$(rgb_to_hex 0 0 0)" 137 | assert_equals "$result" "#000000" 138 | } 139 | 140 | test_date() { 141 | result="$(date "%C")" 142 | assert_equals "$result" "20" 143 | } 144 | 145 | test_read_sleep() { 146 | result="$((SECONDS+1))" 147 | read_sleep 1 148 | assert_equals "$result" "$SECONDS" 149 | } 150 | 151 | test_bar() { 152 | result="$(bar 50 10)" 153 | assert_equals "${result//$'\r'}" "[----- ]" 154 | } 155 | 156 | test_get_functions() { 157 | IFS=$'\n' read -d "" -ra functions < <(get_functions) 158 | assert_equals "${functions[0]}" "assert_equals" 159 | } 160 | 161 | test_extract() { 162 | printf '{\nhello, world\n}\n' > test_file 163 | result="$(extract test_file "{" "}")" 164 | assert_equals "$result" "hello, world" 165 | } 166 | 167 | test_split() { 168 | IFS=$'\n' read -d "" -ra result < <(split "hello,world,my,name,is,john" ",") 169 | assert_equals "${result[*]}" "hello world my name is john" 170 | } 171 | 172 | assert_equals() { 173 | if [[ "$1" == "$2" ]]; then 174 | ((pass+=1)) 175 | status=$'\e[32m✔' 176 | else 177 | ((fail+=1)) 178 | status=$'\e[31m✖' 179 | local err="(\"$1\" != \"$2\")" 180 | fi 181 | 182 | printf ' %s\e[m | %s\n' "$status" "${FUNCNAME[1]/test_} $err" 183 | } 184 | 185 | main() { 186 | trap 'rm readme_code test_file' EXIT 187 | 188 | # Extract code blocks from the README. 189 | while IFS=$'\n' read -r line; do 190 | [[ "$code" && "$line" != \`\`\` ]] && printf '%s\n' "$line" 191 | [[ "$line" =~ ^\`\`\`sh$ ]] && code=1 192 | [[ "$line" =~ ^\`\`\`$ ]] && code= 193 | done < README.md > readme_code 194 | 195 | # Run shellcheck and source the code. 196 | shellcheck -s bash readme_code test.sh build.sh || exit 1 197 | . readme_code 198 | 199 | head="-> Running tests on the Pure Bash Bible.." 200 | printf '\n%s\n%s\n' "$head" "${head//?/-}" 201 | 202 | # Generate the list of tests to run. 203 | IFS=$'\n' read -d "" -ra funcs < <(declare -F) 204 | for func in "${funcs[@]//declare -f }"; do 205 | [[ "$func" == test_* ]] && "$func"; 206 | done 207 | 208 | comp="Completed $((fail+pass)) tests. ${pass:-0} passed, ${fail:-0} failed." 209 | printf '%s\n%s\n\n' "${comp//?/-}" "$comp" 210 | 211 | # If a test failed, exit with '1'. 212 | ((fail>0)) || exit 0 && exit 1 213 | } 214 | 215 | main "$@" 216 | -------------------------------------------------------------------------------- /manuscript/chapter1.txt: -------------------------------------------------------------------------------- 1 | #字符串 2 | 3 | ##删除字符串前后空格 4 | 5 | 这是`sed`,`awk`,`perl`和其他工具的替代品。下面的函数通过查找所有头尾空格并在字符串的开头和结尾删除它来实现这一功能。 6 | 7 | `:`内置用于代替临时变量。 8 | 9 | **示例函数:** 10 | 11 | ```sh 12 | trim_string() { 13 | # Usage: trim_string " example string " 14 | : "${1#"${1%%[![:space:]]*}"}" 15 | : "${_%"${_##*[![:space:]]}"}" 16 | printf '%s\n' "$_" 17 | } 18 | ``` 19 | 20 | **用法示例:** 21 | 22 | ```shell 23 | $ trim_string " Hello, World " 24 | Hello, World 25 | 26 | $ name=" John Black " 27 | $ trim_string "$name" 28 | John Black 29 | ``` 30 | 31 | 32 | ##删除字符串中的所有的空白并用空格分割单词 33 | 34 | 这是`sed`,`awk`,`perl`和其他工具的替代品。 35 | 下面的函数通过重复使用单词拆分来创建一个没有前导/尾随空格的新字符串,并用空格分割字符串中的单词。 36 | 37 | **示例函数:** 38 | 39 | ```sh 40 | # shellcheck disable=SC2086,SC2048 41 | trim_all() { 42 | # Usage: trim_all " example string " 43 | set -f 44 | set -- $* 45 | printf '%s\n' "$*" 46 | set +f 47 | } 48 | ``` 49 | 50 | **用法示例:** 51 | 52 | ```shell 53 | $ trim_all " Hello, World " 54 | Hello, World 55 | 56 | $ name=" John Black is my name. " 57 | $ trim_all "$name" 58 | John Black is my name. 59 | ``` 60 | 61 | ##在字符串上匹配正则表达式 62 | 63 | 对于使用`sed`的大部分情况,`bash`的正则表达式匹配结果完全可以替代。 64 | 65 | **警告**: 这是少数平台相关的“bash”功能之一。 66 | `bash`将使用用户系统上安装的任何正则表达式引擎。 67 | 如果要兼容,请坚持使用符合POSIX规范的正则表达式引擎。 68 | 绝大部分发行版Linux中的bash均实现了POSIX规范。 69 | 70 | **警告**: 此示例仅打印第一个匹配组。 使用时多个匹配组需要进行一些修改。 71 | 72 | **示例函数:** 73 | 74 | ```sh 75 | regex() { 76 | # Usage: regex "string" "regex" 77 | [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}" 78 | } 79 | ``` 80 | 81 | **用法示例:** 82 | 83 | ```shell 84 | $ # 删除开头的空白字符. 85 | $ regex ' hello' '^\s*(.*)' 86 | hello 87 | 88 | $ # 验证十六进制颜色. 89 | $ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 90 | #FFFFFF 91 | 92 | $ # 验证十六进制颜色(无效). 93 | $ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 94 | # no output (invalid) 95 | ``` 96 | 97 | **在脚本中的示例:** 98 | 99 | ```shell 100 | is_hex_color() { 101 | if [[ $1 =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then 102 | printf '%s\n' "${BASH_REMATCH[1]}" 103 | else 104 | printf '%s\n' "error: $1 is an invalid color." 105 | return 1 106 | fi 107 | } 108 | 109 | read -r color 110 | is_hex_color "$color" || color="#FFFFFF" 111 | 112 | # Do stuff. 113 | ``` 114 | 115 | 116 | ##指定分隔符拆分字符串 117 | 118 | **警告:** 需要`bash` 4+以上的版本 119 | 120 | 这是`cut`,`awk`和其他工具的替代品。 121 | 122 | **示例函数:** 123 | 124 | ```sh 125 | split() { 126 | # Usage: split "string" "delimiter" 127 | IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" 128 | printf '%s\n' "${arr[@]}" 129 | } 130 | ``` 131 | 132 | **示例用法:** 133 | 134 | ```shell 135 | $ split "apples,oranges,pears,grapes" "," 136 | apples 137 | oranges 138 | pears 139 | grapes 140 | 141 | $ split "1, 2, 3, 4, 5" ", " 142 | 1 143 | 2 144 | 3 145 | 4 146 | 5 147 | 148 | # 多字符分隔符也可以工作! 149 | $ split "hello---world---my---name---is---john" "---" 150 | hello 151 | world 152 | my 153 | name 154 | is 155 | john 156 | ``` 157 | 158 | ##将字符串转换为小写 159 | 160 | **警告:** 需要`bash` 4+以上的版本 161 | 162 | **示例函数:** 163 | 164 | ```sh 165 | lower() { 166 | # Usage: lower "string" 167 | printf '%s\n' "${1,,}" 168 | } 169 | ``` 170 | 171 | **示例用法:** 172 | 173 | ```shell 174 | $ lower "HELLO" 175 | hello 176 | 177 | $ lower "HeLlO" 178 | hello 179 | 180 | $ lower "hello" 181 | hello 182 | ``` 183 | 184 | ##将字符串转换为大写 185 | 186 | **警告:** 需要`bash` 4+以上的版本 187 | 188 | **示例函数:** 189 | 190 | ```sh 191 | upper() { 192 | # Usage: upper "string" 193 | printf '%s\n' "${1^^}" 194 | } 195 | ``` 196 | 197 | **示例用法:** 198 | 199 | ```shell 200 | $ upper "hello" 201 | HELLO 202 | 203 | $ upper "HeLlO" 204 | HELLO 205 | 206 | $ upper "HELLO" 207 | HELLO 208 | ``` 209 | 210 | ##反转字符串大小写 211 | 212 | **警告:** 需要`bash` 4+以上的版本 213 | 214 | **示例函数:** 215 | 216 | ```sh 217 | reverse_case() { 218 | # Usage: reverse_case "string" 219 | printf '%s\n' "${1~~}" 220 | } 221 | ``` 222 | 223 | **示例用法:** 224 | 225 | ```shell 226 | $ reverse_case "hello" 227 | HELLO 228 | 229 | $ reverse_case "HeLlO" 230 | hElLo 231 | 232 | $ reverse_case "HELLO" 233 | hello 234 | ``` 235 | 236 | ##删除字符串中的引号 237 | 238 | **示例函数:** 239 | 240 | ```sh 241 | trim_quotes() { 242 | # Usage: trim_quotes "string" 243 | : "${1//\'}" 244 | printf '%s\n' "${_//\"}" 245 | } 246 | ``` 247 | 248 | **示例用法:** 249 | 250 | ```shell 251 | $ var="'Hello', \"World\"" 252 | $ trim_quotes "$var" 253 | Hello, World 254 | ``` 255 | 256 | ##从字符串中删除所有正则实例 257 | 258 | **示例函数:** 259 | 260 | ```sh 261 | strip_all() { 262 | # Usage: strip_all "string" "pattern" 263 | printf '%s\n' "${1//$2}" 264 | } 265 | ``` 266 | 267 | **示例用法:** 268 | 269 | ```shell 270 | $ strip_all "The Quick Brown Fox" "[aeiou]" 271 | Th Qck Brwn Fx 272 | 273 | $ strip_all "The Quick Brown Fox" "[[:space:]]" 274 | TheQuickBrownFox 275 | 276 | $ strip_all "The Quick Brown Fox" "Quick " 277 | The Brown Fox 278 | ``` 279 | 280 | ##从字符串中删除第一次出现的正则实例 281 | 282 | **示例函数:** 283 | 284 | ```sh 285 | strip() { 286 | # Usage: strip "string" "pattern" 287 | printf '%s\n' "${1/$2}" 288 | } 289 | ``` 290 | 291 | **示例用法:** 292 | 293 | ```shell 294 | $ strip "The Quick Brown Fox" "[aeiou]" 295 | Th Quick Brown Fox 296 | 297 | $ strip "The Quick Brown Fox" "[[:space:]]" 298 | TheQuick Brown Fox 299 | ``` 300 | 301 | ##在字符串开头匹配正则并删除 302 | 303 | **示例函数:** 304 | 305 | ```sh 306 | lstrip() { 307 | # Usage: lstrip "string" "pattern" 308 | printf '%s\n' "${1##$2}" 309 | } 310 | ``` 311 | 312 | **示例用法:** 313 | 314 | ```shell 315 | $ lstrip "The Quick Brown Fox" "The " 316 | Quick Brown Fox 317 | ``` 318 | 319 | ##在字符串末尾匹配正则并删除 320 | 321 | **示例函数:** 322 | 323 | ```sh 324 | rstrip() { 325 | # Usage: rstrip "string" "pattern" 326 | printf '%s\n' "${1%%$2}" 327 | } 328 | ``` 329 | 330 | **示例用法:** 331 | 332 | ```shell 333 | $ rstrip "The Quick Brown Fox" " Fox" 334 | The Quick Brown 335 | ``` 336 | 337 | ##百分号编码字符串 338 | 339 | **示例函数:** 340 | 341 | ```sh 342 | urlencode() { 343 | # Usage: urlencode "string" 344 | local LC_ALL=C 345 | for (( i = 0; i < ${#1}; i++ )); do 346 | : "${1:i:1}" 347 | case "$_" in 348 | [a-zA-Z0-9.~_-]) 349 | printf '%s' "$_" 350 | ;; 351 | 352 | *) 353 | printf '%%%02X' "'$_" 354 | ;; 355 | esac 356 | done 357 | printf '\n' 358 | } 359 | ``` 360 | 361 | **示例用法:** 362 | 363 | ```shell 364 | $ urlencode "https://github.com/dylanaraps/pure-bash-bible" 365 | https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible 366 | ``` 367 | 368 | ##解码用百分比编码的字符串 369 | 370 | **示例函数:** 371 | 372 | ```sh 373 | urldecode() { 374 | # Usage: urldecode "string" 375 | : "${1//+/ }" 376 | printf '%b\n' "${_//%/\\x}" 377 | } 378 | ``` 379 | 380 | **示例用法:** 381 | 382 | ```shell 383 | $ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible" 384 | https://github.com/dylanaraps/pure-bash-bible 385 | ``` 386 | 387 | ##检查字符串是否包含子字符串 388 | 389 | **用于测试:** 390 | 391 | ```shell 392 | if [[ $var == *sub_string* ]]; then 393 | printf '%s\n' "sub_string is in var." 394 | fi 395 | 396 | # 反转 (子串不在字符串中). 397 | if [[ $var != *sub_string* ]]; then 398 | printf '%s\n' "sub_string is not in var." 399 | fi 400 | 401 | # 也可以在数组中运行 402 | if [[ ${arr[*]} == *sub_string* ]]; then 403 | printf '%s\n' "sub_string is in array." 404 | fi 405 | ``` 406 | 407 | **使用case语句:** 408 | 409 | ```shell 410 | case "$var" in 411 | *sub_string*) 412 | # Do stuff 413 | ;; 414 | 415 | *sub_string2*) 416 | # Do more stuff 417 | ;; 418 | 419 | *) 420 | # Else 421 | ;; 422 | esac 423 | ``` 424 | 425 | ##检查字符串是否以子字符串开头 426 | 427 | ```shell 428 | if [[ $var == sub_string* ]]; then 429 | printf '%s\n' "var starts with sub_string." 430 | fi 431 | 432 | # 反转 (变量不是以子串开头). 433 | if [[ $var != sub_string* ]]; then 434 | printf '%s\n' "var does not start with sub_string." 435 | fi 436 | ``` 437 | 438 | ##检查字符串是否以子字符串结尾 439 | 440 | ```shell 441 | if [[ $var == *sub_string ]]; then 442 | printf '%s\n' "var ends with sub_string." 443 | fi 444 | 445 | # Inverse (var does not end with sub_string). 446 | if [[ $var != *sub_string ]]; then 447 | printf '%s\n' "var does not end with sub_string." 448 | fi 449 | ``` 450 | 451 | 452 | 453 | -------------------------------------------------------------------------------- /bash终端中输出字体格式详解.md: -------------------------------------------------------------------------------- 1 | 2 | # bash中的字体格式 3 | 4 | ---------- 5 | 6 | 7 | * [1) 格式](#格式) 8 | * [1.1 Set](#Set) 9 | * [1.2 Reset](#Reset) 10 | 11 | 12 | * [2)8/16 Colors](#8/16Colors) 13 | * [2.1 前景(文字)](#前景(文字)) 14 | * [2.2 背景](#背景) 15 | 16 | 17 | * [3)88/256 颜色](#88/256颜色) 18 | * [3.1 前景(文字)](#前景(文字)) 19 | * [3.2 背景色](#背景色) 20 | 21 | 22 | * [4)组合属性](#组合属性) 23 | 24 | * [5)终端兼容性](#终端兼容性) 25 | 26 | 27 | * [6)示例程序](#示例程序) 28 | * [6.1 Colors and formatting (16 colors)](#Colors-and-formatting) 29 | * [6.2 256 colors](#256-colors) 30 | 31 | 32 | ANSI/VT100 终端和终端仿真器不只是能够显示黑色和白色文本; 由于转义序列,它们可以显示颜色和格式化文本。 33 | 在 Bash 中,可以使用以下语法获取字符: 34 | `\e` 35 | `\033` 36 | `\x1B` 37 | 例子: 38 | 39 | | 代码(Bash) | Preview | 40 | | --- | --- | 41 | | echo -e “\e[31mHello World \e[0m” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619111931351.png) | 42 | | echo -e “\033 [31mHello \e[0m World” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061911202157.png) | 43 | 44 | > 注意: 45 | > 46 | > 1. 该命令的 - e 选项 echo 启用转义序列的解析。 47 | > 2. `\e[0m` 序列删除所有属性(格式和颜色)。在每个彩色文本的末尾添加它是个好主意. 48 | > 3. 本页中的示例使用 Bash,但`ANSI/VT100`转义序列可用于各种编程语言。 49 | 50 | # 格式 51 | ------------- 52 | 53 | ### Set 54 | 55 | | code | Description | Example | Preview | 56 | | --- | --- | --- | --- | 57 | | 1 | 粗体 / 高亮 | `echo -e “Normal \e[1mBold”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134438396.png) | 58 | | 2 | 变暗 | `echo -e “Normal \e[2mDim”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134533628.png) | 59 | | 4 | 下划线 | `echo -e “Normal \e[4mUnderlined”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134709590.png) | 60 | | 5 | 闪烁 | `echo -e “Normal \e[5mBlink”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134657313.gif) | 61 | | 7 | 反转(反转前景色和背景色) | `echo -e “Normal \e[7minverted”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134719632.png) | 62 | | 8 | 隐藏(对密码有用) | `echo -e “Normal \e[8mHidden”` | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619134739654.png) | 63 | 64 | ### Reset 65 | 66 | | Code | Description | Example | Preview | 67 | | --- | --- | --- | --- | 68 | | 0 | 重置所有属性 | echo -e “\e[0mNormal Text” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135720596.png) | 69 | | 21 | 重置粗体 / 高亮 | echo -e “Normal \e[1mBold \e[21mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135736113.png) | 70 | | 22 | 重置变暗 | echo -e “Normal \e[2mDim \e[22mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061913581248.png) | 71 | | 24 | 重置下划线 | echo -e “Normal \e[4mUnderlined \e[24mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135828236.png) | 72 | | 25 | 重置闪烁 | echo -e “Normal \e[5mBlink \e[25mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135843795.gif) | 73 | | 27 | 重置反显 | echo -e “Normal \e[7minverted \e[27mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135900464.png) | 74 | | 28 | 重置隐藏 | echo -e “Normal \e[8mHidden \e[28mNormal” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619135931669.png) | 75 | 76 | # 8/16Colors 77 | ------------- 78 | 79 | ### 前景(文字) 80 | 81 | 以下颜色适用于大多数终端和终端仿真器 2), 请参阅兼容性列表以获取更多信息。 82 | 83 | > 颜色可能因终端配置而异。 84 | 85 | | Code | Color | Example | Preview | 86 | | --- | --- | --- | --- | 87 | | 39 | 默认前景色 | echo -e “Default \e[39mDefault” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141052508.png) | 88 | | 30 | 黑色 | echo -e “Default \e[30mBlack” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141101784.png) | 89 | | 31 | 红色 | echo -e “Default \e[31mRed” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141135101.png) | 90 | | 32 | 绿色 | echo -e “Default \e[32mGreen” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141152927.png) | 91 | | 33 | 黄色 | echo -e “Default \e[33mYellow” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061914121546.png) | 92 | | 34 | 蓝色 | echo -e “Default \e[34mBlue” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141224273.png) | 93 | | 35 | 品红 | echo -e “Default \e[35mMagenta” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141237138.png) | 94 | | 36 | 青色 | echo -e “Default \e[36mCyan” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061914124782.png) | 95 | | 37 | 浅灰 | echo -e “Default \e[37mLight grey” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141257476.png) | 96 | | 90 | 深灰色 | echo -e “Default \e[90mDark grey” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141310443.png) | 97 | | 91 | 红灯 | echo -e “Default \e[91mLight red” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141319563.png) | 98 | | 92 | 浅绿色 | echo -e “Default \e[92mLight green” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141329662.png) | 99 | | 93 | 淡黄色 | echo -e “Default \e[93mLight yellow” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141342103.png) | 100 | | 94 | 浅蓝 | echo -e “Default \e[94mLight blue” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141353126.png) | 101 | | 95 | 浅洋红色 | echo -e “Default \e[95mLight magenta” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141404538.png) | 102 | | 96 | 浅青色 | echo -e “Default \e[96mLight cyan” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141416743.png) | 103 | | 97 | 白色 | echo -e “Default \e[97mWhite” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141425171.png) | 104 | 105 | ### 背景 106 | 107 | | Code | Color | Example | Preview | 108 | | --- | --- | --- | --- | 109 | | 49 | 默认背景颜色 | echo -e “Default \e[49mDefault” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142042919.png) | 110 | | 40 | 黑色 | echo -e “Default \e[40mBlack” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141729838.png) | 111 | | 41 | 红色 | echo -e “Default \e[41mRed” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141715955.png) | 112 | | 42 | 绿色 | echo -e “Default \e[42mGreen” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141813132.png) | 113 | | 43 | 黄色 | echo -e “Default \e[43mYellow” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141818655.png) | 114 | | 44 | 蓝色 | echo -e “Default \e[44mBlue” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141826855.png) | 115 | | 45 | 品红 | echo -e “Default \e[45mMagenta” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141843323.png) | 116 | | 46 | 青色 | echo -e “Default \e[46mCyan” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141851119.png) | 117 | | 47 | 浅灰 | echo -e “Default \e[47mLight grey” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141859165.png) | 118 | | 100 | 深灰色 | echo -e “Default \e[100mDark grey” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141906860.png) | 119 | | 101 | 红灯 | echo -e “默认 \e[101mLight red” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141940807.png) | 120 | | 102 | 浅绿色 | echo -e “默认 \e[102mLight green” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619141948164.png) | 121 | | 103 | 淡黄色 | echo -e “默认 \e[103mLight yellow” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/201906191419546.png) | 122 | | 104 | 浅蓝 | echo -e “Default \e[104mLight blue” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142001372.png) | 123 | | 105 | 浅洋红色 | echo -e “Default \e[105mLight magenta” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142009959.png) | 124 | | 106 | 浅青色 | echo -e “Default \e[106mLight cyan” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142015180.png) | 125 | | 107 | 白色 | echo -e “Default \e[107mWhite” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142021305.png) | 126 | 127 | # 88/256颜色 128 | ----------- 129 | 130 | 某些终端(参见兼容性列表)可以支持 88 或 256 种颜色。以下是允许您使用它们的控制序列。 131 | 132 | 注意:颜色编号 256 仅由 vte 支持(GNOME 终端,XFCE4 终端,Nautilus 终端,终结者…)。 133 | 134 | 注意 2:88 色终端(如 rxvt)与 256 色终端的颜色图不同。 135 | 136 | ### 前景(文字) 137 | 138 | 要使用前景中的 256 种颜色之一(文本颜色),控制序列为 “ `\e[38;5;`ColorNumber`m` ”,其中 ColorNumber 是以下颜色之一: 139 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142347178.png) 140 | **Example**: 141 | 142 | ```shell 143 | echo -e "\e[38;5;82mHello \e[38;5;198mWorld" 144 | 145 | 146 | ``` 147 | 148 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142534957.png) 149 | 150 | ```shell 151 | for i in {16..21} {21..16} ; do echo -en "\e[38;5;${i}m#\e[0m" ; done ; echo 152 | 153 | 154 | ``` 155 | 156 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142540367.png) 157 | 158 | ### 背景色 159 | 160 | 要在背景上使用 256 种颜色中的一种,控制序列为 “ `\e[48;5;`ColorNumber`m` ”,其中 ColorNumber 是以下颜色之一: 161 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142629366.png) 162 | **Example:** 163 | 164 | ```shell 165 | echo -e "\e[40;38;5;82m Hello \e[30;48;5;82m World \e[0m" 166 | 167 | 168 | ``` 169 | 170 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142741691.png) 171 | 172 | ```shell 173 | for i in {16..21} {21..16} ; do echo -en "\e[48;5;${i}m \e[0m" ; done ; echo 174 | 175 | 176 | ``` 177 | 178 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142746703.png) 179 | 180 | ### 组合属性 181 | ------ 182 | 183 | 终端允许属性组合。属性必须用分号(“;”)分隔。 184 | 185 | | Description | Code (Bash) | Preview | 186 | | --- | --- | --- | 187 | | Bold + Underlined | echo -e “\e[1;4mBold and Underlined” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619142956913.png) | 188 | | Bold + Red forground + Green background | echo -e “\e[1;31;42m Yes it is awful \e[0m” | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619143007175.png) | 189 | 190 | ### 终端兼容性 191 | ------- 192 | 193 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061914310225.png) 194 | 195 | > 表中使用的符号: 196 | > “ok”:终端支持。 197 | > “~”:终端以特殊方式支持。 198 | > “-”:终端根本不支持。 199 | 200 | ### 示例程序 201 | ------ 202 | 203 | ### Colors-and-formatting 204 | 205 | 以下 shell 脚本显示了许多可能的属性组合(但不是全部,因为它一次只使用一个格式属性)。 206 | 207 | ```shell 208 | #!/bin/bash 209 | 210 | # This program is free software. It comes without any warranty, to 211 | # the extent permitted by applicable law. You can redistribute it 212 | # and/or modify it under the terms of the Do What The Fuck You Want 213 | # To Public License, Version 2, as published by Sam Hocevar. See 214 | # http://sam.zoy.org/wtfpl/COPYING for more details. 215 | 216 | #Background 217 | for clbg in {40..47} {100..107} 49 ; do 218 | #Foreground 219 | for clfg in {30..37} {90..97} 39 ; do 220 | #Formatting 221 | for attr in 0 1 2 4 5 7 ; do 222 | #Print the result 223 | echo -en "\e[${attr};${clbg};${clfg}m ^[${attr};${clbg};${clfg}m \e[0m" 224 | done 225 | echo #Newline 226 | done 227 | done 228 | exit 0 229 | 230 | 231 | ``` 232 | 233 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/20190619143725445.png) 234 | 235 | ### 256-colors 236 | 237 | 以下脚本显示某些终端和终端仿真器(如 XTerm 和 GNOME Terminal)上可用的 256 种颜色。 238 | 239 | ```shell 240 | #!/bin/bash 241 | 242 | # This program is free software. It comes without any warranty, to 243 | # the extent permitted by applicable law. You can redistribute it 244 | # and/or modify it under the terms of the Do What The Fuck You Want 245 | # To Public License, Version 2, as published by Sam Hocevar. See 246 | # http://sam.zoy.org/wtfpl/COPYING for more details. 247 | 248 | for fgbg in 38 48 ; do # Foreground / Background 249 | for color in {0..255} ; do # Colors 250 | # Display the color 251 | printf "\e[${fgbg};5;%sm %3s \e[0m" $color $color 252 | # Display 6 colors per lines 253 | if [ $((($color + 1) % 6)) == 4 ] ; then 254 | echo # New line 255 | fi 256 | done 257 | echo # New line 258 | done 259 | 260 | exit 0 261 | 262 | 263 | ``` 264 | 265 | ![image](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/images/console_codes_imgs/2019061914383268.png) 266 | 267 | 参考 268 | -- 269 | 270 | [Linux console codes manual (’‘man console_codes’’)](http://linux.die.net/man/4/console_codes) 271 | [XTerm Control Sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) 272 | -------------------------------------------------------------------------------- /README.md.bak: -------------------------------------------------------------------------------- 1 |

2 |

pure bash bible

bash奇巧淫技.

4 | 5 |

6 | 8 |

9 | 10 |
11 | 12 | 13 | 14 | 15 | 本书原作者将书中的内容发布到了[github](https://github.com/dylanaraps/pure-bash-bible)上,我仅仅是将其翻译为中文,并解释了其中的部分语句语法,希望可以对今后的工作有所帮助。 16 | 17 | # 以下是翻译后的原文 # 18 | 19 | 这本书的目的是汇总只使用内置`bash`的特性来实现总所周知和鲜为人知的各项任务。 使用此参考书中的代码段可以帮助你从脚本中删除不需要的依赖项,并且在大多数情况下可以使它们运行的更快。 我偶然碰到了这些技巧并在开发[neofetch](https://github.com/dylanaraps/neofetch), [pxltrm](https://github.com/dylanaraps/pxltrm) 和一些其他小的项目的时候发现了一些别的技巧。 20 | 21 | 下面的片段使用`shellcheck`进行了检查,并将测试写在了适用的地方。 想要贡献自己的代码? 阅读 [CONTRIBUTING-Zh_CN.md](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/CONTRIBUTING-Zh_CN.md). 它概述了向参考书中增加片段时,单元测试的工作方式以及其他所需的内容。 22 | 23 | 看到了一些东西描述是不准确的、有缺陷的更或者是完全错误的?那么请新建一个issue或者发送一个pull request.如果参考书中缺少某些你想要的事物,也请新建一个issue并给出你能找到的解决方法。 24 | 25 | 26 | 27 |
28 | 29 | # 目 录 30 | 31 | 32 | 33 | * [前言](#前言) 34 | * [字符串](#字符串) 35 | * [删除字符串前后空格](#删除字符串前后空格) 36 | * [删除字符串中的所有的空白并用空格分割单词](#删除字符串中的所有的空白并用空格分割单词) 37 | * [在字符串上匹配正则表达式](#在字符串上匹配正则表达式) 38 | * [指定分隔符拆分字符串](#指定分隔符拆分字符串) 39 | * [将字符串转换为小写](#将字符串转换为小写) 40 | * [将字符串转换为大写](#将字符串转换为大写) 41 | * [反转字符串大小写](#反转字符串大小写) 42 | * [删除字符串中的引号](#删除字符串中的引号) 43 | * [从字符串中删除所有正则实例](#从字符串中删除所有正则实例) 44 | * [从字符串中删除第一次出现的正则实例](#从字符串中删除第一次出现的正则实例) 45 | * [在字符串开头匹配正则并删除](#在字符串开头匹配正则并删除) 46 | * [在字符串末尾匹配正则并删除](#在字符串末尾匹配正则并删除) 47 | * [百分号编码字符串](#百分号编码字符串) 48 | * [解码用百分比编码的字符串](#解码用百分比编码的字符串) 49 | * [检查字符串是否包含子字符串](#检查字符串是否包含子字符串) 50 | * [检查字符串是否以子字符串开头](#检查字符串是否以子字符串开头) 51 | * [检查字符串是否以子字符串结尾](#检查字符串是否以子字符串结尾) 52 | * [数组](#数组) 53 | * [反转数组](#反转数组) 54 | * [删除重复的数组元素](#删除重复的数组元素) 55 | * [随机返回一个数组元素](#随机返回一个数组元素) 56 | * [循环迭代一个数组](#循环迭代一个数组) 57 | * [在两个值之间转换](#在两个值之间转换) 58 | * [循环](#循环) 59 | * [循环生成范围内的数字](#循环生成范围内的数字) 60 | * [循环遍历可变数字范围](#循环遍历可变数字范围) 61 | * [循环数组](#循环数组) 62 | * [循环输出带索引的数组](#循环输出带索引的数组) 63 | * [循环遍历文件的内容](#循环遍历文件的内容) 64 | * [循环遍历文件和目录](#循环遍历文件和目录) 65 | * [文本处理](#文本处理) 66 | * [读取文件到一个字符串中](#读取文件到一个字符串中) 67 | * [读取文件到一个数组中 (*按行读取*)](#读取文件到一个数组中) 68 | * [获取文件的前N行](#获取文件的前N行) 69 | * [获取文件的最后N行](#获取文件的最后N行) 70 | * [获取文件中的行数](#获取文件中的行数) 71 | * [计算目录中的文件或目录](#计算目录中的文件或目录) 72 | * [创建一个空文件](#创建一个空文件) 73 | * [提取两个标记之间的行](#提取两个标记之间的行) 74 | * [文件路径](#文件路径) 75 | * [获取文件路径的目录名](#获取文件路径的目录名) 76 | * [获取文件路径的基本名称](#获取文件路径的基本名称) 77 | * [变量](#变量) 78 | * [分配和访问一个变量](#分配和访问一个变量) 79 | * [根据另一个变量命名变量](#根据另一个变量命名变量) 80 | * [转义字符](#转义字符) 81 | * [文本颜色](#文本颜色) 82 | * [文本属性](#文本属性) 83 | * [移动光标](#移动光标) 84 | * [删除文本](#删除文本) 85 | * [参数拓展](#参数拓展) 86 | * [间接](#间接) 87 | * [替换](#替换) 88 | * [长度](#长度) 89 | * [扩展](#扩展) 90 | * [改变大小写](#改变大小写) 91 | * [默认值](#默认值) 92 | * [花括号展开](#花括号展开) 93 | * [范围](#范围) 94 | * [字符串列表](#字符串列表) 95 | * [条件表达式](#条件表达式) 96 | * [文件判断](#文件判断) 97 | * [文件比较](#文件比较) 98 | * [条件变量](#条件变量) 99 | * [比较变量](#比较变量) 100 | * [算术运算符](#算术运算符) 101 | * [指派](#指派) 102 | * [四则运算](#四则运算) 103 | * [位运算](#位运算) 104 | * [逻辑运算](#逻辑运算) 105 | * [复杂运算](#复杂运算) 106 | * [算术运算符-1](#算术运算符-1) 107 | * [用更简单语法设置变量](#用更简单语法设置变量) 108 | * [三元测试](#三元测试) 109 | * [trap命令](#trap命令) 110 | * [在脚本退出时执行操作](#在脚本退出时执行操作) 111 | * [忽略终端中断(CTRL+C,SIGINT)](#忽略终端中断) 112 | * [对窗口调整大小时做出反应](#对窗口调整大小时做出反应) 113 | * [在命令之前执行某些操作](#在命令之前执行某些操作) 114 | * [在shell函数或源文件完成执行时执行某些操作](#在shell函数或源文件完成执行时执行某些操作) 115 | * [性能](#性能) 116 | * [禁用Unicode码](#禁用Unicode码) 117 | * [已过时的语法](#已过时的语法) 118 | * [释伴声明](#释伴声明) 119 | * [命令替换](#命令替换) 120 | * [声明函数](#声明函数) 121 | * [内部变量](#内部变量) 122 | * [获取`bash`二进制文件的位置](#获取`bash`二进制文件的位置) 123 | * [获取当前运行`bash`命令的版本](#获取当前运行`bash`命令的版本) 124 | * [打开用户默认的文本编辑器](#打开用户默认的文本编辑器) 125 | * [获取当前函数的名称](#获取当前函数的名称) 126 | * [获取系统的主机名](#获取系统的主机名) 127 | * [获取操作系统的架构(32位或64位)](#获取操作系统的架构) 128 | * [获取操作系统/内核的名称](#获取操作系统/内核的名称) 129 | * [获取当前的工作目录](#获取当前的工作目录) 130 | * [获取脚本运行的秒数](#获取脚本运行的秒数) 131 | * [获取伪随机整数](#获取伪随机整数) 132 | * [有关终端的信息](#有关终端的信息) 133 | * [获取终端的总行列数(*来自脚本*)](#获取终端的总行列数) 134 | * [获取终端的像素大小](#获取终端的像素大小) 135 | * [获取当前光标位置](#获取当前光标位置) 136 | * [转换](#转换) 137 | * [将十六进制颜色转换为RGB](#将十六进制颜色转换为RGB) 138 | * [将RGB颜色转换为十六进制](#将RGB颜色转换为十六进制) 139 | * [代码高尔夫](#代码高尔夫) 140 | * [更短的`for`循环语法](#更短的`for`循环语法) 141 | * [更短的无限循环](#更短的无限循环) 142 | * [更短的函数声明](#更短的函数声明) 143 | * [更短的`if`语法](#更短的`if`语法) 144 | * [用`case`语句来更简单的设置变量](#用`case`语句来更简单的设置变量) 145 | * [其他](#其他) 146 | * [使用`read`作为`sleep`命令的替代品](#使用`read`作为`sleep`命令的替代品) 147 | * [检查一个命令是否在用户的PATH中](#检查一个命令是否在用户的PATH中) 148 | * [使用`strftime`获取当前日期](#使用`strftime`获取当前日期) 149 | * [获取当前用户的用户名](#获取当前用户的用户名) 150 | * [生成一个V4版本的UUID](#生成一个V4版本的UUID) 151 | * [进度条](#进度条) 152 | * [获取脚本中的函数列表](#获取脚本中的函数列表) 153 | * [绕过shell别名](#绕过shell别名) 154 | * [绕过shell函数](#绕过shell函数) 155 | * [在后台运行命令](#在后台运行命令) 156 | * [后记](#后记) 157 | 158 | 159 | 160 |
161 | 162 | 163 | # 前言 164 | 165 | 纯`bash`脚本替代外部流程和程序的集合。 `bash`脚本语言远比大部分人了解到的更强大,大多数任务都可以在不依赖外部程序的情况下由`bash`独立完成。 166 | 167 | 在`bash`中调用外部进程是昂贵的,过度使用会导致效率明显的下降。 使用内置方法编写的脚本和程序(*在适合的地方*)将更快,依赖性更小,并能对脚本本身有更好的理解。 168 | 169 | 本书的目的是为大家用`bash`编写程序和脚本过程中遇到问题时提供了一种思路。 示例展示了将这些解决方案合并到代码中的函数格式。 170 | 171 | 172 | 173 | 174 | # 字符串 175 | 176 | ## 删除字符串前后空格 177 | 178 | 这是`sed`,`awk`,`perl`和其他工具的替代品。下面的函数通过查找所有头尾空格并在字符串的开头和结尾删除它来实现这一功能。 179 | 180 | `:`内置用于代替临时变量。 181 | 182 | **示例函数:** 183 | 184 | ```sh 185 | trim_string() { 186 | # Usage: trim_string " example string " 187 | : "${1#"${1%%[![:space:]]*}"}" 188 | : "${_%"${_##*[![:space:]]}"}" 189 | printf '%s\n' "$_" 190 | } 191 | ``` 192 | 193 | **用法示例:** 194 | 195 | ```shell 196 | $ trim_string " Hello, World " 197 | Hello, World 198 | 199 | $ name=" John Black " 200 | $ trim_string "$name" 201 | John Black 202 | ``` 203 | 204 | 205 | ## 删除字符串中的所有的空白并用空格分割单词 206 | 207 | 这是`sed`,`awk`,`perl`和其他工具的替代品。 208 | 下面的函数通过重复使用单词拆分来创建一个没有前导/尾随空格的新字符串,并用空格分割字符串中的单词。 209 | 210 | **示例函数:** 211 | 212 | ```sh 213 | # shellcheck disable=SC2086,SC2048 214 | trim_all() { 215 | # Usage: trim_all " example string " 216 | set -f 217 | set -- $* 218 | printf '%s\n' "$*" 219 | set +f 220 | } 221 | ``` 222 | 223 | **用法示例:** 224 | 225 | ```shell 226 | $ trim_all " Hello, World " 227 | Hello, World 228 | 229 | $ name=" John Black is my name. " 230 | $ trim_all "$name" 231 | John Black is my name. 232 | ``` 233 | 234 | ## 在字符串上匹配正则表达式 235 | 236 | 对于使用`sed`的大部分情况,`bash`的正则表达式匹配结果完全可以替代。 237 | 238 | **警告**: 这是少数平台相关的“bash”功能之一。 239 | `bash`将使用用户系统上安装的任何正则表达式引擎。 240 | 如果要兼容,请坚持使用符合POSIX规范的正则表达式引擎。 241 | 绝大部分发行版Linux中的bash均实现了POSIX规范。 242 | 243 | **警告**: 此示例仅打印第一个匹配组。 使用时多个匹配组需要进行一些修改。 244 | 245 | **示例函数:** 246 | 247 | ```sh 248 | regex() { 249 | # Usage: regex "string" "regex" 250 | [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}" 251 | } 252 | ``` 253 | 254 | **用法示例:** 255 | 256 | ```shell 257 | $ # 删除开头的空白字符. 258 | $ regex ' hello' '^\s*(.*)' 259 | hello 260 | 261 | $ # 验证十六进制颜色. 262 | $ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 263 | #FFFFFF 264 | 265 | $ # 验证十六进制颜色(无效). 266 | $ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 267 | # no output (invalid) 268 | ``` 269 | 270 | **在脚本中的示例:** 271 | 272 | ```shell 273 | is_hex_color() { 274 | if [[ $1 =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then 275 | printf '%s\n' "${BASH_REMATCH[1]}" 276 | else 277 | printf '%s\n' "error: $1 is an invalid color." 278 | return 1 279 | fi 280 | } 281 | 282 | read -r color 283 | is_hex_color "$color" || color="#FFFFFF" 284 | 285 | # Do stuff. 286 | ``` 287 | 288 | 289 | ## 指定分隔符拆分字符串 290 | 291 | **警告:** 需要`bash` 4+以上的版本 292 | 293 | 这是`cut`,`awk`和其他工具的替代品。 294 | 295 | **示例函数:** 296 | 297 | ```sh 298 | split() { 299 | # Usage: split "string" "delimiter" 300 | IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" 301 | printf '%s\n' "${arr[@]}" 302 | } 303 | ``` 304 | 305 | **示例用法:** 306 | 307 | ```shell 308 | $ split "apples,oranges,pears,grapes" "," 309 | apples 310 | oranges 311 | pears 312 | grapes 313 | 314 | $ split "1, 2, 3, 4, 5" ", " 315 | 1 316 | 2 317 | 3 318 | 4 319 | 5 320 | 321 | # 多字符分隔符也可以工作! 322 | $ split "hello---world---my---name---is---john" "---" 323 | hello 324 | world 325 | my 326 | name 327 | is 328 | john 329 | ``` 330 | 331 | ## 将字符串转换为小写 332 | 333 | **警告:** 需要`bash` 4+以上的版本 334 | 335 | **示例函数:** 336 | 337 | ```sh 338 | lower() { 339 | # Usage: lower "string" 340 | printf '%s\n' "${1,,}" 341 | } 342 | ``` 343 | 344 | **示例用法:** 345 | 346 | ```shell 347 | $ lower "HELLO" 348 | hello 349 | 350 | $ lower "HeLlO" 351 | hello 352 | 353 | $ lower "hello" 354 | hello 355 | ``` 356 | 357 | ## 将字符串转换为大写 358 | 359 | **警告:** 需要`bash` 4+以上的版本 360 | 361 | **示例函数:** 362 | 363 | ```sh 364 | upper() { 365 | # Usage: upper "string" 366 | printf '%s\n' "${1^^}" 367 | } 368 | ``` 369 | 370 | **示例用法:** 371 | 372 | ```shell 373 | $ upper "hello" 374 | HELLO 375 | 376 | $ upper "HeLlO" 377 | HELLO 378 | 379 | $ upper "HELLO" 380 | HELLO 381 | ``` 382 | 383 | ## 反转字符串大小写 384 | 385 | **警告:** 需要`bash` 4+以上的版本 386 | 387 | **示例函数:** 388 | 389 | ```sh 390 | reverse_case() { 391 | # Usage: reverse_case "string" 392 | printf '%s\n' "${1~~}" 393 | } 394 | ``` 395 | 396 | **示例用法:** 397 | 398 | ```shell 399 | $ reverse_case "hello" 400 | HELLO 401 | 402 | $ reverse_case "HeLlO" 403 | hElLo 404 | 405 | $ reverse_case "HELLO" 406 | hello 407 | ``` 408 | 409 | ## 删除字符串中的引号 410 | 411 | **示例函数:** 412 | 413 | ```sh 414 | trim_quotes() { 415 | # Usage: trim_quotes "string" 416 | : "${1//\'}" 417 | printf '%s\n' "${_//\"}" 418 | } 419 | ``` 420 | 421 | **示例用法:** 422 | 423 | ```shell 424 | $ var="'Hello', \"World\"" 425 | $ trim_quotes "$var" 426 | Hello, World 427 | ``` 428 | 429 | ## 从字符串中删除所有正则实例 430 | 431 | **示例函数:** 432 | 433 | ```sh 434 | strip_all() { 435 | # Usage: strip_all "string" "pattern" 436 | printf '%s\n' "${1//$2}" 437 | } 438 | ``` 439 | 440 | **示例用法:** 441 | 442 | ```shell 443 | $ strip_all "The Quick Brown Fox" "[aeiou]" 444 | Th Qck Brwn Fx 445 | 446 | $ strip_all "The Quick Brown Fox" "[[:space:]]" 447 | TheQuickBrownFox 448 | 449 | $ strip_all "The Quick Brown Fox" "Quick " 450 | The Brown Fox 451 | ``` 452 | 453 | ## 从字符串中删除第一次出现的正则实例 454 | 455 | **示例函数:** 456 | 457 | ```sh 458 | strip() { 459 | # Usage: strip "string" "pattern" 460 | printf '%s\n' "${1/$2}" 461 | } 462 | ``` 463 | 464 | **示例用法:** 465 | 466 | ```shell 467 | $ strip "The Quick Brown Fox" "[aeiou]" 468 | Th Quick Brown Fox 469 | 470 | $ strip "The Quick Brown Fox" "[[:space:]]" 471 | TheQuick Brown Fox 472 | ``` 473 | 474 | ## 在字符串开头匹配正则并删除 475 | 476 | **示例函数:** 477 | 478 | ```sh 479 | lstrip() { 480 | # Usage: lstrip "string" "pattern" 481 | printf '%s\n' "${1##$2}" 482 | } 483 | ``` 484 | 485 | **示例用法:** 486 | 487 | ```shell 488 | $ lstrip "The Quick Brown Fox" "The " 489 | Quick Brown Fox 490 | ``` 491 | 492 | ## 在字符串末尾匹配正则并删除 493 | 494 | **示例函数:** 495 | 496 | ```sh 497 | rstrip() { 498 | # Usage: rstrip "string" "pattern" 499 | printf '%s\n' "${1%%$2}" 500 | } 501 | ``` 502 | 503 | **示例用法:** 504 | 505 | ```shell 506 | $ rstrip "The Quick Brown Fox" " Fox" 507 | The Quick Brown 508 | ``` 509 | 510 | ## 百分号编码字符串 511 | 512 | **示例函数:** 513 | 514 | ```sh 515 | urlencode() { 516 | # Usage: urlencode "string" 517 | local LC_ALL=C 518 | for (( i = 0; i < ${#1}; i++ )); do 519 | : "${1:i:1}" 520 | case "$_" in 521 | [a-zA-Z0-9.~_-]) 522 | printf '%s' "$_" 523 | ;; 524 | 525 | *) 526 | printf '%%%02X' "'$_" 527 | ;; 528 | esac 529 | done 530 | printf '\n' 531 | } 532 | ``` 533 | 534 | **示例用法:** 535 | 536 | ```shell 537 | $ urlencode "https://github.com/dylanaraps/pure-bash-bible" 538 | https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible 539 | ``` 540 | 541 | ## 解码用百分比编码的字符串 542 | 543 | **示例函数:** 544 | 545 | ```sh 546 | urldecode() { 547 | # Usage: urldecode "string" 548 | : "${1//+/ }" 549 | printf '%b\n' "${_//%/\\x}" 550 | } 551 | ``` 552 | 553 | **示例用法:** 554 | 555 | ```shell 556 | $ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible" 557 | https://github.com/dylanaraps/pure-bash-bible 558 | ``` 559 | 560 | ## 检查字符串是否包含子字符串 561 | 562 | **用于测试:** 563 | 564 | ```shell 565 | if [[ $var == *sub_string* ]]; then 566 | printf '%s\n' "sub_string is in var." 567 | fi 568 | 569 | # 反转 (子串不在字符串中). 570 | if [[ $var != *sub_string* ]]; then 571 | printf '%s\n' "sub_string is not in var." 572 | fi 573 | 574 | # 也可以在数组中运行 575 | if [[ ${arr[*]} == *sub_string* ]]; then 576 | printf '%s\n' "sub_string is in array." 577 | fi 578 | ``` 579 | 580 | **使用case语句:** 581 | 582 | ```shell 583 | case "$var" in 584 | *sub_string*) 585 | # Do stuff 586 | ;; 587 | 588 | *sub_string2*) 589 | # Do more stuff 590 | ;; 591 | 592 | *) 593 | # Else 594 | ;; 595 | esac 596 | ``` 597 | 598 | ## 检查字符串是否以子字符串开头 599 | 600 | ```shell 601 | if [[ $var == sub_string* ]]; then 602 | printf '%s\n' "var starts with sub_string." 603 | fi 604 | 605 | # 反转 (变量不是以子串开头). 606 | if [[ $var != sub_string* ]]; then 607 | printf '%s\n' "var does not start with sub_string." 608 | fi 609 | ``` 610 | 611 | ## 检查字符串是否以子字符串结尾 612 | 613 | ```shell 614 | if [[ $var == *sub_string ]]; then 615 | printf '%s\n' "var ends with sub_string." 616 | fi 617 | 618 | # Inverse (var does not end with sub_string). 619 | if [[ $var != *sub_string ]]; then 620 | printf '%s\n' "var does not end with sub_string." 621 | fi 622 | ``` 623 | 624 | 625 | 626 | 627 | # 数组 628 | 629 | ## 反转数组 630 | 631 | 启用`extdebug`允许访问`BASH_ARGV`数组,该数组反向存储当前函数的参数 632 | 633 | **示例函数:** 634 | 635 | ```sh 636 | reverse_array() { 637 | # Usage: reverse_array "array" 638 | shopt -s extdebug 639 | f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@" 640 | shopt -u extdebug 641 | } 642 | ``` 643 | 644 | **示例用法:** 645 | 646 | ```shell 647 | $ reverse_array 1 2 3 4 5 648 | 5 649 | 4 650 | 3 651 | 2 652 | 1 653 | 654 | $ arr=(red blue green) 655 | $ reverse_array "${arr[@]}" 656 | green 657 | blue 658 | red 659 | ``` 660 | 661 | ## 删除重复的数组元素 662 | 663 | 创建临时关联数组。设置关联数组值并发生重复赋值时,bash会覆盖该键。这允许我们有效地删除数组重复。 664 | 665 | **警告:** 版本要求 `bash` 4+ 666 | 667 | **示例函数:** 668 | 669 | ```sh 670 | remove_array_dups() { 671 | # Usage: remove_array_dups "array" 672 | declare -A tmp_array 673 | 674 | for i in "$@"; do 675 | [[ $i ]] && IFS=" " tmp_array["${i:- }"]=1 676 | done 677 | 678 | printf '%s\n' "${!tmp_array[@]}" 679 | } 680 | ``` 681 | 682 | **示例用法:** 683 | 684 | ```shell 685 | $ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 686 | 1 687 | 2 688 | 3 689 | 4 690 | 5 691 | 692 | $ arr=(red red green blue blue) 693 | $ remove_array_dups "${arr[@]}" 694 | red 695 | green 696 | blue 697 | ``` 698 | 699 | ## 随机返回一个数组元素 700 | 701 | **示例函数:** 702 | 703 | ```sh 704 | random_array_element() { 705 | # Usage: random_array_element "array" 706 | local arr=("$@") 707 | printf '%s\n' "${arr[RANDOM % $#]}" 708 | } 709 | ``` 710 | bash的SHELL参数RANDOM可以生成0-32767的随机数 711 | 想设定从1到N的随机数范围的话,可以使用: 712 | $ ( ( (RANDOM % n) + 1 )) 713 | 714 | **示例用法:** 715 | 716 | ```shell 717 | $ array=(red green blue yellow brown) 718 | $ random_array_element "${array[@]}" 719 | yellow 720 | 721 | # Multiple arguments can also be passed. 722 | $ random_array_element 1 2 3 4 5 6 7 723 | 3 724 | ``` 725 | 726 | ## 循环迭代一个数组 727 | 728 | 每次`printf`调用时,都会打印下一个数组元素。当打印到达最后一个数组元素时,它再次从第一个元素开始。 729 | 730 | ```sh 731 | arr=(a b c d) 732 | 733 | cycle() { 734 | printf '%s ' "${arr[${i:=0}]}" 735 | ((i=i>=${#arr[@]}-1?0:++i)) 736 | } 737 | ``` 738 | 739 | 740 | ## 在两个值之间转换 741 | 742 | 这与上面的工作方式相同,这只是一个不同的用例。 743 | 744 | 745 | ```sh 746 | arr=(true false) 747 | 748 | cycle() { 749 | printf '%s ' "${arr[${i:=0}]}" 750 | ((i=i>=${#arr[@]}-1?0:++i)) 751 | } 752 | ``` 753 | 754 | 755 | 756 | 757 | # 循环 758 | 759 | ## 循环生成范围内的数字 760 | 761 | 替代`seq`. 762 | 763 | ```shell 764 | # Loop from 0-100 (no variable support). 765 | for i in {0..100}; do 766 | printf '%s\n' "$i" 767 | done 768 | ``` 769 | 770 | ## 循环遍历可变数字范围 771 | 772 | 替代 `seq`. 773 | 774 | ```shell 775 | # Loop from 0-VAR. 776 | VAR=50 777 | for ((i=0;i<=VAR;i++)); do 778 | printf '%s\n' "$i" 779 | done 780 | ``` 781 | 782 | ## 循环数组 783 | 784 | ```shell 785 | arr=(apples oranges tomatoes) 786 | 787 | # Just elements. 788 | for element in "${arr[@]}"; do 789 | printf '%s\n' "$element" 790 | done 791 | ``` 792 | 793 | ## 循环输出带索引的数组 794 | 795 | ```shell 796 | arr=(apples oranges tomatoes) 797 | 798 | # 元素和索引. 799 | for i in "${!arr[@]}"; do 800 | printf '%s %s\n' "$i ${arr[i]}" 801 | done 802 | 803 | # 替代方法. 804 | for ((i=0;i<${#arr[@]};i++)); do 805 | printf '%s %s\n' "$i ${arr[i]}" 806 | done 807 | ``` 808 | 809 | ## 循环遍历文件的内容 810 | 811 | ```shell 812 | while read -r line; do 813 | printf '%s\n' "$line" 814 | done < "file" 815 | ``` 816 | 817 | ## 循环遍历文件和目录 818 | 819 | 820 | 不要 `ls`. 821 | 822 | ```shell 823 | # 遍历当前目录下的文件和目录. 824 | for file in *; do 825 | printf '%s\n' "$file" 826 | done 827 | 828 | # 遍历目录中的png图片. 829 | for file in ~/Pictures/*.png; do 830 | printf '%s\n' "$file" 831 | done 832 | 833 | # 迭代输出目录. 834 | for dir in ~/Downloads/*/; do 835 | printf '%s\n' "$dir" 836 | done 837 | 838 | # 支持扩展. 839 | for file in /path/to/parentdir/{file1,file2,subdir/file3}; do 840 | printf '%s\n' "$file" 841 | done 842 | 843 | # 递归迭代,输出子目录下的所有文件. 844 | # 845 | shopt -s globstar 846 | for file in ~/Pictures/**/*; do 847 | printf '%s\n' "$file" 848 | done 849 | shopt -u globstar 850 | ``` 851 | globstar是Bash 4.0才引入的选项,当设置启用globstar(shopt -s globstar)时,两个星号意为对通配符进行展开就可以匹配任何当前目录(包括子目录)以及其的文件;若不启用globstar(shopt -u globstar),两个星号通配符的作用和一个星号通配符是相同的。 852 | 853 | 854 | 855 | # 文本处理 856 | 857 | **警告:** `bash`在小于`<4.4`的版本中不能正确处理二进制数据. 858 | 859 | ## 读取文件到一个字符串中 860 | 861 | 替代 `cat` 命令. 862 | 863 | ```shell 864 | file_data="$(<"file")" 865 | ``` 866 | 867 | ## 读取文件到一个数组中 (*按行读取*) 868 | 869 | 替代 `cat` 命令. 870 | 871 | ```shell 872 | # Bash <4 (丢弃空行) 873 | IFS=$'\n' read -d "" -ra file_data < "file" 874 | 875 | # Bash <4 (保留空行). 876 | while read -r line; do 877 | file_data+=("$line") 878 | done < "file" 879 | 880 | # Bash 4+ 881 | mapfile -t file_data < "file" 882 | ``` 883 | 884 | ## 获取文件的前N行 885 | 886 | 替代 `head` 命令. 887 | 888 | **警告:** 版本要求 `bash` 4+ 889 | 890 | **示例函数:** 891 | 892 | ```sh 893 | head() { 894 | # Usage: head "n" "file" 895 | mapfile -tn "$1" line < "$2" 896 | printf '%s\n' "${line[@]}" 897 | } 898 | ``` 899 | 900 | **示例用法:** 901 | 902 | ```shell 903 | $ head 2 ~/.bashrc 904 | # Prompt 905 | PS1='➜ ' 906 | 907 | $ head 1 ~/.bashrc 908 | # Prompt 909 | ``` 910 | 911 | ## 获取文件的最后N行 912 | 913 | 替代 `tail` 命令. 914 | 915 | **警告:** 版本要求 `bash` 4+ 916 | 917 | **示例函数:** 918 | 919 | ```sh 920 | tail() { 921 | # Usage: tail "n" "file" 922 | mapfile -tn 0 line < "$2" 923 | printf '%s\n' "${line[@]: -$1}" 924 | } 925 | ``` 926 | 927 | **示例用法:** 928 | 929 | ```shell 930 | $ tail 2 ~/.bashrc 931 | # Enable tmux. 932 | # [[ -z "$TMUX" ]] && exec tmux 933 | 934 | $ tail 1 ~/.bashrc 935 | # [[ -z "$TMUX" ]] && exec tmux 936 | ``` 937 | 938 | ## 获取文件中的行数 939 | 940 | 941 | 替代 `wc -l`. 942 | 943 | **示例函数 (bash 4):** 944 | 945 | ```sh 946 | lines() { 947 | # Usage: lines "file" 948 | mapfile -tn 0 lines < "$1" 949 | printf '%s\n' "${#lines[@]}" 950 | } 951 | ``` 952 | 953 | **示例函数 (bash 3):** 954 | 955 | 这个方法比`mapfile`方法使用更少的内存,并且在`bash` 3中工作,但对于更大的文件来说它更慢。 956 | 957 | ```sh 958 | lines_loop() { 959 | # Usage: lines_loop "file" 960 | count=0 961 | while IFS= read -r _; do 962 | ((count++)) 963 | done < "$1" 964 | printf '%s\n' "$count" 965 | } 966 | ``` 967 | 968 | **示例用法:** 969 | 970 | ```shell 971 | $ lines ~/.bashrc 972 | 48 973 | 974 | $ lines_loop ~/.bashrc 975 | 48 976 | ``` 977 | 978 | ## 计算目录中的文件或目录 979 | 980 | 981 | 这是通过将glob的输出传递给函数然后计算参数的数量来实现的。 982 | 983 | 984 | 985 | **示例函数:** 986 | 987 | ```sh 988 | count() { 989 | # Usage: count /path/to/dir/* 990 | # count /path/to/dir/*/ 991 | printf '%s\n' "$#" 992 | } 993 | ``` 994 | 995 | **示例用法:** 996 | 997 | ```shell 998 | # Count all files in dir. 999 | $ count ~/Downloads/* 1000 | 232 1001 | 1002 | # Count all dirs in dir. 1003 | $ count ~/Downloads/*/ 1004 | 45 1005 | 1006 | # Count all jpg files in dir. 1007 | $ count ~/Pictures/*.jpg 1008 | 64 1009 | ``` 1010 | 1011 | ## 创建一个空文件 1012 | 1013 | 替代 `touch`. 1014 | 1015 | ```shell 1016 | # 简短的方式. 1017 | >file 1018 | 1019 | # 更长的替代品: 1020 | :>file 1021 | echo -n >file 1022 | printf '' >file 1023 | ``` 1024 | 1025 | ## 提取两个标记之间的行 1026 | 1027 | **示例函数:** 1028 | 1029 | ```sh 1030 | extract() { 1031 | # 用法: extract file "opening marker" "closing marker" 1032 | while IFS=$'\n' read -r line; do 1033 | [[ $extract && $line != "$3" ]] && 1034 | printf '%s\n' "$line" 1035 | 1036 | [[ $line == "$2" ]] && extract=1 1037 | [[ $line == "$3" ]] && extract= 1038 | done < "$1" 1039 | } 1040 | ``` 1041 | 1042 | **示例用法:** 1043 | 1044 | ```shell 1045 | # 从MarkDown文件中提取代码块. 1046 | $ extract ~/projects/pure-bash/README.md '```sh' '```' 1047 | # Output here... 1048 | ``` 1049 | 1050 | 1051 | 1052 | 1053 | # 文件路径 1054 | 1055 | ## 获取文件路径的目录名 1056 | 1057 | 替代 `dirname` 命令. 1058 | 1059 | **示例函数:** 1060 | 1061 | ```sh 1062 | dirname() { 1063 | # 用法: dirname "path" 1064 | local tmp=${1:-.} 1065 | 1066 | [[ $tmp != *[!/]* ]] && { 1067 | printf '/\n' 1068 | return 1069 | } 1070 | 1071 | tmp=${tmp%%"${tmp##*[!/]}"} 1072 | 1073 | [[ $tmp != */* ]] && { 1074 | printf '.\n' 1075 | return 1076 | } 1077 | 1078 | tmp=${tmp%/*} 1079 | tmp=${tmp%%"${tmp##*[!/]}"} 1080 | 1081 | printf '%s\n' "${tmp:-/}" 1082 | } 1083 | ``` 1084 | 1085 | **示例用法:** 1086 | 1087 | ```shell 1088 | $ dirname ~/Pictures/Wallpapers/1.jpg 1089 | /home/black/Pictures/Wallpapers 1090 | 1091 | $ dirname ~/Pictures/Downloads/ 1092 | /home/black/Pictures 1093 | ``` 1094 | 1095 | ## 获取文件路径的基本名称 1096 | 1097 | 1098 | 替代 `basename` 命令. 1099 | 1100 | **示例函数:** 1101 | 1102 | ```sh 1103 | basename() { 1104 | # 用法: basename "path" ["后缀"] 1105 | local tmp 1106 | 1107 | tmp=${1%"${1##*[!/]}"} 1108 | tmp=${tmp##*/} 1109 | tmp=${tmp%"${2/"$tmp"}"} 1110 | 1111 | printf '%s\n' "${tmp:-/}" 1112 | } 1113 | ``` 1114 | 1115 | **示例用法:** 1116 | 1117 | ```shell 1118 | $ basename ~/Pictures/Wallpapers/1.jpg 1119 | 1.jpg 1120 | 1121 | $ basename ~/Pictures/Wallpapers/1.jpg .jpg 1122 | 1 1123 | 1124 | $ basename ~/Pictures/Downloads/ 1125 | Downloads 1126 | ``` 1127 | 1128 | 1129 | 1130 | 1131 | # 变量 1132 | 1133 | ## 分配和访问一个变量 1134 | 1135 | ```shell 1136 | $ hello_world="value" 1137 | 1138 | # 创建一个变量名. 1139 | $ var="world" 1140 | $ ref="hello_$var" 1141 | 1142 | # 打印存储为 'hello_$var' 变量名称的值. 1143 | $ printf '%s\n' "${!ref}" 1144 | value 1145 | ``` 1146 | 1147 | 或者, 在 `bash` 4.3+以上版本: 1148 | 1149 | ```shell 1150 | $ hello_world="value" 1151 | $ var="world" 1152 | 1153 | # 声明一个名称引用. 1154 | $ declare -n ref=hello_$var 1155 | 1156 | $ printf '%s\n' "$ref" 1157 | value 1158 | ``` 1159 | 1160 | ## 根据另一个变量命名变量 1161 | 1162 | ```shell 1163 | $ var="world" 1164 | $ declare "hello_$var=value" 1165 | $ printf '%s\n' "$hello_world" 1166 | value 1167 | ``` 1168 | 1169 | 1170 | 1171 | 1172 | # 转义字符 1173 | 1174 | 与流行的看法相反, 使用原始的转义字符并不会出现问题. 使用`tput`抽象相同的ANSI序列,就像手动打印一样. 更糟糕的是,`tput`实际上并不是便携式的. 有许多`tput`变体,每个变体都有不同的命令和语法 (*尝试运行 `tput setaf 3` 在 FreeBSD 系统里*). 1175 | 1176 | ## 文本颜色 1177 | 1178 | **NOTE:** 需要RGB值的序列仅适用于真彩色终端仿真器. 1179 | 1180 | | 序列 | 它将做什么? | 值 | 1181 | | -------- | ---------------- | ----- | 1182 | | `\e[38;5;m` | 设置文本前景色. | `0-255` 1183 | | `\e[48;5;m` | 设置文本背景颜色. | `0-255` 1184 | | `\e[38;2;;;m` | 将文本前景色设置为RGB颜色. | `R`, `G`, `B` 1185 | | `\e[48;2;;;m` | 将文本背景颜色设置为RGB颜色. | `R`, `G`, `B` 1186 | 1187 | ## 文本属性 1188 | 1189 | | 序列 | 它将做什么? | 1190 | | -------- | ---------------- | 1191 | | `\e[m` | 重置文本格式和颜色. 1192 | | `\e[1m` | 粗体. | 1193 | | `\e[2m` | 微弱的文字. | 1194 | | `\e[3m` | 斜体文字. | 1195 | | `\e[4m` | 下划线文字. | 1196 | | `\e[5m` | 慢速闪烁. | 1197 | | `\e[7m` | 交换前景色和背景色. | 1198 | 1199 | 1200 | ## 移动光标 1201 | 1202 | | 序列 | 它将做什么? | 值 | 1203 | | -------- | ---------------- | ----- | 1204 | | `\e[;H` | 将光标移动到绝对位置. | `line`, `column` 1205 | | `\e[H` | 将光标移动到原位 (`0,0`). | 1206 | | `\e[A` | 将光标向上移动N行. | `num` 1207 | | `\e[B` | 将光标向下移动N行. | `num` 1208 | | `\e[C` | 将光标向右移动N列. | `num` 1209 | | `\e[D` | 将光标向左移动N列. | `num` 1210 | | `\e[s` | 保存光标位置. | 1211 | | `\e[u` | 恢复光标位置. | 1212 | 1213 | 1214 | ## 删除文本 1215 | 1216 | | 序列 | 它将做什么? | 1217 | | -------- | ---------------- | 1218 | | `\e[K` | 从光标位置删除到行尾. 1219 | | `\e[1K` | 从光标位置删除到行首. 1220 | | `\e[2K` | 擦除整个当前行. 1221 | | `\e[J` | 从当前行删除到屏幕底部. 1222 | | `\e[1J` | 从当前行删除到屏幕顶部. 1223 | | `\e[2J` | 清除屏幕. 1224 | | `\e[2J\e[H` | 清除屏幕并将光标移动到 `0,0`. 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | # 参数拓展 1231 | 1232 | ## 间接 1233 | 1234 | | 参数 | 它将做什么? | 1235 | | --------- | ---------------- | 1236 | | `${!VAR}` | 根据`VAR`的值访问一个变量. 1237 | | `${!VAR*}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. | 1238 | | `${!VAR@}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. 如果是双引号,则每个变量名称都会扩展为单独的单词. | 1239 | 1240 | 1241 | ## 替换 1242 | 1243 | | 参数 | 它将做什么? | 1244 | | --------- | ---------------- | 1245 | | `${VAR#PATTERN}` | 删除第一次匹配的模式及其左边的字符. | 1246 | | `${VAR##PATTERN}` | 删除最后一次匹配的模式及其左边的字符. | 1247 | | `${VAR%PATTERN}` | 删除最后一次匹配的模式及其右边的字符. | 1248 | | `${VAR%%PATTERN}` | 删除第一次匹配的模式及其右边的字符. | 1249 | | `${VAR/PATTERN/REPLACE}` | 替换第一次匹配的字符. 1250 | | `${VAR//PATTERN/REPLACE}` | 替换所有匹配的字符. 1251 | | `${VAR/PATTERN}` | 删除第一次匹配的字符. 1252 | | `${VAR//PATTERN}` | 删除所有匹配的字符. 1253 | 1254 | ## 长度 1255 | 1256 | | 参数 | 它将做什么? | 1257 | | --------- | ---------------- | 1258 | | `${#VAR}` | 字符变量的长度. 1259 | | `${#ARR[@]}` | 数组的长度. 1260 | 1261 | ## 扩展 1262 | 1263 | | 参数 | 它将做什么? | 版本要求 | 1264 | | --------- | ---------------- | 1265 | | `${VAR:OFFSET}` | 从变量中删除第OFFSET个字符及之前的字符. 1266 | | `${VAR:OFFSET:LENGTH}` | 获得从`OFFSET`字符之后`LENGTH`个字符的字符串.
(`${VAR:10:10}`: 获得从第10个字符到第20个字符的字符串) 1267 | | `${VAR:: OFFSET}` | 从变量中获取前`OFFSET`个字符. 1268 | | `${VAR:: -OFFSET}` | 从变量中移除前`OFFSET`个字符. 1269 | | `${VAR: -OFFSET}` | 从变量中获取最后`OFFSET`个字符. 1270 | | `${VAR:OFFSET:-OFFSET}` | 删除前`OFFSET`个字符以及最后`OFFSET`个字符. | `bash 4.2+` | 1271 | 1272 | ## 改变大小写 1273 | 1274 | | 参数 | 它将做什么? | 版本要求 | 1275 | | --------- | ---------------- | ------ | 1276 | | `${VAR^}` | 大写第一个字符. | `bash 4+` | 1277 | | `${VAR^^}` | 大写所有字符. | `bash 4+` | 1278 | | `${VAR,}` | 小写第一个字符. | `bash 4+` | 1279 | | `${VAR,,}` | 小写所有字符. | `bash 4+` | 1280 | | `${VAR~}` | 反转第一个字符. | `bash 4+` | 1281 | | `${VAR~~}` | 反转所有字符. | `bash 4+` | 1282 | 1283 | 1284 | ## 默认值 1285 | 1286 | | 参数 | 它将做什么? | 1287 | | --------- | ---------------- | 1288 | | `${VAR:-STRING}` | 如果 `VAR` 为空或未设置,使用 `STRING` 作为它的值. 1289 | | `${VAR-STRING}` | 如果 `VAR` 未设置, 使用 `STRING` 作为它的值. 1290 | | `${VAR:=STRING}` | 如果 `VAR` 为空或未设置, 设置 `VAR` 的值为 `STRING`. 1291 | | `${VAR=STRING}` | 如果 `VAR` 未设置, 设置 `VAR` 的值为 `STRING`. 1292 | | `${VAR:+STRING}` | 如果 `VAR` 不为空, 使用 `STRING` 作为它的值. 1293 | | `${VAR+STRING}` | 如果 `VAR` 已设置, 使用 `STRING` 作为它的值. 1294 | | `${VAR:?STRING}` | 如果为空或未设置,则显示一个错误. 1295 | | `${VAR?STRING}` | 如果未设置则显示一个错误. 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | # 花括号展开 1302 | 1303 | ## 范围 1304 | 1305 | ```shell 1306 | # 符号: {..} 1307 | 1308 | # 打印 1-100 之间的数字. 1309 | echo {1..100} 1310 | 1311 | # 打印一个范围内的浮点数. 1312 | echo 1.{1..9} 1313 | 1314 | # 打印a-z间的字符. 1315 | echo {a..z} 1316 | echo {A..Z} 1317 | 1318 | # 嵌套. 1319 | echo {A..Z}{0..9} 1320 | 1321 | # 打印用零填充的数字. 1322 | # 警告: bash 4+ 1323 | echo {01..100} 1324 | 1325 | # 更改步长. 1326 | # 符号: {....} 1327 | # 警告: bash 4+ 1328 | echo {1..10..2} # 每次增加2. 1329 | ``` 1330 | 1331 | ## 字符串列表 1332 | 1333 | ```shell 1334 | echo {apples,oranges,pears,grapes} 1335 | 1336 | # 示例用法: 1337 | # 删除~/Downloads/目录下的 Movies, Music 和 ISOS 文件夹 . 1338 | rm -rf ~/Downloads/{Movies,Music,ISOS} 1339 | ``` 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | # 条件表达式 1347 | 1348 | ## 文件判断 1349 | 1350 | | 表达式 | 值 | 它有什么作用? | 1351 | | ---------- | ------ | ---------------- | 1352 | | `-a` | `file` | 文件存在 1353 | | `-b` | `file` | 文件存在并且是块特殊文件. 1354 | | `-c` | `file` | 文件存在并且是字符特殊文件. 1355 | | `-d` | `file` | 文件存在且是目录. 1356 | | `-e` | `file` | 文件存在. 1357 | | `-f` | `file` | 文件存在且是常规文件. 1358 | | `-g` | `file` | 文件存在且其set-group-id位已设置. 1359 | | `-h` | `file` | 文件存在并且是符号链接. 1360 | | `-k` | `file` | 文件存在且其sticky-bit已设置 1361 | | `-p` | `file` | 文件存在并且是命名管道 (*FIFO*). 1362 | | `-r` | `file` | 文件存在且可读. 1363 | | `-s` | `file` | 文件存在且其大小大于零. 1364 | | `-t` | `fd` | 文件描述符是打开的并且引用到一个终端. 1365 | | `-u` | `file` | 文件存在且其set-user-id位已设置. 1366 | | `-w` | `file` | 文件存在且可写. 1367 | | `-x` | `file` | 文件存在且可执行. 1368 | | `-G` | `file` | 文件存在且拥有者是一个有效组ID. 1369 | | `-L` | `file` | 文件存在并且是符号链接. 1370 | | `-N` | `file` | 文件存在且自上次读取后已被修改. 1371 | | `-O` | `file` | 文件存在并且拥有者是一个有效用户ID. 1372 | | `-S` | `file` | 文件存在且是套接字. 1373 | 1374 | ## 文件比较 1375 | 1376 | | 表达式 | 它有什么作用? | 1377 | | ---------- | ---------------- | 1378 | | `file -ef file2` | 是否两个文件都引用相同的inode和设备编号. 1379 | | `file -nt file2` | 是否 `file` 比 `file2`更新 (*使用修改时间*) 或者 `file` 存在而 `file2` 不存在. 1380 | | `file -ot file2` | 是否 `file` 比 `file2`更老 (*使用修改时间*) 或者 `file2` 存在而 `file` 不存在. 1381 | 1382 | ## 条件变量 1383 | 1384 | | 表达式 | 值 | 它有什么作用? | 1385 | | ---------- | ----- | ---------------- | 1386 | | `-o` | `opt` | 是否启用了shell选项. 1387 | | `-v` | `var` | 是否变量具有指定的值. 1388 | | `-R` | `var` | 是否变量是一个名称引用. 1389 | | `-z` | `var` | 是否字符串的长度为零. 1390 | | `-n` | `var` | 是否字符串的长度不为零. 1391 | 1392 | ## 比较变量 1393 | 1394 | | 表达式 | 它有什么作用? | 1395 | | ---------- | ---------------- | 1396 | | `var = var2` | 等于. 1397 | | `var == var2` | 等于 (*同义词 `=`*). 1398 | | `var != var2` | 不等于. 1399 | | `var < var2` | 小于 (*以ASCII字母顺序排列.*) 1400 | | `var > var2` | 大于 (*以ASCII字母顺序排列.*) 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | # 算术运算符 1407 | 1408 | ## 指派 1409 | 1410 | | 操作符 | 它有什么作用? | 1411 | | --------- | ---------------- | 1412 | | `=` | 初始化或更改变量的值。 1413 | 1414 | ## 四则运算 1415 | 1416 | | 操作符 | 它有什么作用? | 1417 | | --------- | ---------------- | 1418 | | `+` | 加 1419 | | `-` | 减 1420 | | `*` | 乘 1421 | | `/` | 除 1422 | | `**` | 幂运算 1423 | | `%` | 求模 1424 | | `+=` | 先加后赋值 (*`x += y`等同于`x = x + y`*) 1425 | | `-=` | 先减后赋值 (*`x -= y`等同于`x = x - y`*) 1426 | | `*=` | 先乘后赋值 (*`x *= y`等同于`x = x * y`*) 1427 | | `/=` | 先除后赋值 (*`x /= y`等同于`x = x / y`*) 1428 | | `%=` | 先取模后赋值 (*`x %= y`等同于`x = x % y`*) 1429 | 1430 | ## 位运算 1431 | 1432 | | 操作符 | 它有什么作用? | 1433 | | --------- | ---------------- | 1434 | | `<<` | 按位左移 1435 | | `<<=` | 按位左移后赋值 1436 | | `>>` | 按位右移 1437 | | `>>=` | 按位右移后赋值 1438 | | `&` | 按位与操作 1439 | | `&=` | 按位与操作后赋值 1440 | | `\|` | 按位或操作 1441 | | `\|=` | 按位或操作赋值 1442 | | `~` | 按位非操作 1443 | | `^` | 按位异或操作 1444 | | `^=` | 按位异或操作后赋值 1445 | 1446 | ## 逻辑运算 1447 | 1448 | | 操作符 | 它有什么作用? | 1449 | | --------- | ---------------- | 1450 | | `!` | NOT 1451 | | `&&` | AND 1452 | | `\|\|` | OR 1453 | 1454 | ## 复杂运算 1455 | 1456 | | 操作符 | 它有什么作用? | 例子 | 1457 | | --------- | ---------------- | ------- | 1458 | | `,` | 逗号分隔符 | `((a=1,b=2,c=3))` 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | # 算术运算符-1 1465 | 1466 | ## 用更简单语法设置变量 1467 | 1468 | ```shell 1469 | # 简单的数学运算 1470 | ((var=1+2)) 1471 | 1472 | # 递减/递增变量 1473 | ((var++)) 1474 | ((var--)) 1475 | ((var+=1)) 1476 | ((var-=1)) 1477 | 1478 | # 使用变量 1479 | ((var=var2*arr[2])) 1480 | ``` 1481 | 1482 | ## 三元测试 1483 | 1484 | ```shell 1485 | # 如果var2大于var,则将var的值设置为var2,否则设置为var. 1486 | # var: 要设置的变量. 1487 | # var2>var: 条件测试. 1488 | # ?var2: 如果条件测试成功时生效. 1489 | # :var: 条件测试失败时生效. 1490 | ((var=var2>var?var2:var)) 1491 | ``` 1492 | 1493 | 1494 | 1495 | 1496 | # trap命令 1497 | 1498 | Traps允许脚本在各种信号上执行代码。在 [pxltrm](https://github.com/dylanaraps/pxltrm) (*用bash编写的像素艺术编辑器*) traps 用于在窗口大小调整时重绘用户界面。另一个用例是在脚本退出时清理临时文件。 1499 | 1500 | Traps应该在脚本开头附近添加,以便捕获任何早期错误. 1501 | 1502 | **NOTE:** 有关信号的完整列表,请参阅 `trap -l`. 1503 | 1504 | 1505 | ## 在脚本退出时执行操作 1506 | 1507 | ```shell 1508 | # 脚本退出时清除屏幕。 1509 | trap 'printf \\e[2J\\e[H\\e[m' EXIT 1510 | ``` 1511 | 1512 | ## 忽略终端中断(CTRL+C,SIGINT) 1513 | 1514 | ```shell 1515 | trap '' INT 1516 | ``` 1517 | 1518 | ## 对窗口调整大小时做出反应 1519 | 1520 | ```shell 1521 | # 在窗口调整大小时调用函数. 1522 | trap 'code_here' SIGWINCH 1523 | ``` 1524 | 1525 | ## 在命令之前执行某些操作 1526 | 1527 | ```shell 1528 | trap 'code_here' DEBUG 1529 | ``` 1530 | 1531 | ## 在shell函数或源文件完成执行时执行某些操作 1532 | 1533 | ```shell 1534 | trap 'code_here' RETURN 1535 | ``` 1536 | 1537 | 1538 | 1539 | 1540 | # 性能 1541 | 1542 | ## 禁用Unicode码 1543 | 1544 | 如果不需要unicode,则可以禁用它以提高性能。结果可能会有所不同,但是[neofetch](https://github.com/dylanaraps/neofetch) 和其他程序有明显改善。 1545 | 1546 | ```shell 1547 | # 禁用 unicode. 1548 | LC_ALL=C 1549 | LANG=C 1550 | ``` 1551 | 1552 | 1553 | 1554 | 1555 | # 已过时的语法 1556 | 1557 | ## 释伴声明 1558 | 1559 | 使用 `#!/usr/bin/env bash` 而不是 `#!/bin/bash`. 1560 | 1561 | - 前者搜索用户的 `PATH` 以查找 `bash` 二进制文件. 1562 | - 后者假设它始终安装在 `/bin/` 目录,可能导致问题. 1563 | 1564 | ```shell 1565 | # 正确的方式: 1566 | 1567 | #!/usr/bin/env bash 1568 | 1569 | # 错误的方式: 1570 | 1571 | #!/bin/bash 1572 | ``` 1573 | 1574 | ## 命令替换 1575 | 1576 | 使用 `$()`而不是 `` ` ` ``. 1577 | 1578 | ```shell 1579 | # 正确的方式. 1580 | var="$(command)" 1581 | 1582 | # 错误的方式. 1583 | var=`command` 1584 | 1585 | # $() 很容易嵌套,而``不能. 1586 | var="$(command "$(command)")" 1587 | ``` 1588 | 1589 | ## 声明函数 1590 | 1591 | 不要使用`function`关键字,它会降低与旧版本`bash`的兼容性. 1592 | 1593 | ```shell 1594 | # 正确的方式. 1595 | do_something() { 1596 | # ... 1597 | } 1598 | 1599 | # 错误的方式. 1600 | function do_something() { 1601 | # ... 1602 | } 1603 | ``` 1604 | 1605 | 1606 | 1607 | 1608 | # 内部变量 1609 | 1610 | ## 获取`bash`二进制文件的位置 1611 | 1612 | ```shell 1613 | "$BASH" 1614 | ``` 1615 | 1616 | ## 获取当前运行`bash`命令的版本 1617 | 1618 | ```shell 1619 | # 作为字符串. 1620 | "$BASH_VERSION" 1621 | 1622 | # 作为数组. 1623 | "${BASH_VERSINFO[@]}" 1624 | ``` 1625 | 1626 | ## 打开用户默认的文本编辑器 1627 | 1628 | ```shell 1629 | "$EDITOR" "$file" 1630 | 1631 | # NOTE: 这个变量可能是空的,设置一个失败调用值. 1632 | "${EDITOR:-vi}" "$file" 1633 | ``` 1634 | 1635 | ## 获取当前函数的名称 1636 | 1637 | ```shell 1638 | # 当前函数. 1639 | "${FUNCNAME[0]}" 1640 | 1641 | # 父函数. 1642 | "${FUNCNAME[1]}" 1643 | 1644 | # 等等. 1645 | "${FUNCNAME[2]}" 1646 | "${FUNCNAME[3]}" 1647 | 1648 | # 包括父类的所有函数 1649 | "${FUNCNAME[@]}" 1650 | ``` 1651 | 1652 | ## 获取系统的主机名 1653 | 1654 | ```shell 1655 | "$HOSTNAME" 1656 | 1657 | # NOTE: 这个变量可能是空的. 1658 | # (可选):将失败调用设置为hostname命令 1659 | "${HOSTNAME:-$(hostname)}" 1660 | ``` 1661 | 1662 | ## 获取操作系统的架构(32位或64位) 1663 | 1664 | ```shell 1665 | "$HOSTTYPE" 1666 | ``` 1667 | 1668 | ## 获取操作系统/内核的名称 1669 | 1670 | 这可用于条件判断不同的操作系统,而无需调用`uname`。 1671 | 1672 | ```shell 1673 | "$OSTYPE" 1674 | ``` 1675 | 1676 | ## 获取当前的工作目录 1677 | 1678 | 这是内置`pwd`的替代方案。 1679 | 1680 | ```shell 1681 | "$PWD" 1682 | ``` 1683 | 1684 | ## 获取脚本运行的秒数 1685 | 1686 | ```shell 1687 | "$SECONDS" 1688 | ``` 1689 | 1690 | ## 获取伪随机整数 1691 | 1692 | 每次使用`$RANDOM`时, 返回`0` and `32767`之间的不同整数。 此变量不应用于与安全性相关的任何内容(包括加密密钥等)。 1693 | 1694 | 1695 | ```shell 1696 | "$RANDOM" 1697 | ``` 1698 | 1699 | 1700 | 1701 | 1702 | # 有关终端的信息 1703 | 1704 | ## 获取终端的总行列数(*来自脚本*) 1705 | 1706 | 在纯bash中编写脚本和`stty`/`tput`无法调用时,这很方便。 1707 | 1708 | **示例函数:** 1709 | 1710 | ```sh 1711 | get_term_size() { 1712 | # 用法: get_term_size 1713 | 1714 | # (:;:) 是一个短暂暂停,以确保变量立即导出 1715 | shopt -s checkwinsize; (:;:) 1716 | printf '%s\n' "$LINES $COLUMNS" 1717 | } 1718 | ``` 1719 | 1720 | **示例用法:** 1721 | 1722 | ```shell 1723 | # 输出: 行数 列数 1724 | $ get_term_size 1725 | 15 55 1726 | ``` 1727 | 1728 | ## 获取终端的像素大小 1729 | 1730 | **警告**: 这在某些终端仿真器中不起作用。 1731 | 1732 | **示例函数:** 1733 | 1734 | ```sh 1735 | get_window_size() { 1736 | # 用法: get_window_size 1737 | printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}" 1738 | IFS=';t' read -d t -t 0.05 -sra term_size 1739 | printf '%s\n' "${term_size[1]}x${term_size[2]}" 1740 | } 1741 | ``` 1742 | 1743 | **示例用法:** 1744 | 1745 | ```shell 1746 | # 输出: 长度x高度 1747 | $ get_window_size 1748 | 1200x800 1749 | 1750 | # 输出 (失败): 1751 | $ get_window_size 1752 | x 1753 | ``` 1754 | 1755 | ## 获取当前光标位置 1756 | 1757 | 用纯bash创建TUI时,是很有用的。 1758 | TUI是指文本用户界面(Text-based User Interface),通过文本实现交互窗口展示内容,定位光标和鼠标实现用户交互。 1759 | 1760 | **示例函数:** 1761 | 1762 | ```sh 1763 | get_cursor_pos() { 1764 | # 用法: get_cursor_pos 1765 | IFS='[;' read -p $'\e[6n' -d R -rs _ y x _ 1766 | printf '%s\n' "$x $y" 1767 | } 1768 | ``` 1769 | 1770 | **示例用法:** 1771 | 1772 | ```shell 1773 | # Output: X Y 1774 | $ get_cursor_pos 1775 | 1 8 1776 | ``` 1777 | 1778 | 1779 | 1780 | 1781 | # 转换 1782 | 1783 | ## 将十六进制颜色转换为RGB 1784 | 1785 | **示例函数:** 1786 | 1787 | ```sh 1788 | hex_to_rgb() { 1789 | # Usage: hex_to_rgb "#FFFFFF" 1790 | # hex_to_rgb "000000" 1791 | : "${1/\#}" 1792 | ((r=16#${_:0:2},g=16#${_:2:2},b=16#${_:4:2})) 1793 | printf '%s\n' "$r $g $b" 1794 | } 1795 | ``` 1796 | 1797 | **示例用法:** 1798 | 1799 | ```shell 1800 | $ hex_to_rgb "#FFFFFF" 1801 | 255 255 255 1802 | ``` 1803 | 1804 | 1805 | ## 将RGB颜色转换为十六进制 1806 | 1807 | **示例函数:** 1808 | 1809 | ```sh 1810 | rgb_to_hex() { 1811 | # Usage: rgb_to_hex "r" "g" "b" 1812 | printf '#%02x%02x%02x\n' "$1" "$2" "$3" 1813 | } 1814 | ``` 1815 | 1816 | **示例用法:** 1817 | 1818 | ```shell 1819 | $ rgb_to_hex "255" "255" "255" 1820 | #FFFFFF 1821 | ``` 1822 | 1823 | 1824 | # 代码高尔夫 1825 | 1826 | [CODE GOLF](https://en.wikipedia.org/wiki/Code_golf),看看谁写的代码最短! 1827 | 1828 | 1829 | ## 更短的`for`循环语法 1830 | 1831 | ```shell 1832 | # Tiny C风格. 1833 | for((;i++<10;)){ echo "$i";} 1834 | 1835 | # 未记载的方法. 1836 | for i in {1..10};{ echo "$i";} 1837 | 1838 | # 扩展. 1839 | for i in {1..10}; do echo "$i"; done 1840 | 1841 | # C语言风格. 1842 | for((i=0;i<=10;i++)); do echo "$i"; done 1843 | ``` 1844 | 1845 | ## 更短的无限循环 1846 | 1847 | ```shell 1848 | # 普通方法 1849 | while :; do echo hi; done 1850 | 1851 | # 更短的方式 1852 | for((;;)){ echo hi;} 1853 | ``` 1854 | 1855 | ## 更短的函数声明 1856 | 1857 | ```shell 1858 | # 普通方法 1859 | f(){ echo hi;} 1860 | 1861 | # 用于子shell 1862 | f()(echo hi) 1863 | 1864 | # 用于四则运算 1865 | # 这可以被用来分配整数值。 1866 | # Example: f a=1 1867 | # f a++ 1868 | f()(($1)) 1869 | 1870 | # 用作测试,循环等 1871 | # NOTE: ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’ 也可以使用. 1872 | f()if true; then echo "$1"; fi 1873 | f()for i in "$@"; do echo "$i"; done 1874 | ``` 1875 | 1876 | ## 更短的`if`语法 1877 | 1878 | ```shell 1879 | # 一行 1880 | # Note: 当第一段是正确时执行第三段 1881 | # Note: 此处利用了逻辑运算符的短路规则 1882 | [[ $var == hello ]] && echo hi || echo bye 1883 | [[ $var == hello ]] && { echo hi; echo there; } || echo bye 1884 | 1885 | # 多行(没有else,单条语句) 1886 | # Note: 退出状态可能与if语句不同 1887 | [[ $var == hello ]] && 1888 | echo hi 1889 | 1890 | # 多行 (没有 else) 1891 | [[ $var == hello ]] && { 1892 | echo hi 1893 | # ... 1894 | } 1895 | ``` 1896 | 1897 | ## 用`case`语句来更简单的设置变量 1898 | 1899 | 内置的`:`可以用来避免在case语句中重复的实用`variable =`。 1900 | `$ _`变量存储最后一个命令的最后一个参数。 1901 | `:`总会成功,所以它可以用来存储变量值。 1902 | 1903 | ```shell 1904 | case "$OSTYPE" in 1905 | "darwin"*) 1906 | : "MacOS" 1907 | ;; 1908 | 1909 | "linux"*) 1910 | : "Linux" 1911 | ;; 1912 | 1913 | *"bsd"* | "dragonfly" | "bitrig") 1914 | : "BSD" 1915 | ;; 1916 | 1917 | "cygwin" | "msys" | "win32") 1918 | : "Windows" 1919 | ;; 1920 | 1921 | *) 1922 | printf '%s\n' "Unknown OS detected, aborting..." >&2 1923 | exit 1 1924 | ;; 1925 | esac 1926 | 1927 | # 最后,获取变量值. 1928 | os="$_" 1929 | ``` 1930 | 1931 | 1932 | 1933 | 1934 | # 其他 1935 | 1936 | ## 使用`read`作为`sleep`命令的替代品 1937 | 1938 | 令人惊讶的是,`sleep`是一个外部命令而不是`bash`内置的。 1939 | 1940 | **警告:** 要求`bash`版本 4+ 1941 | 1942 | **示例函数:** 1943 | 1944 | ```sh 1945 | read_sleep() { 1946 | # 用法: sleep 1 1947 | # sleep 0.2 1948 | read -rt "$1" <> <(:) || : 1949 | } 1950 | ``` 1951 | 1952 | **示例用法:** 1953 | 1954 | ```shell 1955 | read_sleep 1 1956 | read_sleep 0.1 1957 | read_sleep 30 1958 | ``` 1959 | 1960 | 对于性能要求较高的情况下,打开和关闭过多的文件描述符是不实用的,对于`read`的所有调用,文件描述符的分配只能进行一次:: 1961 | 1962 | (请参阅最原始的功能实现 https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever) 1963 | 1964 | ```shell 1965 | exec {sleep_fd}<> <(:) 1966 | while some_quick_test; do 1967 | # equivalent of sleep 0.001 1968 | read -t 0.001 -u $sleep_fd 1969 | done 1970 | ``` 1971 | 1972 | ## 检查一个命令是否在用户的PATH中 1973 | 1974 | ```shell 1975 | # 有3种方法可以使用,任何一种都正确。 1976 | type -p executable_name &>/dev/null 1977 | hash executable_name &>/dev/null 1978 | command -v executable_name &>/dev/null 1979 | 1980 | # 用作检测. 1981 | if type -p executable_name &>/dev/null; then 1982 | # Program is in PATH. 1983 | fi 1984 | 1985 | # 反向检测. 1986 | if ! type -p executable_name &>/dev/null; then 1987 | # Program is not in PATH. 1988 | fi 1989 | 1990 | # 示例(如果未安装程序,则提前退出). 1991 | if ! type -p convert &>/dev/null; then 1992 | printf '%s\n' "error: convert is not installed, exiting..." 1993 | exit 1 1994 | fi 1995 | ``` 1996 | 1997 | ## 使用`strftime`获取当前日期 1998 | 1999 | Bash的`printf`有一个内置的获取日期的方法,可用来代替`date`命令。 2000 | 2001 | **警告:** 要求`bash`版本 4+ 2002 | 2003 | **示例函数:** 2004 | 2005 | ```sh 2006 | date() { 2007 | # 用法: date "format" 2008 | printf "%($1)T\\n" "-1" 2009 | } 2010 | ``` 2011 | - 了解时间格式: ['man strftime'](http://www.man7.org/linux/man-pages/man3/strftime.3.html) . 2012 | 2013 | **示例用法:** 2014 | 2015 | ```shell 2016 | # 使用上述函数. 2017 | $ date "%a %d %b - %l:%M %p" 2018 | Fri 15 Jun - 10:00 AM 2019 | 2020 | # 直接使用printf. 2021 | $ printf '%(%a %d %b - %l:%M %p)T\n' "-1" 2022 | Fri 15 Jun - 10:00 AM 2023 | 2024 | # 使用printf分配变量. 2025 | $ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1' 2026 | $ printf '%s\n' "$date" 2027 | Fri 15 Jun - 10:00 AM 2028 | ``` 2029 | 2030 | ## 获取当前用户的用户名 2031 | 2032 | **警告:** 要求`bash`版本 4.4+ 2033 | 2034 | ```shell 2035 | $ : \\u 2036 | # Expand the parameter as if it were a prompt string. 2037 | $ printf '%s\n' "${_@P}" 2038 | black 2039 | ``` 2040 | 2041 | ## 生成一个V4版本的UUID 2042 | 2043 | **警告**: 生成的值不具有加密安全性。 2044 | 2045 | **示例函数:** 2046 | 2047 | ```sh 2048 | uuid() { 2049 | # 用法: uuid 2050 | C="89ab" 2051 | 2052 | for ((N=0;N<16;++N)); do 2053 | B="$((RANDOM%256))" 2054 | 2055 | case "$N" in 2056 | 6) printf '4%x' "$((B%16))" ;; 2057 | 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;; 2058 | 2059 | 3|5|7|9) 2060 | printf '%02x-' "$B" 2061 | ;; 2062 | 2063 | *) 2064 | printf '%02x' "$B" 2065 | ;; 2066 | esac 2067 | done 2068 | 2069 | printf '\n' 2070 | } 2071 | ``` 2072 | 2073 | **示例用法:** 2074 | 2075 | ```shell 2076 | $ uuid 2077 | d5b6c731-1310-4c24-9fe3-55d556d44374 2078 | ``` 2079 | 2080 | ## 进度条 2081 | 2082 | 这是一种绘制进度条的简单方法,无需在函数本身中使用for循环。 2083 | 2084 | **示例函数:** 2085 | 2086 | ```sh 2087 | bar() { 2088 | # 用法: bar 1 10 2089 | # ^----- 已经完成的百分比 (0-100). 2090 | # ^--- 字符总长度. 2091 | ((elapsed=$1*$2/100)) 2092 | 2093 | # 创建空格表示的进度条 2094 | printf -v prog "%${elapsed}s" 2095 | printf -v total "%$(($2-elapsed))s" 2096 | 2097 | printf '%s\r' "[${prog// /-}${total}]" 2098 | } 2099 | ``` 2100 | 2101 | **示例用法:** 2102 | 2103 | ```shell 2104 | for ((i=0;i<=100;i++)); do 2105 | # 纯粹的暂停动作 (为了本例可以更好的演示). 2106 | (:;:) && (:;:) && (:;:) && (:;:) && (:;:) 2107 | 2108 | # Print the bar. 2109 | bar "$i" "10" 2110 | done 2111 | 2112 | printf '\n' 2113 | ``` 2114 | 2115 | ## 获取脚本中的函数列表 2116 | 2117 | ```sh 2118 | get_functions() { 2119 | # Usage: get_functions 2120 | IFS=$'\n' read -d "" -ra functions < <(declare -F) 2121 | printf '%s\n' "${functions[@]//declare -f }" 2122 | } 2123 | ``` 2124 | 2125 | ## 绕过shell别名 2126 | 2127 | ```shell 2128 | # alias 2129 | ls 2130 | 2131 | # command 2132 | # shellcheck disable=SC1001 2133 | \ls 2134 | ``` 2135 | 2136 | ## 绕过shell函数 2137 | 2138 | ```shell 2139 | # function 2140 | ls 2141 | 2142 | # command 2143 | command ls 2144 | ``` 2145 | 2146 | - command命令 调用指定的指令并执行,命令执行时不查询shell函数。command命令只能够执行shell内部的命令。 2147 | 2148 | ## 在后台运行命令 2149 | 2150 | 这将运行给定命令并使其保持后台运行,即使终端或SSH连接中断后也是如此。但是会忽略所有输出。 2151 | 2152 | 2153 | ```sh 2154 | bkr() { 2155 | (nohup "$@" &>/dev/null &) 2156 | } 2157 | 2158 | bkr ./some_script.sh 2159 | ``` 2160 | 2161 | 2162 | 2163 | # 后记 2164 | 2165 | 感谢各位的阅读,如果对语法有啥疑问或者文章中有错误的地方,请及时告知,谢谢! 2166 | 2167 | 2168 | Rock on. 🤘 2169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

pure bash bible

bash奇巧淫技.

4 | 5 |

6 | 8 |

9 | 10 |
11 | 12 | 13 | 14 | 15 | 本书原作者将书中的内容发布到了[github](https://github.com/dylanaraps/pure-bash-bible)上,我仅仅是将其翻译为中文,并解释了其中的部分语句语法,希望可以对今后的工作有所帮助。 16 | 17 | # 以下是翻译后的原文 # 18 | 19 | 这本书的目的是汇总只使用内置`bash`的特性来实现总所周知和鲜为人知的各项任务。 使用此参考书中的代码段可以帮助你从脚本中删除不需要的依赖项,并且在大多数情况下可以使它们运行的更快。 我偶然碰到了这些技巧并在开发[neofetch](https://github.com/dylanaraps/neofetch), [pxltrm](https://github.com/dylanaraps/pxltrm) 和一些其他小的项目的时候发现了一些别的技巧。 20 | 21 | 下面的片段使用`shellcheck`进行了检查,并将测试写在了适用的地方。 想要贡献自己的代码? 阅读 [CONTRIBUTING-Zh_CN.md](https://github.com/A-BenMao/pure-bash-bible-zh_CN/blob/master/CONTRIBUTING-Zh_CN.md). 它概述了向参考书中增加片段时,单元测试的工作方式以及其他所需的内容。 22 | 23 | 看到了一些东西描述是不准确的、有缺陷的更或者是完全错误的?那么请新建一个issue或者发送一个pull request.如果参考书中缺少某些你想要的事物,也请新建一个issue并给出你能找到的解决方法。 24 | 25 | 26 | 27 |
28 | 29 | # 目 录 30 | 31 | 32 | 33 | * [前言](#前言) 34 | * [字符串](#字符串) 35 | * [删除字符串前后空格](#删除字符串前后空格) 36 | * [删除字符串中的所有的空白并用空格分割单词](#删除字符串中的所有的空白并用空格分割单词) 37 | * [在字符串上匹配正则表达式](#在字符串上匹配正则表达式) 38 | * [指定分隔符拆分字符串](#指定分隔符拆分字符串) 39 | * [将字符串转换为小写](#将字符串转换为小写) 40 | * [将字符串转换为大写](#将字符串转换为大写) 41 | * [反转字符串大小写](#反转字符串大小写) 42 | * [删除字符串中的引号](#删除字符串中的引号) 43 | * [从字符串中删除所有正则实例](#从字符串中删除所有正则实例) 44 | * [从字符串中删除第一次出现的正则实例](#从字符串中删除第一次出现的正则实例) 45 | * [在字符串开头匹配正则并删除](#在字符串开头匹配正则并删除) 46 | * [在字符串末尾匹配正则并删除](#在字符串末尾匹配正则并删除) 47 | * [百分号编码字符串](#百分号编码字符串) 48 | * [解码用百分比编码的字符串](#解码用百分比编码的字符串) 49 | * [检查字符串是否包含子字符串](#检查字符串是否包含子字符串) 50 | * [检查字符串是否以子字符串开头](#检查字符串是否以子字符串开头) 51 | * [检查字符串是否以子字符串结尾](#检查字符串是否以子字符串结尾) 52 | * [数组](#数组) 53 | * [反转数组](#反转数组) 54 | * [删除重复的数组元素](#删除重复的数组元素) 55 | * [随机返回一个数组元素](#随机返回一个数组元素) 56 | * [循环迭代一个数组](#循环迭代一个数组) 57 | * [在两个值之间转换](#在两个值之间转换) 58 | * [循环](#循环) 59 | * [循环生成范围内的数字](#循环生成范围内的数字) 60 | * [循环遍历可变数字范围](#循环遍历可变数字范围) 61 | * [循环数组](#循环数组) 62 | * [循环输出带索引的数组](#循环输出带索引的数组) 63 | * [循环遍历文件的内容](#循环遍历文件的内容) 64 | * [循环遍历文件和目录](#循环遍历文件和目录) 65 | * [文本处理](#文本处理) 66 | * [读取文件到一个字符串中](#读取文件到一个字符串中) 67 | * [读取文件到一个数组中 (*按行读取*)](#读取文件到一个数组中) 68 | * [获取文件的前N行](#获取文件的前N行) 69 | * [获取文件的最后N行](#获取文件的最后N行) 70 | * [获取文件中的行数](#获取文件中的行数) 71 | * [计算目录中的文件或目录](#计算目录中的文件或目录) 72 | * [创建一个空文件](#创建一个空文件) 73 | * [提取两个标记之间的行](#提取两个标记之间的行) 74 | * [文件路径](#文件路径) 75 | * [获取文件路径的目录名](#获取文件路径的目录名) 76 | * [获取文件路径的基本名称](#获取文件路径的基本名称) 77 | * [变量](#变量) 78 | * [分配和访问一个变量](#分配和访问一个变量) 79 | * [根据另一个变量命名变量](#根据另一个变量命名变量) 80 | * [转义字符](#转义字符) 81 | * [文本颜色](#文本颜色) 82 | * [文本属性](#文本属性) 83 | * [移动光标](#移动光标) 84 | * [删除文本](#删除文本) 85 | * [参数拓展](#参数拓展) 86 | * [间接](#间接) 87 | * [替换](#替换) 88 | * [长度](#长度) 89 | * [扩展](#扩展) 90 | * [改变大小写](#改变大小写) 91 | * [默认值](#默认值) 92 | * [花括号展开](#花括号展开) 93 | * [范围](#范围) 94 | * [字符串列表](#字符串列表) 95 | * [条件表达式](#条件表达式) 96 | * [文件判断](#文件判断) 97 | * [文件比较](#文件比较) 98 | * [条件变量](#条件变量) 99 | * [比较变量](#比较变量) 100 | * [算术运算符](#算术运算符) 101 | * [指派](#指派) 102 | * [四则运算](#四则运算) 103 | * [位运算](#位运算) 104 | * [逻辑运算](#逻辑运算) 105 | * [复杂运算](#复杂运算) 106 | * [算术运算符-1](#算术运算符-1) 107 | * [用更简单语法设置变量](#用更简单语法设置变量) 108 | * [三元测试](#三元测试) 109 | * [trap命令](#trap命令) 110 | * [在脚本退出时执行操作](#在脚本退出时执行操作) 111 | * [忽略终端中断(CTRL+C,SIGINT)](#忽略终端中断) 112 | * [对窗口调整大小时做出反应](#对窗口调整大小时做出反应) 113 | * [在命令之前执行某些操作](#在命令之前执行某些操作) 114 | * [在shell函数或源文件完成执行时执行某些操作](#在shell函数或源文件完成执行时执行某些操作) 115 | * [性能](#性能) 116 | * [禁用Unicode码](#禁用Unicode码) 117 | * [已过时的语法](#已过时的语法) 118 | * [释伴声明](#释伴声明) 119 | * [命令替换](#命令替换) 120 | * [声明函数](#声明函数) 121 | * [内部变量](#内部变量) 122 | * [获取`bash`二进制文件的位置](#获取`bash`二进制文件的位置) 123 | * [获取当前运行`bash`命令的版本](#获取当前运行`bash`命令的版本) 124 | * [打开用户默认的文本编辑器](#打开用户默认的文本编辑器) 125 | * [获取当前函数的名称](#获取当前函数的名称) 126 | * [获取系统的主机名](#获取系统的主机名) 127 | * [获取操作系统的架构(32位或64位)](#获取操作系统的架构) 128 | * [获取操作系统/内核的名称](#获取操作系统/内核的名称) 129 | * [获取当前的工作目录](#获取当前的工作目录) 130 | * [获取脚本运行的秒数](#获取脚本运行的秒数) 131 | * [获取伪随机整数](#获取伪随机整数) 132 | * [有关终端的信息](#有关终端的信息) 133 | * [获取终端的总行列数(*来自脚本*)](#获取终端的总行列数) 134 | * [获取终端的像素大小](#获取终端的像素大小) 135 | * [获取当前光标位置](#获取当前光标位置) 136 | * [转换](#转换) 137 | * [将十六进制颜色转换为RGB](#将十六进制颜色转换为RGB) 138 | * [将RGB颜色转换为十六进制](#将RGB颜色转换为十六进制) 139 | * [代码高尔夫](#代码高尔夫) 140 | * [更短的`for`循环语法](#更短的`for`循环语法) 141 | * [更短的无限循环](#更短的无限循环) 142 | * [更短的函数声明](#更短的函数声明) 143 | * [更短的`if`语法](#更短的`if`语法) 144 | * [用`case`语句来更简单的设置变量](#用`case`语句来更简单的设置变量) 145 | * [其他](#其他) 146 | * [使用`read`作为`sleep`命令的替代品](#使用`read`作为`sleep`命令的替代品) 147 | * [检查一个命令是否在用户的PATH中](#检查一个命令是否在用户的PATH中) 148 | * [使用`strftime`获取当前日期](#使用`strftime`获取当前日期) 149 | * [获取当前用户的用户名](#获取当前用户的用户名) 150 | * [生成一个V4版本的UUID](#生成一个V4版本的UUID) 151 | * [进度条](#进度条) 152 | * [获取脚本中的函数列表](#获取脚本中的函数列表) 153 | * [绕过shell别名](#绕过shell别名) 154 | * [绕过shell函数](#绕过shell函数) 155 | * [在后台运行命令](#在后台运行命令) 156 | * [后记](#后记) 157 | 158 | 159 | 160 |
161 | 162 | 163 | # 前言 164 | 165 | 纯`bash`脚本替代外部流程和程序的集合。 `bash`脚本语言远比大部分人了解到的更强大,大多数任务都可以在不依赖外部程序的情况下由`bash`独立完成。 166 | 167 | 在`bash`中调用外部进程是昂贵的,过度使用会导致效率明显的下降。 使用内置方法编写的脚本和程序(*在适合的地方*)将更快,依赖性更小,并能对脚本本身有更好的理解。 168 | 169 | 本书的目的是为大家用`bash`编写程序和脚本过程中遇到问题时提供了一种思路。 示例展示了将这些解决方案合并到代码中的函数格式。 170 | 171 | 172 | 173 | 174 | # 字符串 175 | 176 | ## 删除字符串前后空格 177 | 178 | 这是`sed`,`awk`,`perl`和其他工具的替代品。下面的函数通过查找所有头尾空格并在字符串的开头和结尾删除它来实现这一功能。 179 | 180 | `:`内置用于代替临时变量。 181 | 182 | **示例函数:** 183 | 184 | ```sh 185 | trim_string() { 186 | # Usage: trim_string " example string " 187 | : "${1#"${1%%[![:space:]]*}"}" 188 | : "${_%"${_##*[![:space:]]}"}" 189 | printf '%s\n' "$_" 190 | } 191 | ``` 192 | 193 | **用法示例:** 194 | 195 | ```shell 196 | $ trim_string " Hello, World " 197 | Hello, World 198 | 199 | $ name=" John Black " 200 | $ trim_string "$name" 201 | John Black 202 | ``` 203 | 204 | 205 | ## 删除字符串中的所有的空白并用空格分割单词 206 | 207 | 这是`sed`,`awk`,`perl`和其他工具的替代品。 208 | 下面的函数通过重复使用单词拆分来创建一个没有前导/尾随空格的新字符串,并用空格分割字符串中的单词。 209 | 210 | **示例函数:** 211 | 212 | ```sh 213 | # shellcheck disable=SC2086,SC2048 214 | trim_all() { 215 | # Usage: trim_all " example string " 216 | set -f 217 | set -- $* 218 | printf '%s\n' "$*" 219 | set +f 220 | } 221 | ``` 222 | 223 | **用法示例:** 224 | 225 | ```shell 226 | $ trim_all " Hello, World " 227 | Hello, World 228 | 229 | $ name=" John Black is my name. " 230 | $ trim_all "$name" 231 | John Black is my name. 232 | ``` 233 | 234 | ## 在字符串上匹配正则表达式 235 | 236 | 对于使用`sed`的大部分情况,`bash`的正则表达式匹配结果完全可以替代。 237 | 238 | **警告**: 这是少数平台相关的“bash”功能之一。 239 | `bash`将使用用户系统上安装的任何正则表达式引擎。 240 | 如果要兼容,请坚持使用符合POSIX规范的正则表达式引擎。 241 | 绝大部分发行版Linux中的bash均实现了POSIX规范。 242 | 243 | **警告**: 此示例仅打印第一个匹配组。 使用时多个匹配组需要进行一些修改。 244 | 245 | **示例函数:** 246 | 247 | ```sh 248 | regex() { 249 | # Usage: regex "string" "regex" 250 | [[ $1 =~ $2 ]] && printf '%s\n' "${BASH_REMATCH[1]}" 251 | } 252 | ``` 253 | 254 | **用法示例:** 255 | 256 | ```shell 257 | $ # 删除开头的空白字符. 258 | $ regex ' hello' '^\s*(.*)' 259 | hello 260 | 261 | $ # 验证十六进制颜色. 262 | $ regex "#FFFFFF" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 263 | #FFFFFF 264 | 265 | $ # 验证十六进制颜色(无效). 266 | $ regex "red" '^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$' 267 | # no output (invalid) 268 | ``` 269 | 270 | **在脚本中的示例:** 271 | 272 | ```shell 273 | is_hex_color() { 274 | if [[ $1 =~ ^(#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3}))$ ]]; then 275 | printf '%s\n' "${BASH_REMATCH[1]}" 276 | else 277 | printf '%s\n' "error: $1 is an invalid color." 278 | return 1 279 | fi 280 | } 281 | 282 | read -r color 283 | is_hex_color "$color" || color="#FFFFFF" 284 | 285 | # Do stuff. 286 | ``` 287 | 288 | 289 | ## 指定分隔符拆分字符串 290 | 291 | **警告:** 需要`bash` 4+以上的版本 292 | 293 | 这是`cut`,`awk`和其他工具的替代品。 294 | 295 | **示例函数:** 296 | 297 | ```sh 298 | split() { 299 | # Usage: split "string" "delimiter" 300 | IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}" 301 | printf '%s\n' "${arr[@]}" 302 | } 303 | ``` 304 | 305 | **示例用法:** 306 | 307 | ```shell 308 | $ split "apples,oranges,pears,grapes" "," 309 | apples 310 | oranges 311 | pears 312 | grapes 313 | 314 | $ split "1, 2, 3, 4, 5" ", " 315 | 1 316 | 2 317 | 3 318 | 4 319 | 5 320 | 321 | # 多字符分隔符也可以工作! 322 | $ split "hello---world---my---name---is---john" "---" 323 | hello 324 | world 325 | my 326 | name 327 | is 328 | john 329 | ``` 330 | 331 | ## 将字符串转换为小写 332 | 333 | **警告:** 需要`bash` 4+以上的版本 334 | 335 | **示例函数:** 336 | 337 | ```sh 338 | lower() { 339 | # Usage: lower "string" 340 | printf '%s\n' "${1,,}" 341 | } 342 | ``` 343 | 344 | **示例用法:** 345 | 346 | ```shell 347 | $ lower "HELLO" 348 | hello 349 | 350 | $ lower "HeLlO" 351 | hello 352 | 353 | $ lower "hello" 354 | hello 355 | ``` 356 | 357 | ## 将字符串转换为大写 358 | 359 | **警告:** 需要`bash` 4+以上的版本 360 | 361 | **示例函数:** 362 | 363 | ```sh 364 | upper() { 365 | # Usage: upper "string" 366 | printf '%s\n' "${1^^}" 367 | } 368 | ``` 369 | 370 | **示例用法:** 371 | 372 | ```shell 373 | $ upper "hello" 374 | HELLO 375 | 376 | $ upper "HeLlO" 377 | HELLO 378 | 379 | $ upper "HELLO" 380 | HELLO 381 | ``` 382 | 383 | ## 反转字符串大小写 384 | 385 | **警告:** 需要`bash` 4+以上的版本 386 | 387 | **示例函数:** 388 | 389 | ```sh 390 | reverse_case() { 391 | # Usage: reverse_case "string" 392 | printf '%s\n' "${1~~}" 393 | } 394 | ``` 395 | 396 | **示例用法:** 397 | 398 | ```shell 399 | $ reverse_case "hello" 400 | HELLO 401 | 402 | $ reverse_case "HeLlO" 403 | hElLo 404 | 405 | $ reverse_case "HELLO" 406 | hello 407 | ``` 408 | 409 | ## 删除字符串中的引号 410 | 411 | **示例函数:** 412 | 413 | ```sh 414 | trim_quotes() { 415 | # Usage: trim_quotes "string" 416 | : "${1//\'}" 417 | printf '%s\n' "${_//\"}" 418 | } 419 | ``` 420 | 421 | **示例用法:** 422 | 423 | ```shell 424 | $ var="'Hello', \"World\"" 425 | $ trim_quotes "$var" 426 | Hello, World 427 | ``` 428 | 429 | ## 从字符串中删除所有正则实例 430 | 431 | **示例函数:** 432 | 433 | ```sh 434 | strip_all() { 435 | # Usage: strip_all "string" "pattern" 436 | printf '%s\n' "${1//$2}" 437 | } 438 | ``` 439 | 440 | **示例用法:** 441 | 442 | ```shell 443 | $ strip_all "The Quick Brown Fox" "[aeiou]" 444 | Th Qck Brwn Fx 445 | 446 | $ strip_all "The Quick Brown Fox" "[[:space:]]" 447 | TheQuickBrownFox 448 | 449 | $ strip_all "The Quick Brown Fox" "Quick " 450 | The Brown Fox 451 | ``` 452 | 453 | ## 从字符串中删除第一次出现的正则实例 454 | 455 | **示例函数:** 456 | 457 | ```sh 458 | strip() { 459 | # Usage: strip "string" "pattern" 460 | printf '%s\n' "${1/$2}" 461 | } 462 | ``` 463 | 464 | **示例用法:** 465 | 466 | ```shell 467 | $ strip "The Quick Brown Fox" "[aeiou]" 468 | Th Quick Brown Fox 469 | 470 | $ strip "The Quick Brown Fox" "[[:space:]]" 471 | TheQuick Brown Fox 472 | ``` 473 | 474 | ## 在字符串开头匹配正则并删除 475 | 476 | **示例函数:** 477 | 478 | ```sh 479 | lstrip() { 480 | # Usage: lstrip "string" "pattern" 481 | printf '%s\n' "${1##$2}" 482 | } 483 | ``` 484 | 485 | **示例用法:** 486 | 487 | ```shell 488 | $ lstrip "The Quick Brown Fox" "The " 489 | Quick Brown Fox 490 | ``` 491 | 492 | ## 在字符串末尾匹配正则并删除 493 | 494 | **示例函数:** 495 | 496 | ```sh 497 | rstrip() { 498 | # Usage: rstrip "string" "pattern" 499 | printf '%s\n' "${1%%$2}" 500 | } 501 | ``` 502 | 503 | **示例用法:** 504 | 505 | ```shell 506 | $ rstrip "The Quick Brown Fox" " Fox" 507 | The Quick Brown 508 | ``` 509 | 510 | ## 百分号编码字符串 511 | 512 | **示例函数:** 513 | 514 | ```sh 515 | urlencode() { 516 | # Usage: urlencode "string" 517 | local LC_ALL=C 518 | for (( i = 0; i < ${#1}; i++ )); do 519 | : "${1:i:1}" 520 | case "$_" in 521 | [a-zA-Z0-9.~_-]) 522 | printf '%s' "$_" 523 | ;; 524 | 525 | *) 526 | printf '%%%02X' "'$_" 527 | ;; 528 | esac 529 | done 530 | printf '\n' 531 | } 532 | ``` 533 | 534 | **示例用法:** 535 | 536 | ```shell 537 | $ urlencode "https://github.com/dylanaraps/pure-bash-bible" 538 | https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible 539 | ``` 540 | 541 | ## 解码用百分比编码的字符串 542 | 543 | **示例函数:** 544 | 545 | ```sh 546 | urldecode() { 547 | # Usage: urldecode "string" 548 | : "${1//+/ }" 549 | printf '%b\n' "${_//%/\\x}" 550 | } 551 | ``` 552 | 553 | **示例用法:** 554 | 555 | ```shell 556 | $ urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible" 557 | https://github.com/dylanaraps/pure-bash-bible 558 | ``` 559 | 560 | ## 检查字符串是否包含子字符串 561 | 562 | **用于测试:** 563 | 564 | ```shell 565 | if [[ $var == *sub_string* ]]; then 566 | printf '%s\n' "sub_string is in var." 567 | fi 568 | 569 | # 反转 (子串不在字符串中). 570 | if [[ $var != *sub_string* ]]; then 571 | printf '%s\n' "sub_string is not in var." 572 | fi 573 | 574 | # 也可以在数组中运行 575 | if [[ ${arr[*]} == *sub_string* ]]; then 576 | printf '%s\n' "sub_string is in array." 577 | fi 578 | ``` 579 | 580 | **使用case语句:** 581 | 582 | ```shell 583 | case "$var" in 584 | *sub_string*) 585 | # Do stuff 586 | ;; 587 | 588 | *sub_string2*) 589 | # Do more stuff 590 | ;; 591 | 592 | *) 593 | # Else 594 | ;; 595 | esac 596 | ``` 597 | 598 | ## 检查字符串是否以子字符串开头 599 | 600 | ```shell 601 | if [[ $var == sub_string* ]]; then 602 | printf '%s\n' "var starts with sub_string." 603 | fi 604 | 605 | # 反转 (变量不是以子串开头). 606 | if [[ $var != sub_string* ]]; then 607 | printf '%s\n' "var does not start with sub_string." 608 | fi 609 | ``` 610 | 611 | ## 检查字符串是否以子字符串结尾 612 | 613 | ```shell 614 | if [[ $var == *sub_string ]]; then 615 | printf '%s\n' "var ends with sub_string." 616 | fi 617 | 618 | # Inverse (var does not end with sub_string). 619 | if [[ $var != *sub_string ]]; then 620 | printf '%s\n' "var does not end with sub_string." 621 | fi 622 | ``` 623 | 624 | 625 | 626 | 627 | # 数组 628 | 629 | ## 反转数组 630 | 631 | 启用`extdebug`允许访问`BASH_ARGV`数组,该数组反向存储当前函数的参数 632 | 633 | **示例函数:** 634 | 635 | ```sh 636 | reverse_array() { 637 | # Usage: reverse_array "array" 638 | shopt -s extdebug 639 | f()(printf '%s\n' "${BASH_ARGV[@]}"); f "$@" 640 | shopt -u extdebug 641 | } 642 | ``` 643 | 644 | **示例用法:** 645 | 646 | ```shell 647 | $ reverse_array 1 2 3 4 5 648 | 5 649 | 4 650 | 3 651 | 2 652 | 1 653 | 654 | $ arr=(red blue green) 655 | $ reverse_array "${arr[@]}" 656 | green 657 | blue 658 | red 659 | ``` 660 | 661 | ## 删除重复的数组元素 662 | 663 | 创建临时关联数组。设置关联数组值并发生重复赋值时,bash会覆盖该键。这允许我们有效地删除数组重复。 664 | 665 | **警告:** 版本要求 `bash` 4+ 666 | 667 | **示例函数:** 668 | 669 | ```sh 670 | remove_array_dups() { 671 | # Usage: remove_array_dups "array" 672 | declare -A tmp_array 673 | 674 | for i in "$@"; do 675 | [[ $i ]] && IFS=" " tmp_array["${i:- }"]=1 676 | done 677 | 678 | printf '%s\n' "${!tmp_array[@]}" 679 | } 680 | ``` 681 | 682 | **示例用法:** 683 | 684 | ```shell 685 | $ remove_array_dups 1 1 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 686 | 1 687 | 2 688 | 3 689 | 4 690 | 5 691 | 692 | $ arr=(red red green blue blue) 693 | $ remove_array_dups "${arr[@]}" 694 | red 695 | green 696 | blue 697 | ``` 698 | 699 | ## 随机返回一个数组元素 700 | 701 | **示例函数:** 702 | 703 | ```sh 704 | random_array_element() { 705 | # Usage: random_array_element "array" 706 | local arr=("$@") 707 | printf '%s\n' "${arr[RANDOM % $#]}" 708 | } 709 | ``` 710 | bash的SHELL参数RANDOM可以生成0-32767的随机数 711 | 想设定从1到N的随机数范围的话,可以使用: 712 | $ ( ( (RANDOM % n) + 1 )) 713 | 714 | **示例用法:** 715 | 716 | ```shell 717 | $ array=(red green blue yellow brown) 718 | $ random_array_element "${array[@]}" 719 | yellow 720 | 721 | # Multiple arguments can also be passed. 722 | $ random_array_element 1 2 3 4 5 6 7 723 | 3 724 | ``` 725 | 726 | ## 循环迭代一个数组 727 | 728 | 每次`printf`调用时,都会打印下一个数组元素。当打印到达最后一个数组元素时,它再次从第一个元素开始。 729 | 730 | ```sh 731 | arr=(a b c d) 732 | 733 | cycle() { 734 | printf '%s ' "${arr[${i:=0}]}" 735 | ((i=i>=${#arr[@]}-1?0:++i)) 736 | } 737 | ``` 738 | 739 | 740 | ## 在两个值之间转换 741 | 742 | 这与上面的工作方式相同,这只是一个不同的用例。 743 | 744 | 745 | ```sh 746 | arr=(true false) 747 | 748 | cycle() { 749 | printf '%s ' "${arr[${i:=0}]}" 750 | ((i=i>=${#arr[@]}-1?0:++i)) 751 | } 752 | ``` 753 | 754 | 755 | 756 | 757 | # 循环 758 | 759 | ## 循环生成范围内的数字 760 | 761 | 替代`seq`. 762 | 763 | ```shell 764 | # Loop from 0-100 (no variable support). 765 | for i in {0..100}; do 766 | printf '%s\n' "$i" 767 | done 768 | ``` 769 | 770 | ## 循环遍历可变数字范围 771 | 772 | 替代 `seq`. 773 | 774 | ```shell 775 | # Loop from 0-VAR. 776 | VAR=50 777 | for ((i=0;i<=VAR;i++)); do 778 | printf '%s\n' "$i" 779 | done 780 | ``` 781 | 782 | ## 循环数组 783 | 784 | ```shell 785 | arr=(apples oranges tomatoes) 786 | 787 | # Just elements. 788 | for element in "${arr[@]}"; do 789 | printf '%s\n' "$element" 790 | done 791 | ``` 792 | 793 | ## 循环输出带索引的数组 794 | 795 | ```shell 796 | arr=(apples oranges tomatoes) 797 | 798 | # 元素和索引. 799 | for i in "${!arr[@]}"; do 800 | printf '%s %s\n' "$i ${arr[i]}" 801 | done 802 | 803 | # 替代方法. 804 | for ((i=0;i<${#arr[@]};i++)); do 805 | printf '%s %s\n' "$i ${arr[i]}" 806 | done 807 | ``` 808 | 809 | ## 循环遍历文件的内容 810 | 811 | ```shell 812 | while read -r line; do 813 | printf '%s\n' "$line" 814 | done < "file" 815 | ``` 816 | 817 | ## 循环遍历文件和目录 818 | 819 | 820 | 不要 `ls`. 821 | 822 | ```shell 823 | # 遍历当前目录下的文件和目录. 824 | for file in *; do 825 | printf '%s\n' "$file" 826 | done 827 | 828 | # 遍历目录中的png图片. 829 | for file in ~/Pictures/*.png; do 830 | printf '%s\n' "$file" 831 | done 832 | 833 | # 迭代输出目录. 834 | for dir in ~/Downloads/*/; do 835 | printf '%s\n' "$dir" 836 | done 837 | 838 | # 支持扩展. 839 | for file in /path/to/parentdir/{file1,file2,subdir/file3}; do 840 | printf '%s\n' "$file" 841 | done 842 | 843 | # 递归迭代,输出子目录下的所有文件. 844 | # 845 | shopt -s globstar 846 | for file in ~/Pictures/**/*; do 847 | printf '%s\n' "$file" 848 | done 849 | shopt -u globstar 850 | ``` 851 | globstar是Bash 4.0才引入的选项,当设置启用globstar(shopt -s globstar)时,两个星号意为对通配符进行展开就可以匹配任何当前目录(包括子目录)以及其的文件;若不启用globstar(shopt -u globstar),两个星号通配符的作用和一个星号通配符是相同的。 852 | 853 | 854 | 855 | # 文本处理 856 | 857 | **警告:** `bash`在小于`<4.4`的版本中不能正确处理二进制数据. 858 | 859 | ## 读取文件到一个字符串中 860 | 861 | 替代 `cat` 命令. 862 | 863 | ```shell 864 | file_data="$(<"file")" 865 | ``` 866 | 867 | ## 读取文件到一个数组中 (*按行读取*) 868 | 869 | 替代 `cat` 命令. 870 | 871 | ```shell 872 | # Bash <4 (丢弃空行) 873 | IFS=$'\n' read -d "" -ra file_data < "file" 874 | 875 | # Bash <4 (保留空行). 876 | while read -r line; do 877 | file_data+=("$line") 878 | done < "file" 879 | 880 | # Bash 4+ 881 | mapfile -t file_data < "file" 882 | ``` 883 | 884 | ## 获取文件的前N行 885 | 886 | 替代 `head` 命令. 887 | 888 | **警告:** 版本要求 `bash` 4+ 889 | 890 | **示例函数:** 891 | 892 | ```sh 893 | head() { 894 | # Usage: head "n" "file" 895 | mapfile -tn "$1" line < "$2" 896 | printf '%s\n' "${line[@]}" 897 | } 898 | ``` 899 | 900 | **示例用法:** 901 | 902 | ```shell 903 | $ head 2 ~/.bashrc 904 | # Prompt 905 | PS1='➜ ' 906 | 907 | $ head 1 ~/.bashrc 908 | # Prompt 909 | ``` 910 | 911 | ## 获取文件的最后N行 912 | 913 | 替代 `tail` 命令. 914 | 915 | **警告:** 版本要求 `bash` 4+ 916 | 917 | **示例函数:** 918 | 919 | ```sh 920 | tail() { 921 | # Usage: tail "n" "file" 922 | mapfile -tn 0 line < "$2" 923 | printf '%s\n' "${line[@]: -$1}" 924 | } 925 | ``` 926 | 927 | **示例用法:** 928 | 929 | ```shell 930 | $ tail 2 ~/.bashrc 931 | # Enable tmux. 932 | # [[ -z "$TMUX" ]] && exec tmux 933 | 934 | $ tail 1 ~/.bashrc 935 | # [[ -z "$TMUX" ]] && exec tmux 936 | ``` 937 | 938 | ## 获取文件中的行数 939 | 940 | 941 | 替代 `wc -l`. 942 | 943 | **示例函数 (bash 4):** 944 | 945 | ```sh 946 | lines() { 947 | # Usage: lines "file" 948 | mapfile -tn 0 lines < "$1" 949 | printf '%s\n' "${#lines[@]}" 950 | } 951 | ``` 952 | 953 | **示例函数 (bash 3):** 954 | 955 | 这个方法比`mapfile`方法使用更少的内存,并且在`bash` 3中工作,但对于更大的文件来说它更慢。 956 | 957 | ```sh 958 | lines_loop() { 959 | # Usage: lines_loop "file" 960 | count=0 961 | while IFS= read -r _; do 962 | ((count++)) 963 | done < "$1" 964 | printf '%s\n' "$count" 965 | } 966 | ``` 967 | 968 | **示例用法:** 969 | 970 | ```shell 971 | $ lines ~/.bashrc 972 | 48 973 | 974 | $ lines_loop ~/.bashrc 975 | 48 976 | ``` 977 | 978 | ## 计算目录中的文件或目录 979 | 980 | 981 | 这是通过将glob的输出传递给函数然后计算参数的数量来实现的。 982 | 983 | 984 | 985 | **示例函数:** 986 | 987 | ```sh 988 | count() { 989 | # Usage: count /path/to/dir/* 990 | # count /path/to/dir/*/ 991 | printf '%s\n' "$#" 992 | } 993 | ``` 994 | 995 | **示例用法:** 996 | 997 | ```shell 998 | # Count all files in dir. 999 | $ count ~/Downloads/* 1000 | 232 1001 | 1002 | # Count all dirs in dir. 1003 | $ count ~/Downloads/*/ 1004 | 45 1005 | 1006 | # Count all jpg files in dir. 1007 | $ count ~/Pictures/*.jpg 1008 | 64 1009 | ``` 1010 | 1011 | ## 创建一个空文件 1012 | 1013 | 替代 `touch`. 1014 | 1015 | ```shell 1016 | # 简短的方式. 1017 | >file 1018 | 1019 | # 更长的替代品: 1020 | :>file 1021 | echo -n >file 1022 | printf '' >file 1023 | ``` 1024 | 1025 | ## 提取两个标记之间的行 1026 | 1027 | **示例函数:** 1028 | 1029 | ```sh 1030 | extract() { 1031 | # 用法: extract file "opening marker" "closing marker" 1032 | while IFS=$'\n' read -r line; do 1033 | [[ $extract && $line != "$3" ]] && 1034 | printf '%s\n' "$line" 1035 | 1036 | [[ $line == "$2" ]] && extract=1 1037 | [[ $line == "$3" ]] && extract= 1038 | done < "$1" 1039 | } 1040 | ``` 1041 | 1042 | **示例用法:** 1043 | 1044 | ```shell 1045 | # 从MarkDown文件中提取代码块. 1046 | $ extract ~/projects/pure-bash/README.md '```sh' '```' 1047 | # Output here... 1048 | ``` 1049 | 1050 | 1051 | 1052 | 1053 | # 文件路径 1054 | 1055 | ## 获取文件路径的目录名 1056 | 1057 | 替代 `dirname` 命令. 1058 | 1059 | **示例函数:** 1060 | 1061 | ```sh 1062 | dirname() { 1063 | # 用法: dirname "path" 1064 | local tmp=${1:-.} 1065 | 1066 | [[ $tmp != *[!/]* ]] && { 1067 | printf '/\n' 1068 | return 1069 | } 1070 | 1071 | tmp=${tmp%%"${tmp##*[!/]}"} 1072 | 1073 | [[ $tmp != */* ]] && { 1074 | printf '.\n' 1075 | return 1076 | } 1077 | 1078 | tmp=${tmp%/*} 1079 | tmp=${tmp%%"${tmp##*[!/]}"} 1080 | 1081 | printf '%s\n' "${tmp:-/}" 1082 | } 1083 | ``` 1084 | 1085 | **示例用法:** 1086 | 1087 | ```shell 1088 | $ dirname ~/Pictures/Wallpapers/1.jpg 1089 | /home/black/Pictures/Wallpapers 1090 | 1091 | $ dirname ~/Pictures/Downloads/ 1092 | /home/black/Pictures 1093 | ``` 1094 | 1095 | ## 获取文件路径的基本名称 1096 | 1097 | 1098 | 替代 `basename` 命令. 1099 | 1100 | **示例函数:** 1101 | 1102 | ```sh 1103 | basename() { 1104 | # 用法: basename "path" ["后缀"] 1105 | local tmp 1106 | 1107 | tmp=${1%"${1##*[!/]}"} 1108 | tmp=${tmp##*/} 1109 | tmp=${tmp%"${2/"$tmp"}"} 1110 | 1111 | printf '%s\n' "${tmp:-/}" 1112 | } 1113 | ``` 1114 | 1115 | **示例用法:** 1116 | 1117 | ```shell 1118 | $ basename ~/Pictures/Wallpapers/1.jpg 1119 | 1.jpg 1120 | 1121 | $ basename ~/Pictures/Wallpapers/1.jpg .jpg 1122 | 1 1123 | 1124 | $ basename ~/Pictures/Downloads/ 1125 | Downloads 1126 | ``` 1127 | 1128 | 1129 | 1130 | 1131 | # 变量 1132 | 1133 | ## 分配和访问一个变量 1134 | 1135 | ```shell 1136 | $ hello_world="value" 1137 | 1138 | # 创建一个变量名. 1139 | $ var="world" 1140 | $ ref="hello_$var" 1141 | 1142 | # 打印存储为 'hello_$var' 变量名称的值. 1143 | $ printf '%s\n' "${!ref}" 1144 | value 1145 | ``` 1146 | 1147 | 或者, 在 `bash` 4.3+以上版本: 1148 | 1149 | ```shell 1150 | $ hello_world="value" 1151 | $ var="world" 1152 | 1153 | # 声明一个名称引用. 1154 | $ declare -n ref=hello_$var 1155 | 1156 | $ printf '%s\n' "$ref" 1157 | value 1158 | ``` 1159 | 1160 | ## 根据另一个变量命名变量 1161 | 1162 | ```shell 1163 | $ var="world" 1164 | $ declare "hello_$var=value" 1165 | $ printf '%s\n' "$hello_world" 1166 | value 1167 | ``` 1168 | 1169 | 1170 | 1171 | 1172 | # 转义字符 1173 | 1174 | 与流行的看法相反, 使用原始的转义字符并不会出现问题. 使用`tput`抽象相同的ANSI序列,就像手动打印一样. 更糟糕的是,`tput`实际上并不是便携式的. 有许多`tput`变体,每个变体都有不同的命令和语法 (*尝试运行 `tput setaf 3` 在 FreeBSD 系统里*). 1175 | 1176 | ## 文本颜色 1177 | 1178 | **NOTE:** 需要RGB值的序列仅适用于真彩色终端仿真器. 1179 | 1180 | | 序列 | 它将做什么? | 值 | 1181 | | -------- | ---------------- | ----- | 1182 | | `\e[38;5;m` | 设置文本前景色. | `0-255` 1183 | | `\e[48;5;m` | 设置文本背景颜色. | `0-255` 1184 | | `\e[38;2;;;m` | 将文本前景色设置为RGB颜色. | `R`, `G`, `B` 1185 | | `\e[48;2;;;m` | 将文本背景颜色设置为RGB颜色. | `R`, `G`, `B` 1186 | 1187 | ## 文本属性 1188 | 1189 | | 序列 | 它将做什么? | 1190 | | -------- | ---------------- | 1191 | | `\e[m` | 重置文本格式和颜色. 1192 | | `\e[1m` | 粗体. | 1193 | | `\e[2m` | 微弱的文字. | 1194 | | `\e[3m` | 斜体文字. | 1195 | | `\e[4m` | 下划线文字. | 1196 | | `\e[5m` | 慢速闪烁. | 1197 | | `\e[7m` | 交换前景色和背景色. | 1198 | 1199 | 1200 | ## 移动光标 1201 | 1202 | | 序列 | 它将做什么? | 值 | 1203 | | -------- | ---------------- | ----- | 1204 | | `\e[;H` | 将光标移动到绝对位置. | `line`, `column` 1205 | | `\e[H` | 将光标移动到原位 (`0,0`). | 1206 | | `\e[A` | 将光标向上移动N行. | `num` 1207 | | `\e[B` | 将光标向下移动N行. | `num` 1208 | | `\e[C` | 将光标向右移动N列. | `num` 1209 | | `\e[D` | 将光标向左移动N列. | `num` 1210 | | `\e[s` | 保存光标位置. | 1211 | | `\e[u` | 恢复光标位置. | 1212 | 1213 | 1214 | ## 删除文本 1215 | 1216 | | 序列 | 它将做什么? | 1217 | | -------- | ---------------- | 1218 | | `\e[K` | 从光标位置删除到行尾. 1219 | | `\e[1K` | 从光标位置删除到行首. 1220 | | `\e[2K` | 擦除整个当前行. 1221 | | `\e[J` | 从当前行删除到屏幕底部. 1222 | | `\e[1J` | 从当前行删除到屏幕顶部. 1223 | | `\e[2J` | 清除屏幕. 1224 | | `\e[2J\e[H` | 清除屏幕并将光标移动到 `0,0`. 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | # 参数拓展 1231 | 1232 | ## 间接 1233 | 1234 | | 参数 | 它将做什么? | 1235 | | --------- | ---------------- | 1236 | | `${!VAR}` | 根据`VAR`的值访问一个变量. 1237 | | `${!VAR*}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. | 1238 | | `${!VAR@}` | 扩展为以`VAR`开头的变量名列表,并用`IFS`分隔. 如果是双引号,则每个变量名称都会扩展为单独的单词. | 1239 | 1240 | 1241 | ## 替换 1242 | 1243 | | 参数 | 它将做什么? | 1244 | | --------- | ---------------- | 1245 | | `${VAR#PATTERN}` | 删除第一次匹配的模式及其左边的字符. | 1246 | | `${VAR##PATTERN}` | 删除最后一次匹配的模式及其左边的字符. | 1247 | | `${VAR%PATTERN}` | 删除最后一次匹配的模式及其右边的字符. | 1248 | | `${VAR%%PATTERN}` | 删除第一次匹配的模式及其右边的字符. | 1249 | | `${VAR/PATTERN/REPLACE}` | 替换第一次匹配的字符. 1250 | | `${VAR//PATTERN/REPLACE}` | 替换所有匹配的字符. 1251 | | `${VAR/PATTERN}` | 删除第一次匹配的字符. 1252 | | `${VAR//PATTERN}` | 删除所有匹配的字符. 1253 | 1254 | ## 长度 1255 | 1256 | | 参数 | 它将做什么? | 1257 | | --------- | ---------------- | 1258 | | `${#VAR}` | 字符变量的长度. 1259 | | `${#ARR[@]}` | 数组的长度. 1260 | 1261 | ## 扩展 1262 | 1263 | | 参数 | 它将做什么? | 版本要求 | 1264 | | --------- | ---------------- | 1265 | | `${VAR:OFFSET}` | 从变量中删除第OFFSET个字符及之前的字符. 1266 | | `${VAR:OFFSET:LENGTH}` | 获得从`OFFSET`字符之后`LENGTH`个字符的字符串.
(`${VAR:10:10}`: 获得从第10个字符到第20个字符的字符串) 1267 | | `${VAR:: OFFSET}` | 从变量中获取前`OFFSET`个字符. 1268 | | `${VAR:: -OFFSET}` | 从变量中移除前`OFFSET`个字符. 1269 | | `${VAR: -OFFSET}` | 从变量中获取最后`OFFSET`个字符. 1270 | | `${VAR:OFFSET:-OFFSET}` | 删除前`OFFSET`个字符以及最后`OFFSET`个字符. | `bash 4.2+` | 1271 | 1272 | ## 改变大小写 1273 | 1274 | | 参数 | 它将做什么? | 版本要求 | 1275 | | --------- | ---------------- | ------ | 1276 | | `${VAR^}` | 大写第一个字符. | `bash 4+` | 1277 | | `${VAR^^}` | 大写所有字符. | `bash 4+` | 1278 | | `${VAR,}` | 小写第一个字符. | `bash 4+` | 1279 | | `${VAR,,}` | 小写所有字符. | `bash 4+` | 1280 | | `${VAR~}` | 反转第一个字符. | `bash 4+` | 1281 | | `${VAR~~}` | 反转所有字符. | `bash 4+` | 1282 | 1283 | 1284 | ## 默认值 1285 | 1286 | | 参数 | 它将做什么? | 1287 | | --------- | ---------------- | 1288 | | `${VAR:-STRING}` | 如果 `VAR` 为空或未设置,使用 `STRING` 作为它的值. 1289 | | `${VAR-STRING}` | 如果 `VAR` 未设置, 使用 `STRING` 作为它的值. 1290 | | `${VAR:=STRING}` | 如果 `VAR` 为空或未设置, 设置 `VAR` 的值为 `STRING`. 1291 | | `${VAR=STRING}` | 如果 `VAR` 未设置, 设置 `VAR` 的值为 `STRING`. 1292 | | `${VAR:+STRING}` | 如果 `VAR` 不为空, 使用 `STRING` 作为它的值. 1293 | | `${VAR+STRING}` | 如果 `VAR` 已设置, 使用 `STRING` 作为它的值. 1294 | | `${VAR:?STRING}` | 如果为空或未设置,则显示一个错误. 1295 | | `${VAR?STRING}` | 如果未设置则显示一个错误. 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | # 花括号展开 1302 | 1303 | ## 范围 1304 | 1305 | ```shell 1306 | # 符号: {..} 1307 | 1308 | # 打印 1-100 之间的数字. 1309 | echo {1..100} 1310 | 1311 | # 打印一个范围内的浮点数. 1312 | echo 1.{1..9} 1313 | 1314 | # 打印a-z间的字符. 1315 | echo {a..z} 1316 | echo {A..Z} 1317 | 1318 | # 嵌套. 1319 | echo {A..Z}{0..9} 1320 | 1321 | # 打印用零填充的数字. 1322 | # 警告: bash 4+ 1323 | echo {01..100} 1324 | 1325 | # 更改步长. 1326 | # 符号: {....} 1327 | # 警告: bash 4+ 1328 | echo {1..10..2} # 每次增加2. 1329 | ``` 1330 | 1331 | ## 字符串列表 1332 | 1333 | ```shell 1334 | echo {apples,oranges,pears,grapes} 1335 | 1336 | # 示例用法: 1337 | # 删除~/Downloads/目录下的 Movies, Music 和 ISOS 文件夹 . 1338 | rm -rf ~/Downloads/{Movies,Music,ISOS} 1339 | ``` 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | # 条件表达式 1347 | 1348 | ## 文件判断 1349 | 1350 | | 表达式 | 值 | 它有什么作用? | 1351 | | ---------- | ------ | ---------------- | 1352 | | `-a` | `file` | 文件存在 1353 | | `-b` | `file` | 文件存在并且是块特殊文件. 1354 | | `-c` | `file` | 文件存在并且是字符特殊文件. 1355 | | `-d` | `file` | 文件存在且是目录. 1356 | | `-e` | `file` | 文件存在. 1357 | | `-f` | `file` | 文件存在且是常规文件. 1358 | | `-g` | `file` | 文件存在且其set-group-id位已设置. 1359 | | `-h` | `file` | 文件存在并且是符号链接. 1360 | | `-k` | `file` | 文件存在且其sticky-bit已设置 1361 | | `-p` | `file` | 文件存在并且是命名管道 (*FIFO*). 1362 | | `-r` | `file` | 文件存在且可读. 1363 | | `-s` | `file` | 文件存在且其大小大于零. 1364 | | `-t` | `fd` | 文件描述符是打开的并且引用到一个终端. 1365 | | `-u` | `file` | 文件存在且其set-user-id位已设置. 1366 | | `-w` | `file` | 文件存在且可写. 1367 | | `-x` | `file` | 文件存在且可执行. 1368 | | `-G` | `file` | 文件存在且拥有者是一个有效组ID. 1369 | | `-L` | `file` | 文件存在并且是符号链接. 1370 | | `-N` | `file` | 文件存在且自上次读取后已被修改. 1371 | | `-O` | `file` | 文件存在并且拥有者是一个有效用户ID. 1372 | | `-S` | `file` | 文件存在且是套接字. 1373 | 1374 | ## 文件比较 1375 | 1376 | | 表达式 | 它有什么作用? | 1377 | | ---------- | ---------------- | 1378 | | `file -ef file2` | 是否两个文件都引用相同的inode和设备编号. 1379 | | `file -nt file2` | 是否 `file` 比 `file2`更新 (*使用修改时间*) 或者 `file` 存在而 `file2` 不存在. 1380 | | `file -ot file2` | 是否 `file` 比 `file2`更老 (*使用修改时间*) 或者 `file2` 存在而 `file` 不存在. 1381 | 1382 | ## 条件变量 1383 | 1384 | | 表达式 | 值 | 它有什么作用? | 1385 | | ---------- | ----- | ---------------- | 1386 | | `-o` | `opt` | 是否启用了shell选项. 1387 | | `-v` | `var` | 是否变量具有指定的值. 1388 | | `-R` | `var` | 是否变量是一个名称引用. 1389 | | `-z` | `var` | 是否字符串的长度为零. 1390 | | `-n` | `var` | 是否字符串的长度不为零. 1391 | 1392 | ## 比较变量 1393 | 1394 | | 表达式 | 它有什么作用? | 1395 | | ---------- | ---------------- | 1396 | | `var = var2` | 等于. 1397 | | `var == var2` | 等于 (*同义词 `=`*). 1398 | | `var != var2` | 不等于. 1399 | | `var < var2` | 小于 (*以ASCII字母顺序排列.*) 1400 | | `var > var2` | 大于 (*以ASCII字母顺序排列.*) 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | # 算术运算符 1407 | 1408 | ## 指派 1409 | 1410 | | 操作符 | 它有什么作用? | 1411 | | --------- | ---------------- | 1412 | | `=` | 初始化或更改变量的值。 1413 | 1414 | ## 四则运算 1415 | 1416 | | 操作符 | 它有什么作用? | 1417 | | --------- | ---------------- | 1418 | | `+` | 加 1419 | | `-` | 减 1420 | | `*` | 乘 1421 | | `/` | 除 1422 | | `**` | 幂运算 1423 | | `%` | 求模 1424 | | `+=` | 先加后赋值 (*`x += y`等同于`x = x + y`*) 1425 | | `-=` | 先减后赋值 (*`x -= y`等同于`x = x - y`*) 1426 | | `*=` | 先乘后赋值 (*`x *= y`等同于`x = x * y`*) 1427 | | `/=` | 先除后赋值 (*`x /= y`等同于`x = x / y`*) 1428 | | `%=` | 先取模后赋值 (*`x %= y`等同于`x = x % y`*) 1429 | 1430 | ## 位运算 1431 | 1432 | | 操作符 | 它有什么作用? | 1433 | | --------- | ---------------- | 1434 | | `<<` | 按位左移 1435 | | `<<=` | 按位左移后赋值 1436 | | `>>` | 按位右移 1437 | | `>>=` | 按位右移后赋值 1438 | | `&` | 按位与操作 1439 | | `&=` | 按位与操作后赋值 1440 | | `\|` | 按位或操作 1441 | | `\|=` | 按位或操作赋值 1442 | | `~` | 按位非操作 1443 | | `^` | 按位异或操作 1444 | | `^=` | 按位异或操作后赋值 1445 | 1446 | ## 逻辑运算 1447 | 1448 | | 操作符 | 它有什么作用? | 1449 | | --------- | ---------------- | 1450 | | `!` | NOT 1451 | | `&&` | AND 1452 | | `\|\|` | OR 1453 | 1454 | ## 复杂运算 1455 | 1456 | | 操作符 | 它有什么作用? | 例子 | 1457 | | --------- | ---------------- | ------- | 1458 | | `,` | 逗号分隔符 | `((a=1,b=2,c=3))` 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | # 算术运算符-1 1465 | 1466 | ## 用更简单语法设置变量 1467 | 1468 | ```shell 1469 | # 简单的数学运算 1470 | ((var=1+2)) 1471 | 1472 | # 递减/递增变量 1473 | ((var++)) 1474 | ((var--)) 1475 | ((var+=1)) 1476 | ((var-=1)) 1477 | 1478 | # 使用变量 1479 | ((var=var2*arr[2])) 1480 | ``` 1481 | 1482 | ## 三元测试 1483 | 1484 | ```shell 1485 | # 如果var2大于var,则将var的值设置为var2,否则设置为var. 1486 | # var: 要设置的变量. 1487 | # var2>var: 条件测试. 1488 | # ?var2: 如果条件测试成功时生效. 1489 | # :var: 条件测试失败时生效. 1490 | ((var=var2>var?var2:var)) 1491 | ``` 1492 | 1493 | 1494 | 1495 | 1496 | # trap命令 1497 | 1498 | Traps允许脚本在各种信号上执行代码。在 [pxltrm](https://github.com/dylanaraps/pxltrm) (*用bash编写的像素艺术编辑器*) traps 用于在窗口大小调整时重绘用户界面。另一个用例是在脚本退出时清理临时文件。 1499 | 1500 | Traps应该在脚本开头附近添加,以便捕获任何早期错误. 1501 | 1502 | **NOTE:** 有关信号的完整列表,请参阅 `trap -l`. 1503 | 1504 | 1505 | ## 在脚本退出时执行操作 1506 | 1507 | ```shell 1508 | # 脚本退出时清除屏幕。 1509 | trap 'printf \\e[2J\\e[H\\e[m' EXIT 1510 | ``` 1511 | 1512 | ## 忽略终端中断(CTRL+C,SIGINT) 1513 | 1514 | ```shell 1515 | trap '' INT 1516 | ``` 1517 | 1518 | ## 对窗口调整大小时做出反应 1519 | 1520 | ```shell 1521 | # 在窗口调整大小时调用函数. 1522 | trap 'code_here' SIGWINCH 1523 | ``` 1524 | 1525 | ## 在命令之前执行某些操作 1526 | 1527 | ```shell 1528 | trap 'code_here' DEBUG 1529 | ``` 1530 | 1531 | ## 在shell函数或源文件完成执行时执行某些操作 1532 | 1533 | ```shell 1534 | trap 'code_here' RETURN 1535 | ``` 1536 | 1537 | 1538 | 1539 | 1540 | # 性能 1541 | 1542 | ## 禁用Unicode码 1543 | 1544 | 如果不需要unicode,则可以禁用它以提高性能。结果可能会有所不同,但是[neofetch](https://github.com/dylanaraps/neofetch) 和其他程序有明显改善。 1545 | 1546 | ```shell 1547 | # 禁用 unicode. 1548 | LC_ALL=C 1549 | LANG=C 1550 | ``` 1551 | 1552 | 1553 | 1554 | 1555 | # 已过时的语法 1556 | 1557 | ## 释伴声明 1558 | 1559 | 使用 `#!/usr/bin/env bash` 而不是 `#!/bin/bash`. 1560 | 1561 | - 前者搜索用户的 `PATH` 以查找 `bash` 二进制文件. 1562 | - 后者假设它始终安装在 `/bin/` 目录,可能导致问题. 1563 | 1564 | ```shell 1565 | # 正确的方式: 1566 | 1567 | #!/usr/bin/env bash 1568 | 1569 | # 错误的方式: 1570 | 1571 | #!/bin/bash 1572 | ``` 1573 | 1574 | ## 命令替换 1575 | 1576 | 使用 `$()`而不是 `` ` ` ``. 1577 | 1578 | ```shell 1579 | # 正确的方式. 1580 | var="$(command)" 1581 | 1582 | # 错误的方式. 1583 | var=`command` 1584 | 1585 | # $() 很容易嵌套,而``不能. 1586 | var="$(command "$(command)")" 1587 | ``` 1588 | 1589 | ## 声明函数 1590 | 1591 | 不要使用`function`关键字,它会降低与旧版本`bash`的兼容性. 1592 | 1593 | ```shell 1594 | # 正确的方式. 1595 | do_something() { 1596 | # ... 1597 | } 1598 | 1599 | # 错误的方式. 1600 | function do_something() { 1601 | # ... 1602 | } 1603 | ``` 1604 | 1605 | 1606 | 1607 | 1608 | # 内部变量 1609 | 1610 | ## 获取`bash`二进制文件的位置 1611 | 1612 | ```shell 1613 | "$BASH" 1614 | ``` 1615 | 1616 | ## 获取当前运行`bash`命令的版本 1617 | 1618 | ```shell 1619 | # 作为字符串. 1620 | "$BASH_VERSION" 1621 | 1622 | # 作为数组. 1623 | "${BASH_VERSINFO[@]}" 1624 | ``` 1625 | 1626 | ## 打开用户默认的文本编辑器 1627 | 1628 | ```shell 1629 | "$EDITOR" "$file" 1630 | 1631 | # NOTE: 这个变量可能是空的,设置一个失败调用值. 1632 | "${EDITOR:-vi}" "$file" 1633 | ``` 1634 | 1635 | ## 获取当前函数的名称 1636 | 1637 | ```shell 1638 | # 当前函数. 1639 | "${FUNCNAME[0]}" 1640 | 1641 | # 父函数. 1642 | "${FUNCNAME[1]}" 1643 | 1644 | # 等等. 1645 | "${FUNCNAME[2]}" 1646 | "${FUNCNAME[3]}" 1647 | 1648 | # 包括父类的所有函数 1649 | "${FUNCNAME[@]}" 1650 | ``` 1651 | 1652 | ## 获取系统的主机名 1653 | 1654 | ```shell 1655 | "$HOSTNAME" 1656 | 1657 | # NOTE: 这个变量可能是空的. 1658 | # (可选):将失败调用设置为hostname命令 1659 | "${HOSTNAME:-$(hostname)}" 1660 | ``` 1661 | 1662 | ## 获取操作系统的架构(32位或64位) 1663 | 1664 | ```shell 1665 | "$HOSTTYPE" 1666 | ``` 1667 | 1668 | ## 获取操作系统/内核的名称 1669 | 1670 | 这可用于条件判断不同的操作系统,而无需调用`uname`。 1671 | 1672 | ```shell 1673 | "$OSTYPE" 1674 | ``` 1675 | 1676 | ## 获取当前的工作目录 1677 | 1678 | 这是内置`pwd`的替代方案。 1679 | 1680 | ```shell 1681 | "$PWD" 1682 | ``` 1683 | 1684 | ## 获取脚本运行的秒数 1685 | 1686 | ```shell 1687 | "$SECONDS" 1688 | ``` 1689 | 1690 | ## 获取伪随机整数 1691 | 1692 | 每次使用`$RANDOM`时, 返回`0` and `32767`之间的不同整数。 此变量不应用于与安全性相关的任何内容(包括加密密钥等)。 1693 | 1694 | 1695 | ```shell 1696 | "$RANDOM" 1697 | ``` 1698 | 1699 | 1700 | 1701 | 1702 | # 有关终端的信息 1703 | 1704 | ## 获取终端的总行列数(*来自脚本*) 1705 | 1706 | 在纯bash中编写脚本和`stty`/`tput`无法调用时,这很方便。 1707 | 1708 | **示例函数:** 1709 | 1710 | ```sh 1711 | get_term_size() { 1712 | # 用法: get_term_size 1713 | 1714 | # (:;:) 是一个短暂暂停,以确保变量立即导出 1715 | shopt -s checkwinsize; (:;:) 1716 | printf '%s\n' "$LINES $COLUMNS" 1717 | } 1718 | ``` 1719 | 1720 | **示例用法:** 1721 | 1722 | ```shell 1723 | # 输出: 行数 列数 1724 | $ get_term_size 1725 | 15 55 1726 | ``` 1727 | 1728 | ## 获取终端的像素大小 1729 | 1730 | **警告**: 这在某些终端仿真器中不起作用。 1731 | 1732 | **示例函数:** 1733 | 1734 | ```sh 1735 | get_window_size() { 1736 | # 用法: get_window_size 1737 | printf '%b' "${TMUX:+\\ePtmux;\\e}\\e[14t${TMUX:+\\e\\\\}" 1738 | IFS=';t' read -d t -t 0.05 -sra term_size 1739 | printf '%s\n' "${term_size[1]}x${term_size[2]}" 1740 | } 1741 | ``` 1742 | 1743 | **示例用法:** 1744 | 1745 | ```shell 1746 | # 输出: 长度x高度 1747 | $ get_window_size 1748 | 1200x800 1749 | 1750 | # 输出 (失败): 1751 | $ get_window_size 1752 | x 1753 | ``` 1754 | 1755 | ## 获取当前光标位置 1756 | 1757 | 用纯bash创建TUI时,是很有用的。 1758 | TUI是指文本用户界面(Text-based User Interface),通过文本实现交互窗口展示内容,定位光标和鼠标实现用户交互。 1759 | 1760 | **示例函数:** 1761 | 1762 | ```sh 1763 | get_cursor_pos() { 1764 | # 用法: get_cursor_pos 1765 | IFS='[;' read -p $'\e[6n' -d R -rs _ y x _ 1766 | printf '%s\n' "$x $y" 1767 | } 1768 | ``` 1769 | 1770 | **示例用法:** 1771 | 1772 | ```shell 1773 | # Output: X Y 1774 | $ get_cursor_pos 1775 | 1 8 1776 | ``` 1777 | 1778 | 1779 | 1780 | 1781 | # 转换 1782 | 1783 | ## 将十六进制颜色转换为RGB 1784 | 1785 | **示例函数:** 1786 | 1787 | ```sh 1788 | hex_to_rgb() { 1789 | # Usage: hex_to_rgb "#FFFFFF" 1790 | # hex_to_rgb "000000" 1791 | : "${1/\#}" 1792 | ((r=16#${_:0:2},g=16#${_:2:2},b=16#${_:4:2})) 1793 | printf '%s\n' "$r $g $b" 1794 | } 1795 | ``` 1796 | 1797 | **示例用法:** 1798 | 1799 | ```shell 1800 | $ hex_to_rgb "#FFFFFF" 1801 | 255 255 255 1802 | ``` 1803 | 1804 | 1805 | ## 将RGB颜色转换为十六进制 1806 | 1807 | **示例函数:** 1808 | 1809 | ```sh 1810 | rgb_to_hex() { 1811 | # Usage: rgb_to_hex "r" "g" "b" 1812 | printf '#%02x%02x%02x\n' "$1" "$2" "$3" 1813 | } 1814 | ``` 1815 | 1816 | **示例用法:** 1817 | 1818 | ```shell 1819 | $ rgb_to_hex "255" "255" "255" 1820 | #FFFFFF 1821 | ``` 1822 | 1823 | 1824 | # 代码高尔夫 1825 | 1826 | [CODE GOLF](https://en.wikipedia.org/wiki/Code_golf),看看谁写的代码最短! 1827 | 1828 | 1829 | ## 更短的`for`循环语法 1830 | 1831 | ```shell 1832 | # Tiny C风格. 1833 | for((;i++<10;)){ echo "$i";} 1834 | 1835 | # 未记载的方法. 1836 | for i in {1..10};{ echo "$i";} 1837 | 1838 | # 扩展. 1839 | for i in {1..10}; do echo "$i"; done 1840 | 1841 | # C语言风格. 1842 | for((i=0;i<=10;i++)); do echo "$i"; done 1843 | ``` 1844 | 1845 | ## 更短的无限循环 1846 | 1847 | ```shell 1848 | # 普通方法 1849 | while :; do echo hi; done 1850 | 1851 | # 更短的方式 1852 | for((;;)){ echo hi;} 1853 | ``` 1854 | 1855 | ## 更短的函数声明 1856 | 1857 | ```shell 1858 | # 普通方法 1859 | f(){ echo hi;} 1860 | 1861 | # 用于子shell 1862 | f()(echo hi) 1863 | 1864 | # 用于四则运算 1865 | # 这可以被用来分配整数值。 1866 | # Example: f a=1 1867 | # f a++ 1868 | f()(($1)) 1869 | 1870 | # 用作测试,循环等 1871 | # NOTE: ‘while’, ‘until’, ‘case’, ‘(())’, ‘[[]]’ 也可以使用. 1872 | f()if true; then echo "$1"; fi 1873 | f()for i in "$@"; do echo "$i"; done 1874 | ``` 1875 | 1876 | ## 更短的`if`语法 1877 | 1878 | ```shell 1879 | # 一行 1880 | # Note: 当第一段是正确时执行第三段 1881 | # Note: 此处利用了逻辑运算符的短路规则 1882 | [[ $var == hello ]] && echo hi || echo bye 1883 | [[ $var == hello ]] && { echo hi; echo there; } || echo bye 1884 | 1885 | # 多行(没有else,单条语句) 1886 | # Note: 退出状态可能与if语句不同 1887 | [[ $var == hello ]] && 1888 | echo hi 1889 | 1890 | # 多行 (没有 else) 1891 | [[ $var == hello ]] && { 1892 | echo hi 1893 | # ... 1894 | } 1895 | ``` 1896 | 1897 | ## 用`case`语句来更简单的设置变量 1898 | 1899 | 内置的`:`可以用来避免在case语句中重复的实用`variable =`。 1900 | `$ _`变量存储最后一个命令的最后一个参数。 1901 | `:`总会成功,所以它可以用来存储变量值。 1902 | 1903 | ```shell 1904 | case "$OSTYPE" in 1905 | "darwin"*) 1906 | : "MacOS" 1907 | ;; 1908 | 1909 | "linux"*) 1910 | : "Linux" 1911 | ;; 1912 | 1913 | *"bsd"* | "dragonfly" | "bitrig") 1914 | : "BSD" 1915 | ;; 1916 | 1917 | "cygwin" | "msys" | "win32") 1918 | : "Windows" 1919 | ;; 1920 | 1921 | *) 1922 | printf '%s\n' "Unknown OS detected, aborting..." >&2 1923 | exit 1 1924 | ;; 1925 | esac 1926 | 1927 | # 最后,获取变量值. 1928 | os="$_" 1929 | ``` 1930 | 1931 | 1932 | 1933 | 1934 | # 其他 1935 | 1936 | ## 使用`read`作为`sleep`命令的替代品 1937 | 1938 | 令人惊讶的是,`sleep`是一个外部命令而不是`bash`内置的。 1939 | 1940 | **警告:** 要求`bash`版本 4+ 1941 | 1942 | **示例函数:** 1943 | 1944 | ```sh 1945 | read_sleep() { 1946 | # 用法: sleep 1 1947 | # sleep 0.2 1948 | read -rt "$1" <> <(:) || : 1949 | } 1950 | ``` 1951 | 1952 | **示例用法:** 1953 | 1954 | ```shell 1955 | read_sleep 1 1956 | read_sleep 0.1 1957 | read_sleep 30 1958 | ``` 1959 | 1960 | 对于性能要求较高的情况下,打开和关闭过多的文件描述符是不实用的,对于`read`的所有调用,文件描述符的分配只能进行一次:: 1961 | 1962 | (请参阅最原始的功能实现 https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever) 1963 | 1964 | ```shell 1965 | exec {sleep_fd}<> <(:) 1966 | while some_quick_test; do 1967 | # equivalent of sleep 0.001 1968 | read -t 0.001 -u $sleep_fd 1969 | done 1970 | ``` 1971 | 1972 | ## 检查一个命令是否在用户的PATH中 1973 | 1974 | ```shell 1975 | # 有3种方法可以使用,任何一种都正确。 1976 | type -p executable_name &>/dev/null 1977 | hash executable_name &>/dev/null 1978 | command -v executable_name &>/dev/null 1979 | 1980 | # 用作检测. 1981 | if type -p executable_name &>/dev/null; then 1982 | # Program is in PATH. 1983 | fi 1984 | 1985 | # 反向检测. 1986 | if ! type -p executable_name &>/dev/null; then 1987 | # Program is not in PATH. 1988 | fi 1989 | 1990 | # 示例(如果未安装程序,则提前退出). 1991 | if ! type -p convert &>/dev/null; then 1992 | printf '%s\n' "error: convert is not installed, exiting..." 1993 | exit 1 1994 | fi 1995 | ``` 1996 | 1997 | ## 使用`strftime`获取当前日期 1998 | 1999 | Bash的`printf`有一个内置的获取日期的方法,可用来代替`date`命令。 2000 | 2001 | **警告:** 要求`bash`版本 4+ 2002 | 2003 | **示例函数:** 2004 | 2005 | ```sh 2006 | date() { 2007 | # 用法: date "format" 2008 | # 通过 "man strftime"看格式 2009 | printf "%($1)T\\n" "-1" 2010 | } 2011 | ``` 2012 | - 了解时间格式: ['man strftime'](http://www.man7.org/linux/man-pages/man3/strftime.3.html) . 2013 | 2014 | **示例用法:** 2015 | 2016 | ```shell 2017 | # 使用上述函数. 2018 | $ date "%a %d %b - %l:%M %p" 2019 | Fri 15 Jun - 10:00 AM 2020 | 2021 | # 直接使用printf. 2022 | $ printf '%(%a %d %b - %l:%M %p)T\n' "-1" 2023 | Fri 15 Jun - 10:00 AM 2024 | 2025 | # 使用printf分配变量. 2026 | $ printf -v date '%(%a %d %b - %l:%M %p)T\n' '-1' 2027 | $ printf '%s\n' "$date" 2028 | Fri 15 Jun - 10:00 AM 2029 | ``` 2030 | 2031 | ## 获取当前用户的用户名 2032 | 2033 | **警告:** 要求`bash`版本 4.4+ 2034 | 2035 | ```shell 2036 | $ : \\u 2037 | # Expand the parameter as if it were a prompt string. 2038 | $ printf '%s\n' "${_@P}" 2039 | black 2040 | ``` 2041 | 2042 | ## 生成一个V4版本的UUID 2043 | 2044 | **警告**: 生成的值不具有加密安全性。 2045 | 2046 | **示例函数:** 2047 | 2048 | ```sh 2049 | uuid() { 2050 | # 用法: uuid 2051 | C="89ab" 2052 | 2053 | for ((N=0;N<16;++N)); do 2054 | B="$((RANDOM%256))" 2055 | 2056 | case "$N" in 2057 | 6) printf '4%x' "$((B%16))" ;; 2058 | 8) printf '%c%x' "${C:$RANDOM%${#C}:1}" "$((B%16))" ;; 2059 | 2060 | 3|5|7|9) 2061 | printf '%02x-' "$B" 2062 | ;; 2063 | 2064 | *) 2065 | printf '%02x' "$B" 2066 | ;; 2067 | esac 2068 | done 2069 | 2070 | printf '\n' 2071 | } 2072 | ``` 2073 | 2074 | **示例用法:** 2075 | 2076 | ```shell 2077 | $ uuid 2078 | d5b6c731-1310-4c24-9fe3-55d556d44374 2079 | ``` 2080 | 2081 | ## 进度条 2082 | 2083 | 这是一种绘制进度条的简单方法,无需在函数本身中使用for循环。 2084 | 2085 | **示例函数:** 2086 | 2087 | ```sh 2088 | bar() { 2089 | # 用法: bar 1 10 2090 | # ^----- 已经完成的百分比 (0-100). 2091 | # ^--- 字符总长度. 2092 | ((elapsed=$1*$2/100)) 2093 | 2094 | # 创建空格表示的进度条 2095 | printf -v prog "%${elapsed}s" 2096 | printf -v total "%$(($2-elapsed))s" 2097 | 2098 | printf '%s\r' "[${prog// /-}${total}]" 2099 | } 2100 | ``` 2101 | 2102 | **示例用法:** 2103 | 2104 | ```shell 2105 | for ((i=0;i<=100;i++)); do 2106 | # 纯粹的暂停动作 (为了本例可以更好的演示). 2107 | (:;:) && (:;:) && (:;:) && (:;:) && (:;:) 2108 | 2109 | # Print the bar. 2110 | bar "$i" "10" 2111 | done 2112 | 2113 | printf '\n' 2114 | ``` 2115 | 2116 | ## 获取脚本中的函数列表 2117 | 2118 | ```sh 2119 | get_functions() { 2120 | # Usage: get_functions 2121 | IFS=$'\n' read -d "" -ra functions < <(declare -F) 2122 | printf '%s\n' "${functions[@]//declare -f }" 2123 | } 2124 | ``` 2125 | 2126 | ## 绕过shell别名 2127 | 2128 | ```shell 2129 | # alias 2130 | ls 2131 | 2132 | # command 2133 | # shellcheck disable=SC1001 2134 | \ls 2135 | ``` 2136 | 2137 | ## 绕过shell函数 2138 | 2139 | ```shell 2140 | # function 2141 | ls 2142 | 2143 | # command 2144 | command ls 2145 | ``` 2146 | 2147 | - command命令 调用指定的指令并执行,命令执行时不查询shell函数。command命令只能够执行shell内部的命令。 2148 | 2149 | ## 在后台运行命令 2150 | 2151 | 这将运行给定命令并使其保持后台运行,即使终端或SSH连接中断后也是如此。但是会忽略所有输出。 2152 | 2153 | 2154 | ```sh 2155 | bkr() { 2156 | (nohup "$@" &>/dev/null &) 2157 | } 2158 | 2159 | bkr ./some_script.sh 2160 | ``` 2161 | 2162 | 2163 | 2164 | # 后记 2165 | 2166 | 感谢各位的阅读,如果对语法有啥疑问或者文章中有错误的地方,请及时告知,谢谢! 2167 | 2168 | 2169 | Rock on. 🤘 2170 | --------------------------------------------------------------------------------