计算机教育中缺失的一课 笔记一

The Missing Semester of Your CS Education Note 1

目录

前言

2020 MIT公开课:The Missing Semester of Your CS Education(计算机教育中缺失的一课)

官方 github 仓库:https://github.com/missing-semester/missing-semester

2020 讲义:https://missing.csail.mit.edu/

2020 中文讲义:https://missing-semester-cn.github.io/

2019 讲义:https://missing.csail.mit.edu/2019/

感谢课程制作者和 刘黑黑a 的翻译上传。

我的笔记基本是课程内容和讲义的二次整理,方便自己查看。

Shell 工具和脚本

Shell 基于空格分割命令并进行解析。如果传递的参数中包含空格,使用单引号,双引号,或使用转义符号 \ 。 在 Shell 中执行 dateecho 时,Shell 通过环境变量来找到程序并执行。也就是说 Shell 是一个编程环境,它实际具备变量、条件、循环和函数等。

Shell 查看环境变量 echo $PATH。 查看程序路径 which echo

Shell 中的路径是一组被分割的目录,在 Linux 和 macOS 上使用 / 分割,而在Windows上是 \。

pwd 获取当前目录路径。 cd 切换目录,. 当前目录, .. 上级目录。

ls 打印当前目录下的文件 mv 用于重命名或移动文件 cp 拷贝文件 mkdir 新建文件夹

在 Shell 中,程序有两个主要的“流”:它们的输入流和输出流。 最简单的重定向是 <>。还可以使用 >> 来向一个文件追加内容,使用管道 | 操作符,我们能够更好的利用文件重定向。 在 Bash 中为变量赋值的语法是 foo=bar,访问变量中存储的数值,其语法为 $foo。 Bash 中的字符串通过 '" 分隔符来定义,但是它们的含义并不相同。

' 定义的字符串为原义字符串,其中的变量不会被转义 " 定义的字符串会将变量值进行替换。

Bash 支持 if, case, whilefor 这些控制流关键字。 Bash 支持函数,它可以接受参数并基于参数进行操作。与其他脚本语言不同的是,bash使用了很多特殊的变量来表示参数、错误代码和相关变量。下面是列举来其中一些变量,更完整的列表可以参考 高级 Bash 脚本编写指南:第三章特殊字符

$0 - 脚本名 $1$9 - 脚本的参数。 $1 是第一个参数,依此类推。 $@ - 所有参数 $# - 参数个数 $? - 前一个命令的返回值 $$ - 当前脚本的进程识别码 !! - 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!再尝试一次。 $_ - 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 . 来获取这个值。

命令通常使用 STDOUT 来返回输出值,使用 STDERR 来返回错误及错误码。 返回码或退出状态是脚本/命令之间交流执行状态的方式。返回值0表示正常执行,其他所有非0的返回值都表示有错误发生。 退出码可以搭配 &&(与操作符)和 ||(或操作符)使用,用来进行条件判断,决定是否执行其他程序。同一行的多个命令可以用 ; 分隔。程序 true 的返回码永远是 0false 的返回码永远是 1。 另一个常见的模式是以变量的形式获取一个命令的输出,这可以通过命令替换(command substitution)实现。 $(CMD) 这样的方式来执行CMD 这个命令时,它的输出结果会替换掉 $(CMD)。 在 Bash 中进行比较时,尽量使用双方括号 [[ ]] 而不是单方括号 [ ]

通配符 - 当你想要利用通配符进行匹配时,你可以分别使用 ?* 来匹配一个任意个字符。 花括号 {} - 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令。

shell函数和脚本有如下一些不同点:

函数只能与shell使用相同的语言,脚本可以使用任意语言。因此在脚本中包含 shebang 是很重要的。 函数仅在定义时被加载,脚本会在每次被执行时加载。这让函数的加载比脚本略快一些,但每次修改函数定义,都要重新加载一次。 函数会在当前的shell环境中执行,脚本会在单独的进程中执行。因此,函数可以对环境变量进行更改,比如改变当前工作目录,脚本则不行。脚本需要使用 export 将环境变量导出,并将值传递给环境变量。 与其他程序语言一样,函数可以提高代码模块性、代码复用性并创建清晰性的结构。shell脚本中往往也会包含它们自己的函数定义。

命令行基本工具

shellcheck 定位sh/bash脚本中的错误。
TLDR pages 它提供了一些命令案例,帮助快速找到正确的选项。
find 它是 shell 上用于查找文件的绝佳工具。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 查找所有名称为src的文件夹
find . -name src -type d
# 查找所有文件夹路径中包含test的python文件
find . -path '*/test/*.py' -type f
# 查找前一天修改的所有文件
find . -mtime -1
# 查找所有大小在500k至10M的tar.gz文件
find . -size +500k -size -10M -name '*.tar.gz'
# 删除全部扩展名为.tmp 的文件
find . -name '*.tmp' -exec rm {} \;
# 查找全部的 PNG 文件并将其转换为 JPG
find . -name '*.png' -exec convert {} {}.jpg \;
fd 就是一个更简单、更快速、更友好的查找程序,它可以用来作为find的替代品。输出着色、默认支持正则匹配、支持unicode并且我认为它的语法更符合直觉。
locate 使用一个由 updatedb负责更新的数据库,通过编译索引或建立数据库的方式来实现更加快速地搜索。在大多数系统中 updatedb 都会通过 cron 每日更新。find 和类似的工具可以通过别的属性比如文件大小、修改时间或是权限来查找文件,locate则只能通过文件名。
grep 有很多选项,这也使它成为一个非常全能的工具。其中经常使用的有 -C :获取查找结果的上下文(Context);-v 将对结果进行反选(Invert),也就是输出不匹配的结果。举例来说, grep -C 5 会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R 会递归地进入子目录并搜索所有的文本文件。
ack, agrg 都是 grep 的替代品,比较常用的是 ripgrep (rg) ,速度快,而且用法符合直觉。
1
2
3
4
5
6
7
8
# 查找所有使用了 requests 库的文件
rg -t py 'import requests'
# 查找所有没有写 shebang 的文件(包含隐藏文件)
rg -u --files-without-match "^#!"
# 查找所有的foo字符串,并打印其之后的5行
rg foo -A 5
# 打印匹配的统计信息(匹配的行和文件的数量)
rg --stats PATTERN
history 命令允许您以程序员的方式来访问shell中输入的历史命令。

对于大多数的 Shell 来说,您可以使用 Ctrl+R 对命令历史记录进行回溯搜索。敲 Ctrl+R 后您可以输入子串来进行匹配,查找历史命令行。zsh 支持直接方向键上、下,同时 zsh 还支持基于历史的自动补全,非常方便。

你可以修改 shell history 的行为,例如,如果在命令的开头加上一个空格,它就不会被加进 Shell 记录中。当你输入包含密码或是其他敏感信息的命令时会用到这一特性。 为此你需要在 .bashrc 中添加 HISTCONTROL=ignorespace 或者向 .zshrc 添加 setopt HIST_IGNORE_SPACE。 如果你不小心忘了在前面加空格,可以通过编辑。bash_history.zhistory 来手动地从历史记录中移除那一项。

fzf 是一个通用对模糊查找工具,它可以和很多命令一起使用。
fasdautojump 这两个工具来查找最常用或最近使用的文件和目录。

Fasd 基于 frecency 对文件和文件排序,也就是说它会同时针对频率(frequency)和时效(recency)进行排序。默认情况下,fasd使用命令 z 帮助我们快速切换到最常访问的目录。 还有一些更复杂的工具可以用来概览目录结构,例如 tree, broot 或更加完整的文件管理器,例如 nnnranger

xargs

命令传递参数的一个过滤器,也是组合多个命令的一个工具。它能够捕获一个命令的输出,然后传递给另外一个命令。有时候您要利用数据整理技术从一长串列表里找出你所需要安装或移除的东西。我们之前讨论的相关技术配合 xargs 即可实现。之所以能用到这个命令,关键是由于很多命令不支持|管道来传递参数,而日常工作中有有这个必要,所以就有了 xargs 命令。 命令格式 somecommand |xargs -item command

数据分析相关工具

less 文件分页器

less 为我们创建来一个文件分页器,使我们可以通过翻页的方式浏览较长的文本。

sed 流编辑器

sed 是一个基于文本编辑器 ed 构建的”流编辑器” 。在 sed 中,可以利用一些简短的命令来修改文件,而不是直接操作文件的内容。最常用的是 s 替换命令。sed 可以搭配其他命令,完成一些数据的处理。例如 cat server.log | grep sshd | grep "Disconnected from" | sed 's/.*Disconnected from //' 通过 grep 过滤最后使用 sed 匹配正则表达式完成了一次输出。 sed 还可以做很多各种各样有趣的事情,例如文本注入:(使用 i 命令),打印特定的行 (使用 p 命令),基于索引选择特定行等等。 关于 sed 的更多使用,可以通过 Sed 备忘清单 了解。

正则表达式(RegEX)

正则表达式非常常见也非常有用,值得您花些时间去理解它。

. 除换行符之外的”任意单个字符” * 匹配前面字符零次或多次 + 匹配前面字符一次或多次 [abc] 匹配 a, b 和 c 中的任意一个 (RX1|RX2) 任何能够匹配RX1 或 RX2的结果 ^ 行首 $ 行尾

sed 使用正则表达式有些时候是比较奇怪的,它需要你在这些模式前添加 \ 才能使其具有特殊含义。或者,您也可以添加 -E 选项来支持这些匹配。 正则表达式是出了名的难以写对,但是它仍然会是您强大的常备工具之一。我在此不准备写更多关于正则表达式的用例。下面列出来一些非常有用的网站。

正则表达式在线调试工具regex debugger
RegExp Example 正则实例大全
RegEX 备忘清单

awk 用于文本处理的编程语言

Awk、sed与grep,俗称Linux下的三剑客,它们之间有很多相似点,但是同样也各有各的特色,相似的地方是它们都可以匹配文本,其中sed和awk还可以用于文本编辑,而grep则不具备这个功用。sed是一种非交互式且面向字符流的编辑器(a “non-interactive” stream-oriented editor),而awk则是一门模式匹配的编程语言,因为它的主要功能是用于匹配文本并处理,同时它有一些编程语言才有的语法,例如函数、分支循环语句、变量等等,当然比起我们常见的编程语言,Awk相对比较简单。

语法 awk [选项参数] 'script' var=value file(s)awk [选项参数] -f scriptfile var=value file(s),例子:每行按空格或TAB分割,输出文本中的1、4项 awk '{print $1,$4}' log.txt。 在代码块中,$0 表示整行的内容,$1$n 为一行中的 n 个区域,区域的分割基于 awk 的域分隔符(默认是空格,可以通过 -F 来修改)。 更多内容可以看 菜鸟教程Awk 备忘清单

sort 数据排序uniq 次数统计head 查看开头tail 查看内容paste 合并列bc 计算器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ssh myserver journalctl
 | grep sshd # 筛选
 | grep "Disconnected from"
 | sed -E 's/.*Disconnected from (invalid |authenticating )?user (.*) [^ ]+ port [0-9]+( \[preauth\])?$/\2/'
 | sort | uniq -c 
 # sort 会对其输入数据进行排序。
 # uniq -c 会把连续出现的行折叠为一行并使用出现次数作为前缀。
 # 这句最终是按照出现次数排序,过滤出最常出现的用户名
 | sort -nk1,1 | tail -n10
 # sort -n 会按照数字顺序对输入进行排序
 # 默认情况下是按照字典序排序 -k1,1 则表示“仅基于以空格分割的第一列进行排序”
 # n 部分表示“仅排序到第n个部分”,默认情况是到行尾。
 # tail -n10 表示从尾部显示十行,也就是次数多的
 | awk '{print $2}' | paste -sd,
 # awk 对于每一行文本,打印其第二个部分,也就是用户名。
 # paste 以 ',' 分隔,将用户名放到一行
1
2
| paste -sd+ | bc -l
# paste 将一列数字用 '+' 分隔放到一行,然后 bc 执行

R语言

R 也是一种编程语言,它非常适合被用来进行数据分析绘制图表。这里我们不会讲的特别详细, 您只需要知道summary 可以打印某个向量的统计结果。我们将输入的一系列数据存放在一个向量后,利用R语言就可以得到我们想要的统计数据。

gnuplot 二维曲线绘图工具

整理二进制数据

虽然到目前为止我们的讨论都是基于文本数据,但对于二进制文件其实同样有用。例如我们可以用 ffmpeg 从相机中捕获一张图片,将其转换成灰度图后通过SSH将压缩后的文件发送到远端服务器,并在那里解压、存档并显示。

1
2
3
4
ffmpeg -loglevel panic -i /dev/video0 -frames 1 -f image2 -
 | convert - -colorspace gray -
 | gzip
 | ssh mymachine 'gzip -d | tee copy.jpg | env DISPLAY=:0 feh -'

编辑器(Vim)

写作和写代码其实是两项非常不同的活动。当我们编程的时候,会经常在文件间进行切换、阅读、浏览和修改代码,而不是连续编写一大段的文字。因此代码编辑器和文本编辑器是很不同的两种工具。

Vim 的哲学——多模态编辑

多模态编辑,插入文字、操纵文字有不同模式,Vim 接口本身就是一种程序语言可以进行编程,键入操作(以及他们的助记名) 本身是命令,这些命令可以组合使用。这使得移动和编辑更加高效。 Vim 会维护一系列打开的文件,称为**“缓存”。一个 Vim 会话包含一系列标签页**,每个标签页包含 一系列窗口(分隔面板)。每个窗口显示一个缓存。但值得注意的是,“缓存”和“窗口”之间不是对应关系,窗口只是其中一个视角,我们可以打开多个窗口显示同一个缓存。

模式 作用 备注
正常模式(默认) 在文件中四处移动光标进行修改 <ESC> 回到正常模式
插入模式 插入文本 i 进入
替换模式 替换文本 R 进入
可视化模式(一般,行,块) 选中文本块 v 一般、V 行、<C-v>
命令模式 用于执行命令 : 进入

在不同模式下,按键的含义也不同,一般当前的模式会显示在左下角。

Vim 命令

除了以下课程上提到的操作之外我推荐 Vim 备忘清单

命令行模式

:q 退出(关闭窗口) :w 保存(写) :wq 保存然后退出 :e {文件名} 打开要编辑的文件 :ls 显示打开的缓存 :help {标题} 打开帮助文档 :help :w 打开 :w 命令的帮助文档 :help w 打开 w 移动的帮助文档

移动

**基本移动: *h``j``k``l (左, 下, 上, 右) 词: w (下一个词), b (词初), e (词尾) 行: 0 (行初), ^ (第一个非空格字符), $ (行尾) 屏幕: H (屏幕首行), M (屏幕中间), L (屏幕底部) 翻页: Ctrl-u (上翻), Ctrl-d (下翻) 文件: gg (文件头), G (文件尾) 行数: :{行数}<CR> 或者 {行数}G ({行数}为行数) 杂项: % (找到配对,比如括号或者 / */ 之类的注释对) 查找: f{字符}t{字符}F{字符}T{字符} 查找/到 向前/向后 在本行的{字符}

  • ,/; 用于导航匹配

**搜索: **/{正则表达式}, n/N 用于导航匹配

编辑

i 进入插入模式 O/o 在之上/之下插入行 d{移动命令} 删除 {移动命令}

  • 例如,dw 删除词, d$ 删除到行尾, d0 删除到行头。

c{移动命令} 改变 {移动命令}

  • 例如,cw 改变词
  • 比如 d{移动命令}i

x 删除字符(等同于 dls 替换字符(等同于 xi) 可视化模式 + 操作

  • 选中文字, d 删除 或者 c 改变

u 撤销, <C-r> 重做 y 复制 / “yank” (其他一些命令比如 d 也会复制) p 粘贴 ~ 改变字符的大小写

可视化模式下的选择

可视化:v 可视化行:V 可视化块:Ctrl+v

使用移动命令选中

计数使用

所有你需要用鼠标做的事, 你现在都可以用键盘:采用编辑命令和移动命令的组合来完成。 这就是 Vim 的界面开始看起来像一个程序语言的时候。Vim 的编辑命令也被称为 “动词”, 因为动词可以施动于名词。下面是一些例子,通过结合使用,来指定操作多次。

3w 向前移动三个词 5j 向下移动5行 7dw 删除7个词

使用修饰语

你可以用修饰语改变“名词”的意义。 修饰语有 i,表示**“内部”或者“在内”,和 a, 表示“周围”**。

ci( 改变当前括号内的内容 ci[ 改变当前方括号内的内容 da' 删除一个单引号字符串, 包括周围的单引号

进阶操作:搜索与替换

:s (替换)命令(文档)。

%s/foo/bar/g 在整个文件中将 foo 全局替换成 bar %s/[._]((._))/\1/g 将有命名的 Markdown 链接替换成简单 URLs

进阶操作:多窗口

:sp/:vsp 来分割窗口 同一个缓存可以在多个窗口中显示。

进阶操作:宏

q{字符} 来开始在寄存器{字符}中录制宏 q 停止录制 @{字符} 重放宏,宏的执行遇错误会停止 {计数}@{字符} 执行一个宏{计数}次 @@ 重复上一个宏

配置文件与扩展插件

自定义你的 Vim —— 从配置文件开始

Vim 由一个位于 ~/.vimrc 的文本配置文件,如果没有就新建一个即可。Vim 能够被重度自定义,花时间探索自定义选项是值得的。可以参考其他人的在 GitHub 上共享的配置文件。例如下面是三位授课人的配置文件,但尽量在阅读后加入自己用的到的部分。不要直接复制。

Anish 的配置文件 Jon (uses neovim) 的配置文件 Jose 的配置文件

下面提供一个文档详细的基本设置,你可以用它当作你的初始设置。我们推荐使用这个设置因为它修复了一些 Vim 默认设置奇怪行为。将其复制粘贴保存为 ~/.vimrc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
" Vimscript 中的注释以 `"` 开头。

" 如果您在 Vim 中打开此文件,它将为您突出显示语法。

" Vim 是基于 Vi 的。设置 `noknown` 会切换默认的 Vi 兼容模式并启用有用的 Vim 功能。
" 对于名为 '~/.vimrc' 的文件来说,这个配置选项不是必需的,因为 Vim 会自动输入 noknown
" 如果该文件存在,则我们将其包含在此处,以防万一该配置文件以其他方式加载
" (例如保存为 `foo`,然后 Vim 以 `vim -u foo` 启动)。
set nocompatible

" 打开语法高亮。
syntax on

" 禁用默认的 Vim 启动消息。
set shortmess+=I

" 显示行号。
set number

" 这将启用相对行编号模式。同时启用 number 和relativenumber 后,当前行显示真实行号,
" 而所有其他行(上方和下方)均相对于当前行进行编号。这很有用,因为您可以知道,
" 一目了然,需要多少次才能向上或向下跳转到特定行,通过 {count}k 向上或通过 {count}j 向下。
set relativenumber

" 即使只打开一个窗口,也始终在底部显示状态行。
set laststatus=2

" 默认情况下,退格键的行为有点不直观。
" 例如,默认情况下,您无法在用“i”设置的插入点之前退格。
" 此配置使退格键的行为更加合理,因为您可以在任何东西上退格。
set backspace=indent,eol,start

" 默认情况下,Vim 不允许您隐藏具有未保存更改的缓冲区(即具有未在任何窗口中显示的缓冲区)。
" 这是为了防止您 " 忘记未保存的更改然后退出,例如 通过`:qa!`
" 我们发现隐藏缓冲区足以帮助禁用此保护。 有关详细信息,请参阅“:helphidden”。
set hidden

" 当要搜索的字符串中的所有字符均为小写时,此设置使搜索不区分大小写。
" 但是,如果包含任何大写字母,则搜索将区分大小写。这使搜索更加方便。
set ignorecase
set smartcase

" 在键入时启用搜索,而不是等到按 Enter 键。
set incsearch

" 取消绑定一些无用/烦人的默认按键绑定。
" 'Q' 在正常模式下进入 Ex 模式。你几乎永远不会想要这个。
nmap Q <Nop> " 'Q' in normal mode enters Ex mode. You almost never want this.

" 禁用声音铃声,因为它很烦人。
set noerrorbells visualbell t_vb=

" 启用鼠标支持。您应该避免过度依赖它,但有时它很方便。
set mouse+=a

" 尽量避免使用方向键进行移动等坏习惯。这并不是唯一可能的坏习惯。
" 例如,按住 h/j/k/l 键进行移动,而不是使用更高效的移动命令,也是一种可能的坏习惯。
" 前者可以通过 .vimrc 强制执行,而我们不知道如何防止后者。
" 在正常模式下执行此操作...
nnoremap <Left>  :echoe "Use h"<CR>
nnoremap <Right> :echoe "Use l"<CR>
nnoremap <Up>    :echoe "Use k"<CR>
nnoremap <Down>  :echoe "Use j"<CR>
" ...并在插入模式下执行此操作
inoremap <Left>  <ESC>:echoe "Use h"<CR>
inoremap <Right> <ESC>:echoe "Use l"<CR>
inoremap <Up>    <ESC>:echoe "Use k"<CR>
inoremap <Down>  <ESC>:echoe "Use j"<CR>

扩展你的 Vim —— 插件的使用

Vim 从 8.0 开始有了内置的插件管理器。只需要创建一个 ~/.vim/pack/vendor/start/ 的文件夹,然后把插件放到这里即可。至于插件的选择,可以到 Vim Awesome 去浏览并尝试一下。

Vim Awesome

同时,授课讲师们也推荐了一些插件:

ctrlp.vim 模糊文件查找 ack.vim 代码搜索 nerdtree 文件浏览器 vim-easymotion 魔术操作

或者去网路上查找一些博客、文章、回答,关键词是 “best Vim plugins”,当然之前去大佬的配置文件中看看也不失为一个好办法。

万物皆可 Vim —— 其他程式的 Vim 模式

Shell

Bash 用户,用 set -o vi。 Zsh:bindkey -v。 Fish 用 fish_vi_key_bindings

另外,不管利用什么 shell,你可以 export EDITOR=vim。 这是一个用来决定当一个程序需要启动编辑时启动哪个的环境变量。 例如,git 会使用这个编辑器来编辑 commit 信息。

浏览器

甚至有 Vim 的网页浏览快捷键 browsers

Google Chrome 的 Vimium Firefox 的 Tridactyl

这个列表 中列举了支持类 vim 键位绑定的软件

扩展资料

vimtutor 是一个 Vim 安装时自带的教程 Vim Adventures 是一个学习使用 Vim 的游戏 Vim Tips Wiki Vim Advent Calendar 有很多 Vim 小技巧 Vim Golf 是用 Vim 的用户界面作为程序语言的 code golf Vi/Vim Stack Exchange Vim Screencasts Practical Vim(书籍)

0%