Bash 01 - Getting Started
# 前言
# Hello World
TBD
.sh
#!/bin/bash
chmod +x FILENAME
# 基本概念
# Shell 和 Bash
shell (i.e. Unix shell) 既是命令解释器又是编程语言:作为命令解释器,shell 为丰富的 GNU 实用程序集提供了用户接口;作为编程语言,shell 允许组合这些实用程序,创建包含命令的文件。
shell 的大部分功能(和复杂性)都归功于它们的嵌入式编程语言。与任何高级语言一样,shell 提供变量、流控制构造、引用和函数,但不同的是,shell 提供的高级特性是专用于交互使用(包括 job control 、command line editing 、command history 和 alias)而不是增强编程语言。
在 Linux 系统上最常用的 shell 主要是 Dash 和 Bash 。二者都满足 POSIX 规范,但有所不同:
Dash (Debian Almquist Shell) 是现今 Linux 操作系统的默认 shell ,比 Bash 要小得多,脚本的执行速度也更快。
GNU Bash (GNU Bourne-Again Shell) 在 Dash 的基础上增加了许多扩展功能,但要复杂得多,且执行速度较慢。
有关各类 shell 的具体发展史还请参照其它资料,此处不再赘述。
(TBD. echo $SHELL
)还有各种其他的 shell 解释器可用,例如 Korn shell、C shell 等等。由于这个原因,在 shell 脚本文件中显式地指定所用的解释器是一个很好的做法。
(TBD. 参考 https://blog.csdn.net/weixin_39212776/article/details/81079727 和 https://blog.csdn.net/hansel/article/details/9817129 另起一篇文章 Dash、Bash 兼容性问题)
-TBD 大括号展开是bash特有的特性,传统的sh是不支持的。我们可以通过使用set +B来关闭花括号扩展功能,相反的,用set -B使能该功能。
# Shell 终端
从本质上讲,shell 终端也是一个运行在操作系统之上的、普通的 C 程序,它以交互式命令解释器的形式工作——接收来自键盘的输入,将其视为命令进行解析,执行命令,并在屏幕上输出反馈。举个最简单的例子,在终端上输入 echo hello
并敲下回车,就是指示 shell 以一个参数 hello
执行 echo
命令。
事实上,在 shell 中执行命令就是在执行一段 shell 可以解释执行的简短代码,类似于那些解释型的高级编程语言。
除了少部分内置命令和语言关键字外,你在 shell 上执行的大部分命令的功能都和 shell 本身无关,例如 echo
命令可能是由路径为 /bin/echo
的程序实现的,shell 只是负责在文件系统中找到命令所对应的实现(可执行的二进制文件或脚本,等等),然后执行它。
但是 shell 是如何知道去哪里寻找 echo
的呢?暴力搜索整个文件系统显然是不可能的。如果在当前目录下找不到对应的文件,则 shell 会查询环境变量 $PATH
,在 $PATH
中搜索由 :
所分割的一系列路径,在每个路径所对应的目录下搜索以 echo
为名的可执行文件。
可以利用
which
命令来确定指令具体调用的是位于何处的程序,例如which echo
。可以直接指定程序的路径来执行指令。
# Shell 脚本
shell 脚本 (shell script) 指的是包含 shell 命令的文本文件,通常为 .sh
格式。当我们需要自动化地执行大量的 shell 命令时,编写 shell 脚本是必不可少的工作。
脚本编写准则:
如果脚本的第一行以 #!
开头,则第一行内的其余部分为程序指定一个解释器,并根据操作系统为该解释器指定一个或多个可选参数。Bash 脚本通常以 #!/bin/bash
作为第一行。
#!/bin/bash
echo 'hello, world'
大多数版本的 UNIX 操作系统都将此作为操作系统命令执行机制的一部分,所以我们在编写脚本时也应遵循此准则。此外,不仅是 Bash ,编写诸如 sh 、awk 、Perl 以及其它一些语言的脚本文件时也是类似的做法。
Bash 解释器可能会安装在 /bin
以外的目录中。可以使用 env
在 $PATH
中找到第一次出现的 Bash ,只需以 #!/usr/bin/env bash
作为脚本文件的第一行即可。这提高了脚本的可移植性。
脚本的执行:
可以利用 bash
命令来执行一个 Bash 脚本,例如 bash hello.sh arg1 arg2
。此时将忽略前文所述的 #!
规则,而强制解析为 Bash 语言。
执行 shell 脚本的过程和在 shell 上执行命令类似:先查询当前目录,然后查询环境变量 $PATH
,直到找到名称符合的文件,就从 shell 脚本中读取并执行命令,然后退出。
为方便起见,通常我们会用 chmod
命令赋予 shell 脚本可执行权限,例如 chmod +x ./hello.sh
,这样一来 filename [arguments]
将和 bash filename [arguments]
等价。
# 基础语法
为便于初学者入门,此处的基础语法中不会提及哪怕稍微有些复杂的语法特性,有关 shell 语法的更多内容详见后续文档。
# 命令
一个最简单的 shell 命令 (shell command) 是由空格分隔的一系列单词:第一个单词指定要执行的命令,其余单词为该命令的参数。shell 命令终止于首个遇到的控制运算符。
- 例如 shell 命令
echo a b c
指定以三个参数a
b
c
执行echo
命令,并以换行符结束。
按照标准,命令的 退出状态 (exit status) 为 表示正确结束,为非零值表示执行结果异常,不同的非零值代表不同的失败原因。Bash 的内置命令均遵循此标准,因而我们编写的脚本文件最好也遵循同样的标准,以免产生混淆。
复杂的 shell 命令由多个简单命令通过多种方式(包括管道、流重定向、条件、循环等)组合而成。
# 参数
参数 (parameter) 是存储 值 (value) 的实体,它可以是 名称 (name) 、数字或其它具有特殊语义的特殊字符。有关各种参数的内容此处略去不表,仅介绍 shell 参数中最基础的变量和位置参数。
变量 (variable) 是由名称表示的参数,也是最基本、最常用的参数,其概念类似于一般高级语言中的变量。
变量的赋值语法格式如下:
name=[value]
如果未给出值 value
,则对名为 name
的变量赋值空字符串。也可以用内置命令 set
来设置变量值。
可通过 $name
或 ${name}
引用名为 name
的变量的值。获取未赋值的变量会得到空字符串而不会报告错误(除非另行设置 Bash 的内置选项)。在不存在语法解析的二义性时,可以将大括号省略,否则变量引用的结果可能会不符合预期,详见示例代码。
如果一个参数已被赋值,则称该参数已经被 设置 (set) 。一旦变量被设置,就只能用内置命令 unset
来取消设置。注意空字符串也是有效的值。
如下为变量基本使用方法的示例代码。
#!/bin/bash
echo "a = $a"
a=hello
echo "a = $a"
echo 'a = $a' # will not get $a in ''
echo $a
echo "foo$abar" # will not get $a but try to get $abar
echo "foo${a}bar"
unset a
echo "a = $a"
上述代码的输出结果如下:
a =
a = hello
a = $a
hello
foo
foohellobar
a =
# 位置参数
位置参数 (positional parameter) 是由一位或多位数字表示的参数,可通过 $N
或 ${N}
引用,其中 N
为正整数(若为两位数,则引用时不可省略大括号,例如 ${12}
)。注意 $0
在概念上不属于位置参数,而属于特殊参数。
位置参数在调用时被依次赋值为 shell 的各个参数,N
即表示第 个参数。在执行 shell 函数时,位置参数会被临时替换为函数调用时传入的各个参数。
位置参数由调用的上下文决定,不能用赋值语句直接赋值,只能用内置命令 set
和 shift
来设置和取消设置。
假设当前目录下有可执行的脚本文件 sample.sh
包含如下内容:
#!/bin/bash
echo "$1, $2, $3."
调用 ./sample.sh foo bar
的输出结果如下:
foo, bar, .
# Bash 条件表达式
- 01
- Reading Papers - Kernel Concurrency06-01
- 02
- Linux Kernel - Source Code Overview05-01
- 03
- Linux Kernel - Per-CPU Storage05-01