Missing CSE是MIT开设的一门的小学期课程,讲述了许多目前CS高等教育中严重缺失但却在工业界广泛应用或能极大提升生产力的工具与知识。
本文记录了部分Homework的解答。
Course overview + the shell
创建一个名为 missing 的文件夹,并在其中创建一个名为 semester 的文件并写入:1
2
curl --head --silent https://missing.csail.mit.edu
使用 chmod 为 semester 添加执行权限。
写一段命令将 semester 文件输出的最后更改日期信息到 last-modified.txt 中。1
2# Answer
./semester | grep 'last-modified' > 'last-modified.txt'
Shell Tools and Scripting
编写两个bash函数
marco和polo执行下面的操作。 每当你执行marco时,当前的工作目录应当以某种形式保存,当执行polo时,无论现在处在什么目录下,都应当cd回到当时执行marco的目录。 为了方便debug,你可以把代码写在单独的文件marco.sh中,并通过source marco.sh命令,(重新)加载函数。
解答:
macro.sh1
2
3
4
5
6
7
8
macro () {
export MACRO_DIR=$(pwd)
}
polo () {
cd $MACRO_DIR
}运行过程

假设您有一个命令,它很少出错。因此为了在出错时能够对其进行调试,需要花费大量的时间重现错误并捕获输出。 编写一段bash脚本,运行如下的脚本直到它出错,将它的标准输出和标准错误流记录到文件,并在最后输出所有内容。 加分项:报告脚本在失败前共运行了多少次。
1
2
3
4
5
6
7
8
9
10
11
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "Everything went according to plan"解答:
将上述内容写入script.sh,创建test.sh,写入1
2
3
4
5
6
7
8
9
10
status=0
while [[ $status -eq 0 ]]; do
./script.sh >> out 2>> err
status=$?
done
echo "$(wc -l out) before error."运行过程

编写一个命令,它可以递归地查找文件夹中所有的HTML文件,并将它们压缩成zip文件。注意,即使文件名中包含空格,您的命令也应该能够正确执行(提示:查看
xargs的参数-d)
解答:
懒得找有HTML的文件夹,就用.sh文件代替了。创建文件zipsh,写入1
2
3
find $1 -name '*.sh' -print0 | xargs -0 zip -r sh.zip运行过程

Editors (Vim)
这节讲了Vim的一些基本操作,学习一下可以极大提升命令行环境下的工作效率。各种快捷键就不在此赘述(vimtutor是个好东西)。
这里记录一下一个Demo的修改过程。如下是一个有问题的fizz buzz实现:
1 | # fizz.py |
main方法没有调用vim fizz.py
输入G到底文件尾
输入o向后新增一行
输入main方法调用代码1
2if __name__ == '__main__':
main()fizz_buzz方法中的循环从0开始
输入3G跳到第3行
输入%跳到(limit)尾
输入b向前跳1个词
输入i,将代码变为1
for i in range(1, limit+1):
- 5的倍数打印的是”fizz”
输入7G跳到第7行
输入ci'清除”fizz”
输入”buzz” - 15的倍数打印的”fizz”和”buzz”在两行
输入5G跳到第5行
输入$跳到行尾
输入i,将代码变为输入1
print('fizz', end='')
7G跳到第7行
输入$跳到行尾
输入.重复第五行的编辑 main方法中的10是硬编码
输入2G跳到第2行
输入O在前新增一行,添加输入1
2import sys
14G跳到第14行
输入%跳到(10)尾
输入ci(清除并编辑括号内内容,改为修改后的文件内容为1
fizz_buzz(int(sys.argv[1]))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# fizz.py
import sys
def fizz_buzz(limit):
for i in range(1, limit+1):
if i % 3 == 0:
print('fizz', end='')
if i % 5 == 0:
print('buzz', end='')
if i % 3 and i % 5:
print(i)
def main():
fizz_buzz(int(sys.argv[1]))
if __name__ == '__main__':
main()
Data Wrangling
这节介绍了“Data Wrangling”。所谓“Data Wrangling”,描述的是通过一系列操作从原始数据中过滤得到我们需要的数据。在类Unix系统上,这通常可以通过一系列命令和管道来完成。
完成 short interactive regex tutorial
这是一个循序渐进的Regex教程,这里记录一下答案- 匹配三个字符串
abcdefg/abcde/abcabc[d-g]* - 匹配
abc123xyz/define "123"/var g = 123;中的所有数字\d+,或者暴力的123 - 匹配
cat./896./?=+.但不匹配abc1.{3}\.,或...\. - 匹配
can/man/fan但不匹配dan/ran/pan[cmf]an,或者[^drp]an - 匹配
hog/dog但不匹配bog[hd]og,或者[^b]og - 匹配
Ana/Bob/Cpc但不匹配aax/bby/ccz[A-C][n-p][a-c] - 匹配
wazzzzzup/wazzzup但不匹配wazupwaz{2,}up - 匹配
aaaabcc/aabbbbc/aacc但不匹配aa+b*c+ - 匹配
1 file found?/2 files found?/24 files found?但不匹配No files found.\d+ files? found\? - 匹配
1.⊔⊔⊔abc/2.⊔(tab)abc/3.⊔⊔⊔⊔⊔⊔⊔⊔⊔⊔⊔abc但不匹配4.abc\d\.\s+abc - 匹配
Mission: successful但不匹配Last Mission: unsuccessful/Next Mission: successful upon capture of target^Mission: successful$ - 捕获
file_record_transcript.pdf/file_07241999.pdf中的文件名,但不匹配testfile_fake.pdf.tmp^(file.+)\.pdf$ - 捕获
Jan 1987/May 1969/Aug 2011中的整个日期和年份(两个捕获组)(\w+ (\d+)) - 捕获
1280x720/1920x1600/1024x768等分辨率中的宽和高(\d+)x(\d+) - 匹配
I love cats/I love dogs但不匹配I love logs/I love cogsI love (cat|dog)s - 匹配
The quick brown fox jumps over the lazy dog./There were 614 instances of students getting 90.0% or above./The FCC had to censor the network for saying &$#*@!.
偷懒的.*,或^The(re)? (.+( |\.))+$ - 匹配
3.14529/-255.34/128/1.9e10/123,340.00但不匹配720p^\-?(\d,?)+\.?\d+(e\d+)?$或^\-?\d+(,\d+)*(\.\d+(e\d+)?)?$ - 捕获
415-555-1234/650-555-2345/(416)555-3456/202 555 4567/4035555678/1 416 555 9292的区号,即415/650/416/202/403/4161?(\s|-)?\(?(\d{3})\)?(\s|-)?\d{3}(\s|-)?\d{4} - 捕获
tom@hogwarts.com/tom.riddle@hogwarts.com/tom.riddle+regexone@hogwarts.com/tom@hogwarts.eu.com/potter@hogwarts.com/harry@hogwarts.com/hermione+regexone@hogwarts.com中的用户名^(\w+(\.\w+)*)(\+\w+)*@\w+(\.\w+)+$ - 捕获
<a>This is a link</a>/<a href='https://regexone.com'>Link</a>/<div class='test_style'>Test</div>/<div>Hello <span>world</span></div>中的HTML标签<(\w+)( \w+=\'.+\')*>.*</(\w+)> - 捕获下列文件中图片文件的文件名和扩展名,且不匹配其他文件名:
.bash_profile/workspace.doc/img0912.jpg/updated_img0912.png/documentation.html/favicon.gif/img0912.jpg.tmp/access.lock^(\w+)\.((jpg)|(png)|(gif))$ - 去掉字符串头尾的空白符,捕获文本内容:
⊔(tab)⊔(tab)⊔(tab)The quick brown fox.../⊔⊔jumps over the lazy dog.^\s*(.*)\s*$ - 捕获下列日志中的文件名、方法名、行号:
E/( 1553): at widget.List.makeView(ListView.java:1727)E/( 1553): at widget.List.fillDown(ListView.java:652)E/( 1553): at widget.List.fillFrom(ListView.java:709)
跳过:W/dalvikvm( 1553): threadid=1: uncaught exceptionE/( 1553): FATAL EXCEPTION: mainE/( 1553): java.lang.StringIndexOutOfBoundsException[A-Z]/\( \d+\): at \w+\.\w+\.(\w+)\((\w+\.\w+):(\d+)\) - 捕获下列URL中的协议名、主机、端口:
ftp://file_server.com:21/top_secret/life_changing_plans.pdfhttps://regexone.com/lesson/introduction#sectionfile://localhost:4040/zip_filehttps://s3cur3-server.com:9999/market://search/angry%20birds^(\w+)://([\w\-\.]+)(:(\d+))?
- 匹配三个字符串
从
/usr/share/dict/words找出包含至少3个a,且不以s结尾的单词:- 出现次数排名前三的最后两位字母的组合是?这些两位字母组合的个数?
1
2cat /usr/share/dict/words | grep -E '^(.*a.*){3}.*[^s]$' | sed -E 's/.*(..)$/\1/' | sort | uniq -c | sort -r
| head -n3cat /usr/share/dict/words打印出文件中的所有单词grep -E '^(.*a.*){3}.*[^s]$'匹配出其中包含至少3个a,且不以s结尾的单词sed -E 's/.*(..)$/\1/'使用正则替换,提取出这些单词的最后两位字母sort | uniq -c将字母组合排序,合并重复项且计数sort -r | head -n3以出现次数为key逆序排序,并提取出现次数前三名的组合 - 哪些两位字母组合没有出现?
学习自这里1
2
3comm -13
<(cat /usr/share/dict/words | grep -E '^(.*a.*){3}.*[^s]$' | sed -E 's/.*(..)$/\1/' | sort -u)
<(echo {a..z}{a..z} | tr " " "\n")
- 出现次数排名前三的最后两位字母的组合是?这些两位字母组合的个数?
使用
sed进行原地替换是一个很有诱惑力的选择,如sed s/REGEX/SUBSTITUTION/ input.txt > input.txt。但这条命令是无法实现目标的,为什么?是否仅对sed命令有此限制?查询sed的文档,找到原地替换的方法。
学习自这里When the shell sees > index.html in the command line it opens the file index.html for writing, wiping off all its previous contents.
简言之,在
sed真正执行前,重定向已经将文件内容清空了。因此这一问题不仅仅只针对sed,其他命令同样无法通过这一操作进行原地修改。sed的-i选项提供了原地替换,上面的语句应写为:sed -i '.tmp' s/REGEX/SUBSTITUTION/ input.txt
这一语句在执行原地替换前会先生成一个后缀为.tmp的备份文件。如果使用-i '',则不会生成任何中间文件。
Command-line Environment
Job Control
- 我们可以通过
ps aux | grep来获得我们启动的job的pid,并kill它们。事实上还有更方便
的做法。后台启动一个sleep 10000的job:使用pgrep查找pid。使用pkill来结束进程,从而使得无需自行填入pid。(提示:使用-af)
A:
查找pid:pgrep -af 'sleep 10000'
kill进程:pkill -af 'sleep 10000' - 写一个
pidwait函数,接收一个pid,并等到该进程结束,并执行ls(提示:kill -0不会向指定进程发送信号,但会根据进程是否存在返回成功(0)或失败(非0))
A:1
2
3
4
5
6
7
8
9
pidwait () {
while kill -0 $1 2>/dev/null
do
sleep 1
done
echo "process $1 has finished."
ls
}
Terminal Multiplexer
自定义 tmux :1
2
3
4
5
6
7
8
9
10
11
12
13
14# ~/.tmux.conf
#remap prefix from 'C-b' to 'C-a'
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix
# Enable mouse control (clickable windows, panes, resizable panes)
# set -g mouse-select-window on
# set -g mouse-select-pane on
# set -g mouse-resize-pane on
# Enable mouse mode (tmux 2.1 and above)
set -g mouse on
Aliases
使用 history | awk '{$1="";print substr($0,2)}' | sort | uniq -c | sort -n | tail -n 10 可以从history中读取最常使用的10条命令,为它们设置别名
A:
我的Top 10 cmd list如下:
1 | 30 l |
没想到 hexo 的使用频率还挺高,于是做了以下别名:
1 | # ~/.zprofile |
Remote Machines
查询 ssh 的 -N 和 -f 标志,实现后台端口转发:
A:
首先在VM上启动一个HTTP服务,监听8090端口,利用 -L 进行端口转发,可以写出命令:
1 | ssh -L 8090:localhost:8090 VM |
该命令监听本地的8090端口,并将其转发到VM的8090端口。然而该命令的问题在于,在实现端口转发的同时还登录了VM。如果我们不需要登录,就可以使用 -N :
1 | # ssh manpage |
加上 -N 后,端口转发命令变成了
1 | ssh -N -L 8090:localhost:8090 VM |
我们不会再登入VM了,然而,这条命令仍然存在阻塞问题。阻塞的命令导致我们无法在当前终端中继续其他工作。为了让其在后台工作,我们可以引入 -f :
1 | # ssh manpage |
最终,命令变成了
1 | ssh -fN -L 8090:localhost:8090 VM |
实现了不登入VM,运行在后台的端口转发