@jdh8383 翻译的很好
This commit is contained in:
Xingyu Wang 2019-11-08 09:26:20 +08:00
parent 5ef3e95100
commit 3fdce71de0

View File

@ -1,6 +1,6 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: (jdh8383) [#]: translator: (jdh8383)
[#]: reviewer: ( ) [#]: reviewer: (wxy)
[#]: publisher: ( ) [#]: publisher: ( )
[#]: url: ( ) [#]: url: ( )
[#]: subject: (How to program with Bash: Syntax and tools) [#]: subject: (How to program with Bash: Syntax and tools)
@ -9,33 +9,30 @@
怎样用 Bash 编程:语法和工具 怎样用 Bash 编程:语法和工具
====== ======
让我们通过本系列文章来学习基本的 Bash 编程语法和工具,以及如何使用变量和控制运算符,这是三篇中的第一篇。
![bash logo on green background][1]
Shell 是操作系统的命令解释器,其中 Bash 是我最喜欢的。每当用户或者系统管理员将命令输入系统的时候Linux 的 shell 解释器就会把这些命令转换成操作系统可以理解的形式。而执行结果返回 shell 程序后,它会将结果输出到 STDOUT标准输出默认情况下这些结果会[显示在你的终端][2]。所有我熟悉的 shell 同时也是门编程语言 > 让我们通过本系列文章来学习基本的 Bash 编程语法和工具,以及如何使用变量和控制运算符,这是三篇中的第一篇。
Bash 是个功能强大的 shell包含众多便捷特性比如tab 补全、命令回溯和再编辑、aliases 别名等。它的命令行默认编辑模式是 Emacs但是我最喜欢的Bash特性之一是我可以将其更改为 Vi 模式,以使用那些储存在我肌肉记忆中的的编辑命令。 ![](https://img.linux.net.cn/data/attachment/album/201911/08/092559r5wdg0w97dtf350j.jpg)
Shell 是操作系统的命令解释器,其中 Bash 是我最喜欢的。每当用户或者系统管理员将命令输入系统的时候Linux 的 shell 解释器就会把这些命令转换成操作系统可以理解的形式。而执行结果返回 shell 程序后,它会将结果输出到 STDOUT标准输出默认情况下这些结果会[显示在你的终端][2]。所有我熟悉的 shell 同时也是一门编程语言。
Bash 是个功能强大的 shell包含众多便捷特性比如tab 补全、命令回溯和再编辑、别名等。它的命令行默认编辑模式是 Emacs但是我最喜欢的 Bash 特性之一是我可以将其更改为 Vi 模式,以使用那些储存在我肌肉记忆中的的编辑命令。
然而,如果你把 Bash 当作单纯的 shell 来用,则无法体验它的真实能力。我在设计一套包含三卷的 [Linux 自学课程][3]时(这个系列的文章正是基于此课程),了解到许多 Bash 的知识,这些是我在过去 20 年的 Linux 工作经验中所没有掌握的,其中的一些知识就是关于 Bash 的编程用法。不得不说Bash 是一门强大的编程语言,是一个能够同时用于命令行和 shell 脚本的完美设计。 然而,如果你把 Bash 当作单纯的 shell 来用,则无法体验它的真实能力。我在设计一套包含三卷的 [Linux 自学课程][3]时(这个系列的文章正是基于此课程),了解到许多 Bash 的知识,这些是我在过去 20 年的 Linux 工作经验中所没有掌握的,其中的一些知识就是关于 Bash 的编程用法。不得不说Bash 是一门强大的编程语言,是一个能够同时用于命令行和 shell 脚本的完美设计。
本系列文章将要探讨如何使用 Bash 作为命令行界面CLI编程语言。第一篇文章简单介绍 Bash 命令行编程、变量以及控制运算符。其他文章会讨论诸如Bash 文件的类型;字符串、数字和一些逻辑运算符,它们能够提供代码执行流程中的逻辑控制;不同类型的 shell 扩展;通过 **for**、**while** 和 **until** 来控制循环操作。 本系列文章将要探讨如何使用 Bash 作为命令行界面CLI编程语言。第一篇文章简单介绍 Bash 命令行编程、变量以及控制运算符。其他文章会讨论诸如Bash 文件的类型;字符串、数字和一些逻辑运算符,它们能够提供代码执行流程中的逻辑控制;不同类型的 shell 扩展;通过 `for`、`while` 和 `until` 来控制循环操作。
### Shell ### Shell
Shell 是操作系统的命令解释器,其中 Bash 是我最喜欢的。每当用户或者系统管理员将命令输入系统的时候Linux 的 shell 解释器就会把这些命令转换成操作系统可以理解的形式。而执行结果返回 shell 程序后,它会将结果输出到终端。所有我熟悉的 shell 同时也是门编程语言。
Bash 是 Bourne Again Shell 的缩写,因为 Bash shell 是 [基于][4] 更早的 Bourne shell后者是 Steven Bourne 在 1977 年开发的。另外还有很多[其他的 shell][5] 可以使用,但下面四个是我经常见到的: Bash 是 Bourne Again Shell 的缩写,因为 Bash shell 是 [基于][4] 更早的 Bourne shell后者是 Steven Bourne 在 1977 年开发的。另外还有很多[其他的 shell][5] 可以使用,但下面四个是我经常见到的:
* **csh** C shell 适合那些习惯了 C 语言语法的开发者。 * `csh`C shell 适合那些习惯了 C 语言语法的开发者。
* **ksh** Korn shell由 David Korn 开发,在 Unix 用户中更流行。 * `ksh`Korn shell由 David Korn 开发,在 Unix 用户中更流行。
* **tcsh** 一个 csh 的变种,增加了一些易用性。 * `tcsh`:一个 csh 的变种,增加了一些易用性。
* **zsh** Z shell集成了许多其他流行 shell 的特性。 * `zsh`Z shell集成了许多其他流行 shell 的特性。
所有 shell 都有内置命令,用以补充或替代核心工具集。打开 shell 的 man 说明页找到“BUILT-INS”那一段可以查看都有哪些内置命令。 所有 shell 都有内置命令,用以补充或替代核心工具集。打开 shell 的 man 说明页找到“BUILT-INS”那一段可以查看都有哪些内置命令。
每种 shell 都有它自己的特性和语法风格。我用过 csh、ksh 和 zsh但我还是更喜欢 Bash。你可以多试几个寻找更适合你的 shell尽管这可能需要花些功夫。但幸运的是切换不同 shell 很简单。 每种 shell 都有它自己的特性和语法风格。我用过 csh、ksh 和 zsh但我还是更喜欢 Bash。你可以多试几个寻找更适合你的 shell尽管这可能需要花些功夫。但幸运的是切换不同 shell 很简单。
所有这些 shell 既是编程语言又是命令解释器。下面我们来快速浏览一下 Bash 中集成的编程结构和工具。 所有这些 shell 既是编程语言又是命令解释器。下面我们来快速浏览一下 Bash 中集成的编程结构和工具。
@ -54,18 +51,16 @@ Bash 是 Bourne Again Shell 的缩写,因为 Bash shell 是 [基于][4] 更早
本系列用 Bash 举例(因为它无处不在),假如你使用一个不同的 shell 也没关系,尽管结构和语法有所不同,但编程思想是相通的。有些 shell 支持某种特性而其他 shell 则不支持但它们都提供编程功能。Shell 程序可以被存在一个文件中被反复使用,或者在需要的时候才创建它们。 本系列用 Bash 举例(因为它无处不在),假如你使用一个不同的 shell 也没关系,尽管结构和语法有所不同,但编程思想是相通的。有些 shell 支持某种特性而其他 shell 则不支持但它们都提供编程功能。Shell 程序可以被存在一个文件中被反复使用,或者在需要的时候才创建它们。
### 简单 CLI 程序 ### 简单 CLI 程序
最简单的命令行程序只有一或两条语句,它们可能相关,也可能无关,在按**回车**键之前被输入到命令行。程序中的第二条语句(如果有的话)可能取决于第一条语句的操作,但也不是必须的。 最简单的命令行程序只有一或两条语句,它们可能相关,也可能无关,在按回车键之前被输入到命令行。程序中的第二条语句(如果有的话)可能取决于第一条语句的操作,但也不是必须的。
这里需要特别讲解一个标点符号。当你在命令行输入一条命令,按下**回车**键的时候,其实在命令的末尾有一个隐含的分号(**;**)。当一段 CLI shell 程序在命令行中被串起来作为单行指令使用时,必须使用分号来终结每个语句并将其与下一条语句分开。但 CLI shell 程序中的最后一条语句可以使用显式或隐式的分号。 这里需要特别讲解一个标点符号。当你在命令行输入一条命令,按下回车键的时候,其实在命令的末尾有一个隐含的分号(`;`)。当一段 CLI shell 程序在命令行中被串起来作为单行指令使用时,必须使用分号来终结每个语句并将其与下一条语句分开。但 CLI shell 程序中的最后一条语句可以使用显式或隐式的分号。
### 一些基本语法 ### 一些基本语法
下面的例子会阐明这一语法规则。这段程序由单条命令组成,还有一个显式的终止符: 下面的例子会阐明这一语法规则。这段程序由单条命令组成,还有一个显式的终止符:
``` ```
[student@studentvm1 ~]$ echo "Hello world." ; [student@studentvm1 ~]$ echo "Hello world." ;
Hello world. Hello world.
@ -73,8 +68,7 @@ Hello world.
看起来不像一个程序,但它确是我学习每个新编程语言时写下的第一个程序。不同语言可能语法不同,但输出结果是一样的。 看起来不像一个程序,但它确是我学习每个新编程语言时写下的第一个程序。不同语言可能语法不同,但输出结果是一样的。
让我们扩展一下这段微不足道却又无所不在的代码。你的结果可能与我的有所不同,因为我的家目录有点乱,而你可能是在 GUI 桌面中第一次登陆账号。 让我们扩展一下这段微不足道却又无所不在的代码。你的结果可能与我的有所不同,因为我的家目录有点乱,而你可能是在 GUI 桌面中第一次登录账号。
``` ```
[student@studentvm1 ~]$ echo "My home directory." ; ls ; [student@studentvm1 ~]$ echo "My home directory." ; ls ;
@ -87,9 +81,8 @@ TestFile1.dos dmesg1.txt Documents Music random.txt testdir1
现在是不是更明显了。结果是相关的,但是两条语句彼此独立。你可能注意到我喜欢在分号前后多输入一个空格,这样会让代码的可读性更好。让我们再运行一遍这段程序,这次不要带结尾的分号: 现在是不是更明显了。结果是相关的,但是两条语句彼此独立。你可能注意到我喜欢在分号前后多输入一个空格,这样会让代码的可读性更好。让我们再运行一遍这段程序,这次不要带结尾的分号:
``` ```
`[student@studentvm1 ~]$ echo "My home directory." ; ls` [student@studentvm1 ~]$ echo "My home directory." ; ls
``` ```
输出结果没有区别。 输出结果没有区别。
@ -98,14 +91,13 @@ TestFile1.dos dmesg1.txt Documents Music random.txt testdir1
像所有其他编程语言一样Bash 支持变量。变量是个象征性的名字,它指向内存中的某个位置,那里存着对应的值。变量的值是可以改变的,所以它叫“变~量”。 像所有其他编程语言一样Bash 支持变量。变量是个象征性的名字,它指向内存中的某个位置,那里存着对应的值。变量的值是可以改变的,所以它叫“变~量”。
Bash 不像 C 之类的语言,需要强制指定变量类型,比如:整型、浮点型或字符型。在 Bash 中,所有变量都是字符串。整数型的变量可以被用于整数运算,这是 Bash 唯一能够处理的数学类型。更复杂的运算则需要借助 [**bc**][9] 这样的命令,可以被用在命令行编程或者脚本中。 Bash 不像 C 之类的语言,需要强制指定变量类型,比如:整型、浮点型或字符型。在 Bash 中,所有变量都是字符串。整数型的变量可以被用于整数运算,这是 Bash 唯一能够处理的数学类型。更复杂的运算则需要借助 [bc][9] 这样的命令,可以被用在命令行编程或者脚本中。
变量的值是被预先分配好的,这些值可以用在命令行编程或者脚本中。可以通过变量名字给其赋值,但是不能使用 **$** 符开头。比如,**VAR=10** 这样会把 VAR 的值设为 10。要打印变量的值你可以使用语句 **echo $VAR**。变量名必须以文本(即非数字)开始。 变量的值是被预先分配好的,这些值可以用在命令行编程或者脚本中。可以通过变量名字给其赋值,但是不能使用 `$` 符开头。比如,`VAR=10` 这样会把 `VAR` 的值设为 `10`。要打印变量的值,你可以使用语句 `echo $VAR`。变量名必须以文本(即非数字)开始。
Bash 会保存已经定义好的变量,直到它们被取消掉。 Bash 会保存已经定义好的变量,直到它们被取消掉。
下面这个例子在变量被赋值前它的值是空null。然后给它赋值并打印出来检验一下。你可以在同一行 CLI 程序里完成它: 下面这个例子,在变量被赋值前,它的值是空(`null`)。然后给它赋值并打印出来,检验一下。你可以在同一行 CLI 程序里完成它:
``` ```
[student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ; [student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ;
@ -114,15 +106,14 @@ Hello World
[student@studentvm1 ~]$ [student@studentvm1 ~]$
``` ```
_注意变量赋值的语法非常严格等号**=**两边不能有空格。_ *注意:变量赋值的语法非常严格,等号(`=`)两边不能有空格。*
那个空行表明了 **MyVar** 的初始值为空。变量的赋值和改值方法都一样,这个例子展示了原始值和新的值。 那个空行表明了 `MyVar` 的初始值为空。变量的赋值和改值方法都一样,这个例子展示了原始值和新的值。
正如之前说的Bash 支持整数运算,当你想计算一个数组中的某个元素的位置,或者做些简单的算术运算,这还是挺有帮助的。然而,这种方法并不适合科学计算,或是某些需要小数运算的场景,比如财务统计。这些场景有其它更好的工具可以应对。 正如之前说的Bash 支持整数运算,当你想计算一个数组中的某个元素的位置,或者做些简单的算术运算,这还是挺有帮助的。然而,这种方法并不适合科学计算,或是某些需要小数运算的场景,比如财务统计。这些场景有其它更好的工具可以应对。
下面是个简单的算术题: 下面是个简单的算术题:
``` ```
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))" [student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))"
Result = 63 Result = 63
@ -130,7 +121,6 @@ Result = 63
好像没啥问题,但如果运算结果是浮点数会发生什么呢? 好像没啥问题,但如果运算结果是浮点数会发生什么呢?
``` ```
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))" [student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))"
Result = 0 Result = 0
@ -139,29 +129,25 @@ Result = 1
[student@studentvm1 ~]$ [student@studentvm1 ~]$
``` ```
结果会被取整。请注意运算被包含在 **echo** 语句之中,其实计算在 echo 命令结束前就已经完成了,原因是 Bash 的内部优先级。想要了解详情的话,可以在 Bash 的 man 页面中搜索 "precedence" 结果会被取整。请注意运算被包含在 `echo` 语句之中,其实计算在 echo 命令结束前就已经完成了,原因是 Bash 的内部优先级。想要了解详情的话,可以在 Bash 的 man 页面中搜索 “precedence”
### 控制运算符 ### 控制运算符
Shell 的控制运算符是一种语法运算符,可以轻松地创建一些有趣的命令行程序。在命令行上按顺序将几个命令串在一起,就变成了最简单的 CLI 程序: Shell 的控制运算符是一种语法运算符,可以轻松地创建一些有趣的命令行程序。在命令行上按顺序将几个命令串在一起,就变成了最简单的 CLI 程序:
``` ```
`command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;` command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;
``` ```
只要不出错,这些命令都能顺利执行。但假如出错了怎么办?你可以预设好应对出错的办法,这就要用到 Bash 内置的控制运算符, **&&** 和 **||**。这两种运算符提供了流程控制功能,使你能改变代码执行的顺序。分号也可以被看做是一种 Bash 运算符,预示着新一行的开始。 只要不出错,这些命令都能顺利执行。但假如出错了怎么办?你可以预设好应对出错的办法,这就要用到 Bash 内置的控制运算符, `&&``||`。这两种运算符提供了流程控制功能,使你能改变代码执行的顺序。分号也可以被看做是一种 Bash 运算符,预示着新一行的开始。
**&&** 运算符提供了如下简单逻辑,“如果 command1 执行成功,那么接着执行 command2。如果 command1 失败,就跳过 command2。”语法如下
`&&` 运算符提供了如下简单逻辑,“如果 command1 执行成功,那么接着执行 command2。如果 command1 失败,就跳过 command2。”语法如下
``` ```
`command1 && command2` command1 && command2
``` ```
现在,让我们用命令来创建一个新的目录,如果成功的话,就把它切换为当前目录。确保你的家目录(**~**)是当前目录,先尝试在 **/root** 目录下创建,你应该没有权限: 现在,让我们用命令来创建一个新的目录,如果成功的话,就把它切换为当前目录。确保你的家目录(`~`)是当前目录,先尝试在 `/root` 目录下创建,你应该没有权限:
``` ```
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ &&; cd $Dir [student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ &&; cd $Dir
@ -169,18 +155,16 @@ mkdir: cannot create directory '/root/testdir/': Permission denied
[student@studentvm1 ~]$ [student@studentvm1 ~]$
``` ```
上面的报错信息是由 **mkdir** 命令抛出的,因为创建目录失败了。**&&** 运算符收到了非零的返回码,所以 **cd** 命令就被跳过,前者阻止后者继续运行,因为创建目录失败了。这种控制流程可以阻止后面的错误累积,避免引发更严重的问题。是时候讲点更复杂的逻辑了。 上面的报错信息是由 `mkdir` 命令抛出的,因为创建目录失败了。`&&` 运算符收到了非零的返回码,所以 `cd` 命令就被跳过,前者阻止后者继续运行,因为创建目录失败了。这种控制流程可以阻止后面的错误累积,避免引发更严重的问题。是时候讲点更复杂的逻辑了。
当一段程序的返回码大于零时,使用 **||** 运算符可以让你在后面接着执行另一段程序。简单语法如下:
当一段程序的返回码大于零时,使用 `||` 运算符可以让你在后面接着执行另一段程序。简单语法如下:
``` ```
`command1 || command2` command1 || command2
``` ```
解读一下,“假如 command1 失败,执行 command2”。隐藏的逻辑是如果 command1 成功,跳过 command2。下面实践一下仍然是创建新目录 解读一下,“假如 command1 失败,执行 command2”。隐藏的逻辑是如果 command1 成功,跳过 command2。下面实践一下仍然是创建新目录
``` ```
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created." [student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied mkdir: cannot create directory '/root/testdir': Permission denied
@ -190,16 +174,14 @@ mkdir: cannot create directory '/root/testdir': Permission denied
正如预期,因为目录无法创建,第一条命令失败了,于是第二条命令被执行。 正如预期,因为目录无法创建,第一条命令失败了,于是第二条命令被执行。
**&&** 和 **||** 两种运算符结合起来才能发挥它们的最大功效。请看下面例子中的流程控制方法: `&&``||` 两种运算符结合起来才能发挥它们的最大功效。请看下面例子中的流程控制方法:
``` ```
`preceding commands ; command1 && command2 || command3 ; following commands` 前置 commands ; command1 && command2 || command3 ; 跟随 commands
``` ```
语法解释:“假如 command1 退出时返回码为零,就执行 command2否则执行 command3。”用具体代码试试 语法解释:“假如 command1 退出时返回码为零,就执行 command2否则执行 command3。”用具体代码试试
``` ```
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created." [student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied mkdir: cannot create directory '/root/testdir': Permission denied
@ -207,18 +189,16 @@ mkdir: cannot create directory '/root/testdir': Permission denied
[student@studentvm1 ~]$ [student@studentvm1 ~]$
``` ```
现在我们再试一次,用你的家目录替换 **/root** 目录,你将会有权限创建这个目录了: 现在我们再试一次,用你的家目录替换 `/root` 目录,你将会有权限创建这个目录了:
``` ```
[student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created." [student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
[student@studentvm1 testdir]$ [student@studentvm1 testdir]$
``` ```
**command1 && command2** 这样的控制语句能够运行的原因是,每条命令执行完毕时都会给 shell 发送一个返回码,用来表示它执行成功与否。默认情况下,返回码为 0 表示成功,其他任何正值表示失败。一些系统管理员使用的工具用值为 1 的返回码来表示失败,但其他很多程序使用别的数字来表示失败。 `command1 && command2` 这样的控制语句能够运行的原因是,每条命令执行完毕时都会给 shell 发送一个返回码,用来表示它执行成功与否。默认情况下,返回码为 `0` 表示成功,其他任何正值表示失败。一些系统管理员使用的工具用值为 `1` 的返回码来表示失败,但其他很多程序使用别的数字来表示失败。
Bash 的内置变量 **$?** 可以显示上一条命令的返回码,可以在脚本或者命令行中非常方便地检查它。要查看返回码,让我们从运行一条简单的命令开始,返回码的结果总是上一条命令给出的。
Bash 的内置变量 `$?` 可以显示上一条命令的返回码,可以在脚本或者命令行中非常方便地检查它。要查看返回码,让我们从运行一条简单的命令开始,返回码的结果总是上一条命令给出的。
``` ```
[student@studentvm1 testdir]$ ll ; echo "RC = $?" [student@studentvm1 testdir]$ ll ; echo "RC = $?"
@ -234,7 +214,6 @@ RC = 0
在这个例子中,返回码为零,意味着命令执行成功了。现在对 root 的家目录测试一下,你应该没有权限: 在这个例子中,返回码为零,意味着命令执行成功了。现在对 root 的家目录测试一下,你应该没有权限:
``` ```
[student@studentvm1 testdir]$ ll /root ; echo "RC = $?" [student@studentvm1 testdir]$ ll /root ; echo "RC = $?"
ls: cannot open directory '/root': Permission denied ls: cannot open directory '/root': Permission denied
@ -242,7 +221,7 @@ RC = 2
[student@studentvm1 testdir]$ [student@studentvm1 testdir]$
``` ```
本例中返回码是 2表明非 root 用户没有权限进入这个目录。你可以利用这些返回码,用控制运算符来改变程序执行的顺序。 本例中返回码是 `2`,表明非 root 用户没有权限进入这个目录。你可以利用这些返回码,用控制运算符来改变程序执行的顺序。
### 总结 ### 总结
@ -255,7 +234,7 @@ via: https://opensource.com/article/19/10/programming-bash-part-1
作者:[David Both][a] 作者:[David Both][a]
选题:[lujun9972][b] 选题:[lujun9972][b]
译者:[jdh8383](https://github.com/jdh8383) 译者:[jdh8383](https://github.com/jdh8383)
校对:[校对者ID](https://github.com/校对者ID) 校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出