BASH坑杀无算

BASH坑杀无算

原文见:https://gitee.com/laokz/OS-kernel-test/blob/master/memo-bash.md

本文只讨论非交互模式的脚本编程,只记录作者认为重要、迷糊、有用的东西,以便理解、避坑、备忘,最终还是要以manpage为权威参考。本文属于BASH高级编程。

BASH有很多小技巧和捷径,能用通常理解的形式就别用这些小技巧,自找麻烦。

用词说明

  • 模式、模式串、模式匹配,均指shell通配符模式,以区别于正则表达式
  • 引起来、引号,除特别说明外,单引号、双引号和\转义都算
  • 字、word,字符外最小的shell处理单元、单词、token(不是很准确),由未引起来的| & ; ( ) < > 空白符分隔。除特别说明外,可以进行各种展开
  • 本文不加区分地交替使用shell、bash,都指的是本文主角–BASH

第0节 对抗各种坑的思想方法


  • Shell是个宏处理器。宏意味着文本和符号能展开组合成新的文本和符号
  • 展开无处不在
  • 引号约束展开
  • 象BASH一样思考:
    1. 遵从引号规则,处理字符,读入字
    2. 遵从引号规则,执行各种各样的展开
    3. 遵从引号规则,再拆成一个个字
    4. 重定向
    5. 执行命令

第1节 引号规则


引号规则用于去除字符的特殊含义,回归字面本意,也影响字拆分效果。不象C语言中的引号,BASH没有字符、字符串的引号区分。引号规则有三种机制:

1. 转义字符\用法类同C语言

2. 单引号去除所有字符的特殊含义,也就不会有任何展开拆分

  • 无例外,\'也不行
  • 有特殊用法$'string',string里允许使用C语言的转义序列

3. 双引号去除大部分字符的特殊含义,包括通配符

  • 绝对例外:$ ` 仍保持展开含义,因此双引号内允许$类展开
if [[ abc == "a*" ]]; then echo yes; else echo no; fi	# 输出:no
  • 相对例外:\后跟 $ ` " \ newline时仍保持转义含义
echo "\n"       # 输出:\n  这里\是个普通字符
echo "\\n"      # 输出:\n  这里\\转义成普通字符\

第2节 命令


由简至繁分为三类:

1. 简单命令

除了内建命令、外部命令外,还有:

  • 函数算简单命令。定义时还可以指定重定向,调用时也可以前缀变量赋值
  • 管道算简单命令。与管道中命令重定向的关系:管道建立在前,重定向在后,因此命令重定向起决定作用(数据流动方向)。取反管道的!要置于第一条命令前。|&将stderr也进行pipe。管道中的每个命令都在子shell中执行,其中shopt的lastpipe选项可以使最后一个命令运行在当前环境。set的pipefail选项可以让管道提前失败,而不是默认的最后一条命令失败才失败
ls >/dev/null |wc   # 管道建立在前,重定向在后,因此wc什么也得不到

2. 系列命令

简单命令用; & && ||进行的排列,; &的优先级低,符合直觉。;用于在一行中书写系列命令,否则回车换行即可。

# 分号前面是一整个命令,然后它又分成前后两个命令,因此显示b,而不是什么也不显示
false && echo a; echo b

3. 复合命令

复合命令是对前述命令更复杂的组合,组合后相当于单条命令,因此可以进行重定向,作用于其中的每条命令。重定向无意义的命令会忽略掉这个重定向,要小心那些有意义的,特别是输入重定向时。有5种:

1)(小括号命令分组),在子shell中执行
2){ 大括号命令分组; },在当前环境中执行

注意 {} 与命令间的空格和分号(或回车)是必须的。

3)((算术表达式))运算命令

算术运算符及其规则同C语言,包括用括号改变运算优先级、逗号分隔多个表达式、常量的各种进制表示等。几个注意事项:

  • 这是个shell命令,相当于C的statement而不是expression,计算结果为0时命令执行状态为1–失败,因此必要时要前缀上!,或后缀上|| true,又或者表达式缀上,1,以防止脚本退出
  • 变量不需要前缀$,unset或为null时按0计算。所有接受算术运算的地方都如此,比如:数组下标计算、整数变量赋值时
  • 表达式里可以使用命令替换
  • BASH不支持浮点数
set -e
i=0
((++i))         # 看看++
echo prefix: i=$i exit-code=$?
i=0
((i++))
echo postfix i=$i exit-code=$?
4)[[ 条件表达式 ]]测试命令

表达式可以测试文件(符号链接测试的是目标文件)、变量、字符串属性,还可以测试set的选项是否设置,算术比较最好由上面的命令来测试。注意[[]]与表达式间的空格、操作符与操作数间的空格是必须的。表达式可用()改变优先级,! && ||进行逻辑运算。不要用过时的test、[]命令。

  • 表达式内不进行3种展开:{}大括号展开、字拆分、路径展开
v="a b"; if [[ $v == a ]]; then echo yes; else echo no; fi	# 输出:no
  • == 和 != 的右侧有通配符时进行模式匹配
v="a b"; if [[ $v == a* ]]; then echo yes; else echo no; fi	# 输出:yes
  • =~ 匹配右侧的正则表达式。注意这种匹配同shell模式一样,受引号规则约束,且\在正则表达式和shell展开都有特殊意义,shell优先,因此别写得太复杂!必须要写时,用变量存储正则表达式是个简单方法。这种匹配也受shopt的nocasematch选项影响,默认大小写敏感
shopt -s nocasematch
if [[ "abcxyZ" =~ ^a.*z$ ]]; then echo yes; fi		# 输出:yes
  • 正则表达式可以进行分组匹配,并用BASH_REMATCH数组变量存储匹配结果,第0个是整个匹配的串,其它的是各组匹配的。匹配是最大匹配
v="Zabc123+-Z"
if [[ $v =~ ([^Z].*)([0-9]+)([+-]{2}) ]]; then
    for ((i=0; i<4; i++)); do
        echo -n ${BASH_REMATCH[i]}' '
    done
fi			# 输出:abc123+- abc12 3 +- 
5)控制块命令

包括for while until if case select。这里只说下case用法:

case word in pattern[|pattern] ...) list;; ... esac
  • word和pattern不进行3种展开:{}大括号展开、字拆分、路径展开
  • pattern是shell模式,为*时可以当作default,当然应作为最后一个分支
  • ;;相当于break,也可用;&相当于fall through,;;&指示在后面的分支上继续匹配

第3节 命令执行


命令解析执行流程

  1. 处理转义字符
  2. 从左至右解析成字
  3. 确定命令类型
  4. 收集赋值和重定向,待用
  5. 执行其它各种展开
展开后确有命令 展开后没有命令
5.创建命令执行环境(不确定,但必在此或前发生)
6.展开重定向并执行 5.展开赋值并执行,加入当前环境
7.展开赋值并执行,不加入当前环境 6.展开重定向并执行
8.执行命令 7.如果上述没出错也无命令展开,就成功退出

注意:命令前缀的赋值是临时性的,只影响命令运行时,不影响当前环境;这个临时赋

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值