The Missing Semester 01:Shell

本篇博客是《The Missing Semester of Your CS Education》系列课程的学习笔记第一篇:Shell。

shell 是什么?

如今的计算机有着多种多样的交互接口来进行指令的输入,例如图形界面、语音输入等。这些接口虽然使用方便,但其从根本上限制了我们的操作方式——我们不能够点击一个不存在的按钮或是用语音输入一个还没有被录入的指令。为了充分利用计算机的能力,我们需要回归根本的方式,使用文字接口——Shell

几乎所有的平台都支持某种形式的 shell,有些甚至提供了多种 shell。这些 shell 在细节上可能有所差异,但是其核心功能都是一样的:允许用户执行程序,输入并获取某种半结构化的输出。这里将使用类 Unix shell,如 bash 或 zsh,适用于 Linux 或 MacOS 系统(Windows 上需要使用 WSL 或虚拟机)。通过 echo $SHELL 命令可以查看当前 shell 的类型(预期输出为 /bin/bash/usr/bin/zsh)。

使用 shell

打开终端后,一般会出现一行提示符,例如:

missing:~$ 

该提示符会告知当前的主机名(missing)与当前所在目录(~,表示 home),用户可以对该提示符进行自由定制(展示不同的内容),在这个提示符下,我们可以输入命令(command),该命令会被 shell 所解析。例如,我们可以查看当前的日期:

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

我们还可以通过 echo 命令来打印任意的参数,如果需要打印带有空格的参数,可以使用引号(单双均可)或转义符号 \ 进行处理:

missing:~$ echo hello
hello
missing:~$ echo "Hello World"
Hello World

但是,shell 是如何知道去哪里寻找 dateecho 指令的呢?实际上,shell 是一个编程环境,具备变量、条件、循环、函数等基本要素(下一篇介绍),当用户在 shell 中执行命令时,实际上是在执行一段 shell 可以解释执行的简短代码。如果 shell 被要求执行某个指令,而该指令并不是 shell 所包含的编程关键字,那么它会去咨询被称为 $PATH环境变量(environment variable),该变量会列出当 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 会在 $PATH 中搜索由 : 所分割的一系列目录,基于该名称搜索对应的程序,如果存在则执行(该文件需要为可执行程序)。我们可以通过 which 关键字来找到具体的程序路径,并直接通过该路径执行程序。

在 shell 中导航

shell 中的路径是一组被分割的目录,在 Linux 和 macOS 上使用 / 分割,在 Windows 上则是 \(本文采用 / 进行说明)。单独的路径 / 表示系统根目录,所有的文件夹都包括在这个路径之下;而 Window 每个盘都有一个根目录,例如 C:\。如果某个路径以 / 开头,则其为一个绝对路径(absolute path),其他的都是相对路径(relative path)。相对路径是指相对于当前工作目录的路径,当前工作目录可以通过 pwd 命令来获取,切换目录则需要使用 cd 指令。此外,在相对路径中,. 表示当前目录,.. 则表示上级目录:

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

在运行一个程序时,如果没有指定路径,则该程序会在当前目录下执行。为了查看指定目录下包含哪些文件,可以使用 ls 命令:

missing:~$ ls
missing:~$ cd ..
missing:/home$ ls
missing
missing:/home$ cd ..
missing:/$ ls
bin
boot
dev
etc
home
...

如果不在第一个参数指定目录,则 ls 会打印当前目录下的文件。此外,还可以通过标记与选项(以 - 开头)改变程序的行为:

missing:~$ ls -l /home
drwxr-xr-x 1 missing users 4096 Jun 15 2019 missing

该选项可以打印出更为详细的文件或文件夹信息,包括文件类型、权限、拥有者、文件大小等内容。具体来说:

  • 第一列共 10 位,第 1 位表示文档类型,例如 d 表示目录,- 表示文件;后 9 位依次对应三种身份所拥有的权限,身份顺序为:owner、group、others,权限顺序为:readable(r)、writable(w)、executable(x),- 表示不具有对应权限
  • 第二列表示链接数,表示有多少个文件链接到该目录(文件则为 1)
  • 第三列表示拥有者(owner)
  • 第四列表示所属群组(group)
  • 第五列表示文档容量大小,单位字节
  • 第六列表示文档最后修改时间(注意与创建时间区分)
  • 第七列表示文档名称,以 . 开头的是隐藏文档

此外,还有一些常用命令需要掌握,包括 mv (重命名或移动文件)、cp(拷贝文件)以及 mkdir(新建文件夹)等。如果想了解某个命令(程序)的使用方式,可以通过 man 进行查询(注意需使用 vim):

missing:~$ man ls

在程序间创建连接

在 shell 中,程序由两个主要的流:输入流输出流。当程序尝试读取信息时,它会从输入流中进行读取;当程序打印信息时,它会将信息输出到输出流中。通常,一个程序的输入输出都是用户终端,即键盘作为输入,显示器作为输出。

不过,我们也可以对这些流进行重定向(redirection)。最简单的重定向是 < file> file。这两个命令可以将程序的输入输出流分别重定向到文件:

missing:~$ echo hello > hello.txt
missing:~$ cat hello.txt # cat 命令默认即为将输入流重定向到指定文件
hello
missing:~$ cat < hello.txt
hello
missing:~$ cat < hello.txt > hello2.txt
missing:~$ cat hello2.txt
hello

我们还可以使用 >> 来向一个文件追加内容。另一方面,通过管道(pipes)操作符 |,我们可以将一个程序的输出和另一个程序的输入连接起来:

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

根用户权限

对于大部分类 Unix 系统,有一类用户非常特殊,即根用户(root user)。根用户几乎不受任何限制,可以创建、读取、更新和删除系统中的任何文件。通常我们并不会以根用户的身份直接登录系统,而是会在需要的时候使用 sudo 命令。

本文提炼自《The Missing Semester of Your CS Education》官方课程笔记及其中文翻译