Bash简单小结

简单介绍 && 为什么要写 Bash

bash 是一个用户态人机接口

使用理由

  1. 工作需要没得选, 比如嵌入式裁剪的 rtos 只能提供一个简单的 posix 兼容的原版 sh
  2. 相较于 python 等重量级选手, bash 轻便快速, 与 Linux 下的各种基础命令交互也简单 (反正都是字符串)

基础知识

shebang

shebang 就是文件开头的 #! 开头, 用来提示文件应该用哪个解释器 (interpreter)

1
2
3
#!/bin/sh

# do something...

听起来很简单…但是 为啥很多时候会我们觉得 bash 很恶心, 因为他的确讲究很多 假设你运行一个脚本 bash test.sh, test.sh 的内容是就是上面这段内容, 那么脚本到底用 bash, 还是 sh 呢? 可以用 这个命令验证 cat /proc/$$/cmdline

写 shebang 也有个小 trick, 尽量写成 /usr/bin/env bash, 因为 /bin/bash 不一定存在

setopt

这些东西很常见, 但是第一次遇见一定是不知所云的, 稍微 google 一下就知道这些标志的意思了

1
2
3
# set -x 表示打印出所有执行的命令
# set -u 等价于 set -o nounset,  意为遇到不存在的变量要报错并退出
# set -e 和 set -o nopipefall 表示命令报错退出和pipe中有一个报错则整个pipe报错

常见的就这几个, 遇到不懂的也可以当场 google, 不丢人哈

字符串

bash 中是没有类型系统的, 默认所有的变量都是字符串, 下面说一些小知识

  • 大部分特殊字符比如 .+*? 这种符号在引号中是没有特殊含义的, 但是双引号中 \`$ 这三个字符是会被转义的, 单引号中众生平等没有任何转义
  • echo-e 选项是 respect 转义字符, -n 选项是取消默认的换行符, 不区分单双引号

常见的特殊符号

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# | 管道

# > 输出重定向

# < 输入重定向

# >> 输出重定向,  append

# << here document
## eg.
cat << EOF
aaa
EOF
# output aaa

# <<< 把 string 给前一个命令,  相当于 pipe
## eg.
cat <<< "aaa"
## 等价于
echo "aaa" | cat

# & 0 1 2 以及经典后台运行组合
nohup cmd 1>/dev/null 2>&1 &
## 解释: & 代表该命令后台运行
## 0 1 2 分别代表stdin,  stdout,  stderr
## nohup: Allows for a process to live when the terminal gets killed.

# $0 $1 $2 $# 代表当前cmdline的第0, 1, 2个参数,  以及参数总数

# $? $$ 表示上一条命令的退出码和当前命令pid

# $@ 和 $* 代表全部参数,  写得好累,  解释略了

数字

上文说到 bash 中没有变量系统, 所有内容都是字符串, 那我要算术运算的时候咋办呢 比如

1
2
3
4
a=1
a=$a+2
echo $a
#output: 1+2

下面是几种正确使用方法

  • declare -i var 上面这个表达式会把 var 声明为一个整数类型, 这样之后, 写 var=$var+1, 是改变不了 var 的内容的, 但是要注意即使这样也达不到想要的效果
  • `expr $var+1
  • var=$(($var+2))

条件表达式

bash 中常见判断的就 if 和 case, 这里只介绍下 if 模板如下:

1
2
3
4
5
6
7
if  expression ; then
do something
elif  ... ; then
do ...
else  ... ; then
do ...
fi

这里的 elifelse 可以省略, expression 有一些常见形式, 举例如下

1
2
3
4
#  -f file 判断file是否存在,  且为普通文件
#  -z $var 判断var是否是长度为0的str
#  $a == $b 这里==可以是=,  含义相同,  判断str是否相等,  != 则判断是否不等
#  $a -eq $b 比较数字是否相等,  其他类似的有 -ne -gt -lt

[ ] 和 [[ ]] 的区别

很多地方都会说, 尽量用 [[ ]] 而不要用 [ ], 为什么呢 单中括号 [ ] 的类型是 builtin, 存在于/bin 目录下, 等于 test, 其参数会被 shell 解释 双中括号 [[ ]] 是 keyword, 是 bash 的扩展, 其作为一个整体被 shell 解释

1
2
3
4
5
6
7
8
a=a
b=b
 $a < $b  && echo true
# true
[ $a < $b ] && echo true
# bash: b: No such file or directory
[ $a \< $b ] && echo true
true

另外, [[ ]] 支持 && || 之类的表达式, 他的兄弟并不支持

循环和数组

网上抄了几个例子, 自行感受吧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

while condition; do
  commands
done

# 打印参数列表
for i in "$@" ; do
  echo "$i"
done

# C-style
for (( expression1; expression2; expression3 )); do
  commands
done


array=(value1 value2 ... valueN)
array=([2]=c [0]=a [1]=b)
array[0]=a

# 数组遍历时,  最好用以下形式,  注意用 @ 和双引号,  没有双引号则会被成员中的空格分隔
# 用 * 时,  和用 @ 的行为相反
for i in "${names[@]}"; do
  echo $i
done

# 读数组
echo ${array[0]} # 没有下标则为0号元素
# 读全部数组
echo ${array[@]} # 用*也可以
# 长度
echo ${#array[@]} # 用*也可以,  如果用的是数字下标,  表示该成员长度
# 删除
unset array[2] # 没有中括号表述删除整个数组
# 字典
declare -A colors
colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"
echo ${colors["blue"]}

函数和 source

同上, 抄了几个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 第一种
fn() {
  # codes
}
# 第二种
function fn() {
  # codes
}
# 第二种可能部分shell不兼容,  尽量用第一种

# $0 $1 $# $@ $* 这些东西和全局的符号类似,  除了$0表示脚本名外,  其他都表示函数的参数
# 函数内的变量是默认全局的,  local 关键字可以声明函数作用域内的变量

这里写的是 bash 的基础中的基础, bash 中的 tricks 也有很多, 见参考文档 1, 数量很吓人

一些 Bash 中比较容易犯的错误

  1. [ ] 两侧的括号忘记写
  2. windows 下面写的脚本放到 linux 中执行, 忘记用 dos2unix
  3. 中的值没有正确使用双引号, 这会导致模式匹配而非字符串匹配
  4. (( )) 来做算术运算而非
  5. 在同一个管道中同时读写 cat file | sed s/foo/bar/ > file

总之, 要认识到 bash 中很多地方会用 $IFS (默认空格) 做分隔符, 这会导致异常行为, 要避免这种情况, 需要你正确地使用双引号, 很多细节可以见参考文档 2

posix shell 和 bash 区别

posix shell 这位爷更是重量级, 他是各类衍生 shell 的老祖宗, 所以很多 bash 特有的语法他是不认的, 大概正常人都巴不得脚本都用 python 写来减少心智负担, 没办法, 不得不用的时候终究是逃不掉的 内容自己写好累, 参考下 chromium 的 coding style 的内容吧

  • Tests using the double-bracket test operator, e.g. "${SOMEVAR}" == "test value"
  • Arrays, e.g. foo=( 1 2 3 )
  • The declare built-in command
  • Using the function keyword to define a shell function (you should never use this though, even with bash)
  • Brace expansion, e.g. echo {a,b}{c,d}

what happens when open a shell

用户登录

登录 Session 一般进行整个系统环境的初始化, 启动的初始化脚本依次如下.

  • /etc/profile:所有用户的全局配置脚本.
  • /etc/profile.d 目录里面所有 .sh 文件
  • ~/.bash_profile:用户的个人配置脚本.如果该脚本存在, 则执行完就不再往下执行.
  • ~/.bash_login:如果 ~/.bash_profile 没找到, 则尝试执行这个脚本(C shell 的初始化脚本).如果该脚本存在, 则执行完就不再往下执行.
  • ~/.profile:如果 ~/.bash_profile~/.bash_login 都没找到, 则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本).

非登录, 比如手敲一个 bash 命令

  • /etc/bash.bashrc
  • ~/.bashrc

一些常见的技巧

在 bash 中 trap 各类信号来做 cleanup

TODO

和 rg/fd/sed/awk 等协同使用

TODO

参考文档

Advanced Bash-Scripting Guide

Bash 脚本教程

BashPitfalls

chromium doc

0%