bash 速描
bash 速描
资料来源:
https://wangdoc.com/bash/index.html
更新
1
2
3
4
5
620.05.19 初始化
20.05.22 变量
20.05.23 字符串
20.05.24 行操作
20.05.26 条件判断
20.05.27 函数
导语
- 重刷一遍 bash.
- 最开始接触 bash 是跟着 鸟哥的 linux 私房菜,之后没有太多应用就放弃了.然而现在又到了大量需要时候…
- 正好阮一峰老师的 bash 教程 发布了,wsl2 也随着 win10 2004 到了正式版中,条件成熟,开搞.
简介
- shell,这个词听说了很久,所谓 shell ,相对于 kernel 而言,就是壳的意思.
- shell 可以说是用户通过命令与内核交互的途径.shell 可以有很多种,我们熟悉的 sh bash zsh 就是几种常见的 shell 类型.
- 使用最广的自然是 bash 了 (其实挺想学学 zsh 的,但是别着急).
- 2019 年 bash 最新版本是
5.0.3(1)-release
. - 这一篇仅仅是 bash 的速描,而非教程.
基础语法
- echo(ow 新英雄😂)
- 文本输出到屏幕
- 多行需要 “”
- -n: 去掉默认会在文本末尾添加的 \n 换行符.
- -e: 解释 “” 内部的特殊字符,没有 -e 例如 \n 就会直接输出 \n 字符,而不是换行.
- 命令格式
- 基本上是
$ command [ arg1 … [ argN ]]
- 命令 + 参数 1 …参数 N
- 一般参数有两种
-
: 短形式,简写,在命令行居多.--
: 长形式,脚本中居多.
- 多行编辑时,通过 \ 连接多行命令,执行时会合并成一行.
- 基本上是
- 空格
- bash 的不同参数之间以空格区分
- 会忽略多个空格
- 分号
- 分割两个命令
- 即使第一个命令执行失败,第二个命运也会继续执行.
- 命令组合
Command1 && Command2
: 只有命令 1 执行成功,才执行命令 2.Command1 || Command2
: 不管命令 1 执行如何,始终执行命令 2.
- type 命令
- 可以区分命令本身来源于内置命令,还是外部程序.
- type 本身也是内置命令
- -a: 输出命令的全部定义
- -t: 返回命令的全部类型,
- 别名 alias
- 关键字 keyword
- 函数 function
- 内置命令 builtin
- 文件 file
- 快捷键
Ctrl + L
: 清屏,把当前行移动到页面最上位置.Ctrl + C
: 中断命令Ctrl + U
: 由光标位置,删除到行首Ctrl + K
: 由光标位置,删除到行末Ctrl + D
: 等于 exitShift + PageUP
: 向上滚动Shift + PageDown
: 向下滚动
- TAB 补全,如果遇到无法补全,检查一下
bash-completion
这个插件,装没装.
模式拓展
- 如果要完成一项复杂的工作,纯粹的 shell 命令会烦死的,由此引入的模式拓展.
- 模式拓展,大概是将
?*
等等通过预定的模式展开成原始的命令,最大的作用是省纸. - 有点类似于正则啊,实际上模式拓展要早于正则,但是模式拓展要简单方便很多.
- 当然模式拓展是可关闭的.
set -f
关闭,set +f
打开. - 波浪线拓展
- ~: 用户当前的主目录
- ~user: 用户 user 的主目录.
- ~+: =pwd 当前目录
?
字符拓展?
: 文件路径中任意的单字符,不包括空字符.- 如果匹配的文件存在才进行转换,否则只是输出成
?
本身.
*
字符拓展*
: 文件路径中任意数量的任意字符 (类似正则的.*
).*
: 匹配隐藏文件..[!.]*
: 排除.
..
以外的隐藏文件,以.
开头,第二个字符不是.
,之后是任意字符.**/*
: 匹配包括任意层级子目录的任意文件.- 只有文件确实存在时,才会进行转换,否则只是输出
*
本身.
- 方括号拓展
[..]
: 一个方括号内任意字符.[^..]
或[!..]
: 任何一个不在方括号内任意字符[[..]
: 匹配[
这个字符本身.[-..]
或[..-]
: 匹配-
这个字符时,只能放在开头或结尾.- 只有文件确实存在时,才会进行转换,否则只是输出
[..]
本身.
[start-end]
拓展[a-z]
: 所有小写字母任意一个[a-zA-Z]
: 所有小写大写字母任意一个[a-zA-Z0-9]
: 所有大小写字母和数字任意一个[!a-zA-Z]
: 除了英文字母以外的任意一个字符
- 大括号拓展
- 非文件拓展,会自动转换成所有值
{…}
: 自动拓展成大括号内所有值,各个值期使用逗号隔开.逗号前后不能有空格.{j{p,pe}g,png}
: 大括号嵌套,可以一定程度上当生成器了{cat,d*}
: 大括号可以与其它模式连用,但是大括号总是优先于其他模式.这里就是匹配cat
和d*
的文件.
- {start…end} 扩展,类似中括号
[start-end]
{a..c}
: a b c{1..4}
: 1 2 3 4{c..b}
: c b a{4..1}
: 4 3 2 1{01..5}
: 01 02 03 04{0..8..2}
: 0 2 4 6 8 (步长是 2)- 无法转换的简写,原样输出.
- 变量拓展
- 以一个
$
代表变量值…嗯…kotlin 有这味. $value
: 拓展成对应变量值${value}
: 同上.${!string*}
或${!string@}
: 返回所有匹配给定字符串 string 的变量名
- 以一个
- 子命令拓展
$(…)
: 将括号内的命令运行结果作为拓展输出.- $ `…`: 同上.
- 可以嵌套.
- 算术拓展
echo $((2 + 2))
: 结果拓展为 4 .
- 字符类
[[:class:]]
: 表示一个字符类,扩展成某一类特定字符之中的 一个.- 当没有匹配时,会直接输出.
[[:alnum:]]
:匹配任意英文字母与数字[[:alpha:]]
:匹配任意英文字母[[:blank:]]
:空格和 Tab 键[[:cntrl:]]
:ASCII 码 0-31 的不可打印字符[[:digit:]]
:匹配任意数字 0-9[[:graph:]]
:A-Z、a-z、0-9 和标点符号[[:lower:]]
:匹配任意小写字母 a-z[[:print:]]
:ASCII 码 32-127 的可打印字符[[:punct:]]
:标点符号(除了 A-Z、a-z、0-9 的可打印字符)[[:space:]]
:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)[[:upper:]]
:匹配任意大写字母 A-Z[[:xdigit:]]
:16 进制字符(A-F、a-f、0-9)- 例子:
$ echo [[:upper:]]*
: 所有以大写字母开头的文件.$ echo [![:digit:]]*
: 所有不以数字开头的文件
- 量词语法
- 量词语法控制拓展的匹配次数.由
extglob
参数控制,一般是开着的. - 当没有匹配时,原样输出.
?(pattern-list)
:0 或 1*(pattern-list)
:0 或 多+(pattern-list)
:1 或 多@(pattern-list)
:1!(pattern-list)
:匹配零个或一个以上的模式,但不匹配单独一个的模式 (??)- 例子
abc?(.)txt
: 不取 或 只取第一个符合开头 abc 结尾 .txt 的文件abc+(.txt|.php)
: 不取 或 匹配多个以 adc 开头,结尾是 .txt 或 .php 的文件
- 量词语法控制拓展的匹配次数.由
- 匹配注意点
- 通配符是先拓展再执行.
- 文件名拓展不存在匹配时,基本都是按照原样输出.
- 通配符文件名拓展一般只适用于本层路径.子路径需要声明子路径才行
xx/xx
.也可以使用**/xx
,直接匹配所有的文件 (不可控). - Bash 允许文件名使用通配符,即文件名包括特殊字符,这时引用文件名,需要把文件名放在单引号里面.
fo*
.
- shopt 命令
- shopt 命令可以调整 Bash 的行为.
$ shopt -s [optionname]
: 打开某个参数$ shopt -u [optionname]
: 关闭某个参数$ shopt [optionname]
: 查询某个参数关闭还是打开
dotglob
: 拓展结果包含隐藏文件nullglob
: 拓展没有结果时,返回空字符.failglob
: 拓展没有结果时,直接报错,不再执行.extglob
: Bash 支持 ksh 的一些扩展语法,量词拓展就是之一.nocaseglob
: 通配符拓展不再区分大小写.globsta
:**
匹配零个或多个子目录,默认是关闭的.
- shopt 命令可以调整 Bash 的行为.
引号和转义
- bash 只有字符串一直类型的变量.对应特殊字符有一套转义的规则,整体和 c/kotlin 差不多.
- 转义
- 输出特殊字符
\
+ 特殊字符$ * &
\\
输出自身\a
响铃 (??);\b
退格;\n
换行;\r
回车;\t
制表符 (??)
- 输出特殊字符
- 单引号
- 单引号内部所有字符均视为普通字符,不会有拓展的效果.
- 单引号内部使用单引号,
$`it\'s'
需要在外层单引号前加$
,同时在 单引号前 + 转义. - 比较合理的做法是在双引号中使用单引号.
- 双引号
- 大部分特殊字符,双引号会保留原意,除
$ ` \
除外,会自动拓展. - 特别留意
*
在双引号中也只是普通字符,不会进行文件名拓展. - 文件名包含空格,必须使用双引号.
test kot.txt
,而且双引号会原样保持多余空格. - 双引号还能保存原始命令的输出格式.如果没有双引号,基本都堆到了单行.
echo "$(cal)"
- 大部分特殊字符,双引号会保留原意,除
- Here 文档
一种输入多行字符串的方法.
格式
1
2
3<< token
text
token<< token
: 开始标记,<<
打头,之后是文件名,文件名任意,但最后一个必须是换行符.token
: 结束标记,文件名,必须是定格.- 中间是多行的字符串内容
Here 内部变量替换,转义字符依旧有效,但拓展和单引号双引号失效.
如果不希望有变量替换,可以将开始标记的文件名加上单引号.
<< `example`
Here 文档的本质是重定向,将字符串重定向到了某个命令,相当于加了
echo
.echo string | command
因此 Here 文档只能适用于那些接收标准输入为参数的命令.
- Here 字符串
- 算是 Here 文档的变体,使用
<<< string
将字符串通过标准输入传递给命令. - 原理大致相同,等同于添加了
echo
.
- 算是 Here 文档的变体,使用
变量
bash 下的变量分为两种,一是环境变量,另一个是自定义变量.
- 环境变量是 bash 自带的变量,由系统定义.
- 自定义变量即用户自行定义,当关闭 shell 后就不再存在.
env
: 输出所有环境变量.echo xx
: 输出单个变量.set
: 输出所有变量及 bash 函数.
创建变量
- 变量名通常是大写字母表示.(约定俗成)
- bash 区分大小写.
- 字母/数字/下划线.
- 开头必须是字母/下划线.
- 不允许空格/标点符号.
- 所有类型均为字符串.
- 变量值有空格等,需要单/双引号.
读取变量
$xxx
或${xxx}
: 读取变量\$
: 要正常使用$
,需要转义.${!xxx}
: 如果变量值本身是另一个变量,可以加 ! 读取到最终的值.
删除变量
unset NAME
: 删除变量,但是并不怎么好用…变量都是字符串类型,删除后还是空字符串.NAME=''
或NAME=
: 都等同于unset
.
输出变量 (更多是传递变量吧)
export xxx
: 子 shell 可以读取到父 shell 对应的变量值.而且修改不影响父 shell.(类似继承)
特殊变量
- 由 shell 定义,用户只能使用不能赋值.
$?
: 上一个命令的退出码,为 0 ,则上一条命令执行成功.$$
: 当前 shell 的 pid.$_
: 上一个命令的最后参数.$!
: 最近一个后台执行异步命令的进程 id .$0
: 当前 shell 的名称.$-
: 当前 shell 的启动参数.$@
$#
: 脚本的参数数量.
变量的默认值
- Bash 可以通过默认值防止变量为空.
${varname:-word}
: varname 空返回 word.${varname:=word}
: varname 空设置 varname=word 并返回.${varname:+word}
: varname 存在且不为空,返回 word,否则返回空值.这是个测试命令.${varname:?message}
: varname 为空,打印 varname: message 并中断脚本的执行.防止变量未定义.
declare 命令
- declare 可以为变量设置一些限制.像只读,整数类型等.
declare OPTION VARIABLE=value
: 基本格式-i
: 声明整形参数,可以直接运算了.但是即使声明成整形了,依然可以被改写成其他字符串.-x
: 等同于 export ,可以将变量继承到子 shell.-r
: 声明只读变量,unset 也不行.-u
: 声明为大写字母,自动将变量值的小写转大写…(强迫症专用)-l
: 声明为小写字母,同上 (强迫症专用)-p
: 输出变量信息,不指定会输出全部.-f
: 输出当前环境所有函数,包括定义.-F
: 输出当前环境所有函数,不包括定义.
readonly 命令
- 单 readonly 等同于
declare -r
只读. - 参数
-f
: 声明变量为函数名-p
: 打印出所有只读变量-a
: 声明变量为数组
- 单 readonly 等同于
let 命令
let foo=1+2
: 可以直接执行运算.let "foo = 1 + 2"
: 运算包含空格,需要引号.let "v1 = 1" "v2 = v1++"
: 多个表达式可以用空格隔离.
字符串操作
字符串是 bash 唯一的变量类型,这一节是对字符串的操作.
字符串长度
实例
1
${#varname} : 输出字符串长度,大括号必须,否则 `$#` 会被拓展成脚本的参数个数.
子串提取
${varname:offset:length}
: 返回 varname 的子串,从 offset 长度为 length.索引从 0 开始.{varname:offset:length}
: 同上,但$
可省略${varname:offset}
: 从 offset 开始一直到结尾.${varname: offset:length}
: 这里 offset 可以为负值,但是前面需要加一个空格隔开.而且 length 必须大于 0.
搜索和替换 (原始变量并不会被改变)
${variable#pattern}
: 从头开始,非贪婪匹配,删除匹配部分并返回.${variable##pattern}
: 从头开始,贪婪匹配,删除匹配部分并返回.${variable%pattern}
: 从尾开始,非贪婪匹配,删除匹配部分并返回.${variable%%pattern}
: 从尾开始,贪婪匹配,删除匹配部分并返回.- 匹配部分可以有个
* ? []
等,具体模式与上文相同. - 没有匹配时,会直接原样返回.
${variable/pattern/string}
: 贪婪匹配,但仅替换第一个匹配.${variable//pattern/string}
: 贪婪匹配,替换所有.${variable/#pattern/string}
: 模式必须在字符串开头${variable/%pattern/string}
: 模式必须在字符串的结尾.
改变大小写
${varname^^}
: 转为大写${varname,,}
: 转为小写
算术运算
- 虽然 bash 变量都是字符串,但是还是能运算的.
- 算术表达式
((…))
: 自动运算双括号内算术,并且忽略算式空格.结果如果为 0 算是命令执行失败.$?
会查询到非 0.$((…))
: 这样就算是算术表达式,可以返回算术的值.+ - * %
: 没啥区别/
: 整除,结果是整数.**
: 指数++ --
: 与 c 相同,有前后之分.前面有命令根据位置会有先运算还是先执行命令的差别.$(( (2 + 3) * 4 ))
: 内部可以使用括号,优先级最高.echo $(($((5**2)) * 3))
:$((…))
可以嵌套,但是只能计算整数,否则报错.- 算术表达式内可以加
$varname
,也可以不加varname
,如果对应的变量并不存在,对应会返回空值. - ps: 还有一种写法是
$[…]
,已经过时了,不要再用了.
- 进制
- bash 默认是十进制,当然也能使用其他进制.
0number
: 8 进制0xnumber
: 16 进制base#number
: base 进制的数.
- 位运算 (跟 c 差不多)
<<
: 左移>>
: 右移&
: 与运算|
: 或运算~
: 取反^
: 亦或- 位运算一般配合 8/16 进制数.
- 逻辑运算
<; >; <=; >=; ==; !=; &&; ||; !
: 与 c 完全相同.expr1?expr2:expr3
: 支持三目运算符让我有点意外.- 逻辑表达式为真返回 1,假返回 0.(跟 c 一样)
- 赋值运算
$((…))
: 执行赋值运算.- 支持的运算符
=; +=; -=; *=; /=; %=; <<=; >>=; &=; |=; ^=;
- 跟 c 也一样.
- 求值运算
echo $((foo = 1 + 2, 3 * 4))
: 在$((…))
双括号内,,
是求值运算符.- 上面的表达式会输出 12 ,然后对 foo 赋值.
- expr 命令
- 有时双括号有点眼花 (◎﹏◎).
- 可以使用
exper xxx
代替 - 支持变量替换,但不支持非整数参数.
行操作
- bash 内置了 Readline 库,行操作基于此,默认是 Emacs 快捷键,没必要不需要改.
- 光标移动 (alt 可由 esc 代替)
Ctrl + a
:移到行首Ctrl + b
:向行首移动一个字符,与左箭头作用相同Ctrl + e
:移到行尾Ctrl + f
:向行尾移动一个字符,与右箭头作用相同Alt + f
:移动到当前单词的词尾Alt + b
:移动到当前单词的词首
- 清屏
ctrl + l
: 等同于 clear
- 编辑操作
- 编辑行的内容
ctrl + d
: 删除光标位置的字符.千万小心,当前行没有字符,直接退出终端ctrl + w
: 删除光标前的单词ctrl + t
: 光标位置字符与前一位交换alt + t
: 光标位置词与前一位词交换alt + l
: 光标位置到词尾转为小写alt + u
: 光标位置到词尾转为大写
- 自动补全
Tab
: 不说了,连恩两次,列出所有可能补全.alt + tab
: 从 bash_history 里提取补全,但是吧和多任务切换冲突了…- 其他用的就少了
- 操作历史
所有历史命令都保存在
~/.bash_history
文件中.上下键切换,不多说了.
history
: 显示 history 文件history -c
: 清理 historyctrl + r
: 会显示显示操作历史,键入命令可显示历史文件中查询的命令.常用.快捷键
ctrl + o
: 执行当前条目,并显示下一条命令
!快捷键
!string
: 相当于搜索历史,找出最近的以 string 开头的命令并执行.但只会匹配命令,没有参数什么事!?string
: 执行最近一条包含字符串 string 的命令^string1^string2
: 执行最近一条包含 string1 的命令,将其替换成 string2!n
: 执行 history 第 n 行的命令!-n
: 执行 history 末尾开始 -n 行命令
- 其他快捷键
Ctrl + v
: 下一个输入的特殊字符变成字面量…Alt + .
或Alt + _
: 非常重要,插入上一条命令的最后一个词.对于很长文件路径非常适用.
目录堆栈
- 大意是有个堆栈可以把目录当作变量一样操作.进栈出栈.
cd -
: 返回前一次进入的目录pushd dir
: 进入 dir 同上 dir 放入堆栈.popd
: 取栈顶目录,出栈并进入该目录.popd -n
: 仅删除栈顶目录,不改变当前 shell 路径.pushd +3
: 从栈顶起,第三个目录移动到栈顶.pushd -3
: 从栈底起,第三个目录移动到栈顶.popd +3
: 从栈顶起,删除第三个目录.popd -3
: 从栈底起,删除第三个目录.- dirs 命令
- 显示目录堆栈
-c
: 清空目录堆栈-l
: 用户主目录不显示波浪号前缀,而打印完整的目录 (???)-p
: 打印目录堆栈.-v
: 打印目录堆栈带编号.+N
: 显示自栈顶起,第 N 个目录.-N
: 显示自栈底起,第 N 个目录.
脚本入门
脚本就不多说了,学习 bash 就是为了写个脚本,各类自动化真是羡慕.
Shebang 行
- 就是脚本第一行,实际作用是指示脚本解释器.一般是 sh 或 bash
#!/bin/sh
或#!/bin/bash
: 有没有空格都行.#!/usr/bin/env bash
: 阮老师推荐这样写,以防万一 bash 解释器没有在 /bin 下面.- 这一行不是必须的,只要不通过
./script.sh
这样调用.
权限和路径
chmod +x script.sh
: 所有用户,执行权限chmod +rx script.sh
或chmod 755 script.sh
: 所有用户.读 执行权限chmod u+rx script.sh
或chmod 700 script.sh
: 仅所有者,读 执行权限.路径,新建 ~/bin 存放脚本,加入 PATH ,这样可以直接输入脚本名执行脚本了.
1
2mkdir ~/bin
export PATH=$PATH:~/bin
env 命令
#!/usr/bin/env bash
: shell 查找 bash 的路径并加载,如果 bash 的解释器没有在 /bin 下,可以兼容.- 参数
-i
: 不带环境变量启动-u xx
: 从环境变量中删除一个变量.
注释
- 所有注释都是以
#
开头,可以在结尾,也可以另起一行. - 建议在脚本开头,说明当前脚本的作用.
- 所有注释都是以
脚本参数
调用脚本时后面跟随的参数.bash 对这种情况进行了处理.
$0
: 脚本名,脚本自身名称,不同的调用方式名称不同.$1
~$9
: 脚本第一个到第九个参数.$#
: 参数的总数$@
: 全部参数,参数之间空格隔开.$*
: 全部参数,参数使用$IFS
的第一个字符隔开,默认是空格.${10}
~${xx}
: 10 个以后的参数可以加大括号引用.command -o foo bar
: 参数依次是-o
foo
bar
多个参数在同一个双引号中,算是一个参数.
也可以使用 for 循环
$@
每一个参数.1
2
3for i in "$@"; do
echo $i
done
shift 命令
可以改变脚本的参数,每次都会移除当前的第一个参数
$1
,移除后剩余参数依次填充,原来的$2
变成$1
…结合 while 实现另一类的循环
1
2
3
4
5while [ "$1" != "" ]; do
echo "剩下 $# 个参数"
echo "参数:$1"
shift
donesheift number
: 一次性移除多个参数
getopts 命令
可以解析复杂的脚本命令行参数,通常与 while 连用,取出带
-
的参数.getopts optstring name
: 第一个参数是字符串,就是-
前面的字符.例如需要解析-l
-a
两个配置,且-a
是带参数值的.optstring 要写成la:
,顺序不重要,带参数值的需要带:
.第二个参数是变量,-
后面的值就存在name
对应的变量中.这是原教程的一个示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21while getopts 'lha:' OPTION; do
case "$OPTION" in
l)
echo "linuxconfig"
;;
h)
echo "h stands for h"
;;
a)
avalue="$OPTARG"
echo "The value provided is $OPTARG"
;;
?)
echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2
exit 1
;;
esac
done
shift "$(($OPTIND - 1))"
配置项参数终止符
--
- 指定变量只作为实体参数,而不是作为配置项参数.
exit 命令
- 终止脚本执行,返回一个值.
exit bumber
: 返回 0 即执行成功.
命令执行结果
- 脚本大多是命令的连续执行,
$?
代表了前一个命令的返回值,返回值为 0 则,上一个命令执行成功. - 一般配合 if.
- 或者上文中的
&&
||
.
- 脚本大多是命令的连续执行,
source 命令
- source 可以用来执行脚本,但是 source 只会在当前 shell 下执行,不需要 export.
. .bashrc
: source 简写成.
.
别名
alias
命令alias NAME=DEFINITION
: 别名,不表.unalias lt
: 解除别名.
Read 命令
读取转化用户输入的命令.
read [-options] [variable…]
: options 是参数,variable 是一系列的变量名,储存用户的输入,如果没有变量名,默认使用REPLY
环境变量保存.read FN LN
: read 可以接受多个用户输入,空格分割开,输入不够 read 的数量,缺失自动为空,超过数量,都挤到最后一个变量上.read 亦可以读取文件,done 后面的命令
<
将文件导入变量myline
,一次读取一行.1
2
3
4while read myline
do
echo "$myline"
done < $filename-t second
: 设置超时,超时后用户没有输入即放弃等待.-p "text"
: 显示提示语句,提示用户输入.-a xx
: 将用户的输入赋值给数组,从 0 开始.-n number xxx
: 只取 xxx 的前 nubmber 个字符.-e xx
: 允许参数使用 bash 的自动补全功能.-r
: raw 模式,不将反斜杠解释为转义字符.-s
: 不显示用户输入,常用在输密码什么的.IFS 变量
- 默认 read 读取的不同值以空格隔离.
- 可以修改 IFS 变量,修改默认的分隔符.
- IFS 默认有空格 TAB 换行.
- 常用自定义分隔符有
;
:
等. - IFS 为空,则将整行作为一个变量.
条件判断
if
是最常用的了
1 | if commands |
- 特殊一点的是分支是
elif
结束时是fi
结尾. then
与条件判断写在一行要以;
分隔.其他也类似,如果愿意可以都写在一行,然后以;
分隔.- 两个特殊指令
true
false
. - 判断部分可以跟随任意多个命令,但是判断只以最后一个为准.
判断部分常用 test
,有 3 种
1 | # 写法一 |
- 三种形式等价.执行成功返回 0 ,失败返回 1.
- 形式 2 3,
[
与]
之间必须要有空格. - 形式 3 还支持正则.
示例
1 | # 写法一 |
判断表达式,test
是测试命令是否执行成功,测试表达式则更多的是判断状态.(这一块内容太多照抄了…)
文件判断
- 示例
if [ -e "$FILE" ]; then
[ -a file ]
:如果 file 存在,则为true
。[ -b file ]
:如果 file 存在并且是一个块(设备)文件,则为true
。[ -c file ]
:如果 file 存在并且是一个字符(设备)文件,则为true
。[ -d file ]
:如果 file 存在并且是一个目录,则为true
。[ -e file ]
:如果 file 存在,则为true
。[ -f file ]
:如果 file 存在并且是一个普通文件,则为true
。[ -g file ]
:如果 file 存在并且设置了组 ID,则为true
。[ -G file ]
:如果 file 存在并且属于有效的组 ID,则为true
。[ -h file ]
:如果 file 存在并且是符号链接,则为true
。[ -k file ]
:如果 file 存在并且设置了它的 “sticky bit”,则为true
。[ -L file ]
:如果 file 存在并且是一个符号链接,则为true
。[ -N file ]
:如果 file 存在并且自上次读取后已被修改,则为true
。[ -O file ]
:如果 file 存在并且属于有效的用户 ID,则为true
。[ -p file ]
:如果 file 存在并且是一个命名管道,则为true
。[ -r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。[ -s file ]
:如果 file 存在且其长度大于零,则为true
。[ -S file ]
:如果 file 存在且是一个网络 socket,则为true
。[ -t fd ]
:如果 fd 是一个文件描述符,并且重定向到终端,则为true
。 这可以用来判断是否重定向了标准输入/输出错误。[ -u file ]
:如果 file 存在并且设置了 setuid 位,则为true
。[ -w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。[ -x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。[ file1 -nt file2 ]
:如果 FILE1 比 FILE2 的更新时间最近,或者 FILE1 存在而 FILE2 不存在,则为true
。[ file1 -ot file2 ]
:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
。[ FILE1 -ef FILE2 ]
:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
。
字符串判断
- 示例
if [ -z "$ANSWER" ]; then
- test 命令内部的>和<,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符
[ string ]
:如果string
不为空(长度大于 0),则判断为真。[ -n string ]
:如果字符串string
的长度大于零,则判断为真。[ -z string ]
:如果字符串string
的长度为零,则判断为真。[ string1 = string2 ]
:如果string1
和string2
相同,则判断为真。[ string1 == string2 ]
等同于[ string1 = string2 ]
。[ string1 != string2 ]
:如果string1
和string2
不相同,则判断为真。[ string1 '>' string2 ]
:如果按照字典顺序string1
排列在string2
之后,则判断为真。[ string1 '<' string2 ]
:如果按照字典顺序string1
排列在string2
之前,则判断为真。
整数判断
- 示例:
if [ -z "$INT" ]; then
[ integer1 -eq integer2 ]
:如果integer1
等于integer2
,则为true
。[ integer1 -ne integer2 ]
:如果integer1
不等于integer2
,则为true
。[ integer1 -le integer2 ]
:如果integer1
小于或等于integer2
,则为true
。[ integer1 -lt integer2 ]
:如果integer1
小于integer2
,则为true
。[ integer1 -ge integer2 ]
:如果integer1
大于或等于integer2
,则为true
。[ integer1 -gt integer2 ]
:如果integer1
大于integer2
,则为true
。
正则判断 (最头疼的)
1 | [[ string1 =~ regex ]] |
test 判断的逻辑运算
AND
:$$
或-a
OR
:||
或-o
NOT
:!
- test 命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释。
算术判断
1 | if ((3 > 2)); then |
算术判断非 0 值为 true, 0 值为 false.
算术判断还可以用于赋值操作,两者都对 foo 进行了赋值,但是第一个返回 5 第二个返回 0,所以第一个判断为 true,第二个判断为 false.
1 | if (( foo = 5 ));then echo "foo is $foo"; fi |
普通命令的逻辑运算
实际上就是 if 与 &&
||
的混用.
1 | [[ -d "$dir_name" ]] && cd "$dir_name" && rm * # 等同于查看文件夹是否存在,存在则进入并执行 rm * |
case 结构…类似 kotlin 的 when,能避免 if 的地狱.
1 | case expression in |
- expression 是一个表达式,pattern 是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号
;
结尾. - 最后是
*
如果都没匹配上.
匹配模式,可以使用各种通配符
a)
: 匹配 aa|b)
: 匹配 a b[[:alpha:]])
: 单个字母???)
: 3 个字符的单词*.txt)
: xxx.txt*)
: 任意输入
bash 4.0 增加了多项匹配,即允许匹配多个条件,要以 ;;&
结束.
1 | case $REPLY in |
只要条件符合,会匹配 3 个条件,而不是匹配一个就结束.
循环
总共有 3 种循环 for
while
until
while
和其他编程语言一样,条件为 true 就一直执行.
1 | while condition |
- 上面 3 种完全等效
- while 条件部分,可以有任意条指令,判断只取决于最后一条.
- 条件同样可以使用 test 命令.
until
有点不一样,与 while
相反,false 才执行,true 中断循环.
1 | until condition; do |
- 3 种方式与 while 相同,不加赘述.
- 条件也与 while 完全相同.
- until 有点没必要…
for .. in
常用于遍历.
1 | for variable in list |
- do 写在同一行
;
.整个循环写在一行,不再赘述. - 列表可以是通配符
for i in *.png; do
. - 列表可以通过子命令产生
for i in $(cat ~/.bash_profile); do
in list
部分可以省略,但是非必要时,为了可读性,不要省略.
for
循环…跟 c 一样…
1 | for (( expression1; expression2; expression3 )); do |
- 定义等与 c 相同,不加赘述.
- 循环条件是双括号.
- 双括号内使用变量,不需要
$
.
break
continue
…定义与 c 相同.
select 结构
结构与 for..in
基本相同,用来生成一个菜单,用户可以选择.
1 | select name |
- select 会生成 list 的带编号菜单,用户选择编号,对应内容存放在
name
的变量中. - 与
case
连用,当个完整的选择菜单了.
函数
bash 的函数定义上更多的是重复使用代码段的封装,而不是传统意义上的函数.
- 函数执行不会新建 shell 这是与脚本的区别.
- 重名下优先级 别名 > 函数 > 脚本
- 注意函数并不依附于脚本,可以直接定义在 shell 中.
1 | # 第一种 |
- 两者等价
- 最大的不适应是变量,没有所谓变量一说.跟脚本处理变量一样
$0
是函数所在的脚本名.前 9 个是$1
~$9
,10 个往上就是${10}
.$#
参数总数.$@
所有参数,空格隔开.$*
所有参数,以 IFS 的第一个字符隔开.
return
命令,这个 return 可是和普通函数的 return 是两码事.因为 bash 函数不带参数变量,所以 return 的返回要使用 $?
拿到.本质上和上一个命令的返回没区别.
全局变量也是个坑,如果仅仅在函数中定义 foo=1
,这个 foo
是整个脚本的…想获得局部变量的效果要 local foo ; foo = 1
,这一点有点坑…
数组
与一般编程语言的定义有很大不同.
- 索引从 0 开始,没啥不一样.
- 但是数量没有上限
- 也没有连续索引的要求.
创建数组
逐个赋值给
1 | ARRAY[INDEX]=value |
一次性赋值
1 | ARRAY=(value1 value2 ... valueN) |
通配符
1 | mp3s=( *.mp3 ) |
用户输入
1 | read -a dice |
读取数组
单个元素
1 | # 大括号不可少 |
遍历数组,@
或 *
.
1 | # 访问全部元素 |
默认位置是 0.
数组长度
1 | ${#array[*]} |
数组序号: {!array[@]}
或 {!array[*]}
返回数组非空的索引.
1 | for i in ${!arr[@]};do |
提取数组成员,类似切片.{array[@]:position:length}
1 | echo ${food[@]:1:3} |
追加, +=
追加到
1 | foo+=(d e f) |
删除成员
unset
彻底抹除- 单纯将值置为空,只会在输出值的遍历中 " 隐身 ",在索引中依然存在.
1 | $ foo=(a b c d e f) |
关联数组
允许使用字符串作为索引而不是数字,有点像字典.
declare -A
声明数组- 其他几乎相同
1 | declare -A colors |
Set 命令
set 命令是用来修改子 Shell 环境的运行参数的,在调试等极为重要.
set -u
或 set -o nounset
: 当遇到变量不存在时,报错终止运行.(默认是忽略错误)
set -x
/ set +x
: 脚本执行时每执行一个命令,先输出到控制台,再执行.-x
是开启 +x
关闭,一般对易出错代码段开启.
错误处理
1 | # 有错误,command 会返回非 0 值 |
set -e
/ set +e
: 有错误即终止运行,-e
开启,+e
关闭.
set -o pipefail
: -e
不适用于管道的命令,-o pipefail
适用.
连用重要
1 | # 写法一 |
Shopt 命令
与 set 命令类似,但是 set 来自 POSIX,shopt 是 bash 特有的.
shopt
: 可以查看所有参数及其状态.
-s
: 打开参数-u
: 关闭参数-q
: 查询参数是否打开,但是结果不直接返回,而在$?
中.
脚本除错
-x
: 执行脚本前加上 -x
会输出所有执行的命令.
一些环境 bianl
LINENO
: 返回所在的行号FUNCNAME
: 返回数组,是当前函数的调用堆栈.BASH_SOURCE
: 返回数组,是当前脚本的调用堆栈.BASH_LINENO
: 返回数组,内容是每一轮调用对应的行号.${BASH_LINENO[$i]}
跟${FUNCNAME[$i]}
是一一对应关系,表示${FUNCNAME[$i]}
在调用它的脚本文件${BASH_SOURCE[$i+1]}
里面的行号.
Mktemp 命令
一般临时文件创建在 /tmp 下,用完就删.但是这样存在几个问题.
/tmp
下对所有用户可读可写.- 意外退出,没法清理.
mktemp: 创建时不会检查临时文件,但是支持唯一文件名和删除机制.
1 | # 指定脚本退出时清理 |
mktemp 会随机创建一个文件,并返回文件路径,这个文件只有当前用户拥有权限.
其他参数
-d
: 创建临时文件夹-p
: 指定临时文件创建位置,默认是$TMPDIR
下,$TMPDIR
不存在则使用/tmp
-t
: 可指定临时文件名的模板.末尾必须是XXX
表示随机字符.默认是tmp.XXXXXXXXXX
Trap 命令
trap
可以用来响应系统信号,再执行命令.
trap -l
: 可以查看所有支持的系统信号.
常见格式是 trap [动作] [信号1] [信号2]
.
1 | trap 'rm -f "$TMPFILE"' EXIT |