shell intro

June 01, 2021 by Sylvenas

Shell VS GUI

如今的计算机有着多种多样的交互接口让我们可以进行指令的的输入,从炫酷的图像用户界面(graphical user interface,GUI),语音输入甚至是 AR/VR 都已经无处不在。 这些交互接口可以覆盖 80% 的使用场景,但是它们也从根本上限制了您的操作方式——你不能点击一个不存在的按钮或者是用语音输入一个还没有被录入的指令。 为了充分利用计算机的能力,我们不得不回到最根本的方式,使用文字接口:Shell

shell 是外壳的意思,就是操作系统的外壳,同样 GUI 也是操作系统的外壳。不同的是一个通过调用命令,一个通过用户操作来与操作系统交互

shell 相比较于 GUI 的优势在于可以把复杂的操作组合在一起,形成一个自动化的工具(比如:持续集成),但是 GUI 就强依赖于“人”的操作。

几乎所有您能够接触到的平台都支持某种形式的 shell,有些甚至还提供了多种 shell 供您选择。虽然它们之间有些细节上的差异,但是其核心功能都是一样的:它允许你执行程序,输入并获取某种半结构化的输出。

我们可以通过 shell 命令来操作和控制操作系统,比如 Linux 中的 Shell 命令就包括 lscdpwd 等等。总结来说,Shell 是一个命令解释器,它通过接受用户输入的 Shell 命令来启动、暂停、停止程序的运行或对计算机进行控制。shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。

再次重申:shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序。

使用 shell

几乎所有您能够接触到的平台都支持某种形式的 shell,有些甚至还提供了多种 shell 供您选择。虽然它们之间有些细节上的差异,但是其核心功能都是一样的:它允许你执行程序,输入并获取某种半结构化的输出。

本节课我们会使用 Bourne Again Shell, 简称 bash 。 这是被最广泛使用的一种 shell,它的语法和其他的 shell 都是类似的。打开 shell 提示符(您输入指令的地方),您首先需要打开 终端(terminal) 。您的设备通常都已经内置了终端。

当您打开终端时,您会看到一个提示符,它看起来一般是这个样子的:

missing:~$

这是 shell 最主要的文本接口。它告诉你,你的主机名是 missing 并且您当前的工作目录("current working directory")或者说您当前所在的位置是 ~ (表示 home or root 目录)。 `$符号表示您现在的身份不是root` 用户(稍后会介绍)。在这个提示符中,您可以输入 命令 ,命令最终会被 shell 解析。最简单的命令是执行一个程序:

missing:~$ date
Fri 10 Jan 2020 11:49:31 AM EST
missing:~$

这里,我们执行了 date 这个程序,不出意料地,它打印出了当前的日前和时间。然后,shell 等待我们输入其他命令。我们可以在执行命令的同时向程序传递 参数 :

missing:~\$ echo hello
hello

我们让 shell 执行 echo ,同时指定参数 helloecho 程序将该参数打印出来。 shell 基于空格分割命令并进行解析,然后第一个单词代表的程序,并将后续的单词作为程序可以访问的参数。如果您希望传递的参数中包含空格(例如一个名为 My Photos 的文件夹),您要么使用单引号,双引号将其包裹起来,要么使用转义符号 \ 进行处理(My\ Photos)

但是,shell 是如何知道去哪里寻找 dateecho 的呢?其实,类似于 Python 或 Ruby,shell 是一个编程环境,所以它具备变量条件循环函数(下一课进行讲解)。当你在 shell 中执行命令时,您实际上是在执行一段 shell 可以解释执行的简短代码。如果你要求 shell 执行某个指令,但是该指令并不是 shell 所了解的编程关键字,那么它会去咨询 环境变量 `$PATH`,它会列出当 shell 接到某条指令时,进行程序搜索的路径:

missing:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
missing:~$ which echo
/bin/echo
missing:~$ /bin/echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

当我们执行 echo 命令时,shell 了解到需要执行 echo 这个程序,随后它便会在 $PATH 中搜索由 : 所分割的一系列目录,基于名字搜索该程序。

当找到该程序时便执行(假定该文件是 可执行程序,后续课程将详细讲解)。

确定某个程序名代表的是哪个具体的程序,可以使用 which 程序。我们也可以绕过 \$PATH,通过直接指定需要执行的程序的路径来执行该程序

shell 中导航

shell 中的路径是一组被分割的目录,在 Linux 和 macOS 上使用 / 分割,而在 Windows 上是 \。路径 / 代表的是系统的根目录,所有的文件夹都包括在这个路径之下,在 Windows 上每个盘都有一个根目录(例如: C:\)。 我们假设您在学习本课程时使用的是 Linux 文件系统。如果某个路径以 / 开头,那么它是一个绝对路径,其他的都是相对路径。相对路径是指相对于当前工作目录的路径,当前工作目录可以使用 (present work directory) pwd 命令来获取。此外,切换目录需要使用 (change directory) cd 命令。在路径中,. 表示的是当前目录,而..表示上级目录。

一般来说,当我们运行一个程序时,如果我们没有指定路径,则该程序会在当前目录下执行。例如,我们常常会搜索文件,并在需要时创建文件。

为了查看指定目录下包含哪些文件,我们使用 (list) ls 命令,ls 也可以接受第一个参数:查询的目录,比如查询当前目录的上一级目录包含哪些文件:

ls ..

程序之间建立链接-管道机制

在 shell 中,程序有两个主要的“流”:它们的输入流输出流。 当程序尝试读取信息时,它们会从输入流中进行读取,当程序打印信息时,它们会将信息输出到输出流中。 通常,一个程序的输入输出流都是您的终端。也就是,您的键盘作为输入,显示器作为输出。 但是,我们也可以重定向这些流!

最简单的重定向是 < file(输入) 和 > file(输出)。这两个命令可以将程序的输入输出流分别重定向到文件:

missing:~$ echo hello > hello.txt
missing:~$ cat hello.txt
hello
missing:~$ cat < hello.txt
hello
missing:~$ cat < hello.txt > hello2.txt
missing:~$ cat hello2.txt
hello

您还可以使用 >> 来向一个文件追加内容。使用管道( pipes ),我们能够更好的利用文件重定向。 | 操作符允许我们将一个程序的输出和另外一个程序的输入连接起来:

missing:~$ curl --head --silent google.com | grep --ignore-case content-length

Content-Length: 219

这段链接表示:1.获取 google.com 请求头的全部数据,2.将请求头的全部数据作为参数,传递给 grep 命令

权限

对于大多数的类 Unix 系统,有一类用户是非常特殊的,那就是:根用户(root user)。 您应该已经注意到了,在上面的输出结果中,根用户几乎不受任何限制,他可以创建、读取、更新和删除系统中的任何文件。 通常在我们并不会以根用户的身份直接登录系统,因为这样可能会因为某些错误的操作而破坏系统。 取而代之的是我们会在需要的时候使用 sudo 命令。顾名思义,它的作用是让您可以以 susuper userroot 的简写)的身份执行一些操作。 当您遇到拒绝访问(permission denied)的错误时,通常是因为此时您必须是根用户才能操作。然而,请再次确认您是真的要执行此操作。

有一件事情是您必须作为根用户才能做的,那就是向 sysfs 文件写入内容。系统被挂载在 /sys 下,sysfs 文件则暴露了一些内核(kernel)参数。 因此,您不需要借助任何专用的工具,就可以轻松地在运行期间配置系统内核。注意 Windows 和 macOS 没有这个文件。

那么什么是 shell 脚本呢?

shell 脚本就是由 Shell 命令组成的执行文件,将一些命令整合到一个文件中,进行处理业务逻辑,脚本不用编译即可运行。

它通过解释器解释运行,所以速度相对来说比较慢。shell 脚本中最重要的就是对 shell 命令的使用与组合,再使用 shell 脚本支持的一些语言特性,完成想要的功能。

通俗的说,shell 脚本 就是为了实现某个目标的多个 shell 命令的集合体

Shell 就是命令行工具的胶水,没有任何语言能像 Shell 一样方便地将一大堆命令行工具组合起来。原则上来说,Shell 做什么都可以,但显然它最适合的是自动化,因为只需要将你原来手动敲的命令都复制到一个文件里面就行了。

Shell 跟标准的编程语言区别很大,它基本上是一个面向字符串的编程语言,组合用好 awk/sed/grep,偶尔配合 eval,有时候会发挥奇效,但也有可能原地爆炸。

可以跟 Python 之类的其他语言配合起来,比如某个复杂的功能使用一个 Python 脚本来实现,然后在 shell 中调用这个脚本实现较复杂的功能;或者反过来,在 Python 脚本中调用外部的 Shell 脚本来提高自动化的效率,也是可以的。

shell 中调用 python,javascript 脚本

脚本并不一定只有用 bash 写才能在终端里调用。比如说,这是一段 Python 脚本,作用是将输入的参数倒序输出:

#!/usr/local/bin/python

import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

调用方式:

python example.py a b c

c
b
a

内核知道去用 python 解释器而不是 shell 命令来运行这段脚本,是因为脚本的开头第一行的shebang

在 shebang 行中使用 env 命令是一种好的实践,它会利用$PATH中的程序来解析该脚本,这样就提高来您的脚本的可移植性。env 会利用前面中介绍过的 $PATH 环境变量来进行定位。 例如:使用了 env 的 shebang 看上去时这样的 #!/usr/bin/env python

  • #!/usr/bin/python 相当于写死了 python 路径:一定要在/usr/bin下寻找,但是用户可能并没有把 python 安装到/usr/bin中;
  • #!/usr/bin/env python 这种用法是为了防止操作系统用户没有将 python 装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到 env 设置里查找 python 的安装路径,再调用对应路径下的解释器程序完成操作。

Node.js 也是完全类似的,建议使用第二种方案,可移植性更高。

其他常用命令

shell 脚本的校验工具:shellcheck

其他常用的 shell 命令为:(move file)mv,(copy)cp,(make directory)mkdir,(secure copy)scp,可以通过查阅文档来尝试