参考教程MIT公开课——The Missing Semester of Your CS Education

课程公开笔记

What is the shell?

why we need shell?

These are great for 80% of use-cases(use GUI), but they are often fundamentally restricted in what they allow you to do — you cannot press a button that isn’t there or give a voice command that hasn’t been programmed.To take full advantage of the tools your computer provides, we have to go old-school and drop down to a textual interface: The Shell.

总而言之就是,图形化界面给我们提供了极大的便利,但由于将功能封装在一个个特定功能的按键等交互上,使得其灵活性大打折扣,而更加古老的命令行工具很大程度上能够解决这一问题。话说这结论我好像在哪听过,像是最近在读的Orange's:一个操作系统的实现里也有相似论点。

Using the shell

在终端的命令提示符中输入想要执行的指令,这些命令会被shell所解析,以下是两个较为简单的命令

frechen026@frechen026-virtual-machine:~$ date
2023年 05月 24日 星期三 20:22:36 CST
frechen026@frechen026-virtual-machine:~$ echo hello
hello
frechen026@frechen026-virtual-machine:~$ echo "hello world"
hello world
frechen026@frechen026-virtual-machine:~$ echo hello\ world
hello world

传入的参数以空格分隔,一个多单词的参数,我们可以使用引号或者转义字符处理

shell借助环境变量$PATH知道某个程序存放的位置

shell说白了也是一种程序设计语言,提示符中甚至可以有条件、循环等等语句

可以通过echo $PATH查看有哪些环境变量,同时使用which指令可以知晓某程序所在路径,可以发现将echo替换成/usr/bin/echo效果相同。

frechen026@frechen026-virtual-machine:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
frechen026@frechen026-virtual-machine:~$ which echo
/usr/bin/echo
frechen026@frechen026-virtual-machine:~$ /usr/bin/echo hello
hello

path用以描述计算机中文件所在位置,LinuxMacOS起始路径都为根目录,位于整个文件系统的最顶层,Windows下每一个磁盘分区下都有一个根目录。Linux下可使用pwd指令打印出当前所在目录路径。

相对路径与绝对路径,Linux课程笔记中已有备注,不加以赘述。

...是较为特殊两个目录,其实也都知道了,.表示当前目录,..表示当前目录的父目录。

cd命令中两个较为特殊的符号~-~等价于/home/username,所以回到用户目录可以使用以下命令

frechen026@frechen026-virtual-machine:/$ cd ~
frechen026@frechen026-virtual-machine:~$ pwd
/home/frechen026

(其实直接cd不加任何参数也行)

frechen026@frechen026-virtual-machine:/$ cd
frechen026@frechen026-virtual-machine:~$ pwd
/home/frechen026

不过下列情形就有优势了

frechen026@frechen026-virtual-machine:~$ cd ~/Desktop/
frechen026@frechen026-virtual-machine:~/Desktop$ pwd
/home/frechen026/Desktop

-可以切换至上一次cd的目录,用于在两个目录之间跳转是极好的。

frechen026@frechen026-virtual-machine:~/Desktop$ cd -
/home/frechen026
frechen026@frechen026-virtual-machine:~$ cd -
/home/frechen026/Desktop
frechen026@frechen026-virtual-machine:~/Desktop$ cd -
/home/frechen026
frechen026@frechen026-virtual-machine:~$ cd -
/home/frechen026/Desktop

还有很多命令的讲解如ls man move…都与Linux相重复了,也就不加以赘述。

一个好用的快捷键Ctrl + l清屏操作,我居然还只会傻乎乎用clear指令,改天对快捷键也加以整理吧,无疑使用好快捷键可以大大提高效率。

在程序间创建连接

重定向

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

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

frechen026@frechen026-virtual-machine:~/Desktop$ echo hello > hello.txt
frechen026@frechen026-virtual-machine:~/Desktop$ cat hello.txt
hello

上述指令实现了将本应该输出到终端的output stream重定向到了hello.txt

frechen026@frechen026-virtual-machine:~/Desktop$ cat < hello.txt
hello

上述指令实现了将hello.txt内容作为输入重定向到cat指令中,效果等同于cat hello.txt

frechen026@frechen026-virtual-machine:~/Desktop$ cat < hello.txt > world.txt
frechen026@frechen026-virtual-machine:~/Desktop$ cat hello.txt
hello
frechen026@frechen026-virtual-machine:~/Desktop$ cat world.txt
hello

上述指令实现了将hello.txt内容作为input stream重定向给cat指令作为输入,又将cat指令的输出重定向给world.txt作为输入。事实上cat指令并不知道重定向这回事,他只管接受属于他的input stream并输出它的output stream,重定向这回事是shell该干的事。

使用>>来向一个文件追加内容,注意,不单单是写入,而是追加:

frechen026@frechen026-virtual-machine:~/Desktop$ cat < hello.txt >> world.txt
frechen026@frechen026-virtual-machine:~/Desktop$ cat world.txt
hello
hello

pipe

|分隔两个程序,将左侧程序的输出作为右侧程序的输入,看jyy第一节课就用到这个玩意的,当时也没去多想,看似高深实际了解了好像也就那么回事。

frechen026@frechen026-virtual-machine:~/Desktop$ ls -l
total 16
-rw-rw-r-- 1 frechen026 frechen026 6 5月 24 21:15 hello.txt
drwxrwxr-x 2 frechen026 frechen026 4096 5月 18 10:27 MakeTest
drwxrwxr-x 7 frechen026 frechen026 4096 5月 23 18:55 OS
-rw-rw-r-- 1 frechen026 frechen026 12 5月 24 21:38 world.txt
frechen026@frechen026-virtual-machine:~/Desktop$ ls -l | tail -n1
-rw-rw-r-- 1 frechen026 frechen026 12 5月 24 21:38 world.txt
frechen026@frechen026-virtual-machine:~/Desktop$ ls -l | head -n1
total 16

照着教程敲了tail,猜了一下对应应该有head指令吧,还真有。嘿嘿,合理的命名确实易于理解和记忆。上述指令将ls -l所输出的文件作为输入传递给了后续程序。

ls指令并不认识tailhead,反之亦是如此,并非刻意兼容,他们所知道的只有从输入读数据,写到输出中,连接他们的是pipe

frechen026@frechen026-virtual-machine:~/Desktop$ ls -l
total 16
-rw-rw-r-- 1 frechen026 frechen026 6 5月 24 21:15 hello.txt
drwxrwxr-x 2 frechen026 frechen026 4096 5月 18 10:27 MakeTest
drwxrwxr-x 7 frechen026 frechen026 4096 5月 23 18:55 OS
-rw-rw-r-- 1 frechen026 frechen026 12 5月 24 21:38 world.txt
frechen026@frechen026-virtual-machine:~/Desktop$ ls -l | tail -n1 > hello.txt
frechen026@frechen026-virtual-machine:~/Desktop$ cat hello.txt
-rw-rw-r-- 1 frechen026 frechen026 12 5月 24 21:38 world.txt

上述shell实现管道连接的同时将输出重定向到hello.txt

确实pipe是个十分神奇的东西,将命令联合在一起,对于文本数据甚至图片、视频都可以处理吗?

root用户

对于大多数的类 Unix 系统,有一类用户是非常特殊的,那就是:根用户(root user)。 您应该已经注意到了,在上面的输出结果中,根用户几乎不受任何限制,他可以创建、读取、更新和删除系统中的任何文件。

通常在我们并不会以根用户的身份直接登录系统,因为这样可能会因为某些错误的操作而破坏系统。 取而代之的是我们会在需要的时候使用 sudo 命令。

Linux中一切皆文件这个观点我们早就知道了,因此我们也可以对内核、外设那些相关文件进行直接的读写操作,这确实是件amazing的事情,尽管可能无法承受随意更改带来的后果。

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

这里我就纳闷了,Windows如今也支持Linux,如我就在使用的wsl2,利用它我可以不用虚拟机,不用双系统,就能在Windows上拥有Linuxwsl2Ubuntu20.04我也看了,也是存在该文件的,虚拟机下也存在该文件,不过其中包含的内容却有些许差异,就比如两者都没有例子中所提及的brightness,所以下面所尝试的例子我本人并没有实际去实验。

$ 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
An error occurred while redirecting file 'brightness'
open: Permission denied

This error may come as a surprise.我们明明已经使用了sudo去执行相关的操作,却仍然得到了有关于权限的报错,sad!But why?

先前对于重定向以及管道的说明就以提及,输入输出的重定向是程序所不知道的,这些都是由shell所设计好的。echo 等程序并不知道 | 的存在,它们只知道从自己的输入输出流中进行读写。

额,这里中文翻译的感觉不是太过于确切。以下是中英对照:
In the case above, the shell (which is authenticated just as your user) tries to open the brightness file for writing, before setting that as sudo echo’s output, but is prevented from doing so since the shell does not run as root.

对于上面这种情况, shell (权限为您的当前用户) 在设置 sudo echo 前尝试打开 brightness 文件并写入,但是系统拒绝了 shell 的操作因为此时 shell 不是根用户。

shell (权限为您的当前用户) 在设置 sudo echo 前尝试打开 brightness 文件并写入翻译的过于令人误解,应当是再将其设置为sudo echo的输出前,shell尝试去打开并写入该文件。

$ echo 3 | sudo tee brightness

我们可以尝试上述写法,由tee获得了root权限,由它去写brightness,在整条指令前面加上#,表示以root权限执行整条指令,也可解决该问题。这样您就可以在 /sys 中愉快地玩耍了,例如修改系统中各种LED的状态(路径可能会有所不同)

这里其实还是很迷糊的,因为该节课并未对shell是什么加以很明确的说明,虽然它的第一部分是what is the shell,但遗憾的是不管是课件还是讲解都未对shell加以说明,只知道他是古老的文本接口,它可以在终端中运行,可它究竟是什么?与终端有何区别却是缺失的。

课后习题:
1.To make sure you’re running an appropriate shell, you can try the command echo $SHELL.

frechen026@pine64:~$ echo $SHELL
/usr/bin/bash

2.Create a new directory called missing under /tmp.

frechen026@pine64:/tmp$ ls
systemd-private-e3b18eeb618d4974ab23387a737d64a5-chrony.service-b5aaey
systemd-private-e3b18eeb618d4974ab23387a737d64a5-haveged.service-aOe4WV
systemd-private-e3b18eeb618d4974ab23387a737d64a5-systemd-logind.service-472wpQ
systemd-private-e3b18eeb618d4974ab23387a737d64a5-systemd-resolved.service-JWR6oR
systemd-private-e3b18eeb618d4974ab23387a737d64a5-vnstat.service-GWob1k
frechen026@pine64:/tmp$ mkdir missing
frechen026@pine64:/tmp$ ls
missing
systemd-private-e3b18eeb618d4974ab23387a737d64a5-chrony.service-b5aaey
systemd-private-e3b18eeb618d4974ab23387a737d64a5-haveged.service-aOe4WV
systemd-private-e3b18eeb618d4974ab23387a737d64a5-systemd-logind.service-472wpQ
systemd-private-e3b18eeb618d4974ab23387a737d64a5-systemd-resolved.service-JWR6oR
systemd-private-e3b18eeb618d4974ab23387a737d64a5-vnstat.service-GWob1k

3.Look up the touch program. The man program is your friend.

frechen026@pine64:/$ man touch

4.Use touch to create a new file called semester in missing.

frechen026@pine64:/tmp$ cd missing
frechen026@pine64:/tmp/missing$ touch semester
frechen026@pine64:/tmp/missing$ ls
semester

5.Write the following into that file, one line at a time:

#!/bin/sh
curl --head --silent https://missing.csail.mit.edu
frechen026@pine64:/tmp/missing$ echo '#!/bin/sh' >> semester 
frechen026@pine64:/tmp/missing$ echo 'curl --head --silent https://missing.csail.mit.edu' >> semester
frechen026@pine64:/tmp/missing$ cat semester
#!/bin/sh
curl --head --silent https://missing.csail.mit.edu

使用追加重定向解决,不过直接使用文本编辑器显然更简单,例如vim,这也不算是跳脱命令行。

不顾提示,使用双引号进行尝试

frechen026@pine64:/tmp/missing$ echo "#!/bin/sh" >> semester 
-bash: !/bin/sh: event not found

得到如下报错,果然! has a special meaning even within double-quoted (") strings.
6.Try to execute the file, i.e. type the path to the script (./semester) into your shell and press enter. Understand why it doesn’t work by consulting the output of ls (hint: look at the permission bits of the file)

frechen026@pine64:/tmp/missing$ ./semester
-bash: ./semester: Permission denied
frechen026@pine64:/tmp/missing$ ls -al semester
-rw-rw-r-- 1 frechen026 frechen026 61 May 26 09:35 semester

可以看到该文件并无执行权限,只有读写权限,所以无法执行。

frechen026@pine64:/tmp/missing$ vim hello.c
frechen026@pine64:/tmp/missing$ gcc hello.c
frechen026@pine64:/tmp/missing$ ./a.out
hello world
frechen026@pine64:/tmp/missing$ ls
a.out hello.c semester
frechen026@pine64:/tmp/missing$ ls -al
total 20
drwxrwxr-x 2 frechen026 frechen026 100 May 26 09:48 .
drwxrwxrwt 13 root root 260 May 26 09:48 ..
-rwxrwxr-x 1 frechen026 frechen026 8872 May 26 09:48 a.out
-rw-rw-r-- 1 frechen026 frechen026 60 May 26 09:48 hello.c
-rw-rw-r-- 1 frechen026 frechen026 61 May 26 09:35 semester

快速编写一个可执行文件a.out,权限可以看到是可执行的。

7.Run the command by explicitly starting the sh interpreter, and giving it the file semester as the first argument, i.e. sh semester. Why does this work, while ./semester didn’t?

frechen026@pine64:/tmp/missing$ cat semester 
#!/bin/sh
curl --head --silent https://missing.csail.mit.edu
frechen026@pine64:/tmp/missing$ sh semester
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Sat, 06 May 2023 11:21:52 GMT
access-control-allow-origin: *
etag: "64563850-1f86"
expires: Fri, 26 May 2023 04:47:45 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: E7F6:0C29:113A80B:13B2FD3:64703799
accept-ranges: bytes
date: Fri, 26 May 2023 09:51:45 GMT
via: 1.1 varnish
age: 0
x-served-by: cache-bur-kbur8200051-BUR
x-cache: HIT
x-cache-hits: 1
x-timer: S1685094705.975637,VS0,VE111
vary: Accept-Encoding
x-fastly-request-id: 24d44d7a38558bd753a392070f5e0c6a9f300727
content-length: 8070

哈?居然真的执行了相关的内容,这莫非就是shell脚本的雏形?想来确实是执行了semester文件中的语句,amazing!!!

8.Look up the chmod program (e.g. use man chmod).

frechen026@pine64:/tmp/missing$ man chmod

根据名字判断chmod - change file mode bits,应该可以改变用户对文件的权限。

9.Use chmod to make it possible to run the command ./semester rather than having to type sh semester. How does your shell know that the file is supposed to be interpreted using sh? See this page on the shebang line for more information.

frechen026@pine64:/tmp/missing$ chmod u+x semester 
frechen026@pine64:/tmp/missing$ ls -l
total 20
-rwxrwxr-x 1 frechen026 frechen026 8872 May 26 09:48 a.out
-rw-rw-r-- 1 frechen026 frechen026 60 May 26 09:48 hello.c
-rwxrw-r-- 1 frechen026 frechen026 61 May 26 09:35 semester
frechen026@pine64:/tmp/missing$ ./semester
HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
last-modified: Sat, 06 May 2023 11:21:52 GMT
access-control-allow-origin: *
etag: "64563850-1f86"
expires: Thu, 25 May 2023 12:33:32 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: 2592:9B64:662C5D:74F633:646F5344
accept-ranges: bytes
date: Fri, 26 May 2023 13:11:07 GMT
via: 1.1 varnish
age: 0
x-served-by: cache-sna10737-LGB
x-cache: HIT
x-cache-hits: 1
x-timer: S1685106667.182469,VS0,VE89
vary: Accept-Encoding
x-fastly-request-id: c9c53da7d1dc7383c698ba2d9dac1b9e6a8094bf
content-length: 8070

semester加上可执行权限。有点意外,一个普通文件居然还能够执行,超出我对可执行文件的理解了。不过应该是shell去执行了其中的语句,跟sh同理。

10.Use | and > to write the “last modified” date output by semester into a file called last-modified.txt in your home directory.

frechen026@pine64:/tmp/missing$ ./semester | tail -n2 > last-modified.txt
frechen026@pine64:/tmp/missing$ cat last-modified.txt
content-length: 8070

tail -n1仅仅只是个换行,额,n2看来才是符合要求的结果。

11.Write a command that reads out your laptop battery’s power level or your desktop machine’s CPU temperature from /sys. Note: if you’re a macOS user, your OS doesn’t have sysfs, so you can skip this exercise.

CPU温度相关文件在/sys/class/thermal/

frechen026@pine64:/sys/class/thermal/thermal_zone0$ cat temp
83986

我去,燃爆了,居然温度高达83.98℃,好在我一阵风力降温

frechen026@pine64:/sys/class/thermal/thermal_zone0$ cat temp
54853

温度降到了54℃,不过小风扇没电了,温度也是又回到了80,难怪之前摸都烫手……

frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat capacity 
100
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat health
Good
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat current_now
0
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat constant_charge_current_max
1200000
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat constant_charge_current
1200000
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat type
Battery
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat present
0
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat status
Full
frechen026@pine64:/sys/class/power_supply/axp20x-battery$ cat online
0

查看电池,额,插电使用的卡片电脑,我也不晓得这电量该怎么算,不过capacity应该是容量吧,100应该是满电的。

至此学习完了第一节课的内容。

补充概念

回到上述提及过的问题,shell究竟是什么?其与terminal的联系与区别在哪?

什么是shell

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言

Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

我不清楚将shell称之为一种语言是否合适,shell确实有属于自己的shell语言,但是否可以说shell∈programming language我是不太赞同的,更像是Cgcc的关系。

找到较为权威的文档Bash Reference Manual,其中对于what is a shell做了如下解释:

At its base, a shell is simply a macro processor that executes commands. 
A Unix shell is both a command interpreter and a programming language. As a command interpreter, the shell provides the user interface to the rich set of GNU utilities. The programming language features allow these utilities to be combined.

显然,这里也确实将shell称为a programming language,那也无话可说,shell就姑且定义为一个具有语言特性的解释器。

Ken Thompsonsh 是第一种 Unix ShellWindows Explorer 是一个典型的图形界面 Shell

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell

terminalshell有何区别?

什么是terminal

终端是与计算机系统相连的一种输入、输出设备,通常离计算机较远。
早期的终端是一种叫做 电传打字机 - Teletype 的东西。虽然现在电传打字机已经不再适用,但它也是计算机发展的一个重要象征。所以 Teletype 的缩写 TTY 沿用至今,作为各种类型的终端设备的统称。难怪oranges一书中对于terminal使用的是tty进行描述,但是还纳闷是什么单词的缩写,原来如此。

实际上,前面我们说的终端指的都是物理的终端设备,而我们在 Linux 上常说的终端其实是 终端模拟器 - Terminal Emulator(一种 模拟终端的程序 ),也称为 —— 虚拟终端 ,但平时一般直接称为终端。

可以将 Linux 终端看作一个使用软件来模拟的输入、输出设备,其作用就是提供一个命令的输入、输出环境。Linux 终端是基于物理终端之上的。

shell

  • 在计算机科学中,Shell 俗称 “壳” (区别于 “核”—— Linux 内核)
  • 简单来说,Shell 就是接收用户输入的命令,然后提交给 Linux 内核处理的一个壳程序

shell壳的名称确实及其贴切,相对于系统内核而言,Shell 是包裹在操作系统 外层 的一道程序,负责外界与 Linux “内核” 的交互,但它隐藏了操作系统底层的具体细节,就像是 Linux 内核的一个 “外壳”,所以 Shell(壳)的名称也由此而来。

总结

由上述对于两者的定义不难看出,终端起到模拟输入输出的作用,提供人机交互的接口,将获得的输入转交给shell,而shell对获得的输入进行处理,交给Linux内核加以执行。