shell 是什么?

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

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

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

使用 shell

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

1
missing:~$ 

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

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

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

1
2
missing:~$ echo hello
hello

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

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

1
2
3
4
5
6
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 文件系统。如果某个路径以 / 开头,那么它是一个 绝对路径,其他的都是 相对路径 。**相对路径是指相对于当前工作目录的路径,当前工作目录可以使用 pwd 命令来获取。此外,切换目录需要使用 cd 命令。“cd -”为回到之前路径。 在路径中,. 表示的是当前目录,而 .. 表示上级目录**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
missing:~$ pwd
/home/missing
missing:~$ cd /home
missing:/home$ pwd
/home
missing:/home$ cd ..
missing:/$ pwd
/
missing:/$ cd ./home
missing:/home$ pwd
/home
missing:/home$ cd missing
missing:~$ pwd
/home/missing
missing:~$ ../../bin/echo hello
hello

注意,shell 会实时显示当前的路径信息。您可以通过配置 shell 提示符来显示各种有用的信息,这一内容我们会在后面的课程中进行讨论。

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

为了查看指定目录下包含哪些文件,我们使用 ls 命令:

1
2
3
4
5
6
7
8
9
10
11
12
missing:~$ ls
missing:~$ cd ..
missing:/home$ ls
missing
missing:/home$ cd ..
missing:/$ ls
bin
boot
dev
etc
home
...

除非我们利用第一个参数指定目录,否则 ls 会打印当前目录下的文件。大多数的命令接受标记和选项(带有值的标记),它们以 - 开头,并可以改变程序的行为。通常,在执行程序时使用 -h--help 标记可以打印帮助信息,以便了解有哪些可用的标记或选项。例如,ls --help 的输出如下:

1
2
3
  -l                         use a long listing format
missing:~$ ls -l /home
drwxr-xr-x 1 missing users 4096 Jun 15 2019 missing

这个参数可以更加详细地列出目录下文件或文件夹的信息。首先,本行第一个字符 d 表示 missing 是一个目录。然后接下来的九个字符,每三个字符构成一组。rwx). 它们分别代表了文件所有者(missing),用户组(users) 以及其他所有人具有的权限。其中 - 表示该用户不具备相应的权限。从上面的信息来看,只有文件所有者可以修改(w),missing 文件夹 (例如,添加或删除文件夹中的文件)。为了进入某个文件夹,用户需要具备该文件夹以及其父文件夹的“搜索”权限(以“可执行”:x)权限表示。为了列出它的包含的内容,用户必须对该文件夹具备读权限(r)。对于文件来说,权限的意义也是类似的。注意,/bin 目录下的程序在最后一组,即表示所有人的用户组中,均包含 x 权限,也就是说任何人都可以执行这些程序。(r:read;w:write;x:execute)

在这个阶段,还有几个趁手的命令是您需要掌握的,例如 mv(用于重命名或移动文件)cp(拷贝文件)以及 mkdir(新建文件夹)

如果您想要知道关于程序参数、输入输出的信息,亦或是想要了解它们的工作方式,请试试 man 这个程序。它会接受一个程序名作为参数,然后将它的文档(用户手册)展现给您。注意,使用 q 可以退出该程序。

1
missing:~$ man ls
如何改变权限?

1.使用chmod(change mode)命令,修改权限。

1
2
3
4
chmod u+r file.txt  // 给文件所有者添加读权限
chmod g+w file.txt // 给用户组添加写权限
chmod o-x file.txt // 禁止其他用户执行文件
chmod -R u+rwx blog //-R 选项会将我要修改的权限递归给目录中的所有文件和子目录

2.右键选择“属性”->“安全”,在“安全”中修改user对文件的权限。

在程序间创建连接

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

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

1
2
3
4
5
6
7
8
9
missing:~$ echo hello > hello.txt
missing:~$ cat hello.txt
hello
missing:~$ cat < hello.txt
hello
missing:~$ cat < hello.txt > hello2.txt //cat从hello.txt中读取内容后将内容输入到hello2.txt
//如果hello2.txt中有内容,会被hello.txt完全覆盖!!
missing:~$ cat hello2.txt
hello

cat hello.txtcat < hello.txt 这两个命令在 Linux 或 Unix 系统中都用于查看文件内容,但它们之间有一些区别。

  1. cat hello.txt:这个命令会将 hello.txt 文件的内容输出到标准输出(通常是终端),使你可以查看文件的内容。cat 是 concatenate(连接)的缩写,它的主要作用是将文件内容连接在一起并输出到标准输出,通常用于显示文件内容。

  2. cat < hello.txt:这个命令使用输入重定向 <,将 hello.txt 文件的内容作为输入传递给 cat 命令。这样,cat 命令会从 hello.txt 文件中读取内容并将其输出到标准输出。

主要区别在于输入来源的不同:

  • cat hello.txt 中,cat 直接读取 hello.txt 文件的内容;
  • 而在 cat < hello.txt 中,cat 通过输入重定向符 <hello.txt 文件中读取内容。

总体来说,这两个命令的功能都是用来显示文件内容,不同之处在于输入来源的方式不同。通常情况下,直接使用 cat hello.txt 就可以满足查看文件内容的需求。

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

1
2
3
4
5
missing:~$ ls -l / | tail -n1
drwxr-xr-x 1 root root 4096 Jun 20 2019 var
missing:~$ curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2
219
//最终输出的结果是网页内容的长度,这里是 219。

这个命令的作用是通过curl命令获取google.com的响应头信息,然后使用grep命令筛选出包含”content-length”(不区分大小写)的行,接着使用cut命令以空格为分隔符提取出第2个字段(即content-length的值)。

具体解释如下:

  • curl --head --silent google.com:使用curl命令向google.com发出HEAD请求,只获取响应头信息,同时不显示进度信息。
  • |:管道符号,将curl命令的输出作为grep命令的输入。
  • grep --ignore-case content-length:在curl的输出中查找包含”content-length”(不区分大小写)的行。
  • |:再次使用管道符号,将grep命令的输出作为cut命令的输入。
  • cut --delimiter=' ' -f2:以空格为分隔符,提取出第2个字段,即content-length的值。

所以,该命令的输出结果是219,表示google.com的响应头中的content-length的值为219。

一个功能全面又强大的工具

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

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

例如,您笔记本电脑的屏幕亮度写在 brightness 文件中,它位于

1
/sys/class/backlight

通过将数值写入该文件,我们可以改变屏幕的亮度。现在,蹦到您脑袋里的第一个想法可能是:

1
2
3
4
5
6
$ sudo find -L /sys/class/backlight -maxdepth 2 -name '*brightness*'
/sys/class/backlight/thinkpad_screen/brightness
$ cd /sys/class/backlight/thinkpad_screen
$ sudo echo 3 > brightness //在这个命令中,sudo 只应用于 echo 3 这部分命令,而 > 操作符是由 shell 处理的,而不是由 sudo 处理的
An error occurred while redirecting file 'brightness'
open: Permission denied

出乎意料的是,我们还是得到了一个错误信息。毕竟,我们已经使用了 sudo 命令!关于 shell,有件事我们必须要知道。**|>、和 < 是通过 shell 执行的,而不是被各个程序单独执行。** echo 等程序并不知道 | 的存在,它们只知道从自己的输入输出流中进行读写。 对于上面这种情况, shell (权限为您的当前用户) 在设置 sudo echo 前尝试打开 brightness 文件并写入,但是系统拒绝了 shell 的操作因为此时 shell 不是根用户。

明白这一点后,我们可以这样操作:

1
$ echo 3 | sudo tee brightness

因为打开 /sys 文件的是 tee 这个程序,并且该程序以 root 权限在运行,因此操作可以进行。 这样您就可以在 /sys 中愉快地玩耍了,例如修改系统中各种LED的状态(路径可能会有所不同):

1
$ echo 1 | sudo tee /sys/class/leds/input6::scrolllock/brightness

ps:

  • echo 3:表示输出数字3。
  • |:管道符号,将前一个命令的输出作为后一个命令的输入。
  • sudo:以超级用户权限来执行后面的命令。
  • tee brightness:将输入的内容同时输出到屏幕上和指定的文件brightness中。

tee 是一个常用的命令行工具,用于从标准输入读取数据,并将其同时输出到标准输出(通常是终端屏幕)和一个或多个文件。tee 命令允许用户在命令行中查看输出的同时,将输出保存到文件中

主要作用包括:

  1. 从标准输入读取数据,并将数据输出到标准输出和一个或多个文件。
  2. 允许用户在命令执行过程中查看数据的同时,将数据保存到文件中,方便日后查看或分析。

在使用 tee 命令时,常见的语法为:command | tee file.txt,这样可以将 command 命令的输出输出到终端并保存到 file.txt 文件中。

课后练习

习题解答 本课程中的每节课都包含一系列练习题。有些题目是有明确目的的,另外一些则是开放题,例如“尝试使用 X 和 Y”,我们强烈建议您一定要动手实践,用于尝试这些内容。 此外,我们没有为这些练习题提供答案。如果有任何困难,您可以发送邮件给我们并描述你已经做出的尝试,我们会设法帮您解答。

  1. 本课程需要使用类Unix shell,例如 Bash 或 ZSH。如果您在 Linux 或者 MacOS 上面完成本课程的练习,则不需要做任何特殊的操作。如果您使用的是 Windows,则您不应该使用 cmd 或是 Powershell;您可以使用Windows Subsystem for Linux或者是 Linux 虚拟机。使用echo $SHELL命令可以查看您的 shell 是否满足要求。如果打印结果为/bin/bash/usr/bin/zsh则是可以的。

  2. /tmp 下新建一个名为 missing 的文件夹。

  3. man 查看程序 touch 的使用手册。

  4. touchmissing 文件夹中新建一个叫 semester 的文件。

  5. 将以下内容一行一行地写入

    1
    semester

    文件:

    1
    2
    #!/bin/sh
    curl --head --silent https://missing.csail.mit.edu

    第一行可能有点棘手, # 在Bash中表示注释,而 ! 即使被双引号(")包裹也具有特殊的含义。 单引号(')则不一样,此处利用这一点解决输入问题。更多信息请参考 Bash quoting 手册

  6. 尝试执行这个文件。例如,将该脚本的路径(./semester)输入到您的shell中并回车。如果程序无法执行,请使用 ls 命令来获取信息并理解其不能执行的原因。

  7. 查看 chmod 的手册(例如,使用 man chmod 命令)

  8. 使用 chmod 命令改变权限,使 ./semester 能够成功执行,不要使用 sh semester 来执行该程序。您的 shell 是如何知晓这个文件需要使用 sh 来解析呢?更多信息请参考:shebang

  9. 使用 |> ,将 semester 文件输出的最后更改日期信息,写入主目录下的 last-modified.txt 的文件中

  10. 写一段命令来从 /sys 中获取笔记本的电量信息,或者台式机 CPU 的温度。注意:macOS 并没有 sysfs,所以 Mac 用户可以跳过这一题。