Shell 是操作系统的命令解释器。 Bash 是我最喜欢的 shell,但每个 Linux shell 都会将用户或系统管理员键入的命令解释为操作系统可以使用的形式。当结果返回到 shell 程序时,它会将它们发送到 STDOUT,默认情况下,将它们显示在终端中。我熟悉的所有 shell 也是编程语言。
诸如 tab 补全、命令行回忆和编辑,以及别名之类的快捷方式都为其作为强大的 shell 增添了价值。它的默认命令行编辑模式使用 Emacs,但我最喜欢的 Bash 功能之一是我可以将其更改为 Vi 模式,以使用已经存在于我肌肉记忆中的编辑命令。
但是,如果您仅将 Bash 视为 shell,您会错过它真正的力量。在研究我的三卷Linux 自学课程(本系列文章的基础)时,我了解了 Bash 中一些我在使用 Linux 超过 20 年的时间里从未了解的事情。 其中一些新知识与它作为编程语言的用途有关。Bash 是一种强大的编程语言,非常适合在命令行和 shell 脚本中使用。
本三部分系列探讨了将 Bash 用作命令行界面 (CLI) 编程语言。第一篇文章介绍了一些使用 Bash 进行的简单命令行编程、变量和控制运算符。 其他文章探讨了 Bash 文件的类型;提供执行流程控制逻辑的字符串、数字和各种逻辑运算符;不同类型的 shell 扩展; 以及启用重复操作的 for、while 和 until 循环。他们还将研究一些简化和支持使用这些工具的命令。
Shell
Shell 是操作系统的命令解释器。 Bash 是我最喜欢的 shell,但每个 Linux shell 都会将用户或系统管理员键入的命令解释为操作系统可以使用的形式。当结果返回到 shell 程序时,它会将它们显示在终端中。我熟悉的所有 shell 也是编程语言。
Bash 代表 Bourne Again Shell,因为 Bash shell 基于 Steven Bourne 于 1977 年编写的较旧的 Bourne shell。 许多其他 shell 可用,但以下是我最常遇到的四个 shell
- csh: C shell,适用于喜欢 C 语言语法的程序员
- ksh: Korn shell,由 David Korn 编写,在 Unix 用户中很受欢迎
- tcsh: csh 的一个版本,具有更易于使用的功能
- zsh: Z shell,它结合了其他流行 shell 的许多功能
所有 shell 都具有内置命令,用于补充或替换核心实用程序提供的命令。打开 shell 的 man 页面并找到“BUILT-INS”部分以查看它提供的命令。
每个 shell 都有自己的个性和语法。有些对你来说会更好用。我使用过 C shell、Korn shell 和 Z shell。我仍然比任何一个都更喜欢 Bash shell。使用最适合你的那一个,尽管这可能需要你尝试其他一些。幸运的是,更改 shell 非常容易。
所有这些 shell 都是编程语言,也是命令解释器。这是 Bash 的一些编程结构和工具的快速介绍。
Bash 作为一种编程语言
大多数系统管理员都使用 Bash 来发出通常相当简单明了的命令。但是 Bash 可以超越输入单个命令,许多系统管理员创建简单的命令行程序来执行一系列任务。这些程序是常见的工具,可以节省时间和精力。
我编写 CLI 程序的目的是节省时间和精力(即,成为懒惰的系统管理员)。 CLI 程序通过列出以特定顺序执行的多个命令来支持这一点,因此你无需监视一个命令的进度,并在第一个命令完成后键入下一个命令。你可以去做其他事情,而不必持续监视每个命令的进度。
什么是“程序”?
《免费在线计算词典》(FOLDOC)将程序定义为:“计算机执行的指令,而不是运行它们的物理设备。”普林斯顿大学的WordNet 将程序定义为:“……计算机可以解释和执行的一系列指令……” Wikipedia 也有关于计算机程序的很好的条目。
因此,程序可以包含执行特定相关任务的一条或多条指令。计算机程序指令也称为程序语句。对于系统管理员来说,程序通常是一系列 shell 命令。所有可用于 Linux 的 shell,至少是我熟悉的那些,都至少具有编程功能的基本形式,并且 Bash(大多数 Linux 发行版的默认 shell)也不例外。
虽然本系列使用 Bash(因为它无处不在),但如果你使用不同的 shell,则一般编程概念将是相同的,尽管结构和语法可能有所不同。某些 shell 可能支持其他 shell 不支持的某些功能,但它们都提供一些编程功能。Shell 程序可以存储在文件中以供重复使用,或者可以根据需要在命令行上创建。
简单 CLI 程序
最简单的命令行程序是一个或两个连续的程序语句(可能相关也可能不相关),它们在按下 Enter 键之前在命令行中输入。 程序中的第二个语句(如果存在)可能取决于第一个语句的操作,但不必如此。
还有一个语法标点需要明确说明。 在命令行上输入单个命令时,按下 Enter 键会以隐式分号 (;) 结束该命令。 当在命令行上作为单行输入的 CLI shell 程序中使用时,必须使用分号终止每个语句并将其与下一个语句分开。 CLI shell 程序中的最后一个语句可以使用显式或隐式分号。
一些基本语法
以下示例将阐明此语法。 此程序由一个带有显式终止符的命令组成
[student@studentvm1 ~]$ echo "Hello world." ;
Hello world.
这似乎不是什么大程序,但它是我学习的每种新编程语言遇到的第一个程序。每种语言的语法可能略有不同,但结果是相同的。
让我们稍微扩展一下这个琐碎但无处不在的程序。 你的结果将与我的结果不同,因为我已经做过其他实验,而你可能只有在第一次通过 GUI 桌面登录帐户时在帐户主目录中创建的默认目录和文件。
[student@studentvm1 ~]$ echo "My home directory." ; ls ;
My home directory.
chapter25 TestFile1.Linux dmesg2.txt Downloads newfile.txt softlink1 testdir6
chapter26 TestFile1.mac dmesg3.txt file005 Pictures Templates testdir
TestFile1 Desktop dmesg.txt link3 Public testdir Videos
TestFile1.dos dmesg1.txt Documents Music random.txt testdir1
这更有意义。 结果是相关的,但各个程序语句彼此独立。 请注意,我喜欢在分号前后加上空格,因为它使代码更易于阅读。 再次尝试没有显式分号的那个小 CLI 程序
[student@studentvm1 ~]$ echo "My home directory." ; ls
输出没有区别。
关于变量的一些信息
像所有编程语言一样,Bash shell 可以处理变量。变量是一个符号名称,指的是内存中的特定位置,该位置包含某种值。 变量的值是可以更改的,即它是可变的。
Bash 不像 C 和相关语言那样对变量进行类型化,将它们定义为整数、浮点数或字符串类型。 在 Bash 中,所有变量都是字符串。 可以将整数的字符串用于整数算术,这是 Bash 唯一能够进行的数学运算类型。 如果需要更复杂的数学运算,则可以在 CLI 程序和脚本中使用 bc 命令。
变量被赋值,并且可以用于引用 CLI 程序和脚本中的这些值。 变量的值是使用它的名称设置的,但前面没有 $ 符号。 赋值 VAR=10 将变量 VAR 的值设置为 10。 要打印变量的值,你可以使用语句 echo $VAR。 从文本(即非数字)变量开始。
Bash 变量成为 shell 环境的一部分,直到它们被取消设置。
检查未分配的变量的初始值;它应该为空。 然后为变量赋值并打印它以验证它的值。 你可以在单个 CLI 程序中完成所有这些操作
[student@studentvm1 ~]$ echo $MyVar ; MyVar="Hello World" ; echo $MyVar ;
Hello World
[student@studentvm1 ~]$
注意:变量赋值的语法非常严格。 在赋值语句中,等号 (=) 两侧不得有空格。
空行表示 MyVar 的初始值为空。 更改和设置变量的值以相同的方式完成。 此示例显示了原始值和新值。
如前所述,Bash 可以执行整数算术计算,这对于计算对数组中元素位置的引用或解决简单的数学问题非常有用。 它不适用于科学计算或任何需要小数的计算,例如财务计算。 对于这些类型的计算,有更好的工具。
这是一个简单的计算
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1*Var2))"
Result = 63
当你执行数学运算导致浮点数时会发生什么?
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var1/Var2))"
Result = 0
[student@studentvm1 ~]$ Var1="7" ; Var2="9" ; echo "Result = $((Var2/Var1))"
Result = 1
[student@studentvm1 ~]$
结果是最接近的整数。 请注意,由于 Bash 的优先级顺序,该计算是作为 echo 语句的一部分执行的。 有关详细信息,请参见 Bash 手册页并搜索“precedence”。
控制运算符
Shell 控制操作符是用于轻松创建有趣命令行程序的语法操作符之一。最简单的 CLI 程序形式就是将多个命令按顺序串联在命令行上。
command1 ; command2 ; command3 ; command4 ; . . . ; etc. ;
只要不发生错误,这些命令都可以顺利运行。但是,当发生错误时会发生什么?您可以使用内置的 && 和 || Bash 控制操作符来预测和处理错误。这两个控制操作符提供了一些流程控制,并使您能够更改代码的执行顺序。分号也被认为是 Bash 控制操作符,换行符也是。
&& 操作符简单地说,“如果 command1 成功,则运行 command2。如果 command1 由于任何原因失败,则跳过 command2。” 语法如下:
command1 && command2
现在,看一些将创建一个新目录,并且——如果成功——将其设为当前工作目录 (PWD) 的命令。确保您的主目录 (~) 是 PWD。首先在 /root 中尝试此操作,这是一个您无权访问的目录。
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir/ && cd $Dir
mkdir: cannot create directory '/root/testdir/': Permission denied
[student@studentvm1 ~]$
错误是由 mkdir 命令发出的。您没有收到指示无法创建文件的错误,因为目录创建失败。&& 控制操作符检测到非零返回代码,因此跳过了 touch 命令。使用 && 控制操作符可防止 touch 命令运行,因为创建目录时出现错误。这种类型的命令行程序流程控制可以防止错误恶化并造成混乱。但现在是时候变得更复杂一些了。
|| 控制操作符允许您添加另一个程序语句,该语句在初始程序语句返回大于零的代码时执行。基本语法如下:
command1 || command2
此语法读取为:“如果 command1 失败,则执行 command2。” 这意味着如果 command1 成功,则跳过 command2。通过尝试创建一个新目录来尝试此操作。
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$
这正是您所期望的。由于无法创建新目录,因此第一个命令失败,从而导致第二个命令的执行。
结合这两个操作符可以提供两者的优点。使用一些流程控制的控制操作符语法在使用 && 和 || 控制操作符时采用以下通用形式:
preceding commands ; command1 && command2 || command3 ; following commands
此语法可以这样表述:“如果 command1 以返回代码 0 退出,则执行 command2,否则执行 command3。” 试试看
[student@studentvm1 ~]$ Dir=/root/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
mkdir: cannot create directory '/root/testdir': Permission denied
/root/testdir was not created.
[student@studentvm1 ~]$
现在,再次尝试使用您的主目录而不是 /root 目录运行最后一个命令。您将有权创建此目录。
[student@studentvm1 ~]$ Dir=~/testdir ; mkdir $Dir && cd $Dir || echo "$Dir was not created."
[student@studentvm1 testdir]$
像 command1 && command2 这样的控制操作符语法之所以有效,是因为每个命令都会向 shell 发送一个返回代码 (RC),该代码指示它是否成功完成,或者在执行期间是否存在某种类型的失败。按照惯例,返回代码零 (0) 表示成功,任何正数表示某种类型的失败。系统管理员使用的一些工具只返回一 (1) 来表示失败,但许多工具使用其他代码来指示发生的失败类型。
Bash shell 变量 $? 包含来自上一个命令的 RC。脚本、命令列表中的下一个命令,甚至系统管理员都可以非常容易地检查此 RC。首先运行一个简单的命令并立即检查 RC。RC 将始终是您查看之前运行的最后一个命令的 RC。
[student@studentvm1 testdir]$ ll ; echo "RC = $?"
total 1264
drwxrwxr-x 2 student student 4096 Mar 2 08:21 chapter25
drwxrwxr-x 2 student student 4096 Mar 21 15:27 chapter26
-rwxr-xr-x 1 student student 92 Mar 20 15:53 TestFile1
<snip>
drwxrwxr-x. 2 student student 663552 Feb 21 14:12 testdir
drwxr-xr-x. 2 student student 4096 Dec 22 13:15 Videos
RC = 0
[student@studentvm1 testdir]$
在这种情况下,RC 为零,这意味着命令已成功完成。现在,在 root 的主目录(一个您没有权限的目录)上尝试相同的命令。
[student@studentvm1 testdir]$ ll /root ; echo "RC = $?"
ls: cannot open directory '/root': Permission denied
RC = 2
[student@studentvm1 testdir]$
在这种情况下,RC 为 2;这意味着非 root 用户访问其无权访问的目录的权限被拒绝。控制操作符使用这些 RC 使您能够更改程序执行的顺序。
总结
本文将 Bash 视为一种编程语言,并探讨了它的基本语法以及一些基本工具。它展示了如何将数据打印到 STDOUT 以及如何使用变量和控制操作符。本系列中的下一篇文章将介绍一些控制指令执行流程的 Bash 逻辑运算符。
评论已关闭。