如今的计算机有着多种多样的交互接口来进行指令的输入,例如图形界面、语音输入等。这些接口虽然使用方便,但其从根本上限制了我们的操作方式——我们不能够点击一个不存在的按钮或是用语音输入一个还没有被录入的指令。为了充分利用计算机的能力,我们需要回归根本的方式,使用文字接口——「Shell」。
几乎所有的平台都支持某种形式的 shell,有些甚至提供了多种 shell。这些 shell 在细节上可能有所差异,但是其核心功能都是一样的:允许用户执行程序,输入并获取某种半结构化的输出。这里将使用类 Unix shell,如 bash 或 zsh,适用于 Linux 或 MacOS 系统(Windows 上需要使用 WSL 或虚拟机)。通过 echo $SHELL
命令可以查看当前 shell 的类型(预期输出为 /bin/bash
或 /usr/bin/zsh
)。
打开终端后,一般会出现一行提示符,例如:
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 是如何知道去哪里寻找 date
或 echo
指令的呢?实际上,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 中的路径是一组被分割的目录,在 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
该选项可以打印出更为详细的文件或文件夹信息,包括文件类型、权限、拥有者、文件大小等内容。具体来说:
d
表示目录,-
表示文件;后 9 位依次对应三种身份所拥有的权限,身份顺序为:owner、group、others,权限顺序为:readable(r
)、writable(w
)、executable(x
),-
表示不具有对应权限.
开头的是隐藏文档此外,还有一些常用命令需要掌握,包括 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》官方课程笔记[1]及其中文翻译[2]。
[1]
The Missing Semester of Your CS Education-Topic 1: The Shell: https://missing.csail.mit.edu/2020/course-shell/
[2]
计算机教育中缺失的一课-主题 1: The Shell: https://missing-semester-cn.github.io/2020/course-shell/