📝 Writing docs.

pull/11/head
Zhang Peng 2017-11-20 17:32:37 +08:00
parent e06d7c4c53
commit 71df16456b
2 changed files with 210 additions and 228 deletions

Binary file not shown.

View File

@ -1,110 +1,149 @@
# Shell 快速指南
![image.png](http://upload-images.jianshu.io/upload_images/3101171-4b6e2a6b64bb4722.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
```
███████╗██╗ ██╗███████╗██╗ ██╗
██╔════╝██║ ██║██╔════╝██║ ██║
███████╗███████║█████╗ ██║ ██║
╚════██║██╔══██║██╔══╝ ██║ ██║
███████║██║ ██║███████╗███████╗███████╗
```
## 概述
Bash 是一个 Unix Shell作为 [Bourne shell](https://en.wikipedia.org/wiki/Bourne_shell) 的 free software 替代品,由 [Brian Fox](https://en.wikipedia.org/wiki/Brian_Fox_(computer_programmer)) 为GNU项目编写。它发布于1989年在很长一段时间Linux 系统和 macOS 系统都把 Bash 作为默认的 shell。
### 什么是 shell
那么我们学习这个有着30多年历史的东西意义何在呢答案很简单这是当今最强大、可移植性最好的为所有基于 Unix 的系统编写高效率脚本的工具之一。这就是你需要学习 bash 的原因。
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁
## Shells与模式
Shell 既是一种命令语言,又是一种程序设计语言。
bash shell有交互和非交互两种模式
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务
### 交互模式
Ken Thompson 的 sh 是第一种 Unix ShellWindows Explorer 是一个典型的图形界面 Shell。
Ubuntu用户都知道在Ubuntu中有7个虚拟终端。 桌面环境接管了第7个虚拟终端于是按下`Ctrl-Alt-F7`,可以进入一个操作友好的图形用户界面。
### 什么是 shell 脚本
即便如此,还是可以通过`Ctrl-Alt-F1`来打开shell。打开后熟悉的图形用户界面消失了一个虚拟终端展现出来。
Shell 脚本shell script是一种为 shell 编写的脚本程序,一般文件后缀为 `.sh`
业界所说的 shell 通常都是指 shell 脚本,但 shell 和 shell script 是两个不同的概念。
### Shell 环境
Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Shell 的解释器种类众多,常见的有:
- [sh](https://www.gnu.org/software/bash/) - 即 Bourne Shell。sh 是 Unix 标准默认的 shell。
- [bash](https://www.gnu.org/software/bash/) - 即 Bourne Again Shell。bash 是 Linux 标准默认的 shell。
- [fish](https://fishshell.com/) - 智能和用户友好的命令行 shell。
- [xiki](http://xiki.org/) - 使 shell 控制台更友好,更强大。
- [zsh](http://www.zsh.org/) - 功能强大的 shell 与脚本语言。
#### 指定脚本解释器
在 shell 脚本,`#!` 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。`#!` 被称作[shebang也称为 Hashbang ](https://zh.wikipedia.org/wiki/Shebang)。
所以,你应该会在 shell 中,见到诸如以下的注释:
- 指定 sh 解释器
```sh
#!/bin/sh
```
- 指定 bash 解释器
```sh
#!/bin/bash
```
> **注意**
>
> 上面的指定解释器的方式是比较常见的,但有时候,你可能也会看到下面的方式:
>
> ```sh
> #!/usr/bin/env bash
> ```
>
> 这样做的好处是,系统会自动在 `PATH` 环境变量中查找你指定的程序(本例中的`bash`)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的`PATH`变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的`bash`,我们可能将其路径添加到`PATH`中,来“隐藏”老版本。如果直接用`#!/bin/bash`,那么系统会选择老版本的`bash`来执行脚本,如果用`#!/usr/bin/env bash`,则会使用新版本。
### 模式
shell 有交互和非交互两种模式。
#### 交互模式
> 简单来说,你可以将 shell 的交互模式理解为执行命令行。
看到形如下面的东西说明shell处于交互模式下
```
```sh
user@host:~$
```
接着便可以输入一系列Unix命令比如`ls``grep``cd``mkdir``rm`,来看它们的执行结果。
接着,便可以输入一系列 Linux 命令,比如 `ls``grep``cd``mkdir``rm` 等等
之所以把这种模式叫做交互式是因为shell直接与用户交互。
#### 非交互模式
直接使用虚拟终端其实并不是很方便。设想一下,当你想编辑一个文档,与此同时又想执行另一个命令,这种情况下,下面的虚拟终端模拟器可能更适合:
> 简单来说,你可以将 shell 的非交互模式理解为执行 shell 脚本。
- [GNOME Terminal](https://en.wikipedia.org/wiki/GNOME_Terminal)
- [Terminator](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator))
- [iTerm2](https://en.wikipedia.org/wiki/ITerm2)
- [ConEmu](https://en.wikipedia.org/wiki/ConEmu)
在非交互模式下shell 从文件或者管道中读取命令并执行。
### 非交互模式
在非交互模式下shell从文件或者管道中读取命令并执行。当shell解释器执行完文件中的最后一个命令shell进程终止并回到父进程。
当 shell 解释器执行完文件中的最后一个命令shell 进程终止,并回到父进程。
可以使用下面的命令让shell以非交互模式运行
```
```sh
sh /path/to/script.sh
bash /path/to/script.sh
```
上面的例子中,`script.sh`是一个包含shell解释器可以识别并执行的命令的普通文本文件`sh`和`bash`是shell解释器程序。你可以使用任何喜欢的编辑器创建`script.sh`vimnanoSublime Text, Atom等等
除此之外,你还可以通过`chmod`命令给文件添加可执行的权限,来直接执行脚本文件:
```
chmod +x /path/to/script.sh
```sh
chmod +x /path/to/script.sh #使脚本具有执行权限
/path/to/test.sh
```
这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:
```
```sh
#!/bin/bash
echo "Hello, world!"
```
如果你更喜欢用`sh`,把`#!/bin/bash`改成`#!/bin/sh`即可。`#!`被称作[shebang](https://zh.wikipedia.org/wiki/Shebang)。之后,就能这样来运行脚本了:
```
/path/to/script.sh
```
上面的例子中,我们使用了一个很有用的命令`echo`来输出字符串到屏幕上。
我们还可以这样来使用shebang
```
#!/usr/bin/env bash
echo "Hello, world!"
```
这样做的好处是,系统会自动在`PATH`环境变量中查找你指定的程序(本例中的`bash`)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的`PATH`变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的`bash`,我们可能将其路径添加到`PATH`中,来“隐藏”老版本。如果直接用`#!/bin/bash`,那么系统会选择老版本的`bash`来执行脚本,如果用`#!/usr/bin/env bash`,则会使用新版本。
### 返回值
每个命令都有一个**返回值****返回状态**或者**退出状态**)。命令执行成功的返回值总是`0`零值执行失败的命令返回一个非0值错误码。错误码必须是一个1到255之间的整数。
在编写脚本时,另一个很有用的命令是`exit`。这个命令被用来终止当前的执行并把返回值交给shell。当`exit`不带任何参数时,它会终止当前脚本的执行并返回在它之前最后一个执行的命令的返回值。
一个程序运行结束后shell将其**返回值**赋值给`$?`环境变量。因此`$?`变量通常被用来检测一个脚本执行成功与否。
与使用`exit`来结束一个脚本的执行类似,我们可以使用`return`命令来结束一个函数的执行并将**返回值**返回给调用者。当然,也可以在函数内部用`exit`,这 *不但* 会中止函数的继续执行,*而且* 会终止整个程序的执行。
## Shell 编程
在 bash 脚本文件中的第一行被叫做 `shebang`。这一行决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入`sh`, `bash`, `python`, `php`等。
> 由于 bash 是 Linux 标准默认的 shell可以说 bash 是 shell 编程的基础。
>
> 所以,下面将全部基于 bash 来讲解 shell 编程。
>
> 此外,本篇章主要介绍的是 shell 编程的语法,对于 linux 指令不做任何介绍。
```
### 解释器
前面虽然两次提到了`#!` ,但是本着重要的事情说三遍的精神,这里再强调一遍:
在 shell 脚本,`#!` 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。`#!` 被称作[shebang也称为 Hashbang ](https://zh.wikipedia.org/wiki/Shebang)。
`#!` 决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入`sh`, `bash`, `python`, `php`等。
**示例:**
```sh
# 以下两种方式都可以指定 shell 解释器为 bash第二种方式更好
#!/bin/bash
#!/usr/bin/env bash
```
### 注释
脚本中可以包含 注释。注释是特殊的语句,会被 shell 解释器忽略。它们以 `#` 开头,到行尾结束。
shell 语法支持注释。注释是特殊的语句,会被 shell 解释器忽略。它们以 `#` 开头,到行尾结束。
示例:
**示例:**
```bash
#!/bin/bash
@ -124,9 +163,11 @@ Bash 中没有数据类型bash 中的变量可以保存一个数字、一个
#### 局部变量
**局部变量**是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
> **局部变量**是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
>
> 局部变量可以**用 `=` 声明**(作为一种约定,变量名、`=`、变量的值之间**不应该**有空格),其值可以**用`$` 访问**到。
局部变量可以用 `=` 声明(作为一种约定,变量名、`=`、变量的值之间**不应该**有空格),其值可以用`$` 访问到。举个例子:
**示例:**
```bash
username="zhangpeng" ### 声明变量
@ -134,7 +175,8 @@ echo $username ### 输出变量的值
unset username ### 删除变量
```
我们可以用 `local` 关键字声明属于某个函数的局部变量。这样声明的变量会在函数结束时消失。
> 可以**用 `local` 关键字声明属于某个函数的局部变量**。这样声明的变量会在函数结束时消失。
>
```bash
local local_var="I'm a local value"
@ -142,13 +184,15 @@ local local_var="I'm a local value"
#### 环境变量
**环境变量**是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是 `export` 关键字。
> **环境变量**是对当前 shell 会话内所有的程序或脚本都可见的变量。
>
> 创建它们跟创建局部变量类似,但使用的是 `export` 关键字。
```bash
export GLOBAL_VAR="I'm a global value"
export global_var="I'm a global value"
```
bash 中有非常多的环境变量。你会非常频繁地遇到它们,这里有一张速查表,记录了在实践中最常见的环境变量。
常见的环境变量:
| 变量 | 描述 |
| --------- | --------------------------- |
@ -164,7 +208,9 @@ bash 中有非常多的环境变量。你会非常频繁地遇到它们,这里
#### 位置参数
**位置参数**是在调用一个函数并传给它参数时创建的变量。下表列出了在函数中,位置参数变量和一些其它的特殊变量以及它们的意义。
> **位置参数**是在调用一个函数并传给它参数时创建的变量。
位置参数变量表:
| 变量 | 描述 |
| -------------- | ----------------- |
@ -175,6 +221,8 @@ bash 中有非常多的环境变量。你会非常频繁地遇到它们,这里
| `$#` | 不包括`$0`在内的位置参数的个数 |
| `$FUNCNAME` | 函数名称(仅在函数内部有值) |
**示例:**
在下面的例子中,位置参数为:`$0='./script.sh'``$1='foo'``$2='bar'`
```bash
@ -232,14 +280,14 @@ echo $now ### 19:08:26
在bash中执行算数运算是非常方便的。算数表达式必须包在`$(( ))`中。算数扩展的格式为:
```
```bash
result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result ### 9
```
在算数表达式中,使用变量无需带上`$`前缀:
```
```bash
x=4
y=7
echo $(( x + y )) ### 11
@ -251,24 +299,24 @@ echo $(( x + y )) ### 13
单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子:
```
```bash
echo "Your home: $HOME" ### Your home: /Users/<username>
echo 'Your home: $HOME' ### Your home: $HOME
```
当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用`echo`来输出用户的输入:
```
```bash
INPUT="A string with strange whitespace."
echo $INPUT ### A string with strange whitespace.
echo "$INPUT" ### A string with strange whitespace.
```
调用第一个`echo`时给了它5个单独的参数 —— $INPUT被分成了单独的词`echo`在每个词之间打印了一个空格。第二种情况,调用`echo`时只给了它一个参数(整个$INPUT的值包括其中的空格
调用第一个`echo`时给了它5个单独的参数 —— `$INPUT` 被分成了单独的词,`echo`在每个词之间打印了一个空格。第二种情况,调用`echo`时只给了它一个参数(整个$INPUT的值包括其中的空格
来看一个更严肃的例子:
```
```bash
FILE="Favorite Things.txt"
cat $FILE ### 尝试输出两个文件: `Favorite``Things.txt`
cat "$FILE" ### 输出一个文件: `Favorite Things.txt`
@ -294,19 +342,19 @@ array=([2]=val [0]=val [1]=val)
array=(val val val)
```
#### 数组扩展
#### 获取数组元素
单个数组元素的扩展跟普通变量的扩展类似:
- **获取数组的单个元素:**
```bash
echo ${colors[1]} ### Green
echo ${array[1]}
```
整个数组可以通过把数字下标换成`*`或`@`来扩展:
- **获取数组的所有元素:**
```bash
echo ${colors[*]} ### Red Green Blue
echo ${colors[@]} ### Red Green Blue
echo ${array[*]}
echo ${array[@]}
```
上面两行有很重要(也很微妙)的区别,假设某数组元素中包含空格:
@ -351,19 +399,23 @@ printf "+ %s\n" "${colors[@]}"
在引号内,`${colors[@]}`将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。
#### 数组切片
除此之外,可以通过 *切片* 运算符来取出数组中的某一片元素:
- **访问数组的部分元素:**
```bash
echo ${colors[@]:0:2} ### Red Dark Green
echo ${array[@]:0:2}
```
在上面的例子中,`${colors[@]}`扩展为整个数组,`:0:2`取出了数组中从0开始长度为2的元素。
在上面的例子中,`${array[@]}` 扩展为整个数组,`:0:2`取出了数组中从0开始长度为2的元素。
#### 获取数组长度
```bash
echo ${#array[*]}
```
#### 向数组中添加元素
向数组中添加元素也非常简单。复合赋值在这里显得格外有用。我们可以这样做:
向数组中添加元素也非常简单:
```bash
colors=(Yellow "${colors[@]}" Pink Black)
@ -373,7 +425,7 @@ echo ${colors[@]}
# Yellow Red Dark Green Blue Pink Black
```
上面的例子中,`${colors[@]}`扩展为整个数组,并被置换到复合赋值语句中,接着,对数组`colors`的赋值覆盖了它原来的值。
上面的例子中,`${colors[@]}` 扩展为整个数组,并被置换到复合赋值语句中,接着,对数组`colors`的赋值覆盖了它原来的值。
#### 从数组中删除元素
@ -400,9 +452,9 @@ echo ${colors[@]}
| * | 乘法 | `expr $a \* $b` 结果为 200。 |
| / | 除法 | `expr $b / $a` 结果为 2。 |
| % | 取余 | `expr $b % $a` 结果为 0。 |
| = | 赋值 | a=$b 将把变量 b 的值赋给 a。 |
| == | 相等。用于比较两个数字,相同则返回 true。 | [ $a == $b ] 返回 false。 |
| != | 不相等。用于比较两个数字,不相同则返回 true。 | [ $a != $b ] 返回 true。 |
| = | 赋值 | `a=$b` 将把变量 b 的值赋给 a。 |
| == | 相等。用于比较两个数字,相同则返回 true。 | `[ $a == $b ]` 返回 false。 |
| != | 不相等。用于比较两个数字,不相同则返回 true。 | `[ $a != $b ]` 返回 true。 |
**注意:**条件表达式要放在方括号之间,并且要有空格,例如: **[$a==$b]** 是错误的,必须写成 **[ $a == $b ]**。
@ -445,14 +497,14 @@ fi
下表列出了常用的关系运算符,假定变量 a 为 10变量 b 为 20
| 运算符 | 说明 | 举例 |
| ---- | ----------------------------- | ----------------------- |
| -eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
| -ne | 检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
| -gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
| -lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
| -ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
| -le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
| 运算符 | 说明 | 举例 |
| ---- | ----------------------------- | ------------------------- |
| -eq | 检测两个数是否相等,相等返回 true。 | `[ $a -eq $b ] `返回 false。 |
| -ne | 检测两个数是否相等,不相等返回 true。 | `[ $a -ne $b ]` 返回 true。 |
| -gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | `[ $a -gt $b ]` 返回 false。 |
| -lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | `[ $a -lt $b ]` 返回 true。 |
| -ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | `[ $a -ge $b ]` 返回 false。 |
| -le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | `[ $a -le $b ] `返回 true。 |
**示例:**
@ -502,11 +554,11 @@ fi
下表列出了常用的布尔运算符,假定变量 a 为 10变量 b 为 20
| 运算符 | 说明 | 举例 |
| ---- | ---------------------------------- | ------------------------------------- |
| ! | 非运算,表达式为 true 则返回 false否则返回 true。 | [ ! false ] 返回 true。 |
| -o | 或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。 |
| -a | 与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。 |
| 运算符 | 说明 | 举例 |
| ---- | ---------------------------------- | --------------------------------------- |
| ! | 非运算,表达式为 true 则返回 false否则返回 true。 | `[ ! false ]` 返回 true。 |
| -o | 或运算,有一个表达式为 true 则返回 true。 | `[ $a -lt 20 -o $b -gt 100 ]` 返回 true。 |
| -a | 与运算,两个表达式都为 true 才返回 true。 | `[ $a -lt 20 -a $b -gt 100 ]` 返回 false。 |
**示例:**
@ -546,10 +598,10 @@ fi
以下介绍 Shell 的逻辑运算符,假定变量 a 为 10变量 b 为 20:
| 运算符 | 说明 | 举例 |
| ---- | ------- | --------------------------------------- |
| && | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false |
| \|\| | 逻辑的 OR | [[ $a -lt 100 || $b -gt 100 ]] 返回 true |
| 运算符 | 说明 | 举例 |
| ---- | ------- | ---------------------------------------- |
| && | 逻辑的 AND | `[[ $a -lt 100 && $b -gt 100 ]]` 返回 false |
| \|\| | 逻辑的 OR | `[[ $a -lt 100 || $b -gt 100 ]]` 返回 true |
**示例:**
@ -557,6 +609,8 @@ fi
a=10
b=20
echo "a=$a, b=$b"
if [[ $a -lt 100 && $b -gt 100 ]]
then
echo "返回 true"
@ -576,13 +630,13 @@ fi
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg"
| 运算符 | 说明 | 举例 |
| ---- | ----------------------- | --------------------- |
| = | 检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。 |
| != | 检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。 |
| -z | 检测字符串长度是否为0为0返回 true。 | [ -z $a ] 返回 false。 |
| -n | 检测字符串长度是否为0不为0返回 true。 | [ -n $a ] 返回 true。 |
| str | 检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。 |
| 运算符 | 说明 | 举例 |
| ---- | ----------------------- | ----------------------- |
| = | 检测两个字符串是否相等,相等返回 true。 | `[ $a = $b ]` 返回 false。 |
| != | 检测两个字符串是否相等,不相等返回 true。 | `[ $a != $b ]` 返回 true。 |
| -z | 检测字符串长度是否为0为0返回 true。 | `[ -z $a ]` 返回 false。 |
| -n | 检测字符串长度是否为0不为0返回 true。 | `[ -n $a ]` 返回 true。 |
| str | 检测字符串是否为空,不为空返回 true。 | `[ $a ]` 返回 true。 |
**示例:**
@ -590,6 +644,8 @@ fi
a="abc"
b="efg"
echo "a=$a, b=$b"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
@ -628,21 +684,21 @@ fi
属性检测描述如下:
| 操作符 | 说明 | 举例 |
| ------- | ---------------------------------------- | ---------------------- |
| -b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。 |
| -c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。 |
| -d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。 |
| -f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。 |
| -g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。 |
| -k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。 |
| -p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。 |
| -u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。 |
| -r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。 |
| -w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。 |
| -x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。 |
| -s file | 检测文件是否为空文件大小是否大于0不为空返回 true。 | [ -s $file ] 返回 true。 |
| -e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。 |
| 操作符 | 说明 | 举例 |
| ------- | ---------------------------------------- | ------------------------ |
| -b file | 检测文件是否是块设备文件,如果是,则返回 true。 | `[ -b $file ]` 返回 false。 |
| -c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | `[ -c $file ]` 返回 false。 |
| -d file | 检测文件是否是目录,如果是,则返回 true。 | `[ -d $file ]` 返回 false。 |
| -f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | `[ -f $file ]` 返回 true。 |
| -g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | `[ -g $file ]` 返回 false。 |
| -k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | `[ -k $file ]`返回 false。 |
| -p file | 检测文件是否是有名管道,如果是,则返回 true。 | `[ -p $file ]` 返回 false。 |
| -u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | `[ -u $file ]` 返回 false。 |
| -r file | 检测文件是否可读,如果是,则返回 true。 | `[ -r $file ]` 返回 true。 |
| -w file | 检测文件是否可写,如果是,则返回 true。 | `[ -w $file ]` 返回 true。 |
| -x file | 检测文件是否可执行,如果是,则返回 true。 | `[ -x $file ]` 返回 true。 |
| -s file | 检测文件是否为空文件大小是否大于0不为空返回 true。 | `[ -s $file ]` 返回 true。 |
| -e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | `[ -e $file ]` 返回 true。 |
**示例:**
@ -708,52 +764,6 @@ fi
由`[[ ]]``sh`中是`[ ]`)包起来的表达式被称作 **检测命令** 或 **基元**。这些表达式帮助我们检测一个条件的结果。在下面的表里,为了兼容`sh`,我们用的是`[ ]`。这里可以找到有关[bash中单双中括号区别](http://serverfault.com/a/52050)的答案。
**跟文件系统相关:**
| 基元 | 含义 |
| --------------------- | ------------------------------------ |
| `[ -e FILE ]` | 如果`FILE`存在 (**e**xists),为真 |
| `[ -f FILE ]` | 如果`FILE`存在且为一个普通文件(**f**ile为真 |
| `[ -d FILE ]` | 如果`FILE`存在且为一个目录(**d**irectory为真 |
| `[ -s FILE ]` | 如果`FILE`存在且非空(**s**ize 大于0为真 |
| `[ -r FILE ]` | 如果`FILE`存在且有读权限(**r**eadable为真 |
| `[ -w FILE ]` | 如果`FILE`存在且有写权限(**w**ritable为真 |
| `[ -x FILE ]` | 如果`FILE`存在且有可执行权限e**x**ecutable为真 |
| `[ -L FILE ]` | 如果`FILE`存在且为一个符号链接(**l**ink为真 |
| `[ FILE1 -nt FILE2 ]` | `FILE1`比`FILE2`新(**n**ewer **t**han |
| `[ FILE1 -ot FILE2 ]` | `FILE1`比`FILE2`旧(**o**lder **t**han |
**跟字符串相关:**
| 基元 | 含义 |
| ------------------ | -------------------------- |
| `[ -z STR ]` | `STR`为空长度为0**z**ero |
| `[ -n STR ]` | `STR`非空长度非0**n**on-zero |
| `[ STR1 == STR2 ]` | `STR1`和`STR2`相等 |
| `[ STR1 != STR2 ]` | `STR1`和`STR2`不等 |
**算数二元运算符:**
| 基元 | 含义 |
| ------------------- | ---------------------------------------- |
| `[ ARG1 -eq ARG2 ]` | `ARG1`和`ARG2`相等(**eq**ual |
| `[ ARG1 -ne ARG2 ]` | `ARG1`和`ARG2`不等(**n**ot **e**qual |
| `[ ARG1 -lt ARG2 ]` | `ARG1`小于`ARG2`**l**ess **t**han |
| `[ ARG1 -le ARG2 ]` | `ARG1`小于等于`ARG2`**l**ess than or **e**qual |
| `[ ARG1 -gt ARG2 ]` | `ARG1`大于`ARG2`**g**reater **t**han |
| `[ ARG1 -ge ARG2 ]` | `ARG1`大于等于`ARG2`**g**reater than or **e**qual |
条件语句可以跟 **组合表达式** 配合使用:
| Operation | Effect |
| -------------------- | ---------------------------------------- |
| `[ ! EXPR ]` | 如果`EXPR`为假,为真 |
| `[ (EXPR) ]` | 返回`EXPR`的值 |
| `[ EXPR1 -a EXPR2 ]` | 逻辑 *与* 如果`EXPR1`和(**a**nd`EXPR2`都为真,为真 |
| `[ EXPR1 -o EXPR2 ]` | 逻辑 *或* 如果`EXPR1`或(**o**r`EXPR2`为真,为真 |
当然,还有很多有用的基元,在[Bash的man页面](http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html)能很容易找到它们。
##### 使用`if`
`if`在使用上跟其它语言相同。如果中括号里的表达式为真,那么`then`和`fi`之间的代码会被执行。`fi`标志着条件代码块的结束。
@ -784,7 +794,7 @@ fi
有些时候,`if..else`不能满足我们的要求。别忘了`if..elif..else`,使用起来也很方便。
看下面的例子:
**示例:**
```bash
if [[ `uname` == "Adam" ]]; then
@ -949,13 +959,14 @@ Enter the package name: bash-handbook
```
##### 循环控制
##### break 和 continue
我们会遇到想提前结束一个循环或跳过某次循环执行的情况。这些可以使用shell内建的`break`和`continue`语句来实现。它们可以在任何循环中使用。
如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的`break`和`continue`语句来实现。它们可以在任何循环中使用。
`break`语句用来提前结束当前循环。我们之前已经见过它了。
`continue`语句用来跳过某次迭代。我们可以这么来用它:
> `break`语句用来提前结束当前循环。
>
> `continue`语句用来跳过某次迭代。
>
```bash
for (( i = 0; i < 10; i++ )); do
@ -1005,7 +1016,19 @@ greeting ### Hello, stranger!
我们之前已经介绍过[返回值](https://github.com/denysdovhan/bash-handbook/blob/master/translations/zh-CN/README.md#%E8%BF%94%E5%9B%9E%E5%80%BC)。不带任何参数的`return`会返回最后一个执行的命令的返回值。上面的例子,`return 0`会返回一个成功表示执行的值,`0`。
### 流,管道以及序列
另外,还有几个特殊字符用来处理参数:
| 参数处理 | 说明 |
| ---- | ------------------------------- |
| $# | 传递到脚本的参数个数 |
| $* | 以一个单字符串显示所有向脚本传递的参数 |
| $$ | 脚本运行的当前进程ID号 |
| $! | 后台运行的最后一个进程的ID号 |
| $@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
| $- | 显示Shell使用的当前选项与set命令功能相同。 |
| $? | 显示最后命令的退出状态。0表示没有错误其他任何值表明有错误。 |
### 流和重定向
Bash有很强大的工具来处理程序之间的协同工作。使用流我们能将一个程序的输出发送到另一个程序或文件因此我们能方便地记录日志或做一些其它我们想做的事。
@ -1013,7 +1036,7 @@ Bash有很强大的工具来处理程序之间的协同工作。使用流
学习如何使用这些强大的、高级的工具是非常非常重要的。
#### 流
#### 输入、输出
Bash接收输入并以字符序列或 **字符流** 的形式产生输出。这些流能被重定向到文件或另一个流中。
@ -1025,6 +1048,8 @@ Bash接收输入并以字符序列或 **字符流** 的形式产生输出。
| `1` | `stdout` | 标准输出 |
| `2` | `stderr` | 标准错误输出 |
#### 重定向
重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到:
| Operator | Description |
@ -1052,65 +1077,22 @@ grep da * 2> errors.txt
less < errors.txt
```
#### 管道
#### `/dev/null` 文件
我们不仅能将流重定向到文件中,还能重定向到其它程序中。**管道** 允许我们把一个程序的输出当做另一个程序的输入。
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null
在下面的例子中,`command1`把它的输出发送给了`command2`,然后输出被传递到`command3`
```bash
command1 | command2 | command3
```
$ command > /dev/null
```
这样的结构被称作 **管道**
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果
在实际操作中,这可以用来在多个程序间依次处理数据。在下面的例子中,`ls -l`的输出被发送给了`grep`,来打印出扩展名是`.md`的文件,它的输出最终发送给了`less`
如果希望屏蔽 stdout 和 stderr可以这样写
```bash
ls -l | grep .md$ | less
```
管道的返回值通常是管道中最后一个命令的返回值。shell会等到管道中所有的命令都结束后才会返回一个值。如果你想让管道中任意一个命令失败后管道就宣告失败那么需要用下面的命令设置pipefail选项
```bash
set -o pipefail
$ command > /dev/null 2>&1
```
#### 命令序列
命令序列是由`;``&``&&`或者`||`运算符分隔的一个或多个管道序列。
如果一个命令以`&`结尾shell将会在一个子shell中异步执行这个命令。换句话说这个命令将会在后台执行。
以`;`分隔的命令将会依次执行一个接着一个。shell会等待直到每个命令执行完。
```bash
### command2 会在 command1 之后执行
command1 ; command2
### 等同于这种写法
command1
command2
```
以`&&`和`||`分隔的命令分别叫做 *与**或* 序列。
*与序列* 看起来是这样的:
```bash
### 当且仅当command1执行成功返回0值command2才会执行
command1 && command2
```
*或序列* 是下面这种形式:
```bash
### 当且仅当command1执行失败返回错误码command2才会执行
command1 || command2
```
*与* 或 *或* 序列的返回值是序列中最后一个执行的命令的返回值。
### Debugging
shell提供了用于debugging脚本的工具。如果我们想以debug模式运行某脚本可以在其shebang中使用一个特殊的选项