├── .gitignore ├── images ├── ch0.png ├── ch1.png ├── ch2.png ├── ch3.png ├── logo.jpg ├── sticker.png ├── bookmark.png ├── keychain.png ├── mousepad.png ├── cover_template.psd └── github_social_preview.png ├── ch0 ├── credits.txt ├── minor.sub ├── [1039-1182]CHN.txt ├── [274-404]CHN.txt ├── [400-599]CHN.txt ├── [824-1039]CHN.txt ├── [599-823]CHN.txt └── [1-274]CHN.txt ├── ch1 ├── credits.txt ├── minor.sub ├── [1-104]CHN.txt ├── [105-252]CHN.txt ├── [252-394]CHN.txt ├── [396-594]CHN.txt ├── [595-792]CHN.txt └── major.sub ├── ch2 ├── credits.txt ├── minor.sub ├── [478-558]CHN.txt ├── [1-252]CHN.txt ├── [756-966]CHN.txt ├── [558-756]CHN.txt ├── [252-478]CHN.txt └── [966-1265]CHN.txt ├── ch3 ├── minor.sub ├── [990-1119]CHN.txt ├── [833-990]CHN.txt ├── [641-833]CHN.txt ├── [1-214]CHN.txt ├── [429-641]CHN.txt ├── [214-429]CHN.txt └── major.sub ├── readme.md ├── spec.md ├── license.md └── ch5 └── [1-365]CHN.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp3 2 | *.mp4 3 | temp 4 | *_res 5 | -------------------------------------------------------------------------------- /images/ch0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/ch0.png -------------------------------------------------------------------------------- /images/ch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/ch1.png -------------------------------------------------------------------------------- /images/ch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/ch2.png -------------------------------------------------------------------------------- /images/ch3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/ch3.png -------------------------------------------------------------------------------- /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/logo.jpg -------------------------------------------------------------------------------- /images/sticker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/sticker.png -------------------------------------------------------------------------------- /images/bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/bookmark.png -------------------------------------------------------------------------------- /images/keychain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/keychain.png -------------------------------------------------------------------------------- /images/mousepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/mousepad.png -------------------------------------------------------------------------------- /images/cover_template.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/cover_template.psd -------------------------------------------------------------------------------- /ch0/credits.txt: -------------------------------------------------------------------------------- 1 | 翻译:alphaGem、GNAQ、Zjrua 2 | 校对:GNAQ、kernel.bin、rvalue 3 | ---- 4 | 时间轴:A179、GNAQ 5 | 压制:MikuNotFoundException -------------------------------------------------------------------------------- /images/github_social_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CN-missemi/CN_missemi/HEAD/images/github_social_preview.png -------------------------------------------------------------------------------- /ch1/credits.txt: -------------------------------------------------------------------------------- 1 | 翻译:Coelacanthus、GNAQ、Hypo、Zjrua 2 | 校对:GNAQ、kernel.bin 3 | ---- 4 | 时间轴:A179、TardyPurcell 5 | 压制:GNAQ、MikuNotFoundException -------------------------------------------------------------------------------- /ch2/credits.txt: -------------------------------------------------------------------------------- 1 | 翻译:Coelacanthus、GNAQ、Hypo、ksyx、Zjrua 2 | 校对:GNAQ、ksyx、kernel.bin、Zjrua 3 | ---- 4 | 时间轴:A179、GNAQ、TardyPurcell 5 | 压制:GNAQ、MikuNotFoundException -------------------------------------------------------------------------------- /ch0/minor.sub: -------------------------------------------------------------------------------- 1 | {1}{1}30.000000 2 | {138}{262}*(The Missing Semester of Your CS Education) 3 | {279}{400}翻译:alphaGem, GNAQ, Zjrua | 校对:GNAQ, kernel.bin, rvalue 4 | {401}{519}时间轴:A179, GNAQ | 压制:MikuNotFoundException 5 | {678}{870}*Teaching Assistant,助教 6 | {4186}{4277}*西班牙口音英语警告 7 | {5062}{5119}*那个长得特别标新立异的解构主义建筑。搜个图片欣赏下? 8 | {5127}{5252}*这楼从 4 层以上分成两半:Gates Tower 和 Dreyfoos Tower 9 | {7910}{8045}*这里用了 Visual Interfaces,一般也说 GUI (Graphical User Interface) 10 | {8807}{8888}*这里的 Shell 最好理解为命令行界面(CLI, Command Line Interface) 11 | {8889}{8964}*缩写 pwsh,Windows 7 之前没有内置 pwsh。详细请参看网络资料。 12 | {35148}{35357}*通常来说,这种说法意味着输出更多信息 13 | {45541}{45609}*对应 MacOS 的拷贝(Copy)而非 MacOS 的复制(Duplicate) 14 | {45794}{45824}*意味着你需要明确指定具体文件路径,这个命令没有搜索功能 15 | {52166}{52353}*也就是所谓的「尖角括号」 16 | {56358}{56440}*这里不是指中文的书名号 17 | {56444}{56544}*追加指向文件尾继续添加内容;覆写则清空文件 18 | {62947}{63062}*Google 推出的类似网络机顶盒的东西 19 | {67234}{67329}*此处指软件方面,也即操作系统 20 | {71412}{71486}*程序员的求知交友娱乐网站(全英文) 21 | {75268}{75350}*此处原文的 log file 大致为口误 22 | -------------------------------------------------------------------------------- /ch3/minor.sub: -------------------------------------------------------------------------------- 1 | {279}{400}翻译:ksyx, GNAQ, Zjrua, Hypo | 校对:ksyx, r-value, kernel.bin, GNAQ 2 | {401}{519}时间轴:GNAQ, MikuNotFoundException, kernel.bin | 压制:GNAQ, MikuNotFoundException 3 | {520}{700}监制:MikuNotFoundException | 特别感谢:r-value、NetaP495L 4 | {2581}{2640}* Jon 是挪威人,和荷兰离得很近。 5 | {4709}{4793}* 太坏了,准备用键盘回击.png 6 | {5588}{5767}* Disconnected from invalid user 7 | {6592}{6643}* 就这网速传什么大文件,别想了 8 | {8375}{8483}* 许多命令并不会直接写到 `stdout`,而是缓存一定量以后再一次性输出 9 | {8600}{8696}* 加了 `line-buffering` 选项以后找到一行就输出一行 10 | {12878}{12922}* 可以看到屏幕上 **t**he**s**quare**p**lanet.com 11 | {13513}{13585}* 这里的说法不太常见,可以类比前面讲解 Vim 时所说 12 | {17799}{17878}* 结合 `.` 与 `*` 的效果 13 | {18407}{18455}* `+` 可以跟在一个字母或一串模式后面,详细格式请搜索 14 | {24671}{24718}* 地铁-老人-手机.jpg (因为一般不换行写) 15 | {35430}{35489}* 在部分编辑器中使用 `$2` 16 | {40935}{41049}* 不加 `?` 则会从行首匹配到整行的最后一个 Disconnected from 17 | {47238}{47343}* 这里指括号内的加号 18 | {48776}{48851}* 这个东西大概是来自深渊某处,世界的黑暗面 19 | {55502}{55586}* 指首先根据第一列排序,第一列相同以第二列排序,以此类推 20 | {68244}{68343}* 可以类比 Python 里这两个术语的概念 21 | {78951}{79014}* 一门系统编程语言,由 Mozilla 主持推动。 22 | {80250}{80307}* 就没有 2020 年的(小声 23 | {81972}{82031}* 别一不小心把家扬了 -------------------------------------------------------------------------------- /ch1/minor.sub: -------------------------------------------------------------------------------- 1 | {1}{1}30.000000 2 | {41}{230}*Jose 是西班牙人,不是印度人、俄罗斯人。此名来源于西语。 3 | {279}{400}翻译:Coelacanthus, GNAQ, Hypo, Zjrua | 校对:GNAQ, kernel.bin 4 | {401}{519}时间轴:A179, TardyPurcell | 压制:MikuNotFoundException 5 | {621}{683}*其实是 ZShell 具有对 Bourne Shell (也即 sh)的兼容性,而 bash 是 sh 的后继者。 6 | {1917}{2076}*译者注:其实也要看编程语言类型 7 | {2533}{2601}*bash 也在面对着你 8 | {2601}{2647}*译者注:在 sh、zsh 中同样如此 9 | {7570}{7705}*此处说明较为复杂,详见网络资料 10 | {7872}{7893}*此处的「被保留」指留作特定用途 11 | {9668}{9789}*确切来说,此处应为被保留的「变量」,而非命令 12 | {13969}{14038}*这里把 error code 和 C 语言中的 exit code 做对比 13 | {16219}{16378}*这里是短路运算法则,详细解释参见网络 14 | {20880}{20950}*handle 在国内常译作「句柄」。但这里应该指的就是文件本身,为避免冲突不译做句柄。 15 | {23325}{23458}*一般缩写为 PID,操作系统相关,详见网络资料 16 | {25012}{25102}*这里子串意指一个字符串中连续的一小部分 17 | {41981}{42071}*这个单词源于这行以 `#!` 作为开头。`#` 是 sharp,`!` 是 bang 18 | {57858}{57907}*译者注:这里 `**` 可以匹配零或多个目录名 19 | {61510}{61564}*这课程塞得好满…… 20 | {62121}{62181}*英文为 regex(regular expression),是一种匹配字符串的模式 21 | {62182}{62248}*版本控制工具 Git 相关文件。后续课程有涉及 22 | {65081}{65157}*cron 是 UNIX 下一个基于时间的任务管理系统,可以运行定期任务。 23 | {70278}{70484}*神奇的双重否定?? 24 | {73764}{73893}*这个工具的标语就是「The Silver Searcher」,疑似借梗漫威快银 25 | {79481}{79575}*查找历史记录中,当前输入是其子串的命令 26 | {79785}{79881}*fish 和 zsh 也是一种 Shell 27 | {84151}{84255}*前者是 GNOME 的文件管理器,后者是 macOS 的 28 | -------------------------------------------------------------------------------- /ch2/minor.sub: -------------------------------------------------------------------------------- 1 | {1}{1}30.000000 2 | {279}{400}翻译:Coelacanthus, GNAQ, Hypo, ksyx, Zjrua | 校对:GNAQ, ksyx, kernel.bin, Zjrua 3 | {401}{519}时间轴:A179, GNAQ, TardyPurcell | 压制:GNAQ, MikuNotFoundException 4 | {3864}{4070}*正所谓出口转内销 5 | {5197}{5411}*一个程序设计领域的问答网站,第一讲提到过 6 | {7176}{7517}*REPL 指交互式的编程环境,也能指代命令行的模式 7 | {11726}{11858}*控制用字符指那个 `v`,也就是组合键中的另一个键 8 | {18750}{18865}*一个基于 Ubuntu 的操作系统 9 | {22556}{22755}*这里实际上类似输入 `:` 一样,需要按 `Shift` 10 | {28309}{28408}*请注意区分命令行模式下的 **命令** 和 Normal 下的按键 11 | {33048}{33092}*应当是指部分编程语言中的导入模块/头文件 12 | {35697}{35840}*接口即指用户与它交互的方式、中介物 13 | {38094}{38209}*Vim 的全称为 Vi IMproved,即 Vi 的“升级版” 14 | {38210}{38571}*Vi 诞生的 ADM-3A 终端机的键盘上,方向键位于 HJKL 的位置,Esc 位于如今 Tab 的位置 15 | {43389}{43509}*~~留作课后作业~~ 16 | {44056}{44179}*此处指光标**后**的第一个,下同 17 | {44775}{44923}*这个两键命令可类比 `fo`,但不是英文单词 to 18 | {48610}{48685}*即原文 word, backward word 对应的键 19 | {55038}{55265}*Vim 内的复制粘贴默认不使用操作系统的剪切板 20 | {58353}{58504}*其实大部分“现代”的都可以,如 VSC / Sublime / Atom 21 | {59100}{59143}*这简直不讲武德 22 | {61548}{61689}*这个命令的另一个等价替代是 `d7w` 23 | {61938}{62015}*其实这是混合行号显示(Hybrid line number),绝对+相对混合 24 | {63693}{63770}*流行的标记语言,着力于快速编写文档。中译组字幕压制即采用此语法 25 | {73977}{74032}*我就跟你这样做 26 | {78775}{78868}*别用来上课摸鱼 27 | {80284}{80659}*Windows 下这个东西叫 `_vimrc` 28 | {85092}{85349}*这一大堆都是各种的命令行 Shell 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![](images/github_social_preview.png) 2 | 3 | # MIT 课程《计算机科学教育中缺失的一课》简体中文翻译 4 | 5 | ### **开源课程资料 - CC BY-NC-SA 4.0 协议 - 源地址**:[https://github.com/missing-semester/missing-semester](https://github.com/missing-semester/missing-semester) 6 | 7 | ------------ 8 | 9 | 10 | 11 | Missing Semester 是 MIT 的一系列公开课,内容聚焦于讲授计算机专业实用的软件工具、开发技巧等。 12 | 13 | 这一系列课程涵盖从命令行、文本编辑器、版本控制到代码调试等内容。 14 | 15 | 目前,我们尝试做一些微小的努力,把这一系列课程的中文翻译带给大家。 16 | 17 | ### 视频地址(已发布): 18 | 19 | [第一讲 - 课程概览与 Shell](https://www.bilibili.com/video/BV1Eo4y1d7KZ) - 2021/01/29 20 | 21 | [第二讲 - Shell 工具和脚本](https://www.bilibili.com/video/BV1Vv411v7FR) - 2021/02/17 22 | 23 | [第三讲 - 编辑器 (Vim)](https://www.bilibili.com/video/BV1Dy4y1a7BW) - 2021/03/08 24 | 25 | [第四讲 - 数据整理](https://www.bilibili.com/video/BV1ym4y197iZ) - 2022/02/18 🕊 26 | 27 | ### 协议 28 | 29 | 本课程的所有内容,包括网站源代码、课程笔记、练习和课程视频均以署名-非商业性使用-相同方式共享 4.0 国际 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 协议授权。关于贡献和翻译,在[这里](license.md)获取更多信息。 30 | 31 | ### 字幕组成员(按有贡献计入,字母序) 32 | 33 | 34 | 35 | 36 | 37 | 翻译: 38 | 39 | - alphaGem 40 | 41 | - Coelacanthus 42 | 43 | - GNAQ 44 | 45 | - Hypo 46 | 47 | - ksyx 48 | 49 | - Zjrua 50 | 51 | 校对: 52 | 53 | - GNAQ 54 | 55 | - kernel.bin 56 | 57 | - ksyx 58 | 59 | - Zjrua 60 | 61 | - rvalue 62 | 63 | 时间轴: 64 | 65 | - A179 66 | 67 | - TardyPurcell 68 | 69 | 技术、压制: 70 | 71 | - MikuNotFoundException 72 | 73 | 美术设计、质量总监:GNAQ - 联系方式 - [no1061234176@outlook.com](mailto:no1061234176@outlook.com) 74 | -------------------------------------------------------------------------------- /ch2/[478-558]CHN.txt: -------------------------------------------------------------------------------- 1 | 【478】所以如果我在这里做一些编辑 2 | 3 | 实际上这些变化也会发生在底部的 window 中 4 | 5 | 因为两个 window 打开的是同一个 buffer 6 | 7 | 【482】这就方便你同时去看同文件的不同部分 8 | 9 | 就像这样,当你想看到文件的顶部 10 | 11 | 比如说程序的导入部分[*] 12 | *应当是指部分编程语言中的导入模块/头文件 13 | 14 | 而且还可以同时在下面的 window 处理别的 15 | 16 | 【488】因此,要记住下面这点,就是 17 | 18 | Vim 的想法是 - 有很多 tab 19 | 20 | 每个 tab 都有一定数量的 window 21 | 22 | 然后每个 window 都有对应的 buffer 23 | 24 | 【493】而一个 buffer 可以同时被零或多个 window 打开 25 | 26 | 刚开始学习 Vim 时 27 | 28 | 只有一件事我没整明白 29 | 30 | 所以我想尽早解释一下 31 | 32 | 【497】我们前面提到的 `:q` 命令并不会完全退出 Vim 33 | 34 | 而是只关闭当前 window 35 | 36 | 而后如果没有打开的 window,Vim 才会退出 37 | 38 | 【502】因此,在这里,如果我键入 `:q` 39 | 40 | 它只会关闭顶部的 window 41 | 42 | 因为那是我所在的 window 43 | 44 | 现在,剩余的 window 将变为全屏显示 45 | 46 | 【506】我可以再次键入 `:q` 来关闭它 47 | 48 | 现在,我们在打开的第二个 tab 中 49 | 50 | 如果我最后一次执行 `:q` 51 | 52 | 好的,现在 Vim 退出了 53 | 54 | 如果你不想按太多次 `:q` ... 55 | 56 | 【510】看,这里有三个独立的 window 57 | 58 | 如果我执行 `:qa` ,则退出所有 window 59 | 60 | 这个命令将关闭所有打开的 window 61 | 62 | 【513】好,现在回答你的问题 63 | 64 | Normal 模式实际上是做什么的 65 | 66 | 这是 Vim 中另一个非常酷的想法 67 | 68 | 我认为这实际上是该程序最有趣的设计 69 | 70 | 【518】就像,诸位都是程序员,诸位喜欢编程一样 71 | 72 | Vim 的设计理念是 Vim 的 Normal 模式 73 | 74 | 例如 Vim 的接口,本身是一种编程语言 75 | 76 | 【522】让我重复一遍 77 | 78 | 这就是 Vim 有趣的核心哲学:它的接口是种编程语言 [*] 79 | *接口即指用户与它交互的方式、中介物 80 | 81 | 【526】那意味着什么? 82 | 83 | 这意味着不同的按键组合具有不同的效果 84 | 85 | 一旦你掌握了这些组合的、不同的效果 86 | 87 | 就可以把它们拼到一起 88 | 89 | 就像在编程语言中一样 90 | 91 | 你可以学习不同的功能和内容 92 | 93 | 然后将它们糅合在一起成为一个有趣的程序 94 | 95 | 【534】同样,一旦你了解了 Vim 不同的移动、编辑命令 96 | 97 | 以及与之类似的内容 98 | 99 | 就可以利用 Vim 的接口 100 | 101 | 编写程序去和 Vim 「交谈」 102 | 103 | 【538】而且,一旦形成肌肉记忆 104 | 105 | 你就可以快速编辑文件,思维多快就能写多快 106 | 107 | 【541】至少对我来说 108 | 109 | 换成过去我用的编辑器,我觉得我做不到这一点 110 | 111 | 但 Vim 几乎做到了 112 | 113 | 【544】因此,我们来深入研究一下 Normal 模式是怎么回事 114 | 115 | 你可以试着一起做 116 | 117 | 比如,在 Vim 中随便打开一些文件 118 | 119 | 并跟着我键入一些组合键 120 | 121 | 【549】可能一上来你最想做的事就是 122 | 123 | 在 buffer 中四处移动 124 | 125 | 比如上下左右地移动光标 126 | 127 | 【552】在 Vim 中是用 `hjkl` 键 128 | 129 | 而不是箭头键来操作。 130 | 131 | 尽管默认配置下它们确实能用 132 | 133 | 但尽量避免用 134 | 135 | 因为你不想将手反复移向箭头键 136 | 137 | 这浪费了大量时间,对吧? 138 | -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | # 技术规范 | The Missing Semester 中译组 2 | 3 | > 此规范本着简短、到位的观点制订,可以全部阅读,也可以只阅读相关的部分。 4 | 5 | - [仓库管理说明](#仓库管理说明) 6 | 7 | - [翻译](#翻译) 8 | 9 | - [文本排版](#文本排版) 10 | 11 | - [内容排版](#内容排版) 12 | 13 | - [校对](#校对) 14 | 15 | - [时间轴](#时间轴) 16 | 17 | ## 仓库管理说明 18 | 19 | 建立分支(branch)时,用如下格式处理: 20 | 21 | `(T/R/L/E)_ch(0,1,2...)_ID` 22 | 23 | T 翻译 R 校对 L 时间轴 E 压制 24 | 25 | 例子:`L_ch5_GNAQ` 26 | 27 | 校对时可以改为 R_ch?_翻译ID_校对ID 28 | 29 | 翻译建立新分支时,从 master 签出(checkout)。 30 | 31 | ~~校对和轴处理文本时,从对应翻译的分支签出,完毕后提 Pull Request 给**对应分支**。~~ 32 | 33 | 时间轴从 master 签出分支,对已经校对完成的内容打轴。 34 | 35 | 翻译和时间轴完成之后,向 master 提 PR。 36 | 37 | 文件命名时,使用 `[xxx-xxx]CHN.txt` 处理翻译。 38 | 39 | 使用 `[xxx-xxx]major/minor.sub` 处理时间轴。 40 | 41 | ## 翻译 42 | 43 | 翻译时请勿参照全文机翻。 44 | 45 | 翻译时如有不确定内容,可以在**行末**使用 `# REVIEW` 的格式请求校对协助完成。 46 | 47 | ### 文本排版 48 | 49 | #### 1. 标点 50 | 51 | 使用括号包裹内容的时候(无论其中是中英文),均用中文小括号。 52 | 53 | 枚举内容时看文本长度,酌情使用中文顿号或 / 正斜杠。 54 | 55 | 除下文情况外,不使用**中文中括号**和**任何**大括号。 56 | 57 | - 中括号:标记提示点(见下文) 58 | 59 | - 大括号:当原文的代码内容含有大括号 60 | 61 | 说明语气时每行文本结尾只能出现 !?;(中文) !?;(英文)…… 等这些字符。不出现句号、逗号。说明代码时,无限制。 62 | 63 | #### 2. 中、英、代码混排 64 | 65 | 中、英、数字、代码混排时,中文、英文、数字和代码之间有一个空格。 66 | 67 | - 例子 demo `echo demo` 123 `cat` 例子 ipv4 例子 BeautifulSoup4 68 | 69 | #### 3. 文本长度 70 | 71 | 每行文本的**汉字**数量控制在 25 个字以下。**但可据断句安排酌情打破。** 72 | 73 | **硬性要求**:打轴时,若出现文本速度(字/秒)超过 16 的情况,将由校对和翻译重新讨论此处的翻译内容,并将速度减少到 16 以下。 74 | 75 | ### 内容排版 76 | 77 | **两行文本之间空一行** 78 | 79 | #### 1. 代码高亮的使用 80 | 81 | 主、副字幕的代码高亮使用 \`\` 触发。请勿嵌套使用代码高亮。 82 | 83 | 在 `代码 / 文件 / 按键 / 命令名` 等处使用高亮。 84 | 85 | 普通技术名词、人名等不使用高亮。 86 | 87 | #### 2. 注释的使用 88 | 89 | 主字幕是屏幕下方的字幕,副字幕在屏幕上方,做注释用。如有注释,请在须注释处使用 `[*]` 符号,并紧贴一行写入注释。每行主字幕只允许对应一行副字幕。 90 | 91 | 例子: 92 | 93 | 我知道吗? 94 | 95 | 我知道[*] 96 | *实际上我不知道 97 | 98 | 那也没办法 99 | 100 | #### 3. 标记提示点 101 | 102 | 酌情每间隔 10 - 30 行做提示点。用 【--】 表示,置于行首。 103 | 104 | 括号内填对应的英文字幕 SRT 格式中的**原始行数**。**不需要严格对齐**(见一个可行的例子) 105 | 106 | 822 107 | it's useful. Another one is apple. 108 | 109 | ———— 110 | 111 | 建了果园,而且这很管用。 112 | 113 | 【822】另一个是苹果。 114 | 115 | ## 校对 116 | 117 | 校对分为润色和技术两类。 118 | 119 | 润色校对浏览全文,修正不规范的翻译格式、裁短过长的单行文本、修正不通顺的句子。 120 | 121 | 技术校对针对 REVIRW tag 进行纠正,做出精细准确的调整。 122 | 123 | 修改量少时,校对可以和翻译讨论措辞,但翻译无权决定校对修改的文本。 124 | 125 | 大规模的修改:多句(>3)重翻、多句(>5)顺序互换时,**需要和翻译讨论达成共识。** 126 | 127 | ## 时间轴 128 | 129 | 使用 Aegisub 3.2.2 打轴。Aegisub 使用方法详见网络。 130 | 131 | 输出格式为 `.sub` 文件,按**帧**而非**时间**打轴。**输出时帧速率选择 `从视频获得`** 132 | 133 | 主字幕、副字幕分别打 `major.sub` 和 `minor.sub`,副字幕和相对应的主字幕需要同时出现消失。 -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: "License" 4 | permalink: /license/ 5 | --- 6 | 7 | # License 8 | 9 | 本课程的所有内容,包括网站源代码、课程笔记、练习和课程视频均以署名-非商业性使用-相同方式共享 4.0 国际 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) 协议授权。 10 | 11 | 这意味着您可以自由地: 12 | 13 | 共享 — 在任何媒介以任何形式复制、发行本作品 14 | 15 | 演绎 — 修改、转换或以本作品为基础进行创作 16 | 17 | 只要你遵守许可协议条款,许可人就无法收回你的这些权利。 18 | 19 | 惟须遵守下列条件: 20 | 21 | 署名 — 您必须给出适当的署名,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。 22 | 23 | 非商业性使用 — 您不得将本作品用于商业目的。 24 | 25 | 相同方式共享 — 如果您再混合、转换或者基于本作品进行创作,您必须基于与原先许可协议相同的许可协议 分发您贡献的作品。 26 | 27 | 这里是一个对人类阅读友好的[许可证摘要](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode)(而不是许可证替代) 28 | 29 | All the content in this course, including the website source code, lecture notes, exercises, and lecture videos is licensed under Attribution-NonCommercial-ShareAlike 4.0 International [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). 30 | 31 | This means that you are free to: 32 | - **Share** — copy and redistribute the material in any medium or format 33 | - **Adapt** — remix, transform, and build upon the material 34 | 35 | Under the following terms: 36 | 37 | - **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 38 | - **NonCommercial** — You may not use the material for commercial purposes. 39 | - **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 40 | 41 | This is a human-readable summary of (and not a substitute for) the [license](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). 42 | 43 | ## 贡献大纲 44 | 45 | 你可以通过在 GitHub [仓库](https://github.com/missing-semester/missing-semester) 上提交 issues 和 pull requests 的方式提交课程材料的修正和建议。这包括同样在仓库中的视频字幕。([见此处](https://github.com/missing-semester/missing-semester/tree/master/static/files/subtitles/2020)) 46 | 47 | ## Contribution guidelines 48 | 49 | You can submit corrections and suggestions to the course material by submitting issues and pull requests on our GitHub [repo](https://github.com/missing-semester/missing-semester). This includes the captions for the video lectures which are also in the repo (see [here](https://github.com/missing-semester/missing-semester/tree/master/static/files/subtitles/2020)). 50 | 51 | ## 翻译大纲 52 | 53 | 在协议条款的约束下,您可以自由地翻译课程笔记和练习。 54 | 55 | 如果您的翻译是课程结构的一个镜像,请联系我们,便于我们将您的翻译版本添加到我们的页面。 56 | 57 | 至于翻译视频字幕,请在 YouTube 上以社区贡献的形式提交您的字幕。 58 | 59 | ## Translation guidelines 60 | 61 | You are free to translate the lecture notes and exercises as long as you follow the license terms. 62 | If your translation mirrors the course structure, please contact us so we can link your translated version from our page. 63 | 64 | For translating the video captions, please submit your translations as community contributions in YouTube. -------------------------------------------------------------------------------- /ch3/[990-1119]CHN.txt: -------------------------------------------------------------------------------- 1 | 【990】那我大概就可以把它们删了 2 | 3 | 那我现在想把这个列表整干净些 4 | 5 | 这是一个多行的列表 6 | 7 | 那我先 `grep nightly` 8 | 9 | 然后我把最新版的 `nightly` 排除掉 10 | 11 | 用 `-v` 来表示不匹配它 12 | 13 | 好现在这些 `nightly` 都带着日期 14 | 15 | 我只想留下 2019 年的 [*] 16 | * 就没有 2020 年的(小声 17 | 18 | 那现在我想把这一个个工具链 19 | 20 | 从我的电脑里删掉 21 | 22 | 我固然可以一行行的来回复制粘贴 23 | 24 | `rustup toolchain remove` 25 | 26 | 哦,好像是 `uninstall` 27 | 28 | 那我可以手动的输入这些文件名 29 | 30 | 也可以复制粘贴 31 | 32 | 但是这不是很烦么 33 | 34 | 我都整出来列表了啊 35 | 36 | 那要不这样吧 37 | 38 | 我用 `sed` 把这些版本号的后缀去掉 39 | 40 | 好,就这个样子 41 | 42 | 然后用 `xarg` 43 | 44 | 它可以把输入的列表转成参数 45 | 46 | 这里我想让它变成这个命令的参数 47 | 48 | 我的习惯是这里加个 `echo` 49 | 50 | 这样就可以看到它将会干些什么 [*] 51 | * 别一不小心把家扬了 52 | 53 | 啊这输出没太大用 54 | 55 | 并不是很好读 56 | 57 | 仔细看这个命令 58 | 59 | 删掉 `echo` 之后 60 | 61 | 将会执行 `rustup toolchain uninstall` 62 | 63 | 然后后面是 `nightly` 版本 64 | 65 | 作为**参数**传给程序 66 | 67 | 【1022】那我一跑这个命令 68 | 69 | 它就会把这些工具链删了 70 | 71 | 那我就不用一个个复制粘贴了 72 | 73 | 那这就是数据处理 74 | 75 | 在辅助其他工作上的应用 76 | 77 | 在观察数据之外更进一步 78 | 79 | 也就是把一种形式的数据 80 | 81 | 转换成了另一种 82 | 83 | 你还可以处理二进制数据 84 | 85 | 而图像、视频等 86 | 87 | 就是一个很好的例子 88 | 89 | 你可以拿它们来整点有趣的活 90 | 91 | 例如有个工具叫 `ffmpeg` 92 | 93 | 它是用来编解码视频的工具 94 | 95 | 也可以整点图像活 96 | 97 | 我现在把它的日志级别 98 | 99 | 设成 `panic` 100 | 101 | 不然它会输出超多东西 102 | 103 | 我想让它从 `/dev/video0` 104 | 105 | 也就是从我的摄像头读取 106 | 107 | 然后把第一帧拿出来 108 | 109 | 也就是拍个照 110 | 111 | 然后输出成一张图 112 | 113 | 而不是一个单帧的视频 114 | 115 | 我想让它把输出内容,也就是这个图片 116 | 117 | 输出到 `stdout` 118 | 119 | 【1046】一般用 `-` 就告诉程序 120 | 121 | 从标准流来输入输出 122 | 123 | 而不要用文件 124 | 125 | 这个参数应该是给出一个文件名 126 | 127 | 而用 `-` 作为文件名就代表 `stdout` 128 | 129 | 然后我想把它用管道 130 | 131 | 接到 `convert` 这个程序 132 | 133 | `convert` 是一个图像处理软件 134 | 135 | 我想让 `convert` 从 `stdin` 读入 136 | 137 | 然后把图片转成灰度的 138 | 139 | 然后把结果写到 `-` 文件里 140 | 141 | 也就是标准输出 142 | 143 | 然后我想接给 `gzip` 144 | 145 | 它可以压缩这个图片文件 146 | 147 | 它也会用标准流来输入输出 148 | 149 | 然后我会把它接到 150 | 151 | 我的远程服务器上 152 | 153 | 在那上面解码(解压缩) 154 | 155 | 然后把图像存个副本 156 | 157 | 复习一下 158 | 159 | 【1066】`tee` 会从 `stdin` 输入 160 | 161 | 然后输出到文件和 `stdout` 162 | 163 | 那这样我们就得到了一份 164 | 165 | 名字是 `copy.png` 的 166 | 167 | 解码过的图像副本 168 | 169 | 继续沿着管道推流 170 | 171 | 我再把数据流导向本地 172 | 173 | 我想让它在一个图片查看器里显示出来 174 | 175 | 看看能不能用 176 | 177 | 【1075】瓦! 178 | 179 | 好 这张图经过我的服务器 180 | 181 | 然后又由管道传回来了 182 | 183 | 此外我的服务器理论上 184 | 185 | 有这张图解码后的副本 186 | 187 | 来看看有没有 188 | 189 | `scp copy.png` 到我这里 190 | 191 | 啊这 192 | 193 | 这样呢 194 | 195 | 看!完全一致!这个命令生效了 196 | 197 | 这是个简单的例子 198 | 199 | 但是你会见识到这样构建管道的强大之处 200 | 201 | 它不一定要是文本 202 | 203 | 而是会把任意格式的数据 204 | 205 | 转换成另一种格式 206 | 207 | 如果想要的话 208 | 209 | 我可以 `cat /dev/video0` 210 | 211 | 然后把它用管道接到 212 | 213 | Anish 的服务器上 214 | 215 | 他就可以把它 `pipe` 到视频播放器上 216 | 217 | 然后在他的机器上看视频了 218 | 219 | 【1095】只要知道有这么个操作就好 220 | 221 | 今天有许多的练习可以做 222 | 223 | 有些呢会用到一些数据源 224 | 225 | 像是 macOS 和 Linux 的日志 226 | 227 | 然后我们会告诉你用哪些命令 228 | 229 | 你可以自己玩一下 230 | 231 | 但是记住 232 | 233 | 用什么数据源并不重要 234 | 235 | 更重要的是找些 236 | 237 | 你觉得有意思的数据源 238 | 239 | 然后从里面找出有意思的东西 240 | 241 | 这些练习的重点在这里 242 | 243 | 星期一马丁路德金日不上课 244 | 245 | 下一节课周二 246 | 247 | 我们讲命令行环境 248 | 249 | 现在讲的这些有些什么疑问吗 250 | 251 | 管道啊 正则啊 252 | 253 | 正则这东西真的值得好好看看 254 | 255 | 它太好用了 256 | 257 | 在编程方面也很好用 258 | 259 | 如果有问题 260 | 261 | ~~办公室找我~~ 262 | 263 | 【1119】我会帮你的 -------------------------------------------------------------------------------- /ch0/[1039-1182]CHN.txt: -------------------------------------------------------------------------------- 1 | 【1039】如果我运行这行命令 2 | 3 | 虽然你看不见 4 | 5 | 但是我已经调高了笔记本的亮度 6 | 7 | 我没有收到任何错误提示 8 | 9 | 并且我也不必使用危险的 root 身份运行指令 10 | 11 | 如果你想更深入的了解文件系统的话 12 | 13 | 你在浏览的时候能看到很多有趣的东西 14 | 15 | 【1049】你能发现各种各样有趣的东西 16 | 17 | 举个例子,我们已经发现了有个有趣的亮度指令 18 | 19 | 我想知道我还可以设置什么其他有趣的亮度 20 | 21 | 所以我们可以用下一讲要讲到的查找指令 22 | 23 | 我在当前目录里找找长有点像亮度的东西 24 | 25 | 啊,翻车了…也许它们不是文件? 26 | 27 | 【1059】啊,看来是我拼错了,好烦啊 28 | 29 | 看起来,它不怎么想帮我查找有关的文件 30 | 31 | 走运的是,我其实已经知道一个了 32 | 33 | 有个子目录叫 `LEDs` ,这里面也有亮度相关的文件 34 | 35 | 看看里面有啥样的 LED 灯 36 | 37 | 好家伙,还不少呢 38 | 39 | 举个例子,有滚动锁定键的 LED 灯 40 | 41 | 你们大多数人可能都不知道啥是滚动锁定键了 42 | 43 | 或者很少有人知道啥是滚动锁定 44 | 45 | 【1069】你可能在键盘上见过一个叫 `scroll lock` 的按键 46 | 47 | 基本上再也没人知道这个按键是干什么的了 48 | 49 | 它没啥用了,所以它差不多是个被淘汰的按键了 50 | 51 | 同时也是个被淘汰的 LED 灯 52 | 53 | 如果我把它配置一下 54 | 55 | 使每次在来邮件的时候滚动锁定键都会亮起来怎么样 56 | 57 | 因为没有别的东西会让它亮起来 58 | 59 | 【1079】如果我把你发配到这个有亮度配置的目录里 60 | 61 | 这里也有个 `brightness` 62 | 63 | 并且被设为 `0` 64 | 65 | 如果我们把 `1` 写入进去会发生什么 66 | 67 | 话说回来,别随便把数字写入这个目录的文件里 68 | 69 | 因为你会直接影响到内核 70 | 71 | 你得先弄明白这个文件有什么作用 72 | 73 | 现在我做好了安全措施并且我也做了充分研究 74 | 75 | 所以你们可能没发现 76 | 77 | 【1089】其实我键盘上滚动锁定的灯已经亮了 78 | 79 | 现在,如果我写一个能检查邮件之类的程序 80 | 81 | 我最终就能把 `1` 写入这个文件 82 | 83 | 现在有个让我键盘上的滚动锁定键显示有没有新邮件的方法 84 | 85 | 你大致有个用终端和 Shell 来实现这个的雏形了 86 | 87 | 【1099】并且现在你掌握的足够完成一些基本任务了 88 | 89 | 至少从理论上你不用点击界面去查找文件了 90 | 91 | 这里还有个你应该掌握的小技巧 92 | 93 | 也就是打开文件的能力 94 | 95 | 目前我只告诉你怎么查找文件 96 | 97 | 但是有一点你要知道 98 | 99 | 【1109】`xdg-open` 这个指令可能只在 `Linux` 上运行 100 | 101 | 在 MacOS 上可能叫 `open` 102 | 103 | 至于 Windows 嘛,谁知道 104 | 105 | 你给出一个文件名,然后 `xdg-open` 就会用合适的程序打开它 106 | 107 | 所以如果你用 `xdg-open` 打开一个 `HTML` 文件 108 | 109 | 它就会打开你的浏览器然后打开那个文件 110 | 111 | 所以,理论上,你有了这个程序之后 112 | 113 | 你就再也不用打开 Finder 窗口了 114 | 115 | 【1119】虽然你可能还会打开,因为别的原因 116 | 117 | 但是从理论上讲,你可以用这套工具完成任何任务 118 | 119 | 今天学的东西对你们中的一部分人可能有些基础 120 | 121 | 但是就像我提到过的,这是一个快速提升的阶段 122 | 123 | 现在我们知道了 Shell 是怎么运行的 124 | 125 | 之后还了解了我们之后的讲座中会做些什么 126 | 127 | 那就是用 Shell 去做一些真正有趣的事情 128 | 129 | 【1129】这有点像学习我们将要用到的界面 130 | 131 | 所以我们需要全部了解这些东西,这很重要 132 | 133 | 我们在接下来的讲座中会讲到很多 134 | 135 | 关于诸如这种自动化任务的话题 136 | 137 | 关于怎样写出能运行一大堆程序的脚本 138 | 139 | 并且还得学习怎么在终端里写条件、循环之类的东西 140 | 141 | 【1139】还有运行一个程序,直到它出错 142 | 143 | 你的测试程序出错之后可以容易地停下来 144 | 145 | 所以这就是下周讲座的主题 146 | 147 | 你有什么问题吗 148 | 149 | 像你刚才在 `/sys` 目录下演示的那样 150 | 151 | 你是在 Linux 系统下演示的 152 | 153 | 如果在 Windows 下用 Linux 子系统会怎样呢? 154 | 155 | 这是个好问题 156 | 157 | 我不知道 Windows 的 Linux 子系统会不会展露 `/sys` 文件系统 158 | 159 | 【1149】如果显示的话,也只会展露很少的一部分 160 | 161 | 可能会吧 162 | 163 | 比如说,会有 LED 和 brightness 吗? 164 | 165 | 我不知道,自己去查吧 166 | 167 | 你会发现这个系列讲座的讲义已经上线了 168 | 169 | 并且讲义最底下有一些练习 170 | 171 | 一些练习相对简单,而一些练习有点难度 172 | 173 | 【1159】我们建议你尝试着去做这些练习 174 | 175 | 如果你已经掌握了这些东西的话 176 | 177 | 完成这些练习不会占用你太多时间 178 | 179 | 如果你还没有完全掌握 180 | 181 | 这些练习会教给你一些你可能没意识到自己不会的东西 182 | 183 | 接下来,在课后的工作时间,我们乐意解答你遇到的任何问题 184 | 185 | 【1169】或者想了解如何更高效的使用课内课外遇到的指令 186 | 187 | 下一讲,也就是明天 188 | 189 | 会在你们已经基本掌握练习教给你的东西的基础上进行 190 | 191 | 在网站上还有一个邮箱地址 192 | 193 | 你可以在工作时间之外向我们发送你的问题 194 | 195 | 今天这堂课结束之前,还有什么问题吗 196 | 197 | 没了吗?看来没了 198 | 199 | 【1179】那么大约五分钟后 200 | 201 | 我们会在 Gates Building 的九楼继续办公 202 | 203 | 大家再见! -------------------------------------------------------------------------------- /ch0/[274-404]CHN.txt: -------------------------------------------------------------------------------- 1 | 【274】当我输入 `$PATH` 2 | 3 | 这里会输出一些我电脑上的目录 4 | 5 | 这些目录是 Shell 寻找程序时所查找的目录 6 | 7 | 也许你会觉得 8 | 9 | 这一大行被冒号分隔的东西看起来很费眼睛 10 | 11 | 但重要的事情在于 12 | 13 | 当你输入一个程序名称的时候 14 | 15 | 你的电脑会在这个列表中的每个目录里查找 16 | 17 | 名字与你所输入指令相同的一个程序或一个文件 18 | 19 | 刚刚的例子里当我试图运行 `date` 或者 `echo` 的时候 20 | 21 | 【287】电脑会遍历这些目录 22 | 23 | 直到它找到一个包含 `date` 或者 `echo` 的程序的目录 24 | 25 | 然后电脑就会运行找到的这个程序 26 | 27 | 如果我们想要知道电脑具体运行了哪一个目录里的程序 28 | 29 | 我们有一个叫做 `which` 的指令,它可以整这个活 30 | 31 | 所以我现在可以输入 `which echo` 32 | 33 | 然后它就会告诉我 34 | 35 | 如果我要运行一个叫 `echo` 的程序 36 | 37 | 我就会运行——这个东西 38 | 39 | 这里有必要插入讲一下什么是路径(path) 40 | 41 | 【297】路径是用来描述你的计算机里的文件的位置的东西 42 | 43 | 在 Linux 或者 Mac OS 上 44 | 45 | 路径被用一连串的斜杠分隔 46 | 47 | 你在这里可以看到,这个路径的起点在根目录 48 | 49 | 目录的开头有斜杠来指示它开始的地方: 50 | 51 | 整个文件系统的最顶层 52 | 53 | 然后我们进到叫做 `usr` 的目录 54 | 55 | 然后进到叫做 `bin` 的目录 56 | 57 | 然后找到名叫 `echo` 的文件 58 | 59 | 在 Windows 里,这样的路径一般以反斜杠而非斜杠分隔 60 | 61 | 【310】在 Linux 和 MacOS 上 62 | 63 | 所有东西都在一个叫根(root)的空间下面的某处 64 | 65 | 所以所有以斜杠开头的路径都是绝对路径 66 | 67 | 在 Windows 下,每一个分区都有一个根 68 | 69 | 所以你可能见过类似 `C:\` 或者 `D:\` 的东西 70 | 71 | 所以 Windows 里每一个驱动器(硬盘)下 72 | 73 | 都有独立的一套文件系统的层次结构 74 | 75 | 【319】相比之下,在 Linux 和 MacOS 下 76 | 77 | 所有东西都在一个命名空间里 78 | 79 | 你会注意到我提到了“绝对路径”这个词 80 | 81 | 可能有人会不知道这是什么意思,解释一下: 82 | 83 | 绝对路径是可以绝对准确地确定一个文件的位置的路径 84 | 85 | 所以在屏幕上这个例子里面 86 | 87 | 这行东西唯一地指向一个叫 `echo` 的独一无二的文件 88 | 89 | 它表示了到这个文件的完整路径 90 | 91 | 但是也有一种称作“相对路径”的东西 92 | 93 | “相对路径”是相对于你当前所在位置的路径 94 | 95 | 所以要找出我们当前的位置在哪里 96 | 97 | 我们可以输入 `pwd`,意思是 98 | 99 | 【333】当前工作目录(present working directory)…… 100 | 101 | 当前(present)? 102 | 103 | 打印工作目录(print working directory) 104 | 105 | 所以如果我输入 `pwd` 106 | 107 | 它就会输出当前我所在的目录路径 108 | 109 | 我现在的位置是 `root` 下的 `home` 文件夹 110 | 111 | 其中的 `jon` 里,其中的 `dev` 里 112 | 113 | 然后再叭啦叭啦叭啦这一串的里面 114 | 115 | 我可以选择改变我的当前工作目录 116 | 117 | 所有相对路径都是相对当前工作目录的 118 | 119 | 【343】基本上也就是说是相对于你所在的地方 120 | 121 | 举个例子,我可以输入 `cd /home` 122 | 123 | `cd` 是改变目录(change directory)的意思 124 | 125 | 这是改变当前工作目录的一种方法 126 | 127 | 在这个例子里,我把当前工作目录转到 `home` 128 | 129 | 你可以看到我的 Shell 的提示也改变了 130 | 131 | 它说我在 `home` 里面 132 | 133 | 它只会给出路径的最后一段的名称 134 | 135 | 不过也可以设置下,使得它总能显示当前的完整路径 136 | 137 | 如果现在我再输入 `pwd` 138 | 139 | 它会告诉我我在 `/home` 里 140 | 141 | 【356】同时也有一对特殊的目录 142 | 143 | `.`(点) 和 `..`(点点) 144 | 145 | 点表示当前目录,而点点表示上一层目录 146 | 147 | 通过这种方式,你可以容易地访问整个系统 148 | 149 | 举个例子,如果我输入 `cd ..` 150 | 151 | 它会告诉我我现在在 `/` 152 | 153 | 所以我现在在整个文件系统的根 154 | 155 | 我刚刚在 `/home`,现在在 `/` 了 156 | 157 | 然后如果我输入 `pwd`,运行结果看起来也很对 158 | 159 | 【367】我也可以使用相对路径来向下访问文件系统 160 | 161 | 所以我可以输入 `cd ./home` 162 | 163 | 然后就会 cd 到当前的目录下的 `home` 目录 164 | 165 | 这个操作就把我带回到了 `/home` 里 166 | 167 | 然后如果我现在试着再输入一次 `cd ./home` 168 | 169 | 它会告诉我当前目录下没有 `home` 目录 170 | 171 | 我现在在刚刚 cd 进的文件夹里 172 | 173 | 就可以用相对路径 174 | 175 | 一路 cd 进刚刚我在的那一个文件夹里 176 | 177 | 我也可以用类似点点,点点,点点……的操作 178 | 179 | 来回到我的文件系统的比较靠近根的位置 180 | 181 | 这个应该是回到了根目录 182 | 183 | 【383】然后里面有一个 `bin` 目录 184 | 185 | 然后在里面有一个 `echo` 文件 186 | 187 | 所以我就可以输一个 `world` 188 | 189 | 然后这就会运行 `bin` 目录下的 `echo` 程序 190 | 191 | 【387】好的,所以靠这个办法 192 | 193 | 你可以构建路径到你的文件系统的任意地方 194 | 195 | 有的时候你想要用绝对路径,有的时候你想要用相对路径 196 | 197 | 一般你就用短的那一种 198 | 199 | 但是比如说你想要运行一个或者写一个程序 200 | 201 | 它调用运行了类似 `echo` 或者 `date` 的程序 202 | 203 | 你希望它可以在任何地方都能跑起来 204 | 205 | 那要么你就只给出这个要被运行的程序的名字 206 | 207 | 【395】像 `echo` 或者 `date` 208 | 209 | 然后让 Shell 用 `path` 自己去找出它们在哪里 210 | 211 | 要么你就给出被调用运行的程序的绝对路径 212 | 213 | 因为如果你给出一个相对路径 214 | 215 | 那可能我在我的 home 目录运行,你在别的什么目录运行 216 | 217 | 可能我这里能跑起来,你那里就不能跑起来了 218 | 219 | -------------------------------------------------------------------------------- /ch1/[1-104]CHN.txt: -------------------------------------------------------------------------------- 1 | 【1】好的,欢迎回来[*] 2 | *Jose 是西班牙人,不是印度人。此名来源于西语。 3 | 4 | 今天我们要分别谈谈与 Shell 有关的两个话题 5 | 6 | 首先我们要讲 Shell 脚本,这主要和 bash 有关 7 | 8 | 这将会是你们大多数人一开始在 macOS 9 | 10 | 或者大多数 Linux 里接触的 Shell 11 | 12 | bash 是它们默认的 Shell 13 | 14 | 并且其他 Shell ,像是 zsh 15 | 16 | 对其有良好的向后兼容,这非常棒 [*] 17 | *其实是 ZShell 具有对 Bourne Shell (也即 sh)的兼容性,而 bash 是 sh 的后继者。 18 | 19 | 然后我们要谈谈特别方便的其他 Shell 工具 20 | 21 | 你们可以用它避免重复执行任务 22 | 23 | 【10】像是寻找一段代码 24 | 25 | 或者一些犄角旮旯的文件 26 | 27 | bash 里也有许多很棒的内置命令 28 | 29 | 它们可以帮你做这些事情 30 | 31 | 昨天我们已经介绍了 Shell 32 | 33 | 和它的一些特性 34 | 35 | 就比如说你怎样执行一个命令 36 | 37 | 或者重定向它们(的输入输出) 38 | 39 | 今天我们将多讲一些 Shell 脚本中的 40 | 41 | 操纵变量的语法,控制流以及函数 42 | 43 | 例如,一旦你接触 Shell 44 | 45 | 说你想要定义一个变量 46 | 47 | 那是你学习编程语言第一个接触的事情[*] 48 | *译者注:其实也要看编程语言类型 49 | 50 | 你可以执行像是 `foo=bar` 51 | 52 | 并且我们可以通过 `$foo` 操作 `foo` 的值 53 | 54 | 【26】它是 `bar`,完美~ 55 | 56 | 你需要多加注意的一点是 57 | 58 | 当你面对着 bash 的时候[*] 59 | *bash 也在面对着你 60 | 61 | 空格至关重要[*] 62 | *译者注:在 sh、zsh 中同样如此 63 | 64 | 主要是因为空格是用于分隔参数的保留字符 65 | 66 | 例如,一些像是 `foo = bar` 的操作不管用 67 | 68 | Shell 会告诉你它为什么出错 69 | 70 | 这是它说因为 `foo` 命令无法生效 71 | 72 | 比如这里提示 `foo` 不存在 73 | 74 | 【33】实际发生的是,我们没有将 `bar` 赋给 `foo` 75 | 76 | 而是用 `=` 和 `bar` 作为参数调用了 `foo` 程序 77 | 78 | 通常,你需要特别关注这类问题 79 | 80 | 比如说一些带有空格的文件名 81 | 82 | 你需要小心地把他们用引号引起来 83 | 84 | 让我们更深入些,探讨一下怎样在 bash 中处理字符串 85 | 86 | 我们有两种定义字符串的方法: 87 | 88 | 可以用双引号定义字符串 89 | 90 | 或者可以用单…… 91 | 92 | 【41】呃,对不起 93 | 94 | 使用单引号(定义) 95 | 96 | 虽然对于纯文本字符串,这两种方式是等价的 97 | 98 | 但是对于其余的字符串,则不相同 99 | 100 | 例如,我们执行 `echo "Value is $foo"` 101 | 102 | 其中 `$foo` 将被展开为字符串 103 | 104 | 并且替换掉 Shell 中 `foo` 变量的值 105 | 106 | 如果我们用单引号来重复实验 107 | 108 | 我们仅仅会得到原样的 `$foo` 109 | 110 | 单引号中的变量将不会被替换 111 | 112 | 【50】脚本真的十分易于编写 113 | 114 | 这个就好比…它有点像你可能更熟悉的 Python 115 | 116 | 你可能没意识到这点 117 | 118 | 这就是给变量赋值的方式 119 | 120 | 我们稍后还会看到 bash 也有控制流技术 121 | 122 | 像是 for 循环、while 循环 123 | 124 | 另一个重点是,我们可以定义函数 125 | 126 | 我们可以访问我在此处定义的函数 127 | 128 | 这里我们已经定义了 `mcd` 函数 129 | 130 | 【57】到目前为止,我们已经了解 131 | 132 | 如何利用管道连接并执行几个命令 133 | 134 | 昨天简要地说过 135 | 136 | 但是很多时候你想先做一件事,然后另一件事 137 | 138 | 有点像我们这里的顺序执行 139 | 140 | 看这里,例如,我们可以调用 `mcd` 函数 141 | 142 | 首先我们调用 `mkdir` 命令 143 | 144 | 它会创建一个目录 145 | 146 | 【65】在这里,`$1` 就像是一个特殊变量 147 | 148 | 这就是 bash 运作的方式 149 | 150 | 类似于其他脚本语言的 `argv` 151 | 152 | 数组 `argv` 的第一项将包含参数[*] 153 | *此处说明较为复杂,详见网络资料 154 | 155 | 在 bash 中同样的东西是 `$1` 156 | 157 | 一般来说,bash 中许多 `$` 开头的东西 158 | 159 | 它们都是被保留的[*] 160 | *此处的「被保留」指留作特定用途 161 | 162 | 我们之后会看到更多的例子 163 | 164 | 【72】一旦我们创建了文件夹, 165 | 166 | 我们就 `cd` 进去 167 | 168 | 这其实是个挺常见的流程 169 | 170 | 实际上,我们直接将其键入到 Shell 171 | 172 | 它就会起作用,定义这个函数 173 | 174 | 但是有时候,把代码写到文件里更好 175 | 176 | 然后我们就可以 `source` 这个文件 177 | 178 | 这就会在 Shell 中加载脚本并执行 179 | 180 | 虽然现在看起来无事发生 181 | 182 | 【78】但是现在 Shell 中已经定义了 `mcd` 函数 183 | 184 | 因此我们现在能,比如说执行 `mcd test` 185 | 186 | 就从 `tool` 目录移到了 `test` 目录 187 | 188 | 我们创建了文件夹并且进入其中 189 | 190 | 还有什么。结果是... 191 | 192 | 我们可以通过 `$1` 访问第一个参数 193 | 194 | 这里有许多被保留的命令[*] 195 | *确切来说,此处应为被保留的「变量」,而非命令 196 | 197 | 例如 `$0` 将会是脚本的名字 198 | 199 | `$2` 到 `$9` 是 bash 脚本的 200 | 201 | 【86】第二个到第九个参数 202 | 203 | 有一些保留字可以直接在 Shell 中使用 204 | 205 | 例如 `$?` 能获取上条命令的错误代码(返回值) 206 | 207 | 我会简要解释这些 208 | 209 | 再比如,`$_` 会获取上条命令的最后一个参数 210 | 211 | 因此,我们搞定这个的另一种方式是 212 | 213 | 我们可以执行 `mkdir test` 214 | 215 | 与其重写一遍 `test` 216 | 217 | 【95】不如我们用 `$_` 访问上条命令的一部分 218 | 219 | 也就是最后一个参数 220 | 221 | 它将被替换成 `test` 222 | 223 | 现在我们进去了 `test` 目录 224 | 225 | 像这样的例子很多,你应当熟悉他们 226 | 227 | 另一个我经常用的叫做 `bang bang`(`!!`) 228 | 229 | 每当,比如说,你试着创建某些东西 230 | 231 | 【100】但你没有足够权限的时候 232 | 233 | 正是这个东西的用武之处 234 | 235 | 然后,你可以执行 `sudo !!` 236 | 237 | `!!` 会被你刚刚尝试的命令取代 238 | 239 | 现在来试一下 240 | 241 | 现在它就提示我输入密码 242 | 243 | 因为我有了 sudo 权限 244 | 245 | -------------------------------------------------------------------------------- /ch3/[833-990]CHN.txt: -------------------------------------------------------------------------------- 1 | 【833】 稍等,让我试着修一下这个锅 2 | 3 | 运行这个试试,啊! 4 | 5 | 显然,fish 不想让我这么做 6 | 7 | 看, `BEGIN` 在第一行的开头被匹配到 8 | 9 | `END` 在最后一行的末尾被匹配到 10 | 11 | 然后这些是普通的逐行匹配正则 12 | 13 | 【841】 所以,我写的这些意思是 14 | 15 | 在第零行开始, `rows` 这个变量被赋值为 0 16 | 17 | 对于能匹配这个规则的文本行 18 | 19 | `rows` 的值就会增加 20 | 21 | 当你匹配完最后一行的时候 22 | 23 | 就把 `rows` 这个变量的值打印出来 24 | 25 | 【847】 这和运行 `wc -l` 差不多 26 | 27 | 但是这些都是用 awk 运行的 28 | 29 | 通常 `wc -l` 就有不错的效果 30 | 31 | 但是如果你想做些别的 32 | 33 | 比如维护一个字典或者 map [*] 34 | * 可以类比 Python 里这两个术语的概念 35 | 36 | 或者统计一些数据 37 | 38 | 再或者是…我想找第二个符合匹配的结果 39 | 40 | 【855】所以你需要一个有状态的匹配器 41 | 42 | 比方说忽略匹配到的第一个结果 43 | 44 | 从第二个符合条件的结果开始逐个输出 45 | 46 | 这样的话,懂几行 awk 就很有用了 47 | 48 | 实际上,在现在这种情况下 49 | 50 | 我们可以撇掉之前处理文件使用的 51 | 52 | `sed sort uniq` 和 `grep` 这些命令 53 | 54 | 【863】然后用 awk 取而代之 55 | 56 | 但你大概不愿意这样做 57 | 58 | 这样做不值得,反倒可能让你很痛苦 59 | 60 | 再来说一说命令行里 61 | 62 | 别的非常好用的工具 63 | 64 | 首先是一个很方便的程序,叫做 bc 65 | 66 | 【870】或许 bc 是 **B**erkeley **C**alculator? 67 | 68 | 我想应该是吧 69 | 70 | `man bc` 71 | 72 | 我想 bc 应该是起源于 Berkeley calculator 吧? 73 | 74 | 无所谓了,它是一个简洁的命令行计算器 75 | 76 | 它并没有给你个提示符,让你输入 77 | 78 | 而是直接从标准输入读数据 79 | 80 | 所以我能这样 `echo "1 + 2" | bc -l` 81 | 82 | (要加 `-l`)因为好多这样的程序 83 | 84 | 默认的运行模式都很不智能 85 | 86 | 好,它输出了 3 87 | 88 | 哇,太强了 89 | 90 | 【881】同时这也说明它用起来挺方便的 91 | 92 | 想象一下,你有一个有很多行的文件 93 | 94 | 比如说,唔,不知道整啥好了 95 | 96 | 比方说,在这个文件里 97 | 98 | 我想把登录的次数加起来 99 | 100 | 把出现不止一次的名字个数加起来 101 | 102 | 这里写,第一个匹配组的内容不为 1 103 | 104 | 【889】然后只把这个次数输出 105 | 106 | 程序就会告诉我 107 | 108 | 所有登录了不止一次的用户都登录了几次 109 | 110 | 然后我还想了解一下总数是多少 111 | 112 | 注意我不能只数一下有多少行 113 | 114 | 这样是有问题的,对吧 115 | 116 | 因为每一行都有对应的次数,我得把他们都加起来 117 | 118 | 那么,我可以用 `paste` 命令 119 | 120 | 一边粘贴输入,一边附上加号 121 | 122 | 【897】这样就把所有行用 `+` 连接成了一行加法式 123 | 124 | 这就是一个算术表达式 125 | 126 | 这样就可以把它 pipe 到 `bc -l` 127 | 128 | 可以看到,总共有十九万一千多个用户名 129 | 130 | 登录了不止一次 131 | 132 | 你可能并不关心这个结果 133 | 134 | 这只是展示一下你可以轻松提取这些数据 135 | 136 | 【907】你还可以用这些数据做很多别的事 137 | 138 | 有用来计算和统计输入数据的工具 139 | 140 | 比如说,对于刚刚这列数字 141 | 142 | 【913】这样,我们重新只输出数字 143 | 144 | 按顺序输出数字 145 | 146 | 然后我可以用 R 跑一下 147 | 148 | R 是一门独立的编程语言 149 | 150 | 针对数据的统计分析而设计 151 | 152 | 我可以这样写 153 | 154 | 看看我能不能搞对 155 | 156 | 它也是一个新的编程语言,你要专门去学 157 | 158 | 先假设你会用 R,但也可以 pipe 给别的语言 159 | 160 | 这样我就得到了一个输入数字的统计结果 161 | 162 | 【926】所以各用户名登录次数的中位数是 3 163 | 164 | 最大值是一万多,我们之前看过了,这个是 root 产生的 165 | 166 | 还告诉我平均值是 8 167 | 168 | 这些在目前的这个例子里可能没有意义 169 | 170 | 这些不是什么有意义的数据 171 | 172 | 但是,处理比如统计脚本的输出,或者别的一些 173 | 174 | 【933】会有明显数值分布的数据时 175 | 176 | 如果你想看这种数据,这些工具就有用了 177 | 178 | 我们甚至可以画个简单的图表 179 | 180 | 这里是一堆数字 181 | 182 | 我们回到前面,`sort -nk1,1` 183 | 184 | 然后只保留,就最前面五个吧 185 | 186 | `gnuplot` 是一个画图表的工具 187 | 188 | 可以接受标准输入 189 | 190 | 我不期望你们都会这些编程语言 191 | 192 | 毕竟他们都是实打实的一门门编程语言 193 | 194 | 只是展示一下你的工具选择 195 | 196 | 现在这就有了一个大直方图 197 | 198 | 是前五个用户,自从 1 月 1 日开始 199 | 200 | 都各被用了多少次的图表 201 | 202 | 这只用了一行命令 203 | 204 | 虽然它特别长特别复杂,但只用一行就可以 205 | 206 | 【954】这节课的最后我再说两句 207 | 208 | 还有两种特别的数据处理 209 | 210 | 首先是命令行参数的处理 211 | 212 | 有时候你会遇到一些情况…… 213 | 214 | 比如上节课讲过的 `find` 命令 215 | 216 | 会产生一连串的文件名 217 | 218 | 或者一些命令可能产生一连串的…… 219 | 220 | 参数,传给你的评测脚本 221 | 222 | 比如你想带上有特定数值分布的参数运行 # REVIEW 这句子太长了要不要考虑拆成两个小句 223 | 224 | 比如你有一个脚本 225 | 226 | 会给一个程序提供它迭代次数的数值 227 | 228 | 然后你想让这组数值呈指数分布之类的 229 | 230 | 然后这个脚本会逐行输出每个迭代次数 231 | 232 | 你想要照此依次运行程序 233 | 234 | 正好,这有个叫 `Xargs` 的工具是你的好帮手 235 | 236 | `xargs` 接受若干行输出,把它们转为参数形式 237 | 238 | 这可能有点怪,我看看有啥好例子吗 239 | 240 | 这样,我会用 Rust 编程 [*] 241 | * 一门系统编程语言,由 Mozilla 主持推动。 242 | 243 | Rust 允许你安装前后多个版本的编译器 244 | 245 | 在这里你可以看到稳定版、Beta 版 246 | 247 | 还有几个早期的稳定发布版 248 | 249 | 还有一堆过期了的 nightly 版本 250 | 251 | 这功能还挺不错的 252 | 253 | 但是很长时间过去之后 254 | 255 | 就不需要留着这些 nightly 版本了 256 | 257 | 像这种去年三月份的 258 | 259 | 【990】我可以删掉这些 260 | 261 | 我从今往后可能还想清理一下 262 | 263 | 这是一个多行的列表 264 | 265 | 我可以先找出 nightly 266 | 267 | 我可以去掉……`-V` 是不要匹配 268 | 269 | 【995】我不想匹配最新的 nightly 270 | 271 | 好,这是一些有年头的 nightly 272 | 273 | -------------------------------------------------------------------------------- /ch3/[641-833]CHN.txt: -------------------------------------------------------------------------------- 1 | 【641】来看看这一共多少行 2 | 3 | 如果我键入 `wc -l` 4 | 5 | 这有…… 6 | 7 | 一十九万八千行 8 | 9 | 这个 `wc` 是计数程序(**w**ord **c**ount) 10 | 11 | `-l` 选项是统计行数 12 | 13 | 所以这么多行 14 | 15 | 如果我只是边翻边看,意义也不大 16 | 17 | 对吧,我需要的是统计数据 18 | 19 | 我需要找个方法合计数据 20 | 21 | 虽然 `sed` 这个工具用途很广 22 | 23 | 它支持一个完整的编程语言 24 | 25 | 可以做一些,比如插入文本 26 | 27 | 或者只输出匹配行的操作 28 | 29 | 但它不是应付一切的完美工具,明白吗 30 | 31 | 有时候有更好的选择 32 | 33 | 就比如说,你可以用 `sed` 34 | 35 | 编程实现行数统计 36 | 37 | 但绝对别这么干 38 | 39 | 除了搜索替换之外 40 | 41 | `sed` 的语法挺烂的 42 | 43 | 但是,还有别的好用的工具 44 | 45 | 比如有个叫 `sort` 的 46 | 47 | 虽然它泛用性不高 48 | 49 | `sort` 会接受很多行的输入 50 | 51 | 排一个序,然后输出到输出流 52 | 53 | 现在,我有了这个排序后的列表 54 | 55 | 它仍然有二十万行,所以还不是很好 56 | 57 | 但现在我可以把 `uniq` 结合进来 58 | 59 | 这个工具 `uniq` 60 | 61 | 作用在多行有序的输入上 62 | 63 | 输出去重后的输入 64 | 65 | 也即,如果你有重复的行 66 | 67 | 这些行只会被打印一次 68 | 69 | 我可以执行 `uniq -c` 70 | 71 | 意为,对重复的行,计算它们重复的数量 72 | 73 | 然后将其(从输出中)去除 74 | 75 | 这会输出什么呢? 76 | 77 | 呃,如果我执行它,会处理一会 78 | 79 | 里边有 13 个 `zzz` 用户名 80 | 81 | 10 个 `zxvf` 用户名,等等 82 | 83 | 我可以上下翻看 84 | 85 | 这仍是一个很长的表单,对吧 86 | 87 | 但现在,至少比原来稍微条理点了 88 | 89 | 看看现在我们提出来多少行 90 | 91 | 好,两万四千行,仍然很多 92 | 93 | 虽然对我而言,这些信息没用 94 | 95 | 但我可以用更多工具,不断缩减它 96 | 97 | 比如我可能想知道 98 | 99 | 哪个用户名出现的最多 100 | 101 | 我可以再排个序 102 | 103 | 我想要对输入的第一列做数值排序 104 | 105 | 所以 `-n` 意为数值排序 106 | 107 | `-k` 允许你在输入中 108 | 109 | 选中空白字符分隔的一列,执行排序 110 | 111 | 这里我加了一个 `,1` 的原因是 112 | 113 | 我想要计数第一列到第一列 114 | 115 | 除此之外我也可以要求 116 | 117 | 依据所有的列排序 [*] 118 | * 指首先根据第一列排序,第一列相同以第二列排序,以此类推 119 | 120 | 但这里我只想用第一列 121 | 122 | 然后我只想要最后十列 123 | 124 | `sort` 默认是以升序输出 125 | 126 | 所以计数最高的一条在最底下 127 | 128 | 然后我就只要最后十列 129 | 130 | 现在再跑的时候 131 | 132 | 我就有比较有用的数据了,对吧 133 | 134 | 它告诉我用户名 `root` 有一万多次登录尝试 135 | 136 | 用户名 `123456` 有四千多次 137 | 138 | 这就很棒了 139 | 140 | 现在这个大日志突然就给我有用信息了 141 | 142 | 这是我真正想从日志里要的信息 143 | 144 | 现在我可能就想,比如 145 | 146 | 快速地禁用一下我机器上 147 | 148 | 比如 SSH 登录的 `root` 用户名 149 | 150 | 顺便我也建议你们这样做 151 | 152 | 其实对于这个情况 153 | 154 | 我们不需要 `sort` 的 `-k` 155 | 156 | 因为 `sort` 默认按(从前到后的)列排序 157 | 158 | 而数字又恰巧是最前面一列 159 | 160 | 但了解这些额外的 flag 是有益的 161 | 162 | 你可能想问,我是怎么知道有这些 flag 的 163 | 164 | 我是怎么了解这些程序的存在的 165 | 166 | 【733】嗯,通常这些程序是 167 | 168 | 在这种课堂上知道的 169 | 170 | 至于这些 flag 171 | 172 | 经常是,我想按照某个基准排序 173 | 174 | 但不是按整行 175 | 176 | 那你的第一反应是键入 `man sort` 177 | 178 | 然后把页面读一遍 179 | 180 | 你很快就能知道怎么能 181 | 182 | 优雅地选中一行 183 | 184 | 怎么能像这样,选这行数字 185 | 186 | 好,如果,我们现在有了这个…… 187 | 188 | 就让它是前 20 的表单 189 | 190 | 假设我并不关心具体数量 191 | 192 | 我只要一个逗号分隔开的用户名表单 193 | 194 | 因为我可能打算通过电邮 195 | 196 | 每天都把它发给自己,之类的 197 | 198 | 像是《震惊!今天攻击者最喜欢的二十个用户名竟是...》 199 | 200 | 嗯,我可以这样—— 201 | 202 | 好,出现了更多怪怪的命令 203 | 204 | 但了解它们都是有意义的 205 | 206 | 这个 `awk` 是基于列的流编辑器 207 | 208 | 我们提到了流编辑器 `sed` 209 | 210 | 它主要是编辑输入进来的文本 211 | 212 | 此外,`awk` 也让你编辑文本 213 | 214 | 也是一个完整的编程语言 215 | 216 | 但它专注于基于「列」的数据 217 | 218 | 所以这里 `awk` 会以默认方式 219 | 220 | 解析空格分隔的输入 221 | 222 | 然后你可以分别处理这些行 223 | 224 | 我这里告诉它只打印第二行 225 | 226 | 就是用户名那行,对吧 227 | 228 | `paste` 这个程序 229 | 230 | 能借助 `-s` 选项,将一大堆行的输入 231 | 232 | 处理成以 tab 分隔的一行 233 | 234 | 这里 `-d` 使其以 `,` 分隔,而不是 tab 235 | 236 | 这里,这个例子,我想要一个 237 | 238 | 逗号分隔的最靠前的用户名列表 239 | 240 | 然后我就可以物尽其用 241 | 242 | 比如我把它丢进一个配置文件 243 | 244 | 去禁止这些用户名啥的 245 | 246 | `awk` 值得我多费几句口舌 247 | 248 | 讲白了,对于这样的数据整理 249 | 250 | 它是一个非常有力的语言 251 | 252 | 我简单说了这个 `print $2` 做什么 253 | 254 | 但你可以用 `awk` 施展一些绚丽的魔法 255 | 256 | 比如,我们先回到处理用户名这里 257 | 258 | 然后……我们还是执行 `sort` 和 `uniq` 吧 259 | 260 | 不然这个表单就太长了 261 | 262 | 然后让我们只输出那些 263 | 264 | 和特定模式相符的用户名 265 | 266 | 比如,让我想想…… 267 | 268 | `uniq -c` 269 | 270 | 我要只出现一次,并且 271 | 272 | 以 c 开头、e 结尾的所有用户名 273 | 274 | 虽然我们在搜索一个奇怪的东西 275 | 276 | 但在 `awk` 里面写出来还挺容易 277 | 278 | 我可以让第一列是 `1` 279 | 280 | 并且第二列匹配这个正则 281 | 282 | *好像这里只用 `.` 就行* 283 | 284 | 然后我想按整行打印 285 | 286 | 除非我搞错了什么东西 287 | 288 | 不然这就是所有以 c 开头,e 结尾 289 | 290 | 并且只出现了一次的用户名 291 | 292 | 虽然对数据做这种处理没有意义 293 | 294 | 但我在课上想讲的是 295 | 296 | 各种可以运用的工具 297 | 298 | 并且虽然我们举的例子很奇怪 299 | 300 | 但这个 pattern 并不复杂 301 | 302 | 这是因为某些 Linux 的工具 303 | 304 | 以及普遍的命令行工具 305 | 306 | 都是按照以行为单位的输入输出而设计 307 | 308 | 并且这些行经常会分为多列 309 | 310 | 而 `awk` 就是处理列的能手 311 | 312 | `awk` 不仅能做这种匹配每行的操作 313 | 314 | 而且,比如说…… 315 | 316 | 让我先输出一下行数 317 | 318 | 我想知道多少用户名符合这个模式 319 | 320 | 我可以执行 `wc -l`,这样就挺好 321 | 322 | 有 31 个这样的用户名 323 | 324 | 但 `awk` 是编程语言啊 325 | 326 | 这个黑魔法,你估计不会想去碰它 327 | 328 | 但要知道你可以运用 329 | 330 | 知道这些,对现在和以后都有益 331 | 332 | 在我屏幕上可能不太好读懂 333 | 334 | 我也发现了…… 335 | 336 | 【833】我马上处理一下 337 | -------------------------------------------------------------------------------- /ch1/[105-252]CHN.txt: -------------------------------------------------------------------------------- 1 | 【105】之前我提到了,呃,命令错误什么的 2 | 3 | 昨天我们看过,总体来说…… 4 | 5 | 一个进程有许多方式 6 | 7 | 和其他进程或命令交互 8 | 9 | 我们提到了标准输入(流) 10 | 11 | 它就是好比…… 12 | 13 | (程序)从标准输入获取各种东西 14 | 15 | 然后把东西输到标准输出里 16 | 17 | 还有些东西更有意思 18 | 19 | 也有一个标准错误(流) 20 | 21 | 【114】如果你程序出错了 22 | 23 | 你想输出错误却不污染标准输出 24 | 25 | 就可以写进这个流 26 | 27 | 也有错误代码(error code)这种东西 28 | 29 | 而且它普遍存在于很多编程语言 30 | 31 | 是一种告诉你整个运行过程 32 | 33 | 结果如何的方式 34 | 35 | 所以,比如我们试试 36 | 37 | `echo "Hello"` 38 | 39 | 然后查一下错误代码的值,它是 `0` 40 | 41 | `0` 是因为一切正常,没有出问题 42 | 43 | 这种 `0` 退出码和它在[*] 44 | *这里把 error code 和 C 语言中的 exit code 做对比 45 | 46 | 【124】比如 C 这种语言里,代表的意思一样 47 | 48 | `0` 就代表所有事情正常,没出错误 49 | 50 | 然而,有时候事情会出错 51 | 52 | 比如有时候,我们尝试在 `mcd` 脚本里 53 | 54 | `grep foobar` 的话 55 | 56 | 现在查一下值,就是 `1` 57 | 58 | 这是因为我们试着在 `mcd` 脚本里 59 | 60 | 搜索 `foobar` 字符串,而它不存在 61 | 62 | 所以 `grep` 什么都没输出 63 | 64 | 但是通过反馈一个 `1` 的错误代码 65 | 66 | 它让我们知道这件事没成功 67 | 68 | 【133】有一些有意思的命令,比如 69 | 70 | `true` 的错误代码始终是 `0` 71 | 72 | `false` 的错误代码则是 `1` 73 | 74 | 还有比如这些逻辑运算符 75 | 76 | 你可以用来做条件判断 77 | 78 | 比如……其实你也有 `if-else` 79 | 80 | 之后我们会说 81 | 82 | 但是现在你可以做一些 83 | 84 | 比如 `false`,然后 `echo "Oops fail"` 85 | 86 | 这里有两个被或运算符链接的命令 87 | 88 | 【142】这里 bash 要做的是,执行第一个命令 89 | 90 | 如果第一个命令失败,再去执行第二个[*] 91 | *这里是短路运算法则,详细解释参见网络 92 | 93 | 这里我们有这个结果 94 | 95 | 因为它尝试做一个逻辑或 96 | 97 | 如果第一个(命令)没有 `0` 错误码 98 | 99 | 它就会去执行第二个(命令) 100 | 101 | 相似地,如果我们把 `false` 102 | 103 | 替换成比如 `true` 104 | 105 | 因为我们有一个 `0` 错误代码 106 | 107 | 所以第二个(命令)会被短路 108 | 109 | 所以就不会打印 110 | 111 | 【151】相似地,我们有与运算符 112 | 113 | 它仅当第一个命令执行无错误时 114 | 115 | 才会执行第二个部分 116 | 117 | 这里也是同样的事情: 118 | 119 | 如果第一个失败,那么第二个命令 120 | 121 | 就不会被执行 122 | 123 | 虽然不是很相关,但是另一个事情是 124 | 125 | 无论你执行什么,你都可以通过 126 | 127 | 在同一行内使用分号来连接命令 128 | 129 | 它就会始终被打印出来 130 | 131 | 【160】在这之后,我们还没学到的是 132 | 133 | 怎样把命令的输出存到变量里 134 | 135 | 我们可以这样做 136 | 137 | 这里我们获取 `pwd` 命令的输出 138 | 139 | 它会打印出当前工作目录 140 | 141 | 也就是我们在哪里 142 | 143 | 然后把这个存进 `foo` 变量 144 | 145 | 然后现在我们询问 `foo` 的值 146 | 147 | 我们就能看到这个字符串 148 | 149 | 【168】更广泛的说,我们可以做一个叫 150 | 151 | 命令替换的事情 152 | 153 | 通过把它放进任意字符串中 154 | 155 | 而且因为我们用的不是单引号 156 | 157 | 而是双引号 158 | 159 | 所以这串东西会被展开 160 | 161 | 告诉我们,现在位于这个文件夹 162 | 163 | 另一个有趣的事情是 164 | 165 | 这个会展开成一个字符串 166 | 167 | 而不是…… 168 | 169 | 呃,它只是展开成一个字符串 170 | 171 | 【176】另一个好用但知名度更低的工具 172 | 173 | 叫做过程替换 174 | 175 | 和之前那个是类似的 176 | 177 | 它会做什么呢……它会 178 | 179 | 比如这里的 `<(` ,接一个命令,再接 `)` 180 | 181 | 它的作用是,内部的命令会被执行 182 | 183 | 其输出将被存储到,大概像一个 184 | 185 | 临时文件内,然后把文件 handle(标识符)[*] 186 | *handle 在国内常译作「句柄」。但这里应该指的就是文件本身,为避免冲突不译做句柄。 187 | 188 | 交给(最左面的)命令 189 | 190 | 【182】所以这里我们在……`ls` 这个目录 191 | 192 | 把输出放到临时文件内 193 | 194 | 再对父目录如法炮制 195 | 196 | 然后把两个文件连接 197 | 198 | 而这种写法就非常得劲 199 | 200 | 因为有些命令会从 201 | 202 | 某些文件的内容,而不是标准输入 203 | 204 | 获得输入参数 205 | 206 | 所以我们把这两个命令连起来了 207 | 208 | 感觉讲到现在,讲了真不少东西 209 | 210 | 来看一个里面包含这些内容的 211 | 212 | 【190】简单的示例脚本 213 | 214 | 比如说这里我们有个字符串 215 | 216 | 然后有个 `$(date)` 217 | 218 | 这个 `date` 是个程序 219 | 220 | 重复一下,类 UNIX 系统有很多程序 221 | 222 | 你会慢慢都熟悉它们的 223 | 224 | `date` 就打印出当前的日期 225 | 226 | 你还可以指定各种打印格式 227 | 228 | 然后这里有这个 `$0` 229 | 230 | 是我们运行的这个脚本的文件名 231 | 232 | 然后是这个 `$#`,代表给定的参数个数 233 | 234 | 然后 `$$` 是这个命令的进程 ID[*] 235 | *一般缩写为 PID,操作系统相关,详见网络资料 236 | 237 | 【200】强调,这里有很多 `$`+ 什么什么 238 | 239 | 它们(的含义)并不直观 240 | 241 | 因为你找不到一种巧记的方法 242 | 243 | `$#` 这种大概就是 244 | 245 | 但是……你一直和它们打照面 246 | 247 | 逐渐就能熟络起来 248 | 249 | 这里还有个 `$@` 250 | 251 | 可以展开成所有参数 252 | 253 | 所以比起来……比如有三个参数 254 | 255 | 那我可以键入 `$1 $2 $3` 256 | 257 | 【208】那如果我们不知道有多少参数 258 | 259 | 我们可以用这种方式把这些参数全部放在这里 260 | 261 | 然后这些参数被传给 `for` 循环 262 | 263 | `for` 循环会创建一个 `file` 变量 264 | 265 | 依次地用这些参数赋值给 `file` 变量 266 | 267 | 下一行我们运行 `grep` 命令 268 | 269 | 它会在一堆文件里搜索一个子串[*] 270 | *这里子串意指一个字符串中连续的一小部分 271 | 272 | 这里我们在文件里搜索字符串 `foobar` 273 | 274 | 这里我们让 `file` 变量展开为它的值 275 | 276 | 昨天说过,如果我们在意程序输出的话 277 | 278 | 【218】我们可以把它重定向到某处 279 | 280 | 到一个文件里保存下来,或者连接组合 281 | 282 | 嘿,但有时候情况恰恰相反 283 | 284 | 有时候,比如说,我们想知道 285 | 286 | 这个脚本的错误代码是什么 287 | 288 | 我想知道 `grep` 能不能成功查找 289 | 290 | 所以,我们甚至能直接扔掉整个输出 291 | 292 | 包括标准输出和标准错误(流) 293 | 294 | 【225】这里我们做的是 295 | 296 | 把两个输出重定向到 `/dev/null` 297 | 298 | 它是 UNIX 系统的一种特殊设备 299 | 300 | 输出到它的内容会被丢弃 301 | 302 | 就是你可以随意乱写乱画 303 | 304 | 然后所有内容都会被丢掉 305 | 306 | 还有这个 `>` 符号 307 | 308 | 【232】昨天说过,用来重定向输出的 309 | 310 | 这里有个 `2>` 311 | 312 | 有些人也许猜到了 313 | 314 | 它是重定向标准错误流的 315 | 316 | 因为这两个流是分立的 317 | 318 | 所以你得告诉 bash 去操作哪个 319 | 320 | 所以这里我们执行命令 321 | 322 | 去检查文件有没有 `foobar` 323 | 324 | 如果有的话,返回一个 `0` 错误码 325 | 326 | 如果没有,就是一个非 `0` 码 327 | 328 | 我们正是要检查这个 329 | 330 | 【243】这部分命令里 331 | 332 | 我们先告诉它:「给我错误代码」 333 | 334 | 这个是用 `$?` 335 | 336 | 然后是一个比较运算符 `-ne` 337 | 338 | 代表不等于(`N`on `E`qual) 339 | 340 | 其他编程语言里有像 341 | 342 | `==` 和 `!=` 这种符号 343 | 344 | bash 里有很多预设的比较运算 345 | 346 | 这主要是为了你用 Shell 的时候 347 | 348 | 有很多东西要去做测试 -------------------------------------------------------------------------------- /ch1/[252-394]CHN.txt: -------------------------------------------------------------------------------- 1 | 【252】比如我们现在正在对比两个数 2 | 3 | 两个整数,看它们是否相同 4 | 5 | 又比如,`-f` flag 会让我们知道 6 | 7 | 是否存在一个文件 8 | 9 | 这是你以后会频繁用上的 10 | 11 | 回到例子 12 | 13 | 如果文件中没有 `foobar` 会发生什么 14 | 15 | 像之前有非 `0` 的错误代码 16 | 17 | 【260】我们也输出 18 | 19 | 文件中没有 `foobar` 字符串 20 | 21 | 我们将添加一个,而我们所做的是 22 | 23 | 我们输入 `# foobar` 24 | 25 | 蒙一手这 `#` 是个文件注释格式 26 | 27 | <<<<<<< HEAD 28 | 【265】之后我们用 `>>` 操作符把它添在文件末尾 29 | ======= 30 | 【265】之后我们用 `>>` 运算符把它添在文件末尾 31 | >>>>>>> e692777 (reviewed by GNAQ) 32 | 33 | 这里尽管文件名已经传给了脚本 34 | 35 | 但我们预先并不知道文件名 36 | 37 | <<<<<<< HEAD 38 | 所以我们需要用文件名变量在这里展开 39 | ======= 40 | 需要调用记录文件名的变量(获取文件名) # REVIEW 41 | >>>>>>> e692777 (reviewed by GNAQ) 42 | 43 | 我们可以运行这个试试 44 | 45 | 我们已经有这个脚本的正确权限 46 | 47 | 【270】我可以举一些例子,我们在这个夹里有一些文件 48 | 49 | `mcd` 是我们先前看到的 `mcd` 函数 50 | 51 | 还有其它脚本函数 52 | 53 | 甚至可以把它自己传给它,检查是否有 `foobar` 54 | 55 | 我们运行它,首先我们可以看到 56 | 57 | 我们成功地列出了很多的变量 58 | 59 | 【275】我们有 `date` 命令 60 | 61 | 它成功地被替换成了当前时间 62 | 63 | 接着是这个带着三个参数的程序 64 | 65 | 它的随机的 pid 识别码 66 | 67 | 【280】之后它告诉我们 `mcd` 没有 `foobar` 字符串 68 | 69 | 所以我们新添加了一个 70 | 71 | 并且这个 `script.py` 文件也没有 72 | 73 | 像现在让我们看看 `mcd` 74 | 75 | 它就有我们要找的注释 76 | 77 | 【285】当你在执行脚本时另一个需要知道的是 78 | 79 | 像这里,我们有三个完全不同的参数 80 | 81 | 但通常 82 | 83 | 你会用一些更加简洁的方式输入参数 84 | 85 | 【290】例如这里, 86 | 87 | 如果我想查找所有的 `.sh` 脚本 88 | 89 | 我们只需要键入 `ls *.sh` 90 | 91 | 这是大多数 Shell 都有的一种展开文件名的方式 92 | 93 | 叫做通配 94 | 95 | 【295】这里,如你所想,会显示出 96 | 97 | 所有含有任意字符,且以 `.sh` 为后缀的东西 98 | 99 | 如我们所料,得到了 `example.sh` 和 `mcd.sh` 100 | 101 | 我们也有这些 `project1` 和 `project2` 102 | 103 | 并且如果这里有…… 104 | 105 | 【300】比如,我们可以建一个 `project42` 106 | 107 | 现在如果我只想找有一个特定字符的项 108 | 109 | 而不是两个字符 110 | 111 | 然后,像其它任意的字符 112 | 113 | 我们可以使用 `?` 标记,`?` 标记只会展开一个字符 114 | 115 | 【305】我们得到了列出的 116 | 117 | 先是 `project1` 再是 `project2` 118 | 119 | 总而言之,通配符非常强大,你也可以组合它们 120 | 121 | 一个常用模式是花括号 122 | 123 | 我们在这个文件夹里有一个图片 124 | 125 | 【310】我们想把图片文件格式由 PNG 转为 JPG 126 | 127 | 我们可能会复制它,或者…… 128 | 129 | 这确实是常见情况,有两个或多个挺相似的参数 130 | 131 | 你想把它们当作参数传给命令 132 | 133 | 你可以这样做,但更简洁的做法是 134 | 135 | 【315】你可以只键入 `image.{png,jpg}` 136 | 137 | 这里有一些彩色的反馈…… 138 | 139 | 总之,它会展开成上面的那行 140 | 141 | 实际上,我可以让 zsh 为我做这些 142 | 143 | 也就是这里正进行的 144 | 145 | 这确实很强大,所以比如 146 | 147 | 【320】你可以做一些像……我们可以…… 148 | 149 | `touch` 一串 `foo`,所有 `foo` 都会被展开 150 | 151 | 你也可以进行多层操作,建立笛卡尔系 152 | 153 | 如果我们有一些像这样的组 154 | 155 | 我们这里有一组 `{1,2}` 156 | 157 | 之后这里又有 `{1,2,3}` 158 | 159 | 【325】这会用使两组展开式形成笛卡尔积 160 | 161 | 而后展开积里的所有表达式 162 | 163 | 我们就可以很快地 `touch` 了 164 | 165 | 你也可以将 `*` 通配符与 `{}` 通配符结合 166 | 167 | 甚至你可以用一些范围表示 168 | 169 | 像,我们可以键入 `mkdir` 170 | 171 | 【330】我们创建 `foo`,`bar` 目录 172 | 173 | 之后可以在这些行里搞事情 174 | 175 | 这将会展开到 `foo/a`,`foo/b` …… 176 | 177 | 像所有的组合,直到 `j` 178 | 179 | `bar` 同理,虽然说实话我没试…… 180 | 181 | 【335】但是没错,我们得到了我们所能 touch 的所有组合 182 | 183 | 现在,如果我们在两个目录中建一些不同的东西 184 | 185 | 我们可以再次展示…… 186 | 187 | 用我们之前看到的流程代替 188 | 189 | 我们想查看这两个文件夹中有什么不同文件 190 | 191 | 【340】非常显然,我们刚刚看到了,是 `x` 和 `y` 192 | 193 | 但是我们可以用 Shell 去比对 194 | 195 | 一个 `ls` 和另一个 `ls` 输出的不同 196 | 197 | 如我们所料,我们得到了 198 | 199 | `x` 只在第一个文件夹里 200 | <<<<<<< HEAD 201 | 202 | 【345】`y` 只在第二个文件夹里 203 | 204 | 还有,目前我们只看了 bash 脚本 205 | 206 | 如果你喜欢其它的脚本…… 207 | 208 | ======= 209 | 210 | 【345】`y` 只在第二个文件夹里 211 | 212 | 还有,目前我们只看了 bash 脚本 213 | 214 | 如果你喜欢其它的脚本…… 215 | 216 | >>>>>>> e692777 (reviewed by GNAQ) 217 | 像 bash 对一些工作可能并不是最好的选择 218 | 219 | 它可能会很棘手。事实上你可以 220 | 221 | 用很多语言写和 Shell 工具交互的脚本 222 | 223 | 【350】例如,我们在这里看一个 224 | 225 | <<<<<<< HEAD 226 | Python 脚本,它的开头有神秘的一行代码 227 | ======= 228 | Python 脚本,它的开头是一个魔法行 # REVIEW 229 | >>>>>>> e692777 (reviewed by GNAQ) 230 | 231 | 我暂且不去解释 232 | 233 | 我们有 `import sys` 234 | 235 | 这很像…… Python 默认不会尝试和 Shell 交互 236 | 237 | 【355】所以你需要导入一些库 238 | 239 | 之后我们在做一个 240 | 241 | 确实很傻的事情 242 | 243 | 就只是迭代 `sys.argv[1:]` 244 | 245 | `sys.argv` 是一种类似于 246 | 247 | bash 中 `$0`,`$1` 等等的东西 248 | 249 | 【360】就是一个参数 vector,我们将它倒序输出 250 | 251 | <<<<<<< HEAD 252 | 开始时那神奇的一行叫做 shebang [*] 253 | *这个单词源于这行以 `#!` 作为开头。`#` 是 sharp,`!` 是 bang 254 | ======= 255 | 开始时那神奇的一行叫做 shebang 256 | >>>>>>> e692777 (reviewed by GNAQ) 257 | 258 | Shell 通过它了解怎么运行这个程序 259 | 260 | 你随时可以键入类似 261 | 262 | 【365】`python script.py` 之后是 `a b c` 263 | 264 | 像这样它就会运行 265 | 266 | 但如果我想让它从 Shell 就能执行呢? 267 | 268 | Shell 是用首行识别到 269 | 270 | 需要用 Python 解释器运行这个程序 271 | 272 | 【371】并且第一行 273 | 274 | 给了这东西所在的路径 275 | 276 | 然而,你可能不知道 277 | 278 | 像不同的设备很可能 279 | 280 | 【375】会把 Python 放在不同的地方 281 | 282 | 最好别假设 Python 装在哪儿 283 | 284 | 其它解释器也是一样 285 | 286 | 所以你可以做的是调用 `env` 命令 287 | 288 | 【380】你也可以在 shebang 中给出参数 289 | 290 | 所以我们现在是在调用 `env` 命令 291 | 292 | 这是对于绝大多数系统而言的,有一些例外 293 | 294 | 但是对于绝大多数系统而言它在 `usr/bin` 295 | 296 | 那儿有很多二进制文件 297 | 298 | 【385】之后用参数 Python 调用它 299 | 300 | 它会使用 301 | 302 | 第一节课提到的 `path` 环境变量 303 | 304 | `env` 会在那些路径中找 Python 二进制文件 305 | 306 | 【390】接着用它去解释这个脚本 307 | 308 | 这样有更好的可移植性 309 | 310 | 能让它在我的,你的还有其它的设备上运行 311 | 312 | 另一件事是 bash 并不是真正现代化的 313 | 314 | 它好久之前就被开发出来了 315 | -------------------------------------------------------------------------------- /ch0/[400-599]CHN.txt: -------------------------------------------------------------------------------- 1 | 【400】或者如果你想 2 | 3 | 你可以给程序一个绝对路径 4 | 5 | 因为如果你给了一个相对路径 6 | 7 | 会导致我在我的目录里运行 8 | 9 | 你在你其它的目录下运行 10 | 11 | 我可能可以正常运行但是你运行不了 12 | 13 | 【405】um,总的来说,我们运行程序的时候 14 | 15 | 程序会默认在当前目录运行 16 | 17 | 除非我们再给程序一个参数 18 | 19 | 其实运行在默认目录是相当方便的 20 | 21 | 因为这意味着我们不必给出复杂的完整路径 22 | 23 | 用到的只是文件名或者是当前目录 24 | 25 | 搞清楚我们当前目录里有啥很关键 26 | 27 | 【416】然后我们可以发现 28 | 29 | `pwd` 命令能输出我们当前所在的目录 30 | 31 | 有个叫 `ls` 的命令能列出当前目录下的文件 32 | 33 | 如果我在这打上 `ls` 34 | 35 | 这就是当前目录下所有的文件,对吧 36 | 37 | 这是种方便在文件系统中快速查找的方法 38 | 39 | 你可以看到,如果我输入 `cd ..` 40 | 41 | 然后执行 `ls` 指令 42 | 43 | 会输出上一级目录下的文件 44 | 45 | 【426】我也可以 `ls` 一个 `..` 的参数 46 | 47 | 就像我给它一个路径一样 48 | 49 | 然后他就会 `ls` 上一级目录 50 | 51 | 而不是当前我所处的目录 52 | 53 | 或者直接 `ls` 上一级目录 54 | 55 | 如果我直接返回到根目录 56 | 57 | 你会发现根目录里有不同的路径 58 | 59 | 在这你可能不知道一个便捷的小技巧 60 | 61 | 你可以用两个特殊符号做点别的事 62 | 63 | 第一个,`~` 64 | 65 | 【436】`~` 可以把你带回用户目录 66 | 67 | 所以说 `~` 总是指向用户目录 68 | 69 | 你可以通过它到达用户目录相关的目录 70 | 71 | 所以我可以输入 `cd ~/dev/pdos/classes/missing-semester` 72 | 73 | 现在我就在这个目录里了 74 | 75 | 因为 `~` 在这代表 `/home/jon` 76 | 77 | 尤其在 `cd` 命令中还有一个很方便的参数 `-` 78 | 79 | 如果你运行 `cd -` 80 | 81 | 他会将当前目录跳转到你所处的上一个目录 82 | 83 | 所以如果我运行 `cd -` 84 | 85 | 【446】我就回到了根目录 86 | 87 | 如果我再执行一遍 `cd -` 88 | 89 | 我就回到了 `missing-semester` 这个文件夹 90 | 91 | 由此可见 92 | 93 | 这让你很方便的在两个不同文件夹之间切换 94 | 95 | 关于 `ls` 或者 `cd` 命令 96 | 97 | 还有一些你可能不知道的参数 98 | 99 | 目前为止 100 | 101 | 我们除了给出路径以外啥都没做 102 | 103 | 但是如果你想自己探索的话 104 | 105 | 【456】 你可以首先考虑 `ls` 一个路径 106 | 107 | 大多数程序采用一些 `flag`(标志) 108 | 109 | 和 `option`(选项)等作为参数 110 | 111 | 这些一般都以半角字符 `-` 开头 112 | 113 | 其中大多数程序都涵盖了 `--help` 这个函数 114 | 115 | 举个例子 116 | 117 | 如果你执行 `ls --help` 118 | 119 | 它会很贴心地输出一大堆关于 `ls` 指令的帮助信息 120 | 121 | 【466】 这些信息阐述了 `ls` 的用途 122 | 123 | 并且你可以给出的一堆 `option` 124 | 125 | 和一堆文件 126 | 127 | 阅读用法的时候 128 | 129 | `...` 代表不填或一个或更多 130 | 131 | `[]` 代表可 `option` 132 | 133 | 所以现在这里给出了一些 `option` 134 | 135 | 和一些可选的文档 136 | 137 | 显然它在告诉你这个程序会做什么 138 | 139 | 【476】并且特指了一些 `flag` 和 `option` 140 | 141 | 通常把一个 `-` 加上一个字母叫做一个 `flag`(选项) 142 | 143 | 或者把后面不跟没有数值的叫做 `flag` 144 | 145 | 而后面跟有一个值的叫做一个 `option` 146 | 147 | 举个例子,`-a` 和 `--all` 都叫 `flag` 148 | 149 | 而 `-C` 或 `--color` 都是 `option` 150 | 151 | 如果你向下翻得够远 152 | 153 | 你能发现一个 `-l` 的 `flag` 154 | 155 | 啊,翻车了 156 | 157 | 这个 `-l` 的 `flag` 是采用长列表格式[*] 158 | *通常来说,这种说法意味着输出更多信息 159 | 160 | 【486】通常来说,这很方便 161 | 162 | 我们来看看它到底能做什么 163 | 164 | 如果我执行 `ls -l` 165 | 166 | 它仍然会输出当前路径下的一长串文件 167 | 168 | 但是给出了关于这些文件更多的信息 169 | 170 | 你会发现你以后会经常用到这个 `flag` 171 | 172 | 【496】因为它多给你的信息能提供很大的便利 173 | 174 | 让我们看看多出来的信息都有什么 175 | 176 | 首先,前面带 `d` 的这些条目 177 | 178 | 代表这里面还有些东西 179 | 180 | 举个例子,这个 `_data` 的条目就是一个目录 181 | 182 | 但 `404.html` 不是个目录,而是一个文件 183 | 184 | 【506】后面的字符代表文件被授予的权限 185 | 186 | 这就像我们之前看到的,我并不能打开一个指定的文件 187 | 188 | 或者我 `cd` 不了一个目录 189 | 190 | 这都是由特定文件或目录的权限决定的 191 | 192 | 阅读这一串字母的方法如下 193 | 194 | 把后面的 9 个字母分成三组 195 | 196 | 第一组代表权限被授予给了文件的所有者 197 | 198 | 你可以看到这些文件的所有者都是我 199 | 200 | 【516】第二组的三个字母代表给拥有这些文件的用户组的权限 201 | 202 | 也就是 `jon` 这个用户组 203 | 204 | 最后的一组字符是给非所有者的其他人的权限 205 | 206 | 这个目录某种意义上来说有些“无聊” 207 | 208 | 因为这个目录的所有者是我 209 | 210 | 但是如果我们执行 `cd /` 回到根目录 211 | 212 | 然后再 `ls -l`,会发现所有文件和目录的所有者都是 `root` 213 | 214 | 我们回到什么是根用户来 215 | 216 | 【529】你能发现这里的权限有点意思 217 | 218 | 这一组中的三个字母代表读取、写入和执行 219 | 220 | 这些对于文件和目录又有什么不同呢 221 | 222 | 对于文件,这就很显而易见了 223 | 224 | 如果你有读取权限 225 | 226 | 你就可以读取文件的内容 227 | 228 | 如果你有文件的写入权限 229 | 230 | 【539】你就可以保存,写入甚至是重写一遍 231 | 232 | 如果你有一个 `x` 233 | 234 | 你就可以执行这个文件 235 | 236 | 如果我们运行 `ls -l /bin` 237 | 238 | 啊,我说我是乱打的啊,应该是 `ls -l /usr/bin` 239 | 240 | 你会发现即使不是所有者都有全部的执行权限 241 | 242 | 这是因为,举例来说 243 | 244 | 我们希望这个电脑上的所有用户都能运行显示程序 245 | 246 | 【549】没有任何理由只让某一用户能运行 247 | 248 | 对于目录来说,这些权限会显得稍有不同 249 | 250 | 所以读取权限允许你看这个文件夹里有哪些东西 251 | 252 | 你可以把读取权限当成阅读这个目录的清单 253 | 254 | 这个权限允许你列出目录里的内容 255 | 256 | 【559】目录的写入权限就是你是否能够重命名、新建或者删除里面的文件 257 | 258 | 所以这也是一种权限 259 | 260 | 但是要注意到,这意味着如果你有文件的写入权限 261 | 262 | 但是你没有这个目录的写入权限 263 | 264 | 你就不能删除这个文件 265 | 266 | 即使你清空了文件的内容,你也不能删除它 267 | 268 | 因为这样做的话需要目录的写入权限 269 | 270 | 最后,目录的执行权限困扰了很多人 271 | 272 | 目录的执行权限通常来讲就是搜索 273 | 274 | 但这并不是一个容易理解的名字 275 | 276 | 它意味着你能不能进入这个目录 277 | 278 | 如果你想找个文件 279 | 280 | 或者打开这个目录、读取这个目录还是写入这个目录 281 | 282 | 甚至基础如 `cd` 这个目录 283 | 284 | 【579】你都必须有所有父目录及其自身的执行权限 285 | 286 | 举例来说,如果我想访问 `/usr/bin` 下的文件, 例如 `echo` 287 | 288 | 我必须拥有路径上所有目录的执行权限 289 | 290 | 并且还得拥有 `bin` 这个目录的执行权限 291 | 292 | 如果我没有全部的这些执行权限,我就无法访问这个文件 293 | 294 | 因为我无法通过任何方法进入这个目录 295 | 296 | 【589】另外你可能还会遇到另外一些位 297 | 298 | 你可能会碰到 `s` 或 `t` 299 | 300 | 在这些清单里你可能还会看见 `l` 301 | 302 | 如果你对这些感到好奇的话,我们可以在工作时间聊聊 303 | 304 | 这些大多数对你在这堂课上学的东西都不是那么重要 305 | 306 | 但是了解它们会提高你的便利程度 307 | 308 | 所以如果你们实在好奇,你们可以自己查找相关资料 309 | 310 | 或者在工作时间来问我们 311 | 312 | 了解另外一些程序也能方便你自己 313 | 314 | 【599】噢,对不起,我忘了提到一点 315 | -------------------------------------------------------------------------------- /ch3/[1-214]CHN.txt: -------------------------------------------------------------------------------- 1 | 【1】好,欢迎来到今天的课堂 2 | 3 | 今天我们讲数据的整理(Data Wrangling) 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 | 【24】其实此时,你就在进行某种形式的数据处理 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 | *Jon 是挪威人,和荷兰离得很近。 59 | 60 | 在那个服务器上呢,跑着一个 61 | 62 | 【42】`systemd` 自带的记录日志的后台进程 63 | 64 | 这是一个挺标准的 Linux 日志机制 65 | 66 | 然后我们可以通过 Linux 的一个 67 | 68 | `journalctl` 命令来看系统日志 69 | 70 | 那么我要做的事 71 | 72 | 就是对这个日志做一些转换 73 | 74 | 然后看看里面有没有啥有趣的东西 75 | 76 | 你可以看到我跑完这个命令以后 77 | 78 | 获得了这么多的数据 79 | 80 | 【54】因为这个日志文件,它里面有超多东西 81 | 82 | 我的服务器上发生了不少事情 83 | 84 | 你看这一条是一月一日的 85 | 86 | 后面还有更久远的很多东西 87 | 88 | 那我们要做的第一件事就是缩小日志量 89 | 90 | 我们只看一部分的内容 91 | 92 | 此时 `grep` 就是你的最佳伙伴了 93 | 94 | 我们用管道把 `ssh` 的输出接到 `grep` 上 95 | 96 | 我们还没仔细聊过 `ssh` 97 | 98 | 但它是一种通过命令行,远程访问计算机的方式 99 | 100 | 当你把服务器放到公网上之后 101 | 102 | 世界各地的人都想连接然后登录进去 103 | 104 | 然后控制你的服务器 105 | 106 | 那我就想看看他们是咋整的 [*] 107 | *太坏了,准备用键盘回击.png 108 | 109 | 那我就 `grep SSH` 110 | 111 | 然后你就能够~~很快的~~看到这会输出很多东西 # REVIEW 112 | 113 | 理论上来说是这样的但是实际上很慢... 114 | 115 | 好 116 | 117 | 你可以看到它生成了 118 | 119 | 这么这么这么多的内容 120 | 121 | 【79】这样很难看出发生了什么 122 | 123 | 所以我们只来看看这些人 124 | 125 | 用了什么用户名来尝试登录 126 | 127 | 你可以看到这里有几行写着「无效用户,断开连接」 [*] 128 | *Disconnected from invalid user 129 | 130 | 然后后面是用户名 131 | 132 | 现在我只想要这种日志条目 133 | 134 | 我只关注这些东西 135 | 136 | 那我现在再来点修改 137 | 138 | 我在最后加上个 `Disconnected from`(断开连接) 139 | 140 | 你想想底部的这条命令流水线是如何运作的 141 | 142 | 首先它会通过网络,把整个日志传到这个电脑里 143 | 144 | 然后在本地跑 `grep` 找出所有含 `ssh` 的行 145 | 146 | 然后再在本地更进一步的去筛选 147 | 148 | 这是不是有点浪费 [*] 149 | *就这网速传什么大文件,别想了 150 | 151 | 因为我根本不关心其他的条目 152 | 153 | 远程服务器上也有个 Shell 154 | 155 | 那我就把整个命令搬到服务器上运行 156 | 157 | 那么现在,你,SSH 158 | 159 | 你给我在服务器上整这三个活 160 | 161 | 然后拿回来的数据我再接到 `less` 上面 162 | 163 | 那这会发生什么呢 164 | 165 | 其实是一样的数据筛选 166 | 167 | 只是把工作搬到服务器上了 168 | 169 | 而服务器只会回传我想要的行 170 | 171 | 然后我在本地把数据用管道接到了 `less` 上 172 | 173 | 【111】`less` 是个分页显示程序 174 | 175 | 你会看到一些例子… 176 | 177 | 其实当你键入 `man` 178 | 179 | 然后后面接某些命令 180 | 181 | 你实际上已经见过这个程序了 182 | 183 | 使用分页程序可以方便的把长长的内容 184 | 185 | 适配到终端的大小 186 | 187 | 然后让你上下滚动来浏览 188 | 189 | 而不是在你的屏幕上一滚而过 190 | 191 | 执行这个命令的时候还是要花一些时间 192 | 193 | 因为服务器要解析一堆日志文件 194 | 195 | 特别是 `grep` 会先缓存输出 [*] 196 | *许多命令并不会直接写到 `stdout`,而是缓存一定量以后再一次性输出 197 | 198 | 所以它还卡在这 199 | 200 | 让我看看不这样的话会不会好一点[*] 201 | *加了 `line-buffering` 选项以后找到一行就输出一行 202 | 203 | 为啥不听我的话... 204 | 205 | 好吧让我搞点小手段 206 | 207 | 你假装没看见 208 | 209 | 也有可能是这个网络差得离谱 210 | 211 | 【132】可能是这两个原因之一 212 | 213 | 还好我有备而来 214 | 215 | 上课前我执行了这个命令 216 | 217 | 它会把前面的这串命令的输出 218 | 219 | 放到我电脑里的这个文件里 # REVIEW 这里检查一下轴 220 | 221 | 我在办公室里跑了一次 222 | 223 | 而前面这串命令所做的事就是 224 | 225 | 把所有包含 `disconnect from` 的 226 | 227 | SSH 日志下载到本地 228 | 229 | 这真是个好东西 230 | 231 | 因为我没有必要每次都传输整个日志 232 | 233 | 我只想要以它开头的行 234 | 235 | 那我们现在来看看 `ssh.log` 236 | 237 | 你可以看到它有这么这么多 238 | 239 | 写着「与无效的用户断开连接」 240 | 241 | 或者「已认证的用户」,等等 242 | 243 | 我们要做的就是在这些日志上整活 244 | 245 | 这也意味着 246 | 247 | 在这之后我们并不需要再走 SSH 的流程 248 | 249 | 我们可以直接 `cat` 这个文件 250 | 251 | 然后在它上面进行操作 252 | 253 | 此外让我来展示一下这个分页器 254 | 255 | 如果我 `cat ssh.log` 256 | 257 | 然后把管道接到 `less` 上 258 | 259 | 它就会给我一个分页器 260 | 261 | 我就可以上下滚动了 262 | 263 | 把字体调小一点点? 264 | 265 | 这样我可以滚动浏览这个文件了 266 | 267 | 那么我还可以用些 268 | 269 | 类似 Vim 的按键操作来浏览 270 | 271 | `Ctrl+u` 向上翻,`Ctrl+d` 向下翻 272 | 273 | 以及按 `q` 退出 274 | 275 | 这仍然有很多内容 276 | 277 | 里面还是有很多我不感兴趣的垃圾信息 278 | 279 | 我只想看看这些用户名是些啥 280 | 281 | 那么我们就要来用 282 | 283 | 一个叫做 `sed` 的工具了 284 | 285 | 流编辑器 `sed` 是一个更早期的 286 | 287 | 一个叫做 `ed` 的东西的改版 288 | 289 | 这东西非常之怪,你们肯定不想用 290 | 291 | 诶你有啥问题 292 | 293 | _抱歉我可能漏听了_ 294 | 295 | _但是 `tsp` 是个啥 [*]_ 296 | *可以看到屏幕上 **t**he**s**quare**p**lanet.com # REVIEW 留着这个标,压制的时候要回看这里 297 | 298 | 哦 `tsp` 是我的远程计算机的名字 299 | 300 | 所以 `sed` 是一个“流”编辑器 301 | 302 | 可以让你修改流(stream)中的内容 303 | 304 | 你可以认为这个命令大概是做文本替换 305 | 306 | 但实际上 `sed` 是一个在输入流上操作的 307 | 308 | 完整的编程语言 [*] 309 | *这里的说法不太常见,可以类比前面讲解 Vim 时所说 310 | 311 | 那么 `sed` 的一个最常用操作就是 312 | 313 | 在输入流之上执行替换表达式 314 | 315 | 那么这东西长什么样呢 316 | 317 | 让我写给你看看 318 | 319 | 好 现在我要把管道接到 `sed` 上 320 | 321 | 然后我告诉它我想把 322 | 323 | 所有 `Disconnected from` 前面的东西 324 | 325 | 全部丢掉 326 | 327 | 【193】这可能有些奇怪 328 | 329 | 但是你会观察到 330 | 331 | 这些 SSH 里的日期、域名、进程 ID 332 | 333 | 我并不关心,干脆统统把它删掉 334 | 335 | `Disconnected from` 这几个字每条日志都有 336 | 337 | 也可以删掉 338 | 339 | 那我就要写一个 `sed` 表达式 340 | 341 | 而此处我写的是一个 `s/` 表达式 342 | 343 | 也就是替换表达式(**s**ubstitute)# REVIEW 344 | 345 | 这个表达式接受两个以斜线分隔的参数 346 | 347 | 第一个参数是要找的字符串 348 | 349 | 而第二个是要换成的字符串 350 | 351 | 这个参数现在是置空的 352 | 353 | 这里的意思就是,按这个字符串模式搜索 354 | 355 | 然后把它换成空的 356 | 357 | 最后我把它接到 `less` 上 358 | 359 | 看到了吗?它把这些行的开头剪掉了 360 | 361 | 【214】用起来真的爽 362 | -------------------------------------------------------------------------------- /ch1/[396-594]CHN.txt: -------------------------------------------------------------------------------- 1 | 【396】 有些时候调试起来简直要命 2 | 3 | 一般来讲,调试的时候直觉会时不时地失效 4 | 5 | 就像我们之前看到的 `foo` 命令不存在 6 | 7 | 因此我们在讲义里有一个很高效的工具 8 | 9 | 这个工具叫做 shellcheck 10 | 11 | 链接已经放在讲义里了 12 | 13 | 它能给出 warning 和语法错误 14 | 15 | 【406】 还能指出哪些地方你没正确引用 16 | 17 | 或者是哪些地方你的空格打错了 18 | 19 | 举个很简单的例子 20 | 21 | `mcd.sh` 这个文件,我们得到了一些错误提示 22 | 23 | 这些提示说:「嗨!我们惊奇地发现漏掉了一些东西」 24 | 25 | 这可能导致 `mcd.sh` 在别的系统无法解译成功 26 | 27 | 并且, `cd` 后面有一个指令 28 | 29 | 而 `cd` 可能不会被正确执行 30 | 31 | 【416】 这里你可能想用 `cd ... || exit` 32 | 33 | 之类的东西来代替它 34 | 35 | 回到这行命令 36 | 37 | 如果 `cd` 命令没有正确结束 38 | 39 | 你就不能进入那个文件夹 40 | 41 | 因为要么你没有权限,要么文件夹不存在 42 | 43 | 之后程序会给你一个非零的错误码 44 | 45 | 然后你就会执行 `exit` 命令,停止脚本的运行 46 | 47 | 【426】 而不是在一个不存在的路径继续执行 48 | 49 | 实际上我还没测试 50 | 51 | 但是我想我们可以试一下 `example.sh` 52 | 53 | 这里它告诉我们 54 | 55 | 应该用另外一种方法检查错误码 56 | 57 | 原来写的大概不能很好地达到目的 58 | 59 | 最后一点,我想说的是 60 | 61 | 当你编写这些 bash 脚本或者函数的时候 62 | 63 | 【436】 写你要运行的 bash 脚本 64 | 65 | 和写要载入 Shell 的东西 66 | 67 | 这两者是有区别的 68 | 69 | 我们将会在命令行环境那一讲里了解这些差别 70 | 71 | 同时那一讲会用到 `bashrc` 和 `sshrc` 这两种工具 72 | 73 | 但是,总的来说 74 | 75 | 如果你做了一些改动,比如你的路径 76 | 77 | 比方说你 cd 到了一个 bash 脚本 78 | 79 | 并且你直接运行它 80 | 81 | 【446】 它就不会 `cd` 到 Shell 当前的路径 82 | 83 | 但是如果你直接通过 Shell 加载 bash 代码 84 | 85 | 比如你的函数,然后你运行这些函数 86 | 87 | 这个操作就有相反的副作用 88 | 89 | 在 Shell 中定义变量也是一样 90 | 91 | 现在我会讲一些和 Shell 搭配干活不累的工具 92 | 93 | 第一个昨天已经着重讲过了 94 | 95 | 【456】 怎么去知道 flag 和 command(命令)具体代表什么 96 | 97 | 就像我现在知道 `ls -l` 98 | 99 | 会用列表的形式列出文件 100 | 101 | 或者我运行 `mv -i` 它会给我提示 102 | 103 | 你现在能用的就是 man 命令 104 | 105 | man 命令会给出很多关于命令的信息 106 | 107 | 比如说在这解释了 `-i` 的作用 108 | 109 | 这些就是你能做的全部操作 110 | 111 | 【466】 不仅是系统内封装的简单命令 112 | 113 | 对于一些从网上安装的工具也很方便 114 | 115 | 例如,如果安装完一些工具 116 | 117 | 那么 man 要用的文档也安装好了 118 | 119 | 比如我们要运行这个叫 ripgrep 的工具 120 | 121 | 它可以用 `rg` 调用 122 | 123 | 系统里并没有自带这个工具 124 | 125 | 但是它安装了自己的 man 文档 126 | 127 | 并且我可以查看 128 | 129 | 【476】 对有些命令来说, man 命令直截了当 130 | 131 | 但有时,理解 man 调出来的文档也挺头疼 132 | 133 | 因为它涵盖了这个工具所有的文档和描述 134 | 135 | 有的时候会有样例,但有的时候没有 136 | 137 | 比如我经常用的一些优秀工具 138 | 139 | 像 convert 和 ffmpeg 140 | 141 | 虽然他们处理图像或视频很优秀 142 | 143 | 【486】 但是他们的 man 文档都是庞然大物 144 | 145 | 然后有个好东西叫 `tldr`,你可以装一下 146 | 147 | 然后就会获得关于你如何调用命令的 148 | 149 | 一些深入浅出的命令示例 150 | 151 | 你也可以上网搜一下 152 | 153 | 但这样你就免得去打开浏览器 154 | 155 | 然后找一堆例子,再返回来 156 | 157 | `tldr` 是社区贡献的,确实好用 158 | 159 | 比如用它查 `ffmpeg` 160 | 161 | 就有很多经典的例子,格式易于阅读 162 | 163 | (但我讲课调了特大号字体,打乱了格式所以不明显) 164 | 165 | 甚至如 `tar` 这种简单的命令 166 | 167 | 都有好多 option 要去组合运用 168 | 169 | 比如这里你可以把两三个 flag 结合 170 | 171 | 但结合出的效果可能违反直觉 172 | 173 | 这就是你……要找到更多这样的工具 174 | 175 | 关于查找的主题,我们再来试一下 176 | 177 | 怎么去查找文件 178 | 179 | 你永远可以用 `ls` 180 | 181 | 比如你可以 `ls project1` 182 | 183 | 然后一路 `ls` 下去…… 184 | 185 | 但假设,我们已知要找名为 `src` 的文件夹 186 | 187 | 做这件事有更好的命令 188 | 189 | 它就是 `find` 190 | 191 | `find` 大概是每个 UNIX 系统都有的工具 192 | 193 | 这个 `find`,我们给他一个…… 194 | 195 | 这里意为,在当前文件夹调用 `find` 196 | 197 | 记住 `.` 代表当前文件夹 198 | 199 | 然后我们找名为 `src` 200 | 201 | 而且类型是个目录的东西 202 | 203 | 键入这些,它就可以在当前目录递归 204 | 205 | 查看所有符合规则的文件 206 | 207 | 或者文件夹,在这个例子里 208 | 209 | `find` 也有很多有用的 flag 210 | 211 | 比如你甚至可以查询指定格式的文件路径 212 | 213 | 这里(`**`)是指要有几层文件夹 214 | 215 | 我们并不关心具体是多少个[*] 216 | *译者注:这里 `**` 可以匹配零或多个目录名 217 | 218 | 然后我们想找所有 Python 脚本 219 | 220 | 也即所有扩展名是 `.py` 的文件 221 | 222 | 然后要求它们在一个 `test` 文件夹内 223 | 224 | 然后我们也在确保 225 | 226 | 虽然确实有点多余,但是 227 | 228 | 我们也检查它是否为 `F` 类型 229 | 230 | `F` 是代表文件 231 | 232 | 这样就找到了符合的文件 233 | 234 | 也可以针对非路径和非文件名的查找 235 | 236 | 运用不同的 flag 237 | 238 | 比如可以查找被修改过的文件 239 | 240 | 这里 `-mtime` 代表修改时间 241 | 242 | 在最近一天被修改过的东西 243 | 244 | 啊,基本就是这个文件夹的所有东西 245 | 246 | 打印出了我们刚创建的文件 247 | 248 | 和先前就有的文件 249 | 250 | 你甚至可以用其他条件 251 | 252 | 比如大小,所有者,权限,等等 253 | 254 | 更强大的是,`find` 不仅查找东西 255 | 256 | 找到之后还能做别的 257 | 258 | 我们可以查找所有扩展名是 `.tmp` 的文件 259 | 260 | 是代表临时文件的扩展名 261 | 262 | 然后要求 `find` 对于所有这些文件 263 | 264 | 执行 `rm` 命令 265 | 266 | 这会对所有这些文件调用 `rm` 267 | 268 | 我们先不带 `rm` 执行一下 269 | 270 | 再带着它执行一下 271 | 272 | 再次根据命令行的设计哲学 273 | 274 | 看起来无事发生 275 | 276 | 但我们有 `0` 错误代码,就是有事发生 277 | 278 | 那就是所有命令执行成功,一切顺利 279 | 280 | 然后现在再找下这些文件 281 | 282 | 就找不到了 283 | 284 | 总体来说, Shell 的另一个好处 285 | 286 | 就是即便有了这些工具 287 | 288 | 人们也在创造新的方式 289 | 290 | 用别的方法开发这些工具 291 | 292 | 了解一下挺不错的[*] 293 | *这课程塞得好满…… 294 | 295 | 比如你只想找以 `tmp` 结尾的东西 296 | 297 | 做这种挺另类的事情 298 | 299 | 你看这命令其实挺长的 300 | 301 | 有一个工具叫 `fd` 302 | 303 | 举个栗子,这命令更短 304 | 305 | 而且默认使用正则表达式[*] 306 | *英文为 regex(regular expression),是一种匹配字符串的模式 307 | 308 | 还会忽略你的 gitfile[*] 309 | *版本控制工具 Git 相关文件。后续课程有涉及 310 | 311 | 你不会想搜到那堆东西的 312 | 313 | 还有彩色代码和更好的 Unicode 支持…… 314 | 315 | 了解这些工具挺好的 316 | 317 | 但是重申,核心思想是 318 | 319 | 你要是知道这些东西存在 320 | 321 | 就能省去做重复性、无意义工作的时间 322 | 323 | 另一个要记住的命令是 324 | 325 | 呃,就比如 `find` 326 | 327 | 部分同学可能会好奇…… 328 | 329 | `find` 可能就是遍历目录结构 330 | 331 | 去找匹配的事物 332 | 333 | 那我要是每天高强度 `find` 呢? 334 | 335 | 如果能给整个数据库出来 336 | 337 | 然后建个索引,不断维护它 338 | 339 | 岂不美哉 340 | 341 | 呃,其实大部分 UNIX 系统已经有了 342 | 343 | 可以用 `locate` 命令 344 | 345 | 这个 `locate` 会…… 346 | 347 | 它会查找文件系统中具有指定子串的路径 348 | 349 | 我不知道这行不行…… 350 | 351 | 哦看来可以 352 | 353 | 我来试试找 `missing-semester` 354 | 355 | 得等一会,就能找到这些东西 356 | 357 | 都是在我文件系统里面 358 | 359 | 因为事先建立了索引,它就会快得多 -------------------------------------------------------------------------------- /ch3/[429-641]CHN.txt: -------------------------------------------------------------------------------- 1 | 【429】但是这不太有用 2 | 3 | 相反我们真正想要做的是 4 | 5 | 当我们在这里匹配用户名时 6 | 7 | 我们更想要记录下来用户名是什么 8 | 9 | 因为这是我们想要输出的内容 10 | 11 | 【435】在正则表达式中做这件事的方法 12 | 13 | 是用一个叫「捕获组」的玩意(Capture Groups) 14 | 15 | 「捕获组」用来表示 16 | 17 | 我想要记住这个值 18 | 19 | 并在之后使用 20 | 21 | 在正则表达式中 22 | 23 | 【440】任何圆括号括起来的表达式 24 | 25 | 就是这样一个捕获组 26 | 27 | 所以我们已经在这里用了一个了 28 | 29 | 这是第一组,现在我们在这儿再建第二组 30 | 31 | 【445】注意这些括号并不影响匹配 32 | 33 | 对吧?因为它们仅仅表达了 34 | 35 | 将这个表达式作为一个单元 36 | 37 | 但它的后面没有任何修饰符 38 | 39 | 所以还是只匹配一次 40 | 41 | 【450】然后捕获组有用的原因是 42 | 43 | 你可以在替换式(replacement)中使用它 44 | 45 | 这样,在这里的替换式处 46 | 47 | 我可以键入 `\2` [*] 48 | * 在部分编辑器中使用 `$2` 49 | 50 | 【455】这是你指代捕获组编号的方法 51 | 52 | 这里我写的意思是 53 | 54 | 先匹配整行 55 | 56 | 之后在替换式处放入你在第二个捕获组匹配到的值 57 | 58 | 【460】好的,记住这是第一个捕获组 59 | 60 | 这是第二个 61 | 62 | 现在它给了我所有的用户名 63 | 64 | 现在来看看写出来的表达式 65 | 66 | 它还挺复杂的,对吧 67 | 68 | 当我们一步步地完善它之后 69 | 70 | 【465】你现在可能会明白 71 | 72 | 为什么它必须是它现在这个样子 73 | 74 | 但要搞懂代码执行起来如何 75 | 76 | 其实并不是很直观的事情 77 | 78 | 这就是正则表达式调试器的用武之地了 79 | 80 | 【470】我们这里有一个 81 | 82 | 网上有很多,这个我已经提前填好了 83 | 84 | 我们刚刚用过的表达式 85 | 86 | 注意到,它上面显示了所有的匹配结果 87 | 88 | 【475】这个窗口配这个字体,字太小了 89 | 90 | 但是如果我……这里,这个注释说 91 | 92 | `.*` 匹配所有字符 0 次到任意次 93 | 94 | 后面是 `Disconnected from` 这几个词 95 | 96 | 【480】后面是一个捕获组 97 | 98 | 下面还有各种别的 99 | 100 | 这是一个功能 101 | 102 | 它还允许你指定测试字符串 103 | 104 | 之后对给定的每个测试串跑正则表达式 105 | 106 | 【485】并且像这样给不同的捕获组着色 107 | 108 | 这里,我们将用户作为一个捕获组,对吧 109 | 110 | 它显示整个串都匹配到了 111 | 112 | 整个串是蓝色的所以匹配完成 113 | 114 | 【490】绿色部分是第一个捕获组 115 | 116 | 红色是第二个捕获组 117 | 118 | 这是第三个 119 | 120 | 因为 preauth 也被括号括起来了 121 | 122 | 这会是一个调试正则表达式的好方法 123 | 124 | 【495】例如如果我放 Disconnected from…… 125 | 126 | 我们这里新添一行 127 | 128 | 如果我把 Disconnected from 作为用户名 129 | 130 | 好吧现在这行已经有这个用户名了 131 | 132 | 【500】我这是未卜先知 133 | 134 | 你会注意到利用这种匹配模式 135 | 136 | 这不再是一个问题 137 | 138 | 因为它正确地匹配了用户名 139 | 140 | 如果我们把这整行或这行变成用户名会发生什么 141 | 142 | 【505】如你所见 143 | 144 | 真让人摸不着头脑 145 | 146 | 将正则表达式调对会很痛苦 147 | 148 | 它现在尝试匹配…… 149 | 150 | 它匹配到的第一个组 151 | 152 | 也就是用户名,似乎是第一个 invalid 153 | 154 | 【510】啊,第二个 invalid 155 | 156 | 因为它是贪心的 157 | 158 | 通过在这里加一个 `?` 159 | 160 | 我可以将它变为非贪心的 161 | 162 | 所以如果你在 `+` 或者 `*` 后加 `?` 163 | 164 | 【515】它会变成非贪心匹配 165 | 166 | 也就是不会尽可能地向后匹配 167 | 168 | 这样你可以看到,这个串被正确地解析了 169 | 170 | 因为 `.*` 匹配会在第一个 Disconnected from 处停止 [*] 171 | * 不加 `?` 则会从行首匹配到整行的最后一个 Disconnected from 172 | 173 | 【520】也就是 SSH 指令固定输出的那个 174 | 175 | 是实际出现在我们记录中的那一个 176 | 177 | 讲到现在,你大概也发现了 178 | 179 | 【525】正则表达式会非常复杂 180 | 181 | 你也很可能会在你写的匹配模式中 182 | 183 | 用到各种各样迷惑的修饰符 184 | 185 | 真正学会它的唯一方式 186 | 187 | 是从简单的表达式开始 188 | 189 | 【530】之后堆砌起来 190 | 191 | 直到它匹配到你想要的 192 | 193 | 通常你仅仅是在做一些一次性工作 194 | 195 | 比如刚才我要提取用户名 196 | 197 | 你不需要去考虑那么多特殊情况,对吧 198 | 199 | 【535】你不需要考虑某人的 SSH 的用户名 200 | 201 | 完美地匹配了登录记录的格式 202 | 203 | 这也不算什么大事 204 | 205 | 因为你只是找用户名而已 206 | 207 | 【540】正则表达式确实很强大 208 | 209 | 处理的内容很重要的时候,记得万分小心 210 | 211 | 你要提问吗? 212 | 213 | 【545】总之正则表达式默认只逐行匹配 214 | 215 | 它不会跨行匹配 216 | 217 | 所以 sed 运行的方式是 218 | 219 | 它逐行处理 220 | 221 | 所以 sed 会对每一行匹配这个表达式 222 | 223 | 【550】好,正则表达式或者模式相关问题到此为止 224 | 225 | 它是一个复杂的模式 226 | 227 | 所以如果感到迷惑,别担心 228 | 229 | 课上完了,回去在调试器中看一看 230 | 231 | 【555】所以记住 232 | 233 | 我们在这里假设 234 | 235 | 用户只能控制他们的用户名,对吗? 236 | 237 | 所以他们能做的最坏的事 238 | 239 | 就是把这种整条记录设为用户名 240 | 241 | 【560】我们看会发生什么 242 | 243 | 好的,这是运行结果 244 | 245 | 它的原因是,`?` 意味着 246 | 247 | 我们一遇到 Disconnected (from) 这个词 248 | 249 | 就立刻匹配后面的模式,对吧 250 | 251 | 【565】第一个 Disconnected 是 SSH 自己输出的 252 | 253 | 一定在用户可编辑的内容之前 254 | 255 | 所以在这个特例下 256 | 257 | 即使这样也不会干扰模式串 258 | 259 | 你要提问吗? 260 | 261 | _(学生有关模式串的数据安全性的问题)_ 262 | 263 | 【570】啊,如果你在写一个…… 264 | 265 | 这种比较怪的的匹配模式…… 266 | 267 | 总的来说,你在做数据整理的时候 268 | 269 | 一般它不会涉及(信息)安全 270 | 271 | 但你很可能会得到很怪异的数据 272 | 273 | 【575】所以如果你在做一些像 274 | 275 | 绘制图表之类的事 276 | 277 | 你可能会丢掉重要的数据点 278 | 279 | 你可能解析出错误的数值 280 | 281 | 之后你的表突然出现了原始数据中没有的数据点 282 | 283 | 【580】所以重要的是 284 | 285 | 如果你发现你在写一个复杂的正则 286 | 287 | 多检查几下 288 | 289 | 它匹配出来的是不是你想要的 290 | 291 | 【585】即使它与信息安全无关 292 | 293 | 和你想的一样 294 | 295 | 这些模式串可能会非常复杂 296 | 297 | 例如这里有一个讨论 298 | 299 | 关于如何用正则表达式匹配一个 email 地址 300 | 301 | 【590】你可能会想到像这样的 302 | 303 | 这是一个非常直观的表达式 304 | 305 | 只是字母,数字,一些字符,后面一个 `+` 306 | 307 | 因为在 Gmail 里,email 地址里可以有 `+` [*] 308 | * 这里指括号内的加号 309 | 310 | 【595】这里的 `+` 只表示任何这些字符至少出现一个 311 | 312 | 因为你不会有一个 @ 前为空的 email 地址 313 | 314 | 【600】后面域名的规则也差不多,对吧 315 | 316 | 顶级域需要至少两个字符并且不能包括数字 317 | 318 | 你可以是 .com 但是不能是 .7 319 | 320 | 【605】事实上这并不完全正确 321 | 322 | 这里有一堆有效的 email 地址不会被它匹配 323 | 324 | 还有一堆无效的 email 地址会被它匹配 325 | 326 | 【610】所以有很多很多建议 327 | 328 | 还有热心网友写了的完整的测试套件 329 | 330 | 尝试判断哪一个正则表达式是最好的 331 | 332 | 这是一个专门给 URL 的 333 | 334 | 【615】这是类似的给 email 的 335 | 336 | 他们发现最好的就是这个 337 | 338 | 我不建议你去试着理解这个模式串 [*] 339 | * 这个东西大概是来自深渊某处,世界的黑暗面 340 | 341 | 但这个很明显会几乎完美的匹配到 342 | 343 | 像符合互联网标准的 email 地址 344 | 345 | 【620】就是所说的有效 email 地址 346 | 347 | 它还包含 Unicode 里奇奇怪怪的编码 348 | 349 | 这只是想说明正则表达式可以非常长 350 | 351 | 【625】如果最后你写出像这样的表达式 352 | 353 | 很可能会有更好的方式去做 354 | 355 | 比如,如果你自己在试着解析 HTML 356 | 357 | 或者解析 JSON 格式,对于这种格式来说 358 | 359 | 【630】去用其它工具大概会比较好 360 | 361 | 我们也有这样的练习 362 | 363 | 不是用正则表达式,提醒你 364 | 365 | 这里有各种各样的建议 366 | 367 | 还非常非常深入地展示了它的运行过程 368 | 369 | 【635】如果你想查阅,它在课程笔记里 370 | 371 | 好的,我们有了这些用户名 372 | 373 | 让我们回到数据整理 374 | 375 | 像这列用户名 376 | 377 | 【640】它仍然对我很不友好,对吗? 378 | 379 | 让我们看看总共有几行 -------------------------------------------------------------------------------- /ch2/[1-252]CHN.txt: -------------------------------------------------------------------------------- 1 | 【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 | 【15】文本编辑器 28 | 29 | 和其他的编辑器 30 | 31 | 例如用来写文章的编辑器 32 | 33 | 有些小小的不一样 34 | 35 | 这是因为写程序和写文章不同 36 | 37 | 当你写程序的时候,你将会花大量时间 38 | 39 | 来阅读你写的东西 40 | 41 | 来移动你的光标 42 | 43 | 来对他们做点小小的修改 44 | 45 | 而不是像写文章的时候 46 | 47 | 从上到下一气呵成 48 | 49 | 【27】因此对于不同用途 50 | 51 | 我们会使用不同的编辑器 52 | 53 | 这是很合理的 54 | 55 | 例如我们用 Word 来写报告 56 | 57 | 用 Vim、Emacs、VS Code 58 | 59 | 以及 Sublime 来编写代码 60 | 61 | 要学习并熟练使用一个文本编辑器 62 | 63 | 第一步就是跟着教程学习 64 | 65 | 这也是来听这节课 66 | 67 | 以及完成之后的课后练习 68 | 69 | 能教给你的东西 70 | 71 | 此外,在这节课结束之后 72 | 73 | 请用文本编辑器来进行你的所有编辑工作 74 | 75 | 当你学习一个复杂工具的时候尤其如此 76 | 77 | 【41】那么我们今天学的就是 78 | 79 | 超强编辑器 Vim 80 | 81 | 这个工具在程序员间使用非常广泛 82 | 83 | 当你在学习这样一个复杂的工具的时候 84 | 85 | 在刚开始学习使用它时 86 | 87 | 经常遇到的情况是 88 | 89 | 你的编程效率反而下降了一点 90 | 91 | 但是不要放弃 92 | 93 | 我敢说在这个新的编辑器上 94 | 95 | 花上 20 小时左右 96 | 97 | 就能恢复到你之前的工作效率 98 | 99 | 然后它的益处就慢慢体现出来了 100 | 101 | 【54】学得越久,效率越高 102 | 103 | 这些复杂工具不难入门 104 | 105 | 但精通他们将花费一生的时间 106 | 107 | 因此在整个使用的过程中 108 | 109 | 如果你觉得:呃这样做效率好低啊 110 | 111 | 记得去查一下有没有更好的操作方式 112 | 113 | 答案几乎总是 “是的” 114 | 115 | 因为编辑器是程序员写给程序员用的[*] 116 | *正所谓出口转内销 117 | 118 | 写这些编辑器的程序员 119 | 120 | 肯定碰到了同样的问题,并解决了它 121 | 122 | 所以你就不用去被它困扰了 123 | 124 | 所以你在学习的时候记得去查 125 | 126 | 你可以用搜索引擎,也可以给我们发邮件 127 | 128 | 以及~~(不存在的)~~来办公室 129 | 130 | 我们将很愿意帮助你找出高效的方案 131 | 132 | 【77】关于学哪个编辑器这个问题 133 | 134 | 在前几次的开课中实际上我们避免了 135 | 136 | 去教某一个特定的编辑器 137 | 138 | 因为我们不想把我们的观点强加于你们身上 139 | 140 | 但是我们着实觉得应该 141 | 142 | 教你们怎样使用某一个工具,并精通它 143 | 144 | 另外许多人对编辑器有很强的主见 145 | 146 | 课程笔记里有更多关于此方面的链接 147 | 148 | 你可以去看看各个编辑器这些年来的热门程度 149 | 150 | 我相信你们都听过 StackOverflow[*] 151 | *一个程序设计领域的问答网站,第一讲提到过 152 | 153 | 这个网站每年都会向开发者发放调查问卷 154 | 155 | 其中一个问题便是你用哪个编辑器 156 | 157 | 从结果看来 158 | 159 | 最热门的图形界面编辑器是 VS Code 160 | 161 | 而最热门的基于命令行的编辑器是 Vim 162 | 163 | 基于此以及如下几点原因 164 | 165 | 今天我们会教你们如何使用 Vim 166 | 167 | 第一是这门课的所有教授 168 | 169 | 我、John 和 Jose 都主要使用 Vim 170 | 171 | 我们用这个编辑器用了很久了 172 | 173 | 而且用的很开心 174 | 175 | 此外我们觉得这背后有许多很有趣的东西 176 | 177 | 所以就算最终你并没有打算长期使用它 178 | 179 | 我仍然认为学习这些背后的思想很有价值 180 | 181 | 另外许多的工具也觉得 Vim 哲学很棒 182 | 183 | 从而使它们提供了 Vim 模式 184 | 185 | 例如当下最热门的 VS Code 186 | 187 | 支持 Vim 键位绑定 188 | 189 | 而且那个插件已经被下载了数百万次 190 | 191 | 【117】所以这门课程里面的很多工具 192 | 193 | 你会发现它们都支持 Vim 模式 194 | 195 | 包括 Python 的 REPL[*] 196 | *REPL 指交互式的编程环境,也能指代命令行的模式 197 | 198 | 包括 Jupyter Notebook 199 | 200 | 甚至你的浏览器 201 | 202 | 因此我们今天就会教给你这个好东西 203 | 204 | 但是一个这么强大的工具,这一节课讲不完 205 | 206 | 我们的目标是教给你隐含在其中的核心思想 207 | 208 | 以及一些基本的操作例如打开关闭文件 209 | 210 | 移动你的光标 211 | 212 | 作出一些修改等等 213 | 214 | 我们不期望你听一次就能记下来每一个细节 215 | 216 | 因为这节课会上得挺快的 217 | 218 | 但是在课程笔记和练习里会涵盖他们 219 | 220 | 因此我强烈建议你把所有练习过一遍 221 | 222 | 至少是基础练习 223 | 224 | 有什么疑惑吗? 225 | 226 | 好 227 | 228 | 那隐含在其背后的第一个精妙思想就是 229 | 230 | 【145】Vim 是一个基于模式(Modal)的编辑器 231 | 232 | 这意味着什么呢 233 | 234 | Modal 这个词源于 Mode 235 | 236 | 这就意味着 Vim 有多个模式 237 | 238 | 这个思想就源于,当你在写程序的时候 239 | 240 | 你会经常做不同的事 241 | 242 | 有时候你在读代码 243 | 244 | 有时候你在给代码做小改动 245 | 246 | 在某处寻找、改正一个小地方 247 | 248 | 有时候你在写一长串的代码 249 | 250 | 例如在从零开始写一个函数 251 | 252 | 因此为了这些不同的工作就有了不同的模式 253 | 254 | 【161】那么我现在想先在黑板上写些东西 255 | 256 | 这样方便我后面讲课 257 | 258 | 当你开始使用 Vim 时它会在 Normal 模式下启动 259 | 260 | 在这个模式下,各种键位组合 261 | 262 | 【167】拥有在这个模式下的特定意义 263 | 264 | 其中就有一些按键组合来切换到其他的模式 265 | 266 | 而切换后这些键位组合的意义也相应改变 267 | 268 | 因此你的大部分时间都会在 Insert 和 Normal 模式下 269 | 270 | 在 Normal 模式下按下 `i` 键就可以进入 Insert 模式 271 | 272 | 而在 Insert 模式下按下 `Esc` 就可以回到 Normal 模式 273 | 274 | 这里我们注意一下我们表示按键的方式 275 | 276 | 这个表示方式将会用在课上、课堂笔记里 277 | 278 | 以及 Vim 给你的提示中 279 | 280 | 当仅仅是代表例如 `i` 键这样一个按键本身时 281 | 282 | 那么就会直接写 `i` 283 | 284 | 但是对于例如 `Ctrl`+`v` 这种按键组合 285 | 286 | 就有可能以这么几种形式来体现 287 | 288 | 第一种是一个“脱字符号”后面跟着控制用字符[*] 289 | *控制用字符指那个 `v`,也就是组合键中的另一个键 290 | 291 | 这是一种写法 292 | 293 | 另一种写法可能是你最熟悉的 294 | 295 | 在课程笔记里我们有时候会写成 `Ctrl-v` 这样 296 | 297 | 而有些地方我们可能会写成 `` 这样 298 | 299 | 好这样介绍一下方便后面讲课 300 | 301 | 【202】然后回来 302 | 303 | Vim 有好几种模式 304 | 305 | 而 Normal 模式是用来移动光标、阅读东西 306 | 307 | 以及在文件间切换的 308 | 309 | 而 Insert 模式是用来输入的 310 | 311 | 因此在这个模式下你的按键大多会直接进入 buffer 312 | 313 | 而在 Normal 模式下则不然 314 | 315 | 它们不会进入 buffer 316 | 317 | 而是用来进行一些浏览和编辑操作 318 | 319 | 另外我们要认识到真实的情况会比这更复杂一点 320 | 321 | 此外还有许多的模式 322 | 323 | 我现在把他们列出来 324 | 325 | 方便我后面的讲课 326 | 327 | Vim 里还有个替换模式 328 | 329 | 不像插入模式会把字符往后移 330 | 331 | 替换模式会直接覆盖掉文本 332 | 333 | 【221】在选择方面也有着许多的模式 334 | 335 | 有一个叫 Visual 模式 336 | 337 | 还有一个 Visual Line 模式 338 | 339 | 以及一个 Visual Block 模式 340 | 341 | 按下大写 `R` 键进入替换模式 342 | 343 | 按下 `v` 键则进入 Visual 模式 344 | 345 | 按 `Shift`+`v` 进入 Visual Line 模式 346 | 347 | 而按下 `Ctrl`+`v` 进入 Visual Block 模式 348 | 349 | 此外还有一个命令行模式 350 | 351 | 按下冒号键就可以进入 352 | 353 | 好这样写下来我们后面讲课就方便些了 354 | 355 | 我们现在可以来试试这些模式 356 | 357 | 【233】首先从图中我们可以注意到 358 | 359 | 从 Normal 模式切换到其他模式 360 | 361 | 我们要按下某个键 362 | 363 | 而切换回这个用得最久的 Normal 模式 364 | 365 | 我们只需按下 `Esc` 键 366 | 367 | 因为用 Vim 时要频繁按下 Esc 键 368 | 369 | 而这个键在键盘的角落里 370 | 371 | 按起来真的很不方便 372 | 373 | 许多程序员会把键盘上某个键映射成 `Esc` 374 | 375 | 通常这个键是 `Caps Lock` 376 | 377 | 它就在键盘的中间一行上 378 | 379 | 课程笔记里也有一些链接,指导你如何映射按键 380 | 381 | 到现在为止,我们花了很多时间 382 | 383 | 来讨论 Vim 背后的第一个设计哲学 384 | 385 | 那就是编辑模式 386 | -------------------------------------------------------------------------------- /ch3/[214-429]CHN.txt: -------------------------------------------------------------------------------- 1 | 【214】但是你可能会疑惑 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 | 【231】这些字符不会直接匹配它们本身 28 | 29 | 而是匹配某一类的字符或者字符串 #REVIEW knb: options 意译成 「字符串」 合适吗? 30 | 31 | 本质上来说它生成了一段程序 32 | 33 | 来在文本中进行查找 34 | 35 | 例如 `.` 代表「匹配一个任意字符」 36 | 37 | 而如果在某一字符后面加上 `*` 38 | 39 | 那它代表匹配零次或多次该字符 40 | 41 | 那么这个 pattern(模式)所描述的就是 42 | 43 | 任意的、零个或多个字符 [*] 44 | * 结合 `.` 与 `*` 的效果 45 | 46 | 然后跟着一个字符串 `Disconnected from` 47 | 48 | 这里就是说,我找到这样的字符串 49 | 50 | 然后把它们换成空的 51 | 52 | 正则表达式有一大把像这样的特殊字符 53 | 54 | 各有各的含义 55 | 56 | 你可以好好运用 57 | 58 | 我们已经讲过了 `*` 59 | 60 | 它匹配零或多个字符 61 | 62 | 还有一个 `+` 63 | 64 | 作用是匹配一次或多次左面的模式 [*] 65 | * `+` 可以跟在一个字母或一串模式后面,详细格式请搜索 66 | 67 | 那么这样的意思就是 68 | 69 | 我想要前面那个 pattern 匹配至少一次 70 | 71 | 此外还有方括号 72 | 73 | 可以让你匹配多种字符中的一种 74 | 75 | 好 我现在搞个字符串 76 | 77 | 比如说 `aba` 78 | 79 | 我想把 `a` 和 `b` 换成空的 80 | 81 | 那么我就让 pattern 去把 82 | 83 | 要么是 `a` 要么是 `b` 的字符 84 | 85 | 换成空的 86 | 87 | 就算我把第一个字符换成了 `b` 88 | 89 | 还是会输出 `ba` 90 | 91 | 那你可能就会想了 92 | 93 | 为啥它只替换一次呢 94 | 95 | 这是因为正则表达式 96 | 97 | 在默认模式下 98 | 99 | 每行只匹配一次、替换一次 100 | 101 | 这是 sed **默认**模式下做的事 102 | 103 | 你可以再加个 `g` 修饰符 104 | 105 | 意思是只要能,就尽量多匹配 106 | 107 | 然后整行就没了 108 | 109 | 因为每个字符都是 `a` 或 `b` 之一 110 | 111 | 如果我再加个 `c` 112 | 113 | 它就移除 `c` 之外的所有东西 114 | 115 | 如果再向字符串内加其它字符 116 | 117 | 也都会保留下来 118 | 119 | 【278】但是 `a` 和 `b` 都会被去掉 120 | 121 | 你还可以给他加点修饰符 122 | 123 | 跑这个命令会发生什么呢 124 | 125 | 它的意思是我想要把零个或多个 126 | 127 | `ab` 这个字符串 128 | 129 | 换成空的 130 | 131 | 那么这就意味着 132 | 133 | 单独的一个 `a` 不会被替换掉 134 | 135 | 单独一个 `b` 也不会被替换掉 136 | 137 | 但是 `ab` 连一起 138 | 139 | 它就会被替换掉了 140 | 141 | `sed` 你好蠢啊 142 | 143 | 这里加上 `-E` 是因为 `sed` 真的很老了 144 | 145 | 它只支持很旧版本的正则表达式 146 | 147 | 一般你要加上 `-E` 开关跑 148 | 149 | 这样他就会用一套支持更多东西的 150 | 151 | 更现代的语法 152 | 153 | 如果它没法使用 `-E` 开关 154 | 155 | 那你就得在括号前面加 `\` 156 | 157 | 来告诉它使用“特殊含义”的括号 158 | 159 | 不然它就只会匹配括号本身 160 | 161 | 那可能不是你想要的 162 | 163 | 【302】注意它把这里的 `ab` 替换掉了 164 | 165 | 把这里的 `ab` 也替换掉了 166 | 167 | 但是把这个 `c` 168 | 169 | 还有末尾的 `a` 留下来了 170 | 171 | 因为它和 pattern 不匹配 172 | 173 | 【307】你可以把 pattern 的任意部分括成一组 174 | 175 | 也有「选择」之类的东西 176 | 177 | 例如你可以让它移除 178 | 179 | 任意匹配 `ab` 或 `bc` 的字符串 180 | 181 | 然后你会注意到这个 `ab` 没了 182 | 183 | 但就算这个 `bc` 和 pattern 相匹配 184 | 185 | 它并没有被删除 186 | 187 | 这是因为 `ab` 已经被删除了 188 | 189 | 这个 `ab` 被删掉了,对吧 190 | 191 | `c` 还留着 192 | 193 | 这里的 `ab` 被删去了 194 | 195 | 因为这个 `c` 依然不匹配,还留着 196 | 197 | 如果我把这个 `a` 删掉 198 | 199 | 这个 `ab` 的 pattern 200 | 201 | 就不会匹配到这个 `b` 202 | 203 | 然后它就会被留下来 204 | 205 | 然后 `bc` 就会匹配到这个 `bc` 206 | 207 | 随后就会被删掉 208 | 209 | 你刚开始接触的时候 210 | 211 | 正则用起来会很麻烦 212 | 213 | 【325】就算当你熟练之后 214 | 215 | 这东西看起来也很吓人 [*] 216 | * 地铁-老人-手机.jpg (因为一般不换行写) 217 | 218 | 这也是为什么 219 | 220 | 人们常常会使用正则调试器的原因 221 | 222 | 过一会儿我们会看到 223 | 224 | 但首先让我们编写一个 pattern 225 | 226 | 能够匹配日志条目…呃…匹配我们在处理的条目 227 | 228 | 让我们先从文件里拿几行出来 229 | 230 | 那就前五行吧 231 | 232 | 看,这几行现在是这样一个形式 233 | 234 | 但是我们要做的是,只留用户名 235 | 236 | 那么我们就会想把它写成这样... 237 | 238 | 等下 让我先给你看一个东西 239 | 240 | 我们先整出来 241 | 242 | 写着 _这样一串_ 的一行 243 | 244 | 那么这是一条登录记录 245 | 246 | 有人打算以 `Disconnected from` 作为用户名登录 247 | 248 | _(学生)少了个 `s`_ 249 | 250 | 少了个 `s` 吗?emmmm... 251 | 252 | _(学生)第一个 `Disconnected`_ 253 | 254 | **`Disconnected`** 多谢 255 | 256 | 那么你会发现这个命令连用户名一起移除了 257 | 258 | 【352】这是因为像 `.*` 259 | 260 | 这种匹配一个**范围**的表达式 261 | 262 | 它们是用贪心策略 263 | 264 | 去尽可能多的匹配 265 | 266 | 因此虽然我们在这里想保留用户名 267 | 268 | 但是这个 pattern 会一路匹配到它第二次 269 | 270 | 也就是最后一次出现 271 | 272 | 所以包括用户名在内 273 | 274 | 在这之前出现的文本都会被删掉 275 | 276 | 那么我们就要想一个 277 | 278 | 机智一点的方法来匹配 279 | 280 | 而不仅仅是使用 `.*` 281 | 282 | 这样如果输入比较诡异 283 | 284 | 可能会输出一些诡异的东西 285 | 286 | 好 让我们来看看怎么匹配这些行 287 | 288 | 首先先跑个 `head` (过滤一下) 289 | 290 | 嗯...让我们从头开始构造这个 pattern 291 | 292 | 显然我们不想 `\` 满地跑 293 | 294 | 因此首先我们整个 `-E` 295 | 296 | 这些行是这样一个形式 297 | 298 | 先是 `from` 299 | 300 | 有些写了 `invalid` 301 | 302 | 有些又没有 是吧 303 | 304 | 那这里的问号就是匹配 0 或 1 次 305 | 306 | 那这样写就是说 307 | 308 | 0 或 1 个 invalid 后面跟个空格 309 | 310 | 然后是 `user`……? 311 | 312 | 啊——多了个空格 可不敢乱多 313 | 314 | 然后后面有个用户名 315 | 316 | 然后后面是... 317 | 318 | 然后后面是个 IP 地址 319 | 320 | 这里可以用区间匹配的那些语法 321 | 322 | 这个的意思就是 匹配 `0` 到 `9` 或者 `.` 323 | 324 | 这是 IP 地址的特征 325 | 326 | 而且我们要匹配多次 327 | 328 | 然后后面是 `port`(端口) 329 | 330 | 所以我们匹配一个固定的字符串 `port` 331 | 332 | 然后再来一次数字 `0` 到 `9`,匹配多次 333 | 334 | 除此之外我们还要做一件事 335 | 336 | 我们要给表达式打锚点 337 | 338 | 正则表达式里有两个特殊字符 339 | 340 | `^` 匹配行开头 341 | 342 | 而 `$` 匹配行结尾 343 | 344 | 那我们这样写 345 | 346 | 就代表着这个正则表达式匹配了一整行 347 | 348 | 为什么要这样写呢 349 | 350 | 假设有个人把它的用户名 351 | 352 | 设成了这一整条日志文本 353 | 354 | 那当你匹配的时候 355 | 356 | 就会匹配到用户名 357 | 358 | 【406】坏耶—— 359 | 360 | 一般来说锚点能加尽量加 361 | 362 | 避免这种偶然事件发生 363 | 364 | 现在看看跑这个命令有什么效果 365 | 366 | 这个命令删掉了好多行 367 | 368 | 但还是留下来了一些 369 | 370 | 例如这个 最后有个 `[preauth]` 371 | 372 | 那我们把它炖了吧 373 | 374 | 空格,`preauth`,方括号 375 | 376 | 方括号是特殊字符要转义 377 | 378 | 好耶 379 | 380 | 再多来几行呢 381 | 382 | 啊 还是有奇怪的东西 383 | 384 | 这些行非空 385 | 386 | 那就意味着 pattern 和它不匹配 387 | 388 | 拿这个来说 389 | 390 | 它写的是 `authenticating` 而不是 `invalid` 391 | 392 | 好吧 393 | 394 | 改成 `invalid` 或者 `authenticating` 之一 395 | 396 | 在用户名之前匹配零次或一次 397 | 398 | 现在如何 399 | 400 | 看上去挺稳的 401 | 402 | 但是这个输出没多少用啊 403 | 404 | 它只是成功地 405 | 406 | 【428】把日志的每一行都清空了 407 | 408 | 这不太有用啊 -------------------------------------------------------------------------------- /ch2/[756-966]CHN.txt: -------------------------------------------------------------------------------- 1 | 【756】还有一个命令是 `r` 2 | 3 | 如果我现在移到某个特定的字符上 4 | 5 | 然后按下 `r` 6 | 7 | 它会接受另一个字符作为参数 8 | 9 | 然后用所给的字符替换掉光标处的字符 10 | 11 | 呃,我再讲几个编辑命令 12 | 13 | 我认为我刚刚提到过一个 14 | 15 | 就是你可以撤销你在 Vim 里做的编辑 16 | 17 | 在 Normal 模式下按 `u` 就可以做到 18 | 19 | `u` 代表撤销(Undo),还算好记 20 | 21 | 所以我按很多次 `u`,就会撤销我所有的改动 22 | 23 | 然后,撤销的反面,当然是——重做(Redo) 24 | 25 | Vim 中,这个命令的组合键是 `Ctrl`+`r` 26 | 27 | 【772】好,那么我要说的另一个编辑命令 28 | 29 | 就是复制粘贴,因为…… 30 | 31 | 哦,有问题吗? 32 | 33 | *就是,Undo 是这样吗……比如它是不是撤销掉* 34 | 35 | *上次退出 Insert 模式前,插入的所有内容?* 36 | 37 | *还是只是最后一个字符呢?* 38 | 39 | 啊——问得好! 40 | 41 | 这位同学问的是:Undo 会撤销 42 | 43 | 你进入 Insert 模式以后的所有输入吗? 44 | 45 | 或者只是最后一个字符? 46 | 47 | 呃,答案实际上更复杂一点儿 48 | 49 | 【781】「撤销」会,呃 50 | 51 | 撤销你最新的改动 52 | 53 | 换言之,如果你进入 Insert 模式 54 | 55 | 然后键入一些内容,再退回 Normal 模式 56 | 57 | 然后按下 `u` 撤销 58 | 59 | 它会撤销你在 Insert 模式下的所有输入 60 | 61 | 但如果你执行了一些其他的编辑命令 62 | 63 | 比如按下 `x` 删除一个字符 64 | 65 | 之后按 `u` 撤销的话 66 | 67 | 只会撤销上一个编辑命令的改动 68 | 69 | 这个问题我解释清楚了吗? 70 | 71 | *嗯* 好,还有别的问题吗 72 | 73 | 行。那我就讲复制粘贴了,因为这很常用 74 | 75 | `y` 命令代表复制,`p` 命令代表粘贴[*] 76 | *Vim 内的复制粘贴默认不使用操作系统的剪切板 77 | 78 | `y` 代表复制,因为……「提取,拉拽」(yank) 79 | 80 | 这个词就是他们—— 81 | 82 | Vim 用这个术语指代复制 83 | 84 | 【800】而且 `y` 也接受一个操作范围作为参数 85 | 86 | 所以比如我键入 `yy`,就会复制当前行 87 | 88 | 此时若再按下 `p` 粘贴 89 | 90 | 注意,这里就有一模一样的两行了 91 | 92 | 因为我刚往下面粘贴了一行 93 | 94 | 按一下 `u` 撤销它 95 | 96 | 但如果我执行像 `yw` 这样的命令 97 | 98 | 它会复制一个单词 99 | 100 | 然后按一下 `p` 101 | 102 | 【809】就会在光标位置上粘贴这个词 103 | 104 | 在谈到复制和粘贴的时候,能选择一块东西并且复制它 105 | 106 | 会是一件非常有用的事情,对吧? 107 | 108 | 比如,无论之前你用哪个编辑器 109 | 110 | 你大概都是这样运用复制粘贴的 111 | 112 | 所以此时我们要进入 Visual 模式 113 | 114 | 它们是另外一组彼此相关的模式 115 | 116 | 都可以从 Normal 模式进入 117 | 118 | 用它们来选择大段文字 119 | 120 | 【820】其中一个模式是普通的 Visual 模式 121 | 122 | 你可以(从 Normal 模式)按下 `v` 进入 123 | 124 | 一旦进入这个模式,便可以使用大多数 125 | 126 | Normal 模式下的指令移动你的光标 127 | 128 | 它就会选择移动前后之间的内容 129 | 130 | 所以我可以用,你看,`hjkl` 移动光标 131 | 132 | 【828】或者我可以用 `w` 跳词 133 | 134 | 或者别的这样的命令 135 | 136 | 这样就会选中一段文本 137 | 138 | 一旦我选中这段文本 139 | 140 | 就可以对它们进行各式各样的处理 141 | 142 | 其中常用的有复制这段文本 143 | 144 | 所以我选中之后,可以按一下 `y` 复制 145 | 146 | 之后 Vim 会退回普通模式 147 | 148 | 现在那段文本被存到了粘贴(paste) buffer 里 149 | 150 | 现在我随便移到一个地方 151 | 152 | 【839】然后按一下 `p` 153 | 154 | 就粘贴了刚复制的那段文本 155 | 156 | 相似于这种选择一串连续文字的 Visual 模式 157 | 158 | 还有 Visual Line 模式,按下大写 `V` 进入 159 | 160 | 它可以一次选择一行文字 161 | 162 | 甚至还有 Visual Block 模式 163 | 164 | 可以按 `Ctrl`+`v` 进入 165 | 166 | 【848】它可以选择矩形的文字块儿 167 | 168 | 这可是你原先的编辑器做不到的[*] 169 | *其实大部分“现代”的都可以,如 VSC / Sublime / Atom 170 | 171 | 好了,我们有一大堆 Vim 编辑命令要学呢 172 | 173 | 它有特别多,很无厘头但是非常好玩的命令 174 | 175 | 比如 `~` 命令可以改变字符大小写 176 | 177 | 或者是你选中的选区(的所有字符) 178 | 179 | 我举个例子,比如选中这个「Visual Studio Code」 180 | 181 | 然后通过选中它之后按下 `~` 182 | 183 | 反转这玩意儿的大小写 184 | 185 | 【860】并且还有很多像这样的命令 186 | 187 | 它们一个比一个深奥难懂[*] 188 | *这简直不讲武德 189 | 190 | 我们当然不会面面俱到 191 | 192 | 但是你会在练习中碰到这种东西 193 | 194 | 这些是 Vim 的编辑命令 195 | 196 | 其中很多可以和移动命令组合起来 197 | 198 | 诸位对之前的内容有什么疑问吗? 199 | 200 | 挺好,接着往下讲 201 | 202 | 另一类与 Normal 模式密切相关的东西 203 | 204 | 或者说命令,就是计数 205 | 206 | 【871】换言之,你可以给命令以数字 207 | 208 | 将一件事重复执行若干次 209 | 210 | 就比如我的光标在这里 211 | 212 | 我想下移,比如,一二三四行 213 | 214 | 一种方法是我可以按 `j` 键四次 215 | 216 | 也就是向下(一行)四遍 217 | 218 | 然后 `kkkk` 向上移动四次 219 | 220 | 但与其反反复复按一个键 221 | 222 | 不如我使用计数(count) 223 | 224 | 所以比如我键入 `4j` 225 | 226 | 就会执行四次 `j` 227 | 228 | Vim 的接口是种编程语言,对吧 229 | 230 | 【882】如果我键入 `4k`,就会上移四次 231 | 232 | 现在我在这里,按下 `v` 进入 Visual 模式 233 | 234 | 好,现在可以四处移动光标,选中文字块 235 | 236 | 我可以,比如按下 `eee` 选择几个数字 237 | 238 | 但我也可以——我先返回原处 239 | 240 | 再按 `v` 进入 Visual 模式 241 | 242 | 然后键入 `3e` 去选择,像它字面意思所说 243 | 244 | 「向右移到单词结尾」三次,这个范围 245 | 246 | 当然这些也可以和编辑命令相结合 247 | 248 | 假设我想删除七个单词 249 | 250 | 【893】我可以先把光标移到某处 251 | 252 | 然后键入 `7dw` [*] 253 | *这个命令的另一个等价替代是 `d7w` 254 | 255 | 也就是「删除单词」七次 256 | 257 | 它在一些情况下会特别有用 258 | 259 | 比如假设我的光标在屏幕某处 260 | 261 | 而我看向了屏幕另一处 262 | 263 | 或者说我想要我的光标移到那一行 264 | 265 | 请注意我在左侧设好了「相对行号」 [*] 266 | *其实这是混合行号显示(Hybrid line number),绝对+相对混合 267 | 268 | 也即,光标所在处显示绝对行号 269 | 270 | 【904】而其他处显示相对光标的偏差行数 271 | 272 | 现在来看我的光标在这儿 273 | 274 | 但我想移到下面这个「Microsoft Word」这里 275 | 276 | 所以这是往下 8 行 277 | 278 | 那么我用什么命令组合去搞定这件事呢? 279 | 280 | 想一下最有效率的方式是什么? 281 | 282 | *eight j* 283 | 284 | 没错! 285 | 286 | 来试一下,`8j` 287 | 288 | 然后我的光标就会移到这一行 289 | 290 | 好的。Vim 的最后一类按键命令叫修饰符 291 | 292 | 【915】到现在为止,我们讲了移动、编辑和计数 293 | 294 | 而最后,我们还有修饰符 295 | 296 | 修饰符会略微变更移动命令的意义 297 | 298 | 有俩特别好用的是 `a` 和 `i` 修饰符 299 | 300 | `a` 代表周围(around) 301 | 302 | 而 `i` 代表内部(inside) 303 | 304 | 为了见识一下它的用武之处 305 | 306 | 我可以把光标移到,比如这里 307 | 308 | 呃,希望你们都熟悉 Markdown 语法[*] 309 | *流行的标记语言,着力于快速编写文档。中译组字幕压制即采用此语法 310 | 311 | ——不熟悉也完全没关系,这不太重要 312 | 313 | 【929】这是一段 Markdown 写成的链接 314 | 315 | 方括号内的是其描述文字 316 | 317 | 圆括号内则为链接地址 318 | 319 | 现在我的光标在这里边儿 320 | 321 | 我想更改这个链接对应的描述文字 322 | 323 | 嗯,一种方法是用 `b` 回到这儿 324 | 325 | 然后执行比如 `2dw` 326 | 327 | 然后 `i` 进入 Insert 模式 328 | 329 | 有许多种方式能达到目的,这是其中之一 330 | 331 | 这样我就能随心所欲地打些别的 332 | 333 | 按 `u` 撤销,再撤销 334 | 335 | 【940】另一种能搞定的方式是 336 | 337 | 更改两个单词——「`c2w`」 338 | 339 | 然后输入别的文本 340 | 341 | 但这同一个问题的最终方案是 342 | 343 | 运用修饰符命令来向 Vim 表明 344 | 345 | 我想如何和这些成对的东西— 346 | 347 | —比如圆括号和方括号去交互 348 | 349 | 所以这个最终方案是 350 | 351 | 「在方括号内更改」—`ci[` 352 | 353 | 这样还会在删除括号内的内容之后 354 | 355 | 让我进入 Insert 模式 356 | 357 | 【951】所以你可以看到 358 | 359 | 我们可以怎样去运用这些“组件” 360 | 361 | 就比如提到的「更改」(change) 362 | 363 | 然后与其他移动命令结合 364 | 365 | 我们讲了 **i**nside,它是怎样的一个修饰符 366 | 367 | 然后我们讲了……呃 368 | 369 | 我们没讲括号的事情 370 | 371 | 就是,如果你的光标位于一些不同的,呃 372 | 373 | 不同的成组的东西,比如圆括号、方括号,之上 374 | 375 | 你可以按下移动键——`%` 376 | 377 | 在配对的括号间反复横跳 378 | 379 | 如果我移到这里,然后执行 `di(` 380 | 381 | 【965】就会删除这些括号里边儿的内容 382 | -------------------------------------------------------------------------------- /ch2/[558-756]CHN.txt: -------------------------------------------------------------------------------- 1 | 【558】这想想就很浪费时间,不是吗 2 | 3 | 而 `hjkl` 这几个键刚好就在键盘正中间一行 4 | 5 | 按下 `j` 光标会往下移一行 6 | 7 | 而按 `k` 会上移一行 8 | 9 | 按下 `h` 光标会左移一个字符 10 | 11 | 而 `l` 将光标右移一个字符 12 | 13 | 在刚开始使用时 14 | 15 | 你可能会觉得这有点反直觉 16 | 17 | 然而这背后有一些历史原因 18 | 19 | 就是,这样的一个布局 20 | 21 | 实际上在原先 Vi 的开发者使用的键盘上[*] 22 | * Vim 的全称为 Vi IMproved,即 Vi 的“升级版” 23 | 24 | 会是一个挺合理的样子[*] 25 | *Vi 诞生的 ADM-3A 终端机的键盘上,方向键位于 HJKL 的位置,Esc 位于如今 Tab 的位置 26 | 27 | 但是这个布局很快就会成为一种肌肉记忆 28 | 29 | 那么这就是在 Normal 模式下移动光标的基本方式 30 | 31 | 那么还有什么方式呢 32 | 33 | 看,如果你要这样浏览屏幕上这种文件 34 | 35 | 这个效率简直低得离谱 36 | 37 | 【572】我们不想这样按着按键 38 | 39 | 然后等好长一段时间 40 | 41 | 直到光标走到我们想要的地方 42 | 43 | 因此 Vim 里还有其他的按键组合 44 | 45 | 用来执行不同种类的移动 46 | 47 | 另外这些按键和它们的用途现在并不需要记 48 | 49 | 在课堂笔记中会全部列出来 50 | 51 | 现在要做的就是 52 | 53 | 尽可能的理解 Vim 的界面是一种编程语言 54 | 55 | 这一思想 56 | 57 | 那么另一种移动光标的方式是按下 `w` 键 58 | 59 | 这样做将会使光标往前移一个单词 60 | 61 | 类似的,按下 `b` 键会使光标往后移一个单词 62 | 63 | 那么这样移动起来效率就高一些了 64 | 65 | 此外我们还有一个 `e` 键 66 | 67 | 这个键可以把光标移动到单词末尾 68 | 69 | 让我把这个东西(击键显示工具)挪开 70 | 71 | 例如我在这里按下 `e` 键 72 | 73 | 光标就跑到这个单词末尾去了 74 | 75 | 再按,再下一个词 76 | 77 | 以此类推 78 | 79 | 你也能以行为单位移动 80 | 81 | 按下 `0` 键可以移动到行首 82 | 83 | 而按下 `$` 可以移动到行末 84 | 85 | 按 `^` 则会移动到行首的第一个非空白字符 86 | 87 | 让我来找一行… 88 | 89 | 【600】那就这行吧 90 | 91 | 例如我按下 `0` 它会跳到行首 92 | 93 | 按下 `$` 跳到了行末 94 | 95 | 而猜猜按下 `^` 会跳到哪呢 96 | 97 | 按下它会跳到行首的第一个非空白字符 98 | 99 | 有点像正则里的那个 100 | 101 | *应该是那个横杠* 102 | 103 | 对!跳到这个横线! 104 | 105 | 我们再来谈谈其他的移动方式 106 | 107 | 如果要在 buffer 里上翻下翻 108 | 109 | 按下 `Ctrl`+`u` 就可以往上翻 110 | 111 | 而 `Ctrl`+`d` 往下翻 112 | 113 | 【614】比按住 `j` 和 `k` 键快多了 114 | 115 | 那样比按这两个键翻页慢多了 116 | 117 | 除此之外你还可以跨越整个 buffer 浏览 118 | 119 | 按下大写 `G` 可以跳到最底部 120 | 121 | 而键入 `gg` 则可以跳到顶部 122 | 123 | 有些键位是辅助你记忆的 124 | 125 | 因此对于这些键记忆起来会轻松一些 126 | 127 | 【622】例如 `w` 是 word (即 "单词") 128 | 129 | 而 `b` 是 beginning of word (单词开头) 130 | 131 | `e` 是 end of word(单词结尾) 132 | 133 | 这些按键设计看上去都挺合理的 134 | 135 | 而 `0`, `^`, `$` 大概是受到正则表达式启发 136 | 137 | 这些键的意义还是能讲得通的 138 | 139 | 除此之外还有一些比较离谱,无厘头的 140 | 141 | 但你看键盘上有这么多键闲着 142 | 143 | 有啥办法呢 >︿< 144 | 145 | 例如 146 | 147 | (大写)`L` 键把光标移到屏幕最下端一行 148 | 149 | `L` 代表 Lowest(最低),这说得通 150 | 151 | `M` 代表中间(Middle) 152 | 153 | 我猜 `H` 是最高(Highest) 154 | 155 | 除此之外还有许多的有趣移动方式 156 | 157 | 虽然我们并不会在课上全部涵盖这些东西 158 | 159 | 但是你可以在练习 1 中逐一尝试[*] 160 | *~~留作课后作业~~ 161 | 162 | 除此之外我还想说一个移动方式 163 | 164 | 这个移动方式叫查找 165 | 166 | 也算蛮好用的 167 | 168 | 假设我现在在这行 169 | 170 | 我想跳去第一个啥字符的位置呢… 171 | 172 | 例如我想跳去第一个 o 字符 173 | 174 | 我可以键入 `fo` 175 | 176 | 然后光标就跳到了第一个 o 的位置[*] 177 | *此处指光标**后**的第一个,下同 178 | 179 | 我还可以键入 `fw` 180 | 181 | 这样光标就跳到了第一个 w 的位置 182 | 183 | 键入 `fc` 184 | 185 | 光标就跳到了第一个 c 的位置 186 | 187 | 我还可以键入大写 `F` 小写 `w` 188 | 189 | 这样就会将光标前向移动到最近的 w 190 | 191 | 大写 `F` 小写 `s` 192 | 193 | 则会跳到光标前最近的 s 194 | 195 | 【656】此外 `f` 这东西还有个变体 196 | 197 | ——`t`,代表「跳转至」 198 | 199 | 比如我可以键入 `to`[*] 200 | *这个两键命令可类比 `fo`,但不是英文单词 to 201 | 202 | 但是光标不会落在 o 上面 203 | 204 | 而是 o 前面一个字符 205 | 206 | 而大写 `T` 再加个 `t` 会往反方向寻找 t 207 | 208 | 但不会落到 t 字符上 209 | 210 | 【662】而是它后面一个字符 211 | 212 | 所以你可能已经对我之前说过的 213 | 214 | Vim 是一种编程语言 215 | 216 | 这一思想有了一些认识 217 | 218 | 例如你可以把这些命令组合起来 219 | 220 | `f` 和 `t` 221 | 222 | 分别表示查找和跳至(Find/To) 223 | 224 | 因此你可以查找特定字符 225 | 226 | 或者跳转到某个字符 227 | 228 | 那么这就是 Vim 的几种移动命令了 229 | 230 | 现在有什么问题吗 231 | 232 | 那么这就是… 233 | 234 | 啊,有问题吗 235 | 236 | 啊,没有。好 237 | 238 | 那么这就是几种 Vim 里的移动命令了 239 | 240 | 你可以借助他们来快速地浏览文件 241 | 242 | 接下来另一类常用命令就是编辑命令了 243 | 244 | 我们之前已经说过一个 `i` 键了 245 | 246 | 按下此键可从 Normal 模式进入 Insert 模式 247 | 248 | 然后你就可以开始写东西了 249 | 250 | 那么我现在把光标往上移 251 | 252 | 然后按下 `i`,就可以输入文字了 253 | 254 | `Hello world [ENTER]` 255 | 256 | 然后按 `Esc` 回到 Normal 模式 257 | 258 | 我就对 buffer 做了修改 259 | 260 | 除此之外,还有许多用于高效编辑的命令 261 | 262 | 【688】这些在你编程的时候会派上用场 263 | 264 | 其中之一就是我之前还没讲到这里的时候 265 | 266 | 不小心触发的 `o` 键 267 | 268 | 假设我的光标在这里 269 | 270 | 我现在在 Normal 模式按下 `o` 键 271 | 272 | 它就会在我的光标下面给我开(open)新的一行 273 | 274 | 而这就是 `o` 代表的意思 275 | 276 | 然后它就会给我切入 Insert 模式 277 | 278 | 我现在就可以开始输入东西了 279 | 280 | 然后按 `Esc` 回到 Normal 模式 281 | 282 | 类似的还有一个大写 `O` 键 283 | 284 | 它会在光标上方开一行 285 | 286 | 然后让我进入 Insert 模式 287 | 288 | Vim 里还有一个用来删除的命令 289 | 290 | 我现在把光标放在这个单词上 291 | 292 | 然后按下 `d` 键 293 | 294 | `d` 代表删除(delete) 295 | 296 | 唔,啥都没发生 297 | 298 | 其实 `d` 键还要配上刚讲的移动命令来食用 299 | 300 | 例如 `hjkl` 301 | 302 | 再如 `w`, `b` 键[*] 303 | *即原文 word, backward word 对应的键 304 | 305 | 等等 306 | 307 | 那我现在按下 `d` 308 | 309 | 啊这 ⊙﹏⊙∥ 310 | 311 | 我现在按下 `d` 然后按下 `w` 312 | 313 | 就会删除一个单词 314 | 315 | 让我撤销这个操作 316 | 317 | 【715】在 Vim 里撤销,只需按下 `u` 即可 318 | 319 | 注意光标现在在这儿 320 | 321 | 我键入 `dw` 删掉了一个单词 322 | 323 | 然后让我动一动光标 324 | 325 | 再删一个单词 326 | 327 | 例如… 328 | 329 | 【719】啊这玩意好烦,老挡着我 330 | 331 | 例如我光标在一个单词的中间 332 | 333 | 我想从这里一直删到单词结尾 334 | 335 | 猜猜要用什么命令组合? 336 | 337 | `d` 和什么? 338 | 339 | 对!`de`! 340 | 341 | 它会从这里删到单词结尾 342 | 343 | 另一个常用的编辑命令是 `c` 344 | 345 | `c` 代表更改(Change) 346 | 347 | 这玩意很像删除 348 | 349 | 但是它删完以后会进入 Insert 模式 350 | 351 | 因为我想把它删掉然后改成其他东西 352 | 353 | 例如我光标在这里 354 | 355 | 按下 `ce` 356 | 357 | 代表从这里更改到单词结尾 358 | 359 | 然后它就会把这段内容删掉 360 | 361 | 注意之后它切进了插入模式 362 | 363 | 现在我无论输入什么都会输进 buffer 内 364 | 365 | 然后按下 `Esc` 回到 Normal 模式 366 | 367 | 因此 `c` 和 `d` 这两个命令很像 368 | 369 | 【739】它们都会接受一个操作范围作为参数 370 | 371 | 然后按照操作范围的方向进行删除或更改 372 | 373 | 比如,如果你按下…… 374 | 375 | `c`,呃不是…… 376 | 377 | 话说,还有个规律是,按某个编辑键两次 378 | 379 | 它将作用于这一整行 380 | 381 | 例如当我按下 `dd` 时会删掉整行 382 | 383 | 按下 `cc` 时也会这样 384 | 385 | 但此外还会进入编辑模式 386 | 387 | 这样我就可以把这行的内容改成其他东西 388 | 389 | 我们再多说几个,呃,编辑命令 390 | 391 | 因为后面会讲,如何让几个命令相互结合 392 | 393 | 那么另一个常用的命令就是 `x` 键 394 | 395 | 假设我的光标在某个字符上 396 | 397 | 当我按下 `x` 时 398 | 399 | 【756】它会把那个字符删掉 400 | -------------------------------------------------------------------------------- /ch0/[824-1039]CHN.txt: -------------------------------------------------------------------------------- 1 | 【824】是大约四天之后 2 | 3 | 有一些特别奇妙的工具 4 | 5 | 构建高级管道的时候用得到 6 | 7 | 给你们举一个例子,我们可以做一些操作,比如 8 | 9 | `curl --head --silent google.com` 10 | 11 | 就只是展示个大概 12 | 13 | 这会给我访问 google.com 时候 14 | 15 | 所有的 HTTP Headers 16 | 17 | 我也可以用管道给它接到 18 | 19 | `grep --ignore-case` 20 | 21 | 【833】或者说 `-i` 22 | 23 | 比如说我想(匹配)content-length 24 | 25 | 这样就会打印出 content-length header 26 | 27 | `grep` 是我们之后会讲的程序 28 | 29 | 它支持在输入流里搜索给定关键字 30 | 31 | 我们还可以用管道连到 `cut` 命令 32 | 33 | 它可以接受一个分隔符,我们设成空格 34 | 35 | 【841】我想要第二个字段 36 | 37 | 这样就只输出 content-length(的值) 38 | 39 | 这算是一个很傻的例子 40 | 41 | 只是在命令行里以字节(bytes)的形式 42 | 43 | 提取出 google.com 的内容长度 44 | 45 | 【846】这没啥有用的,但是你可以从中窥见 46 | 47 | 把命令链接在一起 48 | 49 | 你可以做很多文本操作的特技 50 | 51 | 而且 `pipe` 不止用于文本数据 52 | 53 | 还可以拿来处理比如图片 54 | 55 | 当你有一个程序可以接受并处理二进制图片 56 | 57 | 然后输出一个二进制图片的时候 58 | 59 | 也可以像这样把它连进去 60 | 61 | 我们之后也会谈到这类例子 62 | 63 | 【858】你甚至可以这样处理视频 64 | 65 | 你可以 - 比如说你家里有个 Chromecast [*] 66 | *Google 推出的类似网络机顶盒的东西 67 | 68 | 你可以把一个视频文件推流 69 | 70 | 如果把管道的最后一个程序设成 71 | 72 | Chromecast 的发送程序 73 | 74 | 然后以流的形式传给它视频文件 75 | 76 | 它就推流(或者 HTTP 形式)传到你的 Chromecast 77 | 78 | 【867】在数据整理那节会涉及更多 79 | 80 | 但是我还想说一个事情 81 | 82 | 大概就是终端的更有趣和强大的用法 83 | 84 | 对于那些已经熟悉终端的同学来说 85 | 86 | 也许会挺有意思 87 | 88 | 【876】我们要先提一个重要话题 89 | 90 | 就是涉及到 Linux 和 MacOS 的时候 91 | 92 | 有一个叫 root (根)用户的概念 93 | 94 | root 用户类似 Windows 里面的管理员(Administrator) 95 | 96 | 有值为 0 的用户 ID 97 | 98 | root 是很特别的,因为 99 | 100 | 他被允许在系统上做任意行为 101 | 102 | 就算一个文件是任何人不可读的 103 | 104 | 或者任何人不可写的 105 | 106 | root 却可以访问这个文件(且读写) 107 | 108 | 【887】他算是一种「超级用户」 109 | 110 | 可以做任何想做的事 111 | 112 | 大多数时候你不会用超级用户操作 113 | 114 | 你不会是 root 115 | 116 | 你应该是一个类似 `Jon`,或者别的啥 117 | 118 | 按你的名字来的用户 119 | 120 | 你该用这个用户操作(电脑) 121 | 122 | 【894】因为如果一直在 root 下操作电脑 123 | 124 | 如果你运行了错误的程序 125 | 126 | 可能会直接炸了你的电脑 127 | 128 | 你肯定不想这样,对吧 129 | 130 | 但是从今往后你每次想用 root 做点事的话 131 | 132 | 通常这种情况是,用一个叫 `sudo` 的程序 133 | 134 | S-U-D-O 或者说 do as su 135 | 136 | 这里 su 就是 Super User(的缩写) 137 | 138 | 这是一个用超级用户运行程序的办法 139 | 140 | 【905】通常 `sudo` 的用法是 141 | 142 | 你先打 `sudo` 再接上平常调用的命令 143 | 144 | 它就会以 root 身份运行这个命令 145 | 146 | 而不是本来的那个用户 147 | 148 | 你在哪里可能要用到这种东西呢 149 | 150 | 呃,有一个特别的…… 151 | 152 | 在你的电脑上有很多特别的文件系统 153 | 154 | 其中有一个特定的叫 `sysfs` 155 | 156 | 如果你 `cd` 到 `/sys` 157 | 158 | 这整个儿都是新世界 159 | 160 | 这个文件系统不是真实存在的文件 161 | 162 | 相反,这是一大堆内核参数 163 | 164 | 内核(kernel)基本上就是你电脑[*]的核心 165 | *此处指软件方面,也即操作系统 166 | 167 | 这是种访问这些内核参数的方法 168 | 169 | 通过这些看起来是文件系统的东西 170 | 171 | 【923】你看,这里如果我 `cd` 到比如 `class` 172 | 173 | 对于一大把设备,它有各个对应的目录 174 | 175 | 我可以与它们交互 176 | 177 | 或者各种队列(queue) 178 | 179 | 或者各种奇奇怪怪的内核玩意儿 180 | 181 | 由于它们是以文件形式展露(exposed)的 182 | 183 | 这意味着我们可以用先前的所有工具 184 | 185 | 去操作他们 186 | 187 | 【931】举个栗子,如果你去到 `sys/class/backlight` 188 | 189 | 这个 `backlight` 可以直接设置笔记本的亮度 190 | 191 | 如果你是用笔记本电脑 192 | 193 | 所以我 `cd` 进 `intel backlight` 194 | 195 | 这是个 Intel 的笔电 196 | 197 | 里边儿你可以看见有个文件叫 `brightness` 198 | 199 | 我可以 `cat` 这个 `brightness` 200 | 201 | 这是我现在的屏幕亮度 202 | 203 | 不止于此,我也可以更改它 204 | 205 | 来改变我的屏幕亮度 206 | 207 | 你也许认为我要…… 208 | 209 | 来看看最大亮度是多少 210 | 211 | 【944】好,所以现在是最大亮度 212 | 213 | 你可能会想,我会写点这样的命令 214 | 215 | 如果我 `echo`……让我们设个一半 216 | 217 | `echo 500 > brightness` 218 | 219 | 如果我这么干,它提示我没有权限 220 | 221 | 我不允许修改亮度 222 | 223 | 因为……基本上如果你要修改内核的东西 224 | 225 | 你得是管理员才行 226 | 227 | 你也许会以为解决方案是写个 228 | 229 | 【956】`sudo echo 500` 230 | 231 | 但我还是莫得权限 232 | 233 | 为毛呢?这是因为按我之前说的 234 | 235 | 输入输出的重定向是程序不知道的 236 | 237 | 用管道把 `ls` 连接到 `tail` 的时候 238 | 239 | `tail` 不知道 `ls`,`ls` 也不知道 `tail` 240 | 241 | 管道和重定向都是 Shell 设好的 242 | 243 | 所以现在的情况是我告诉 Shell 244 | 245 | 去运行 `sudo`,并且包括参数 `echo 500` 246 | 247 | 然后发送输出到这个 `brightness` 文件 248 | 249 | 但是 Shell 打开 `brightness` 的时候 250 | 251 | 【970】用的不是 `sudo` 252 | 253 | 所以这里 Shell 以我(Jon)的用户身份 254 | 255 | 去试图打开 `brightness` 文件并写入数据 256 | 257 | 而这不被允许,所以我有个无权限的错误 258 | 259 | 你也许见过(下面)这个 260 | 261 | 如果你搜了啥然后最后到了 Stackoverflow 上[*] 262 | *程序员的求知交友娱乐网站(全英文) 263 | 264 | 【977】他们告诉你运行一下这个命令就行 265 | 266 | 你会见到像这样的,他们给你一些说明 267 | 268 | 比如 `echo 1 > sys`,呃,`net/ipv4_forward` 269 | 270 | 在设置防火墙的时候你可能见过这种东西 271 | 272 | 这个命令之所以能行 273 | 274 | 是因为前面这个小小的 `#` 275 | 276 | 意味着以 root 运行整条命令 277 | 278 | 这件事很少有人解释,但就是这个意思 279 | 280 | 【989】你会看见在我的 prompt(提示符)上 281 | 282 | 取而代之有个 `$`(dollar,美元)符号 283 | 284 | `$` 表示你现在不是 root 285 | 286 | 重要的是我现在怎么解决问题 287 | 288 | 一是我可以切换到 root 终端 289 | 290 | 这个方法是运行 `sudo su` 291 | 292 | `sudo su` 是说用 root 运行接下来的命令 293 | 294 | `su` 是个蛮复杂的命令 295 | 296 | 能让你以超级用户登录 Shell 297 | 298 | 如果我运行,然后输入密码 299 | 300 | 你会看到开头的用户名从 `jon` 变成了 `root` 301 | 302 | 提示符从 `$` 变成了 `#` 303 | 304 | 如果我现在处理那个文件 305 | 306 | `echo 500 > brightness` 307 | 308 | 我的屏幕就变暗了一点 309 | 310 | 虽然你看不见,你只能听信我 311 | 312 | 我现在就没得到错误 313 | 314 | 【1007】因为现在 Shell 以 `root` 身份运行 315 | 316 | 而不是 `jon` 317 | 318 | 而 `root` 用户允许打开这个文件 319 | 320 | 但是运用我们现有的终端知识 321 | 322 | 其实还有一个办法 323 | 324 | 能不用进入 root Shell 325 | 326 | 就是,呃,我恢复到 1060 327 | 328 | `sudo`…… 329 | 330 | 你看到这里的区别了吗 331 | 332 | 我告诉 Shell 去运行 `echo 1060` 333 | 334 | 它会输出 `1060`, 然后我告诉它 335 | 336 | 运行 `sudo tee brightness` 命令 337 | 338 | 然后把 `echo` 的输出送入 `sudo tee` 的输入 339 | 340 | 要搞明白这个你得知道 `tee` 命令干啥 341 | 342 | `tee` 命令取它的输入,然后写入到一个文件 343 | 344 | 并且写入到标准输出(流) 345 | 346 | 【1025】所以 `tee` 是一个很方便的命令 347 | 348 | 比如说你有个日志[*],你想 349 | *此处原文的 log file 大致为口误 350 | 351 | 把它传到一个文件里存着之后看 352 | 353 | 但你还想现在瞅一眼 354 | 355 | 那你就可以用管道传给 `tee` 356 | 357 | 再给它文件名,它就能把无论什么输入 358 | 359 | 写到文件和你的屏幕上 360 | 361 | 这里我就利用这个程序 362 | 363 | 这里我说「以 root 运行 `tee`」 364 | 365 | 然后输出到 `brightness` 文件 366 | 367 | 【1035】这个例子里 `tee` 程序打开了 `brightness` 368 | 369 | 并且以 root (身份)运行 370 | 371 | 所以这是可行的 372 | 373 | 如果我现在运行它 374 | 375 | 你还是看不到亮度,但是它被调高了 -------------------------------------------------------------------------------- /ch0/[599-823]CHN.txt: -------------------------------------------------------------------------------- 1 | 【599】哦抱歉,还有一件事情 2 | 3 | 如果它显示的是一个横杠(-) 4 | 5 | 意思就是你没有对应的权限,对吧 6 | 7 | 比如说它显示 r-x 8 | 9 | 那就意味着你有读和执行权限,但没有写权限 10 | 11 | 还有很多别的趁手的工具 12 | 13 | 有一个是 `mv` 命令 14 | 15 | 如果我 `cd` 回 `missing semester` 16 | 17 | 在这里我能用 `mv` 重命名文件 18 | 19 | 【610】它接受两个路径(path)(作为参数) 20 | 21 | 先是原有的路径(path),然后是新的 22 | 23 | 这意味着 `mv` 既可以让你重命名一个文件 24 | 25 | 如果你只想原地重命名文件的话 26 | 27 | 或者把文件移动到一个不同的目录里 28 | 29 | 原理就是你给出现有文件的路径(和文件名) 30 | 31 | 以及目标路径和新文件名 32 | 33 | 然后它可以改变文件的路径和名字 34 | 35 | 比如说我可以 `mv dotfiles.md` 到 `foo.md` 36 | 37 | 对吧,虽然没卵用 38 | 39 | 我也可以 `mv` 回去 40 | 41 | 也有一个 `cp` 命令,也就是复制(Copy) 42 | 43 | `cp` 让你复制[*]文件,用法很相似的 44 | *对应 MacOS 的拷贝(Copy)而非 MacOS 的复制(Duplicate) 45 | 46 | 它也接受两个参数,复制源路径和目标路径 47 | 48 | 这些得是完整路径[*] 49 | *意味着你需要明确指定具体文件路径,这个命令没有搜索功能 50 | 51 | 【628】我可以举个栗子,比如说 52 | 53 | 我想 `cp dotfiles.md ` 到 `../food.md` 54 | 55 | 对,是 `food.md`,好嘞 56 | 57 | 如果我现在 `ls ..` 58 | 59 | 看,现在这个目录里有个 `food.md` 60 | 61 | 所以 `cp` 也是接受两个参数 62 | 63 | 而且不必是同一个目录 64 | 65 | 相似地,有个 `rm` 命令 66 | 67 | 让你可以移除(删除)一个文件 68 | 69 | 这里你可以传一个路径,比如说这里 70 | 71 | 我想把 `../food` (.md)删掉 72 | 73 | 你应该注意的是移除 74 | 75 | 【639】特别是在 Linux 上 76 | 77 | 默认的移除是非递归的(recursive) 78 | 79 | 也就是说你不能用 `rm` 移除目录 80 | 81 | 你可以传一个执行递归移除的 `-r` flag 82 | 83 | 然后传递想移除的路径(path) 84 | 85 | 它就会移除目录下的所有内容 86 | 87 | 也有 `rmdir` 命令可以让你移除目录 88 | 89 | 不过只允许移除空目录 90 | 91 | 【649】所以设计它的匠心是 92 | 93 | 这算是给你提供一种安全机制 94 | 95 | 用它你就不会不小心扔掉一堆重要文件 96 | 97 | 【652】最后有一个趁手的小命令 98 | 99 | 是 `mkdir` (make directory) 100 | 101 | 可以让你创建一个新目录(文件夹) 102 | 103 | 之前说过了,别想着写一些这样的命令 104 | 105 | 这样会给你整出两个目录来 106 | 107 | 一个名为 `My`,一个 `Photo` 108 | 109 | 【660】如果你想创建那种(带空格的)目录 110 | 111 | 要么把空格转义,要么把整个字符串引起来 112 | 113 | 如果你想这些平台上了解在任意一个命令的信息 114 | 115 | 也有个非常奥里给的程序,叫做 `man` 116 | 117 | 意思是手册、说明书(manual pages) 118 | 119 | 这个程序接受其他程序的名字作为一个参数 120 | 121 | 然后显示它的说明书 122 | 123 | 比如说我们可以输入 `man ls` 124 | 125 | 就显示了 `ls` 的说明书 126 | 127 | 你会发现这里显示的内容其实和 128 | 129 | 我们运行 `ls --help` 得到的内容挺相似的 130 | 131 | 不过翻阅起来稍微容易点儿 132 | 133 | 一般来说,翻到底儿瞧瞧 134 | 135 | 【678】你能看到一些提示,基本都是 136 | 137 | 命令示例啊,作者啊,哪儿有更多信息啊 138 | 139 | 这类东西 140 | 141 | 偶尔有个问题挺捉弄人的 142 | 143 | 啊,直到最近一个版本里他们在底下 144 | 145 | 给加了一行 146 | 147 | 【683】显示按 `Q` 退出 148 | 149 | 他们以前不显示这种「按 `Q` 退出」的 150 | 151 | 如果你不知道这事,还挺难退出来的 152 | 153 | 有个好用的快捷键,`Ctrl` + `L` 154 | 155 | 可以清空终端,让光标回到顶部 156 | 157 | 到现在我们只是分开说了各个程序 158 | 159 | 但是 Shell 真正的本领在于 160 | 161 | 当你把不同的程序结合在一起的时候 162 | 163 | 与只是运行 `cd` 和 `ls` 相比很强 164 | 165 | 也许你想把多个程序串起来 166 | 167 | 也许是想和文件交互 168 | 169 | 【698】或者在各个程序之间操作文件 170 | 171 | 我们借助一个叫流(stream)的概念完成 172 | 173 | Shell 默认会给我们的每个程序创建 174 | 175 | 我简化一下,就说程序有两个主要的流(stream) 176 | 177 | 默认,程序会有一个输入流(input stream) 178 | 179 | 和一个输出流(output stream) 180 | 181 | 默认输入流里的内容来自你的键盘 182 | 183 | 基本上输入流是终端 184 | 185 | 【707】无论你向终端输入什么 186 | 187 | 最后都会传到程序里 188 | 189 | 默认的输出流,就是说 190 | 191 | 每当程序想要输出一些内容时 192 | 193 | 它会输出到这个流里去 194 | 195 | 默认也是终端 196 | 197 | 这就是为什么,当我打入 `echo hello` 198 | 199 | 它就显示回了我的终端里面 200 | 201 | 但是 Shell 提供了重定向这些流的方法 202 | 203 | 把输入和输出都改到程序员指明的地方 204 | 205 | 这里最直接的方式是用大于小于号[*] 206 | *也就是所谓的「尖角括号」 207 | 208 | 【720】所以你可以写一些类似这样的事情 209 | 210 | 或者类似那样的事情 211 | 212 | 小于号表示重定向这个程序的输入流 213 | 214 | 变成这个文件的内容 215 | 216 | 大于号表示重定向上述程序的输出流 217 | 218 | 变成输出到这个文件内 219 | 220 | 【727】我们举个例子试一下是什么样 221 | 222 | 如果我 `echo hello`,我想把这个内容 223 | 224 | 存在一个叫 `hello.txt` 的文件里 225 | 226 | 我给了,这是个相对路径(relative path),对吧? 227 | 228 | 这样会在当前目录下创建一个 `hello.txt` 229 | 230 | 至少,理论上来说,它的内容 231 | 232 | 应该是 `hello` 这个单词 233 | 234 | 如果我运行一下,注意,啥都没输出来 235 | 236 | 前一次我运行 `echo hello` 的时候 237 | 238 | 它输出了 `hello` 239 | 240 | 【739】现在 `hello` 已经跑到 `hello.txt` 里了 241 | 242 | 我可以用这个叫 `cat` 的程序验证 243 | 244 | `cat` 打印出一个文件的内容 245 | 246 | 所以我可以 `cat hello.txt` 247 | 248 | 哎,它就显示了 `hello` 249 | 250 | 不过 `cat` 也支持这种流的重定向 251 | 252 | 所以我可以说 `cat` 默认只是将输入打印 253 | 254 | 呃,将输入原封不动复制到输出 255 | 256 | 比如,`cat`,我想让你接受 `hello.txt` 的输入 257 | 258 | 在这个例子下,Shell 就会打开 `hello.txt` 259 | 260 | 取出它的内容,设置成 `cat` 的输入 261 | 262 | 然后 cat 就会把这些内容打印到它的输出(流) 263 | 264 | 我没有重定向它,所以是我的终端 265 | 266 | 总之就是它会把 `hello` 打印到输出 267 | 268 | 我也可以同时使用两种(重定向) 269 | 270 | 【759】比如我想复制一个文件但不用 `cp` 271 | 272 | 我可以用这套方法,具体来说 273 | 274 | 实际上我没有告诉 `cat` 任何事 275 | 276 | 我只是命令它说「你正常干活」,对吧 277 | 278 | `cat` 不知道是不是发生了重定向 279 | 280 | 但是我会告诉 Shell 用 `hello.txt` 281 | 282 | 去作为 `cat` 的输入 283 | 284 | 然后把 `cat` 输出的所有内容存到 285 | 286 | `hello2.txt` 里面去 287 | 288 | 这次还是没有东西打印到终端上 289 | 290 | 但是如果我运行 `cat hello2.txt` 291 | 292 | 我得到了期望的输出 293 | 294 | 也就是一个源文件的副本 295 | 296 | 也有个东西是双大于号[*] 297 | *这里不是指中文的书名号 298 | 299 | 作用是追加(append)而不是覆写(overwrite)[*] 300 | *追加指向文件尾继续添加内容;覆写则清空文件 301 | 302 | 你会注意到如果我 303 | 304 | `cat < hello.txt > hello2.txt` 305 | 306 | 【778】然后我 `cat hello2.txt` 307 | 308 | 它仍然只是包含 `hello` 309 | 310 | 尽管它之前已经有过 `hello` 了 311 | 312 | 如果我给它换成双大于号,意味着追加 313 | 314 | 如果我现在再 `cat` 那个文件 315 | 316 | 它就有了两个 `hello` 317 | 318 | 这些都挺直白的 319 | 320 | 它们仅仅是和文件交互的一些方式 321 | 322 | 不过真正有趣的地方是 323 | 324 | Shell 附赠给你的一个操作符(operator) 325 | 326 | 叫管道符(pipe),管道符就是一个竖线 327 | 328 | 管道的意思是 329 | 330 | 【790】取左侧程序的输出 331 | 332 | 成为右侧程序的输入 333 | 334 | 这看上去会是什么样子呢 335 | 336 | 来试一个例子,比如说 `ls /` 337 | 338 | 或者 `ls -l /` 339 | 340 | 打印出了一堆东西 341 | 342 | 我就说我想要输出的最后一行 343 | 344 | 有一个命令叫 `tail` 345 | 346 | 它打印出它输入的最后 n 行 347 | 348 | 我可以 `-n1`,所以这就是个叫 n 的 flag 349 | 350 | 如果你想,也可以用更长的 `--lines` 351 | 352 | 这个例子里,它表明输出最后一行 353 | 354 | 我可以把这些连接到一起,也就是说 355 | 356 | `ls -l / | tail -n1` 357 | 358 | 【805】注意这里 `ls` 并不了解 `tail` 359 | 360 | `tail` 也不认识 `ls` 361 | 362 | 它们是不同的程序 363 | 364 | 也没有刻意设计和对方相兼容 365 | 366 | 它们只知道要从输入读数据 367 | 368 | 结果写到输出 369 | 370 | `pipe` 才是把它们连结起来的东西 371 | 372 | 这个例子,我想 `ls` 输出作为 `tail` 输入 373 | 374 | `tail` 的输出则会输到我的终端 375 | 376 | 【816】因为我没有重定向它 377 | 378 | 我也可以把它重定向,比如说 379 | 380 | 我想输出到 `ls.txt` 里面 381 | 382 | 这里如果我 `cat ls.txt` ,就得到 383 | 384 | 一个期望的输出 385 | 386 | 这意味着你可以做一些很妙的事情 387 | 388 | 我们会在 data wrangling (数据整理)课程 389 | 390 | 里面涉及更多 -------------------------------------------------------------------------------- /ch1/[595-792]CHN.txt: -------------------------------------------------------------------------------- 1 | 【595】然后,如果要更新它的话 2 | 3 | 用这个 `updatedb` 命令 4 | 5 | 通常由 cron 定期执行来更新数据库。[*] # REVIEW 6 | *cron 是 UNIX 下一个基于时间的任务管理系统,可以运行定期任务。 7 | 8 | 另外,查找文件是很有门道的 9 | 10 | 实际上,有时你不关心文件本身 11 | 12 | 而是文件的内容 13 | 14 | 这方面可以用前面见过的 `grep` 命令 15 | 16 | 比如 `grep foobar mcd.sh` 17 | 18 | 找到了 19 | 20 | 【603】如果你还是想递归当前目录结构 21 | 22 | 去查找更多的文件该怎么办 23 | 24 | 你不会愿意亲手干苦活的 25 | 26 | 我们可以用 `find` 命令结合 `-exec` 27 | 28 | 但 `grep` 有一个大写 `-R` 的 flag 29 | 30 | 是可以找遍整个目录的 31 | 32 | 啊,应该是这样儿 33 | 34 | 它告诉我们,噢 35 | 36 | `example.sh` 中有包含 `foobar` 的行 37 | 38 | 在这三个行的位置都有 39 | 40 | 并且这两个位置也有 `foobar` 41 | 42 | 【612】这个挺省事的 43 | 44 | 主要是当你记得你用一些程序语言 45 | 46 | 写了一些代码的时候 47 | 48 | 你知道它就在你文件系统的某处躺着 49 | 50 | 但你就是想不起来 51 | 52 | 用这招就可以快速搜索 53 | 54 | 比如我可以快速搜索草稿文件夹里 55 | 56 | 所有我用了 `request` 库的 Python 代码 57 | 58 | 【621】如果我执行命令 59 | 60 | 就能查到这些文件,精确到匹配的行 61 | 62 | 比起用 `grep`,虽然它挺好 63 | 64 | 你也可以……我用了 `ripgrep` 65 | 66 | 原理是一样的,但是它也是 67 | 68 | 加了亿点点细节 69 | 70 | 比如代码彩色和文件处理啥的 71 | 72 | 也有 Unicode 支持 73 | 74 | 【629】而且跑的还快 75 | 76 | 所以它没为了这些花招拖慢速度 77 | 78 | 还有很多有用的 flag 79 | 80 | 比如说你想,哦,我想要点上下文 81 | 82 | 这样就是结果附近的五行 83 | 84 | 你就能知道那个 `import` 大概在哪 85 | 86 | 它周围都是什么代码 87 | 88 | 【635】这里找这个 `import` 不怎么实用 89 | 90 | 但是比如,你要查你在哪调用了函数 91 | 92 | 它就很给力 93 | 94 | 我们也可以搜索,比如说 95 | 96 | 一个更高级的用法 97 | 98 | 解释一下,`-u` 是不忽略隐藏文件[*] 99 | *神奇的双重否定?? 100 | 101 | 有时候你想忽略隐藏文件 102 | 103 | 但如果你想查找配置(config)文件 104 | 105 | 【643】它们大多是默认隐藏的,这样子 106 | 107 | 然后,这里不是打印匹配内容 108 | 109 | 而我们要求它,呃,这大概是 110 | 111 | 我觉得 `grep` 做不到的 112 | 113 | 就是,我要你打印出所有 114 | 115 | 不匹配这个模式的内容 116 | 117 | 【648】这么做可能挺奇怪的 118 | 119 | 接着往下看…… 120 | 121 | 这里这个模式(pattern)是一个 122 | 123 | 小巧的正则表达式 124 | 125 | 意思是,匹配行首有 `#!` 的内容 126 | 127 | 这是个 `shebang` 128 | 129 | 也就是说我们在搜索没有 shebang 的文件 130 | 131 | 【655】这里还给了一个 `-t sh` 是说 132 | 133 | 只搜索 `.sh` (后缀名)的文件 134 | 135 | 因为实际来讲 Python 或者文本文件 136 | 137 | 少了 shebang 也没问题 138 | 139 | 这里它告诉我们 140 | 141 | 「哦,`mcd.sh` 明显少了个 shebang」 142 | 143 | 【660】我们还可以……它有一些好用的 flag 144 | 145 | 比如加上这个 `--stats` flag 146 | 147 | 它也会得到这些结果 148 | 149 | 不过它还会告诉我们 # REVIEW 150 | 151 | 比如成功匹配了多少行 152 | 153 | 查找了多少行多少文件 154 | 155 | 打印了多少 byte,等等 156 | 157 | 类似 `fd` 这种,有时候单会一个工具 158 | 159 | 其实不是很好 160 | 161 | 【669】实际上有很多类似 `ripgrep` 的工具 162 | 163 | 比如 `ack`,也是 `grep` 一个替代 164 | 165 | 还有 `ag`,那个“银子”搜索器[*] # REVIEW 166 | *这个工具的标语就是「The Silver Searcher」,疑似借梗漫威快银 167 | 168 | 这些基本都是可替换的 169 | 170 | 有可能你用某个操作系统 171 | 172 | 发现它有某一个,没有另一个 173 | 174 | 只要知道你可以用这些工具就行 175 | 176 | 最后我想讲讲,怎么去做一些 177 | 178 | 不是去找文件或者代码 179 | 180 | 【680】而是找一些已经执行过的命令 # REVIEW 181 | 182 | 首先,显然可以用上箭头 183 | 184 | 慢慢儿翻你的历史记录 185 | 186 | 你可能也觉得,这不是很有效率 187 | 188 | 所以 bash 有一些更简单的方法 189 | 190 | 有个 `history` 命令 191 | 192 | 它会打印出你的命令历史记录 193 | 194 | 这里我用的 zsh,所以只会打印一部分 195 | 196 | 如果我想从开头全打印出来 197 | 198 | 这就不管是啥,都给打印出来了 199 | 200 | 因为这记录挺多的 201 | 202 | 【690】比如我只关心用了 `convert` 的命令 203 | 204 | 它把某种类型的文件转到另一种 205 | 206 | 呃 抱歉,是图片类型(而非所有文件) 207 | 208 | 这里就是所有的结果 209 | 210 | 所有匹配上这个子字符串的 211 | 212 | 更进一步,基本上所有 Shell 213 | 214 | 默认都会把 `Ctrl`+`R` 这个组合键 215 | 216 | 设成(按执行时间)倒序搜索(backward search) # REVIEW 217 | 218 | 这里我们打开倒序搜索 219 | 220 | 然后输入 `convert` 221 | 222 | 就会找到与之匹配的命令 223 | 224 | 如果我们接着按 `Ctrl`+`R` 225 | 226 | 就会倒着往前搜索匹配的命令 227 | 228 | 也可以重新执行命令 229 | 230 | 另一个相关的是 231 | 232 | 【705】你可以用这个叫 `fzf` 的高级货 233 | 234 | 它就是一个模糊搜索工具 235 | 236 | 像是一个交互式的 `grep` 237 | 238 | 举个栗子,先 `cat` 一下我们这个 239 | 240 | `example.sh` 241 | 242 | 就会打印到标准输出 243 | 244 | 然后我们用管道连到 `fzf` 上 245 | 246 | 先是显示出所有行 247 | 248 | 然后可以实时地输入要找的字符串 249 | 250 | `fzf` 有一个好,就是 251 | 252 | 如果你打开默认绑定,它会绑定到 253 | 254 | 【716】Shell 的 `Ctrl`+`R` 执行上 255 | 256 | 然后你就可以动态的查看 257 | 258 | 历史记录里转换 `favicon` 的命令 259 | 260 | 它还是模糊匹配的 261 | 262 | 比起在 `grep` 里默认你得 263 | 264 | 写正则表达式才能搞定这种情况 265 | 266 | 这里就只打 `convert` 和 `favicon` 267 | 268 | 它就能尝试最优的扫描策略 269 | 270 | 在给定的行里匹配出来 271 | 272 | 最后就是这个工具 273 | 274 | 你们已经看到了我一直用的 275 | 276 | 【726】免去打那些又臭又长的命令 277 | 278 | 就是这个历史记录子串查找[*] 279 | *查找历史记录中,当前输入是其子串的命令 280 | 281 | 当我在 Shell 里输入的时候 282 | 283 | (呃,这个忘记介绍了) 284 | 285 | (就是 fish,我以为我提到过的)[*] 286 | *fish 和 zsh 也是一种 Shell 287 | 288 | fish 和 zsh 都有很好的实现 289 | 290 | 它们可以,当你打字的时候 291 | 292 | 动态搜索你的历史记录 293 | 294 | 找到前缀相符的一个命令 295 | 296 | 如果匹配的那条不相符了也会变化 297 | 298 | 【738】如果你按一下右箭头 299 | 300 | 就能选中这个命令,就可以重新执行 301 | 302 | 我们已经见识了一大堆东西了 303 | 304 | 我觉得我还剩下几分钟 305 | 306 | 我打算讲几个工具 307 | 308 | 可以快速列出目录和定位目录的 309 | 310 | 确实可以用 `-R` 递归列出目录结构 311 | 312 | 但是这样不是很好受 313 | 314 | 呃 我轻易读不懂这一堆鬼玩意 315 | 316 | 有个叫 `tree` 的工具可以 317 | 318 | 用比较友好的格式打印这些东西 319 | 320 | 它也会用彩色文本,基于…… 321 | 322 | 【746】就比如说 `foo` 是蓝的 323 | 324 | 代表是个目录 325 | 326 | 这个是红的,因为有执行权限 327 | 328 | 我们还可以再深入些 329 | 330 | 有些好用的,比如最近有个 331 | 332 | `broot`,也是做差不多的事情 333 | 334 | 但是比起列出所有文件 335 | 336 | 比如说在 `bar` 里我们有 337 | 338 | 【752】`a` 一直到 `j` 这些文件 339 | 340 | 它会提示「还有更多文件,未列出」 341 | 342 | 我还可以开始输入,它也会 343 | 344 | 模糊匹配这些文件 345 | 346 | 我可以快速的选择和定位 347 | 348 | 所以还是说 349 | 350 | 知道有这些东西挺好 351 | 352 | 你就不会浪费太多时间 353 | 354 | 【760】还有就是,我记得我装了 355 | 356 | 也是一个,你可能希望你的操作系统该带的 # REVIEW 357 | 358 | 比如 Nautilus 或者 mac 的访达[*] 359 | *前者是 GNOME 的文件管理器,后者是 macOS 的 360 | 361 | 有一个交互式的界面 362 | 363 | 你可以用箭头定向,浏览 364 | 365 | 这也许有点过犹不及了 366 | 367 | 但如果在里面走一圈 # REVIEW 368 | 369 | 你能够很快速地理解目录结构 370 | 371 | 而且基本所有这些工具 372 | 373 | 去看看选项列表 374 | 375 | 它都可以让你编辑和复制文件什么的 376 | 377 | 最后附加一项就是你怎么 378 | 379 | 去到一个位置 380 | 381 | 【769】我们有 `cd`,挺好用的 382 | 383 | 可以让你进入很多地方 384 | 385 | 但是如果你能快速去到 386 | 387 | 你最近访问的,或者经常访问的地方 388 | 389 | 还是挺美妙的 390 | 391 | 这个有挺多实现方式的 392 | 393 | 你可以考虑,哦,我可以做标签 394 | 395 | 我可以在 Shell 里设置别名 396 | 397 | 这个挑时间会讲 398 | 399 | 【778】还有符号链接…… 400 | 401 | 不过当前来说 402 | 403 | 写了这些工具的程序员们 404 | 405 | 他们搞出了一个特别好的方式 406 | 407 | 有一个是用叫「autojump」的项目…… # REVIEW 408 | 409 | ……也许我这里没有……? 410 | 411 | 呃啊。没事儿,我会在讲到 412 | 413 | 命令行环境的时候再讲 414 | 415 | 我觉得大概是我禁用了 `Ctrl`+`R` 416 | 417 | 影响到了脚本的其他部分 418 | 419 | 我认为现在如果任何人 420 | 421 | 【787】有相关问题的话 422 | 423 | 如果有东西我没讲清楚的话 424 | 425 | 我非常乐于解答 426 | 427 | 没有的话,我们搞了一堆习题 428 | 429 | 差不多都是这些主题的 430 | 431 | 我们鼓励你去做一下 432 | 433 | 以及办公时间来找我们 434 | 435 | 我们可以帮你搞明白习题 436 | 437 | 或者没说清楚的一些 bash 的细节 -------------------------------------------------------------------------------- /ch2/[252-478]CHN.txt: -------------------------------------------------------------------------------- 1 | 【252】Vim 的核心思想之一,模式编辑 2 | 3 | 我们可以先讲一些基础 4 | 5 | 像如何打开这个文本编辑器 6 | 7 | 如何打开文件,保存文件 8 | 9 | 等等的操作 10 | 11 | 所以,这是一个基于命令行的编辑器 12 | 13 | 尽管它有一些图像化变体 14 | 15 | 启动这个程序的方式是键入 `vim` 16 | 17 | 【260】你可能会注意到 18 | 19 | 在我的屏幕左下角 20 | 21 | 你能看到我键入的内容 22 | 23 | 这在这节课的后面会很有用 24 | 25 | 当我在输入 Vim 命令时 26 | 27 | 【265】我会说我正在写什么 28 | 29 | 你们也会在屏幕上相应地看到 30 | 31 | 所以当我按下 `^C` 32 | 33 | 屏幕也会显示 `^C` 34 | 35 | 文本大到都能看见吗?很好 36 | 37 | 【270】所以我们要打开 Vim 38 | 39 | 只需要在命令行中键入 `vim` 40 | 41 | 大多数系统已经预装好了 Vim 42 | 43 | 如果你没有的话 44 | 45 | 也可以用包管理器装一个 46 | 47 | 【275】`vim` 也可以接收参数 48 | 49 | 即当我们想用它直接编辑特定的文件 50 | 51 | 而不是先打开程序再打开文件 52 | 53 | 例如,我在这个目录里有一个文件 54 | 55 | 【280】这个文件事实上是这节课的笔记 56 | 57 | 我可以键入 `vim editors.md [ENTER]` 58 | 59 | 砰的一下,编辑开始 60 | 61 | 在本课中 62 | 63 | 我不是完全在 Exton 默认配置中运行 Vim [*] 64 | *一个基于 Ubuntu 的操作系统 65 | 66 | 【285】我已经额外做了一些小配置 67 | 68 | 使它在默认条件下更加美观 69 | 70 | 比如左边有行号 71 | 72 | 再比如底部有更多的状态信息 73 | 74 | 【290】如果你也想要这些设置 75 | 76 | 我在笔记里放了个链接 77 | 78 | 可以让你有一个 79 | 80 | 更稍稍合理的设置 81 | 82 | 当你打开 Vim,你该怎么做? 83 | 84 | 【295】像我之前说的,Vim 以 Normal 模式启动 85 | 86 | 所以如果我直接开始键入 87 | 88 | 比如按下 `x`,它没有被输入到 buffer 89 | 90 | 你可以看到左上方的光标 91 | 92 | 事实上我删掉了一个字符 93 | 94 | 【300】这是因为我在 Normal 模式 95 | 96 | 而不是 Insert 模式 97 | 98 | Insert 模式基本上是你以前惯用的 99 | 100 | 所有其它的文本编辑器 101 | 102 | 比如某处有一个光标 103 | 104 | 【305】你键入字符 105 | 106 | 它进入 buffer 107 | 108 | 而在 Vim 的 Normal 模式中 109 | 110 | 你可以按下 `i` 进入 Insert 模式 111 | 112 | 所以看,我按下了 `i` 113 | 114 | 【310】之后底部的通知显示 `--INSERT--` 115 | 116 | 左下总会显示你所在的模式 117 | 118 | 但 Normal 模式除外 119 | 120 | Normal 模式下它是空白的 121 | 122 | 现在是 Insert 模式,如果我按下 `x` 123 | 124 | 【315】它就会插入到文本缓冲区(text buffer) 125 | 126 | 我可以按下 `Backsapce` 或者其它的字母键 127 | 128 | 现在我的编辑器可以表现得 129 | 130 | 和你想的其它编辑器一样 131 | 132 | 【320】现在如果我要结束插入字符 133 | 134 | 如何退回 Normal 模式呢 135 | 136 | 对,没错 137 | 138 | 我按一下 `Esc` 键 139 | 140 | 这个就是我击键显示工具 141 | 142 | 表示 `Esc` 键的图标 143 | 144 | 【325】要意识到这一点 145 | 146 | Vim 有一个观点,使用鼠标是低效的 147 | 148 | 当你的手正在键盘上 149 | 150 | 再去移到鼠标上其实浪费时间 151 | 152 | 对吧 153 | 154 | 【330】当你编程的时候 155 | 156 | 你就不想浪费这点时间 157 | 158 | 就比如你写什么写到一半沉醉其中 159 | 160 | 取而代之的是,Vim 的所有功能 161 | 162 | 都可以仅通过键盘调用 163 | 164 | 【335】所有你可能习惯了的操作 165 | 166 | 像打开文件什么的操作 167 | 168 | 比如「文件->打开」,「文件->保存」这种 169 | 170 | 我会用键盘实现它们 171 | 172 | 是怎么做到的? 173 | 174 | 【340】这是通过 Vim 的其它模式 175 | 176 | 模式在那边的黑板上,实现的 177 | 178 | 尤其是通过命令行(Command Line)模式 179 | 180 | 在 Normal 模式下,如果你按下 `:` [*] 181 | *这里实际上类似输入 `:` 一样,需要按 `Shift` 182 | 183 | 你会发现光标 184 | 185 | 【345】——我想我的显示工具现在挡住了 186 | 187 | 好在它已经没了 188 | 189 | 光标跳转到了底部,左下方 190 | 191 | 它显示了我刚刚按下的 `:` 192 | 193 | 现在我可以输入命令 194 | 195 | 【350】你可以认为它很像命令行 Shell 196 | 197 | 也就是我们之前几天所讲的 198 | 199 | 不过这是 Vim 的命令行 Shell 200 | 201 | 你在这里输入 Vim 命令 202 | 203 | 【355】而不是 Shell 命令 204 | 205 | 这里有很多内置命令 206 | 207 | 可以搞定你惯常做的所有事 208 | 209 | 例如,你可能很想知道一个命令 210 | 211 | 那就是如何退出编辑器 212 | 213 | 【360】你会发现如果在 Normal 模式下 214 | 215 | 我可以按下 `Esc` 216 | 217 | 从命令行模式返回 Normal 模式 218 | 219 | 我按下 `^c` 220 | 221 | 不像很多其它程序,Vim 不会退出 222 | 223 | 【365】那我如何退出 Vim? 224 | 225 | 我可以按下 `:`,进入命令行模式 226 | 227 | 我就可以键入命令 `quit` 228 | 229 | Q-U-I-T,你会看到 230 | 231 | ——嘿,我得把这个破玩意移到中间去 232 | 233 | 【370】看,显示 `:quit` 234 | 235 | 按一下 `Enter`,Vim 就会退出 236 | 237 | 我可以再打开 Vim 238 | 239 | 事实上这个命令有简写,`:q ` 240 | 241 | 效果是一样的 242 | 243 | 【375】这里还有一堆类似的命令 244 | 245 | 其它应当知道的,比较方便的命令有 246 | 247 | 如何保存文件? 248 | 249 | 假设我做些编辑,像 `hello world` 250 | 251 | 按下 `i` 进入 Insert 模式 252 | 253 | 【380】——让我重按一下 254 | 255 | 我按下 `i` 进入 Insert 模式 256 | 257 | 现在,我可以用下箭头 258 | 259 | ——呜哇,好像翻车了 260 | 261 | Jon,你能搞定它吗? 262 | 263 | 啊,别介意 264 | 265 | 【385】假设我下到这行 266 | 267 | 按下 `i` 进入 Insert 模式 268 | 269 | 输入一些文本, 270 | 271 | 之后按下 `Esc` 回到 Normal 模式 272 | 273 | 我该如何保存这个文件? 274 | 275 | 【390】这个要用别的命令完成 276 | 277 | 按下 `:` 进入命令行模式 278 | 279 | 之后键入 `w` ,按下 `Enter` 280 | 281 | `w` 代表写(Write) 282 | 283 | 底部会随之出现 `editors.md` 284 | 285 | 啥啥啥的已写入 286 | 287 | 【395】这样就保存文件了 288 | 289 | 如果我键入 `:q` 退出再重新打开文件 290 | 291 | 你会看到修改被保存了 292 | 293 | 还有一些其它的 294 | 295 | ——实际上有非常多的 Vim 命令 296 | 297 | 【400】适用于各种情况 298 | 299 | 我现在只多介绍一点儿 300 | 301 | 一个很有用的命令是 `help` 302 | 303 | `:help` 304 | 305 | 键入 `:help`,之后输入 306 | 307 | 【405】特定键或特定命令 308 | 309 | 来获得他们的说明 310 | 311 | 比如我想知道 `:w` 的作用 312 | 313 | 我可以键入 `:help :w` 314 | 315 | 【410】之后就会显示 `:w` 或 `:write` 的文档 316 | 317 | 如果我键入 `:q`,会关闭这个 window 318 | 319 | 并且返回之前的状态 320 | 321 | 注意到 `:help :w` 与 `:help w` 不同 322 | 323 | 因为后者的 `w` 代表 324 | 325 | 【415】你在 Normal 模式下按 `w` 键 326 | 327 | 只是这里的 `w` 键 328 | 329 | 没有 `:` 330 | 331 | 如果我查看 `:w` 的帮助 332 | 333 | 这是 `w` 这条「命令」的资料[*] 334 | *请注意区分命令行模式下的 **命令** 和 Normal 下的按键 335 | 336 | 【420】现在你们掌握了一些使用的基础 337 | 338 | 对吧?你可以打开编辑器 339 | 340 | 用它编辑一个特定文件 341 | 342 | 按下 `i` 进入 Insert 模式 343 | 344 | 输入一些文本,按下 `Esc` 返回 345 | 346 | 【425】Normal 模式,键入 `:w` 保存文件 347 | 348 | `:q` 退出,所以 349 | 350 | 你们已经会了必要的基本原理 351 | 352 | 用于使用 `vim` 编辑文件,尽管效率有些低下 353 | 354 | 目前为止有问题吗? 355 | 356 | 【430】是的,后面这位 357 | 358 | 所以问题是 359 | 360 | Normal 模式的优越性是什么? 361 | 362 | 我会讲更多细节的 363 | 364 | 再等 5 分钟就好 365 | 366 | 简而言之,Insert 模式仅仅用于输入文本 367 | 368 | 【435】我在 Insert 模式 369 | 370 | 我可以输入文本,但是 371 | 372 | 我在编程时实际上花费很多时间 373 | 374 | 在我的文件中移动,做一些小修改 375 | 376 | 我移到这里 377 | 378 | 【440】比如我可能想把这个 `https` 链接 379 | 380 | 修改为 `http` 381 | 382 | 我可以做一些小修改,比如这 383 | 384 | 在 Normal 模式下 385 | 386 | 之后五分钟我们会看到更多相关内容 387 | 388 | 【445】好问题! 389 | 390 | 还有其它问题吗?好的,那就继续 391 | 392 | 另一个有必要知道的是 393 | 394 | 我认为是一些高阶的关于 395 | 396 | 【450】Vim 的 buffer,window 与 tab 的模型 397 | 398 | 很可能是这种情况,就是 399 | 400 | 无论你之前在用什么程序 401 | 402 | 像 Sublime Text 或者 VS Code 之类的 403 | 404 | 你都可以在其中打开多个文件 405 | 406 | 【455】对吧? 407 | 408 | 你会打开很多选项卡 409 | 410 | 并且有多个编辑器,窗口 411 | 412 | Vim 也有这些东西的概念 413 | 414 | 但是,它的模型 415 | 416 | 【460】和大多数其它程序有些不同 417 | 418 | Vim 提供一组打开的 buffer 419 | 420 | ————这是它对打开的文件的描述 421 | 422 | 也就是说,它有一些打开的文件 423 | 424 | 除此之外,你可以有很多 tab 425 | 426 | 【465】tab 里可以有 window 427 | 428 | 这种奇怪的机制使得 Vim 429 | 430 | 相较于你以前用的程序 431 | 432 | 有些许不同 433 | 434 | 就是它的 buffer 与 window 435 | 436 | 【470】不一定是一一对应的关系 437 | 438 | 比如我现在可以举个例子 439 | 440 | 后面我会展示对应的组合键 441 | 442 | 但是现在来说,你可以做的一件事是 443 | 444 | 【475】创建两个不同的 window 445 | 446 | 所以我在上边儿有个 window,下边儿又有一个 447 | 448 | 注意,两个 window 打开了同一个文件 449 | 450 | 如果我在这里做一些编辑 451 | -------------------------------------------------------------------------------- /ch0/[1-274]CHN.txt: -------------------------------------------------------------------------------- 1 | 好的,感谢大家的到来。 2 | 3 | 这门课是《计算机科学教育中缺失的一课》[*] 4 | *(The Missing Semester of Your CS Education) 5 | 6 | 至少这是我们给这门课起的名字 7 | 8 | 如果你来这儿不是上这门课的 9 | 10 | 那你走错地儿了 11 | 12 | 先说一下,我们大约得讲一个小时 13 | 14 | 我想先和你们谈一下为什么我们开这门课 15 | 16 | 这门课源于 Anish, Jose 和我在做 MIT 很多课程的 TA[*] 时 17 | *Teaching Assistant,助教 18 | 19 | 观察到的一个现象: 20 | 21 | 【13】 基本上,我们这些计算机科学家都清楚 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 | 【36】你们应该知道这门课的结构 50 | 51 | 将是一系列,总共 11 次的一小时课 52 | 53 | 每个都涉及一个特定的主题 54 | 55 | 你还可以去看看课程网站 56 | 57 | 上面列出了课程列表和每场的日期 58 | 59 | 它们大致上将互相独立 60 | 61 | 所以你可以只来听你感兴趣的那些 62 | 63 | 但是,我们会假设你一直在跟着上课 64 | 65 | 所以我们讲到后面的时候,就比如说 66 | 67 | 我就不会一遍遍地教 bash 怎么用了 68 | 69 | 【50】上课的时候,我们也会立马把讲义、课程录像发到网上 70 | 71 | 当然我们现在还没上传,显然得等到上完课之后 72 | 73 | 将由我(Jon),Anish ,他坐在那个位置,还有 Jose 来上课 74 | 75 | Jose[*] 今天没到场,不过他会上明天的课 76 | *西班牙口音英语警告 77 | 78 | 提醒一下,在这门仅仅 11 × 1 小时的课里 79 | 80 | 我们会试图涉及很大的范围 81 | 82 | 所以我们的节奏相对会快些 83 | 84 | 但是如果你觉得跟不上了,别介意打断我们 85 | 86 | 【65】如果你觉得有什么东西值得多花点时间讲的 87 | 88 | 也请告诉我们 89 | 90 | 有问题也请打断我们 91 | 92 | 并且每次下课之后我们都在 32 号楼 9 层有办公时间 93 | 94 | 是在计算机科学楼,也就是 Stata Center (史塔特科技中心)[*] 95 | *那个长得特别标新立异的解构主义建筑。搜个图片欣赏下? 96 | 97 | 如果你来 Gates Tower 九楼的休息室 [*] 98 | *这楼从 4 层以上分成两半:Gates Tower 和 Dreyfoos Tower 99 | 100 | 你可以来做做我们每节课后给的练习 101 | 102 | 或者直接问我们别的问题,有关课上讲的内容的 103 | 104 | 或者怎么高效地利用电脑 105 | 106 | 【79】因为我们只有很有限的时间 107 | 108 | 我们不可能讲到所有工具的所有细节 109 | 110 | 只聚焦有趣的工具和怎么使用它们 111 | 112 | 我们也没必要深挖那些细节 113 | 114 | 搞明白它们是怎么运行的,或者使用上的小细节 115 | 116 | 但是如果你对这些有问题,请来问我们 117 | 118 | 讲到的绝大部分工具的是我们用了成年累月的东西 119 | 120 | 我们也许能给告诉你拿这些工具 121 | 122 | 能做什么额外的有趣的活计 123 | 124 | 要利用我们在这儿上实体课的优势 125 | 126 | 我不太想说「让我们开足马力冲吧」 127 | 128 | 但是这门课要讲的,今天这一讲要讲的 129 | 130 | 是很多基础的东西 131 | 132 | 我们也会假设后面的课里你们都了解了这些 133 | 134 | 【100】比如怎么用 Shell 和你的终端(Terminal) 135 | 136 | 对于不熟悉这些的,我马上会解释这些是啥 137 | 138 | 然后我们就会很快速地狂奔到更高级的工具上 139 | 140 | 讲讲怎么用它们 141 | 142 | 现在已经可以从课程讲义上看到所有要讲的主题 143 | 144 | 【107】所以我们今天的课要讲 Shell 145 | 146 | Shell 将会是你和电脑交互的最主要方式之一 147 | 148 | 一旦你想脱离开那些可视化的界面让你做的 149 | 150 | 然后去做点别的 151 | 152 | 其实可视化界面[*]挺受限的 153 | *这里用了 Visual Interfaces,一般也说 GUI (Graphical User Interface) 154 | 155 | 【115】因为它们只让你做一些按钮啊, 156 | 157 | 滑条啊,输入框啊能按出来,输进去的事情 158 | 159 | 但这些基于文本的工具经常是能互相耦合的 160 | 161 | 也有无数种方式能把它们结合起来 162 | 163 | 或者写个程序让它们自动化 164 | 165 | 这就是这门课介绍命令行工具 166 | 167 | 和基于文本的工具的理由 168 | 169 | 【126】Shell 则是你去做这些活计的地方 170 | 171 | 对于不熟悉 Shell 的同学们 172 | 173 | 大多数平台都会提供某种 Shell[*] 174 | *这里的 Shell 最好理解为命令行界面(CLI, Command Line Interface) 175 | 176 | 在 Windows 上基本是 Powershell[*] 177 | *缩写 pwsh,Windows 7 之前没有内置 pwsh。详细请参看网络资料。 178 | 179 | 而且也有其他 Shell 可以用 180 | 181 | 在 Windows 和 Linux 上你会找到成堆的终端(Terminal) 182 | 183 | 这些是能显示 Shell 的(文本)窗口 184 | 185 | 【135】你能找到很多不同种类的 Shell 186 | 187 | 其中最普遍的是 bash,或者叫 Bourne Again SHell 188 | 189 | 因为它非常普遍,我们这些课里就主要用它了 190 | 191 | 如果你用 MacOS,你大概也有 bash 192 | 193 | 【142】如果你打开终端(Terminal)应用看看 194 | 195 | 也许还是个旧版本的 (‾◡◝) 196 | 197 | 如果你想用哪个平台上课,随意 198 | 199 | 不过注意,在我们教课的时候 200 | 201 | 大部分内容会是以 Linux 为中心 202 | 203 | 尽管大部分工具在所有平台上都能用 204 | 205 | 如果你想安装一个终端和 Shell,却不知道该咋办 206 | 207 | 我们很乐意在办公时间教你 208 | 209 | 或者去网上搜搜也挺容易 210 | 211 | 比如输入你的平台,加上像是终端啊,这种关键词 212 | 213 | 就能找到教程 214 | 215 | 好,当你打开一个终端的时候 216 | 217 | 你会看到一个长得像这样的东西 218 | 219 | 也就是说通常是在顶上有单单一行 220 | 221 | 这个一般就叫「命令行提示符」(Shell Prompt) 222 | 223 | 【159】你可以看到我的命令行提示符看起来像这样 224 | 225 | 这里是我的用户名,还有我用的机器的名字 226 | 227 | 还有我当前所在路径(path) 228 | 229 | 我们晚一会儿再说说路径(path) 230 | 231 | 然后就是这个闪烁的东西,它在要求我输点啥 232 | 233 | 这个就是「命令行提示符」(Shell Prompt),在这里告诉 Shell 你想做什么 234 | 235 | 你可以自由地自定义这个提示符 236 | 237 | 所以当你在你机器上打开它的时候 238 | 239 | 它可能不会长得和这个一样 240 | 241 | 如果你设置了一下,那可能会是这样 242 | 243 | 【172】或者会是各种千奇百怪的样子 244 | 245 | 这节课我们不会讲太多关于自定义 Shell 的东西 246 | 247 | 我们晚些时候再说 248 | 249 | 我们只讲怎么用 Shell 去做有用的事 250 | 251 | 这是你电脑上和 Shell 交互的主要文本界面 252 | 253 | 你可以在命令行提示符上写命令 254 | 255 | 【182】命令相对都是一些直白的东西 256 | 257 | 比如,通常是带着参数(argument)执行程序 258 | 259 | 大概类似这样的事情 260 | 261 | 嗯,有一个我们可以运行的程序是 `date` 262 | 263 | 【187】输入 `date` 然后按一下回车 264 | 265 | 不出所料,它会告诉你日期和时间 266 | 267 | 你也可以带着参数(arguments)执行一个程序 268 | 269 | 这是一种修改程序行为的方式 270 | 271 | 比如说有一个程序叫 `echo` 272 | 273 | `echo` 只是打印出你传给它的参数 274 | 275 | 【195】而参数呢,则是一些紧随程序名后面的 276 | 277 | 用空格分隔开的东西 278 | 279 | 我们可以打出 `hello` 然后它就会回显 `hello` 280 | 281 | 也许你不会觉得很惊奇 282 | 283 | 【200】但这是参数最基础的运用了 284 | 285 | 你会注意到一件事就是 286 | 287 | 我提到参数是被空格分隔的 288 | 289 | 你也许会好奇,如果我想传一个多单词的参数会怎样 290 | 291 | 你可以拿引号把东西括起来 292 | 293 | 所以你可以像这样 `echo "Hello` 空格 `world"` 294 | 295 | 这样 `echo` 程序会收到一个 296 | 297 | 【208】字符串参数 `Hello world`,中间还有个空格 298 | 299 | 呃,你也可以用单引号做这件事情 300 | 301 | 单双引号的区别 302 | 303 | 我们等到讲 bash scripting 的时候再说 304 | 305 | 你也可以用单转义字符(Escape character) 306 | 307 | 比如这样,`Hello\ World` 308 | 309 | 这样也能正常起作用 310 | 311 | 【217】关于如何给参数、变量转义,解析和加括号 312 | 313 | 我们之后会涉及到 314 | 315 | 不过别放在心上,这都是小菜一碟 316 | 317 | 只要记好用空格分隔参数就行 318 | 319 | 【223】因此如果你想做什么事情 320 | 321 | 比如建一个叫 my photos 的目录 322 | 323 | 你不能只是输入 `mkdir my photos` 324 | 325 | 这样系统会建立两个目录 326 | 327 | 一个叫 `my`,一个叫 `photos` 328 | 329 | 显然这不是你想要的 330 | 331 | 【229】现在你也许会好奇一点 332 | 333 | Shell 是怎么知道这些程序在哪儿的 334 | 335 | 当我输入 `date` 或者 `echo` 的时候 336 | 337 | Shell 怎么知道这些程序要做什么 338 | 339 | 答案就是你的程序,呃 340 | 341 | 【235】就是你的电脑,有很多内置(Built-in)程序 342 | 343 | 它们是系统自带的 344 | 345 | 就比如你的机器可能内嵌了终端程序 346 | 347 | 或者比如 Windows Explorer,或者某些浏览器 348 | 349 | 也内嵌了很多围绕终端工作的程序 350 | 351 | 这些程序位于你的文件系统(File System) 352 | 353 | 而 Shell 有办法知道某个程序存放在哪 354 | 355 | 说白了就是它有一个搜索程序的方法 356 | 357 | 这借助一个叫寒境,啊,环境变量的东西完成 358 | 359 | 环境变量就类似编程语言里的变量 360 | 361 | 说白了 Shell,或者就说 bash 本身 362 | 363 | 【253】就是一种程序设计语言 364 | 365 | 你输入的提示符(Prompt)不仅能带参运行程序 366 | 367 | 你也可以写出 while 循环,for 循环,条件 368 | 369 | 等等所有这些 370 | 371 | 甚至可以定义函数,甚至变量 372 | 373 | 所有这些你能在 Shell 里做的事情 374 | 375 | 关于 Shell Scripting 的下一讲会有涉及 376 | 377 | 现在我们先关注环境变量(Environment Variable) 378 | 379 | 【265】环境变量是 Shell 本就设定好的 380 | 381 | 无论何时启动 Shell 都无须重新设置 382 | 383 | 有一堆东西会被设定好 384 | 385 | 【270】比如哪里是 home 目录,你的用户名是什么 386 | 387 | 也有一个是为了做这件事情的 388 | 389 | 那就是 `PATH` 变量 390 | -------------------------------------------------------------------------------- /ch2/[966-1265]CHN.txt: -------------------------------------------------------------------------------- 1 | 【966】那么,这些是 Vim 的修饰符 2 | 3 | 这样我们讲过 `i` 了 4 | 5 | 但是没讲过 `a` 6 | 7 | 如果我敲 `da(` 进去 8 | 9 | 它就会删除括号里的内容,也包括括号 10 | 11 | 所以说 `i` 是内部(inside) 12 | 13 | `a` 是周围、包含的意思(around/including) 14 | 15 | 【972】好,所以这基本就是你在 Vim 的接口里 16 | 17 | 能够相互结合的若干类命令 18 | 19 | 那么,现在还有关于这些理念 20 | 21 | 或者接口即编程语言这个纲领的问题吗 22 | 23 | 好,那为了展示这个编辑器的强大 24 | 25 | 我们快速演示一遍 26 | 27 | 这能让我们体会到这个工具有多快 28 | 29 | 【981】甚至和我们思考得一样快 30 | 31 | 好,看这里有一个啥也没输出的 32 | 33 | 坏掉的 fizz_buzz 34 | 35 | 额,但愿你们都听说过 fizz_buzz 36 | 37 | 如果没听说过的话,我简单提一下 38 | 39 | 【987】fizz_buzz 是一种输出 1 到 n 的练习 40 | 41 | 当数字能被 3 整除时,输出 fizz 42 | 43 | 当数字能被 5 整除时,输出 buzz 44 | 45 | 当数字同时被 3 和 5 整除时, 输出 fizzbuzz 46 | 47 | 如果这些条件都没满足,就直接输出数字 48 | 49 | 所以,你的输出看上去应该长这样: 50 | 51 | `1, 2, fizz, 4, buzz` 52 | 53 | 【996】但是,如果我运行这个程序,它啥也没输出 54 | 55 | 我把程序放在左边,终端放在右边 56 | 57 | 好,现在这里有一堆问题 58 | 59 | 其中一个就是 `main` 函数从来没被调用过 60 | 61 | 那我们就开始修这个大锅 62 | 63 | 现在看好我是怎么修这个锅的 64 | 65 | 看好了,我按键的次数到底能少到什么程度 66 | 67 | 大写 `G` 把光标跳到文件底 68 | 69 | 【1005】`o` 在下面新建一行 70 | 71 | 现在我就可以往里输入内容了 72 | 73 | 我现在处在 Insert 模式 74 | 75 | 我已经输入完我想改的了 76 | 77 | `Esc` 回到 Normal 模式 78 | 79 | 如果我键入 `:w` 80 | 81 | 在命令行模式下执行写入 82 | 83 | 让我回到这儿 84 | 85 | 好,至少现在程序运行时输出东西了 86 | 87 | 这个程序还有另一个问题 88 | 89 | 【1013】它是从 0 开始的,而不是 1 90 | 91 | 咱再来修修这个锅 92 | 93 | 移到这个 `range()`…… 94 | 95 | 唔,这个 `range()` 函数 96 | 97 | 它不该从 `0` 开始一直到 `limit` 98 | 99 | 它应该从 `1` 开始到 `limit + 1` 100 | 101 | 我还没给你们看在 Vim 中怎么搜索 102 | 103 | 按下 `/` 104 | 105 | 额,我得把这个关掉重启 106 | 107 | 如果你按下 `/` ,它就开始搜索 108 | 109 | 【1023】如果我输入 `range` 110 | 111 | 我的光标会从当前位置挪到第一个 `range` 那 112 | 113 | 这样能让你把光标高效地移到想要的地方 114 | 115 | `ww` 继续挪两个字符 116 | 117 | `i` 进入 Insert 模式 118 | 119 | 添加 `1, ` 然后按 `Esc` 回到 Normal 模式 120 | 121 | 【1030】这是 Vim 里面一个很常见的情况 122 | 123 | 你先待在 Normal 模式里 124 | 125 | 然后把光标挪到某个地方,进入 Insert 模式 126 | 127 | 做一点小改动,然后再用 `Esc` 回到 Normal 模式 128 | 129 | Normal 模式就像是家一样—— 130 | 131 | 这大概就是你呆的时间最长的地方 132 | 133 | 我还想加上一个 +1 134 | 135 | 那就用 `e` 挪到这个词后面 136 | 137 | 摁 `a` 代表追加,输入 `+1` 然后 `Esc` 退出 138 | 139 | 【1039】好,我们已经解决了这个问题 140 | 141 | 这程序还有个毛病 142 | 143 | 就是被 3 和 5 整除的时候输出的都是 fizz 144 | 145 | 咱再来解决下这个问题 146 | 147 | 用 `/fizz` 来找出 fizz 148 | 149 | 然后按下 `n` 来找到下一个匹配的结果 150 | 151 | 再用 `ci'` 来改变两个单引号中间的内容 152 | 153 | 它就删除了 fizz 然后进入 Insert 模式 154 | 155 | 然后我就可以随意输入了 156 | 157 | 【1049】然后再按下 `Esc` 回到 Normal 模式 158 | 159 | 好极了,我们解决掉了这个问题 160 | 161 | 这程序还有另一个问题 162 | 163 | 当数字是 15 的倍数时 164 | 165 | 它在独立的两行输出 fizz 和 buzz 166 | 167 | 【1053】我们再来修一下这个锅 168 | 169 | 先把光标往下移动到这一行 170 | 171 | 我实际上并不担心程序的内容是什么 172 | 173 | 有些程序写的很蠢,但这并不重要 174 | 175 | 只需注意我在 Vim 中按下了哪些键 176 | 177 | 【1060】这些按键让我在 Vim 中高效的修改程序 178 | 179 | 现在我的光标在这一行 180 | 181 | 我按下 `$` 来把光标移动到行尾 182 | 183 | 按下 `i` 来进入 Insert 模式 184 | 185 | 然后把这些东西输入进去 186 | 187 | 按 `Esc` 来回到 Normal 模式 188 | 189 | 【1066】现在我想在下面的 print 如法炮制 190 | 191 | 请你跟我这样做,`jj.` [*] 192 | *我就跟你这样做 193 | 194 | 在 Vim 中,按下 `.` 会重复之前的编辑命令 195 | 196 | 要想完成重复性工作,这是个好东西 197 | 198 | 【1072】还不用重复地输入相同内容 199 | 200 | 上次它插入了 `, end=''` 201 | 202 | 所以当我按下 `.` 时,它就会在这一行再来一遍 203 | 204 | 【1076】我想,完成这个示例程序的最后一步是 205 | 206 | 我们要修复一下(数值边界问题) 207 | 208 | 使得这个程序可以接受一个命令行参数 209 | 210 | 而不是用写死在程序里的这个 `10` 211 | 212 | 【1082】那接下来我按 `gg` 来回到顶上 213 | 214 | 用大写 `O` 在上面新建一行 215 | 216 | 然后我会输入 `import sys` ,回车 217 | 218 | 然后再用 `Esc` 键回到 Normal 模式 219 | 220 | 然后我想把光标挪到 `10` 那个地方 221 | 222 | 那我就 `/10` ,这样我就直接到那去了 223 | 224 | 【1089】用 `ci(` 来在括号里面编辑 225 | 226 | 现在我就可以把我想要的输入进去了 227 | 228 | 做完这些,我的程序就能好好的 fizzbuzz 了 229 | 230 | 我想我还有一个地方没改,但这已经不重要了 231 | 232 | 这已经能说明你可以迅速的做出许多改动了 233 | 234 | 【1097】那么,关于这个例子 235 | 236 | 还有我们讲的这种思路还有什么问题吗 237 | 238 | *(同学关于命令行环境的问题)* 239 | 240 | 啊,这个大概会在周二详细地讲 241 | 242 | 我这里把 Vim 放在左边,Shell 放在右边 243 | 244 | 然后外边套了个 Tmux 245 | 246 | 这个问题的一个变体会是 247 | 248 | 【1104】比如怎么在 Vim 的 window 之间切换 249 | 250 | 你可以在讲义里找到答案 251 | 252 | 还有关于这些的组合键 253 | 254 | 可以对付打开的多个,相同或者不同的 window 255 | 256 | 还有问题吗? 257 | 258 | ***c**hange 和 **d**elete 有什么区别* 259 | 260 | 【1110】啊,问得好 261 | 262 | 删除(`d` 键)接受一个操作范围并删除 263 | 264 | 但始终保持在 Normal 模式内 265 | 266 | 所以你得以边四处移动,边删除一些内容 267 | 268 | 而更改(`c` 键)和删除很像 269 | 270 | 都接受操作范围,对文件内容做同样之事 271 | 272 | 但是在删除了内容之后 273 | 274 | 【1117】会立即把你切到 Insert 模式里 275 | 276 | 所以它省了你额外敲一个字符的时间 277 | 278 | 在这举个例子,如果我想删掉这个 `main` 279 | 280 | 可以用 `dw` 来删掉这个词 281 | 282 | 但是如果我随便再敲个字符,比如 `j` 283 | 284 | 它就会把光标向下移动 285 | 286 | 如果我先撤销 287 | 288 | 我还可以用 `cw` “重新组织语言” 289 | 290 | 【1123】它实际上把我拐到 Insert 模式里了 291 | 292 | 然后我就可以随便输入点啥东西 293 | 294 | `dwi` 和 `cw` 一模一样 295 | 296 | 但是 `cw` 少敲一次键盘 297 | 298 | 我们在讲义里还有个链接,是关于 Vim golf 的 299 | 300 | 大概就是,他们在线上做了个游戏 301 | 302 | 在这个游戏里你会接到一个编辑任务 303 | 304 | 你的目的就是找到最少敲几次键盘能搞定它 305 | 306 | 【1134】这小游戏玩着简直上瘾 307 | 308 | 所以我建议你们有空再玩[*] 309 | *别用来上课摸鱼 310 | 311 | 我还看到有人举手问问题? 312 | 313 | *重复最后一次操作那个的命令是啥键来着?* 314 | 315 | 英文句号,对 316 | 317 | 这是最有用的 Vim 命令之一,这问题好 318 | 319 | 还有别的问题吗 320 | 321 | 好,咱现在大约还剩五分钟 322 | 323 | 【1141】我会简短的讲一点东西 324 | 325 | 在讲义里会有相关的细节 326 | 327 | 你们一定看看讲义里这一部分的内容 328 | 329 | 总而言之, Vim 是程序员的文本编辑器 330 | 331 | 正因如此,它也是高度程序化的 332 | 333 | 不仅仅是它的接口是一种程序语言 334 | 335 | 它的众多使用方法也是如此 336 | 337 | 【1151】你可以依据偏好,调整它的诸多选项 338 | 339 | 你还可以给它安装很多有用的插件 340 | 341 | 通过硬盘上的 `.vimrc` 文件配置 Vim [*] 342 | * Windows 下这个东西叫 `_vimrc` 343 | 344 | 【1156】你会见到很多基于 Shell 的工具 345 | 346 | 都采用这种惯例 347 | 348 | 也即,以纯文本文件作为它的配置文件 349 | 350 | 所以如果我编辑它… 351 | 352 | 现在你电脑上可能没有这个文件 353 | 354 | 但是我已经下载下来了 355 | 356 | 我们给诸位写了一个默认的 vimrc 配置文档 357 | 358 | 并且挂在课程网站上了 359 | 360 | 你可以用那个文档入门 361 | 362 | 如果我运行 `vim ~/.vimrc` 363 | 364 | 我在这能看到一堆注释 365 | 366 | 【1166】然后是各种命令 367 | 368 | 譬如一般我们都想开启语法高亮 369 | 370 | 或者开个行数显示 371 | 372 | 如果我们不打开一些东西 373 | 374 | 比如让我删掉显示行数的设置 375 | 376 | 【1171】如果我删了这些配置然后重启 Vim 377 | 378 | 你会发现我左边的行号都没了 379 | 380 | 简而言之,你可以配置很多东西 381 | 382 | 我们给了你一个很基础的配置文档 383 | 384 | 试图把 Vim 中默认开启的怪怪的东西关掉 385 | 386 | 但是没把太多的个人设置强加于你 387 | 388 | 【1181】当然,我们三个都是 Vim 老鸟了 389 | 390 | 我们也都有重度个人定制版 `.vimrc` 391 | 392 | 如果你们想参考借鉴的话 393 | 394 | 我们也把自己用的配置文档放到了链接里 395 | 396 | 还有成千上万的人们在 GitHub 上 397 | 398 | 分享了他们自己的 `.vimrc` 399 | 400 | 所以你们能从很多地方得到灵感 401 | 402 | 【1189】这方面还有很多很酷的博客 403 | 404 | 你还可以用插件去拓展 Vim 405 | 406 | 【1192】插件能做各种各样有趣之事 407 | 408 | 比如很多编辑器自带的模糊文件查找 409 | 410 | 所以你可以在一个弹窗里 411 | 412 | 输入文件的,或准确或模糊的名称 413 | 414 | 然后迅速的找到它 415 | 416 | 【1200】还有插件能可视化地撤销历次改动 417 | 418 | 文件管理器等等插件 419 | 420 | 所以我们在课程网站上挂了一些我们钟爱的插件 421 | 422 | 所以我强烈建议你熟悉怎样安装插件 423 | 424 | 因为它不会花多少时间 425 | 426 | 并且有些插件真的很酷 427 | 428 | 最后一个话题,我会在下课之前简单提两嘴 429 | 430 | 【1210】这个话题是 Vim 模式与其它软件 431 | 432 | 事实证明很多程序员 433 | 434 | 都对 Vim 的接口感到激动不已 435 | 436 | 所以他们在其他工具中做了相似的功能 437 | 438 | 举个例子,我可以配置使用 Vim 的模式 439 | 440 | 去运行 Python 的命令行交互环境(REPL) 441 | 442 | 我可以在这输入内容 443 | 444 | 如果我按下 `Esc` 键 445 | 446 | 就回到交互环境的 Normal 模式了 447 | 448 | 【1219】我可以前后移动光标 449 | 450 | 也可以按下 `x` 来删除一些东西 451 | 452 | 或者是用 `cw` 改一个词 453 | 454 | 这些 Vim 的好东西都有 455 | 456 | 不仅是 Python 命令行交互环境 457 | 458 | 我也把我的终端弄成这样 459 | 460 | 【1225】我随便输入点东西 461 | 462 | 然后按下 `Esc` 键就到了 Normal 模式 463 | 464 | 光标移到这,我也能进 Visual 模式 465 | 466 | 可以选中一块文本,然后按下 `~` 来修改大小写 467 | 468 | 【1230】怎样开启像 bash, zsh, fish 等的 Vim 模式[*] 469 | *这一大堆都是各种的命令行 Shell 470 | 471 | 这些也在讲义放了链接 472 | 473 | 还有很多基于 GNU Readline 的软件 474 | 475 | 比如 Jupyter Notebook 476 | 477 | 如果讲义上面没写的话 478 | 479 | 你还可以搜一下 480 | 481 | 因为很多人都喜欢这种功能 482 | 483 | 【1239】如果你笃定的想学习这些东西 484 | 485 | 我想,你应该把你的所有工具都打开 Vim 模式 486 | 487 | 首先会让你对这工具有更深的理解 488 | 489 | 其次,当你熟练掌握了 Vim 之后 490 | 491 | 别的工具都会如臂使指 492 | 493 | 我想对 Vim 的简介就到这里了 494 | 495 | 【1248】还有一些挺好的材料今天这一讲没讲到 496 | 497 | 但是在讲义里都有 498 | 499 | 最后我极力推荐你们完成今天的练习 500 | 501 | 至少对我个人来说,我认为学习这个编辑器 502 | 503 | 是这系列课程的内容中最让你受益的部分 504 | 505 | 【1258】好,今天的课就上到这里了 506 | 507 | 我们明天见 508 | 509 | 顺便提一下,明天的课换成了数据整理 510 | 511 | 现在把周四和周二的课换了一下 512 | 513 | 在我们的课程网站上也能看出来 514 | 515 | 【1266】如果有人只上其中一节 516 | 517 | 注意别上错课 518 | -------------------------------------------------------------------------------- /ch5/[1-365]CHN.txt: -------------------------------------------------------------------------------- 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 | 答疑在 32 号楼 G9 休息室 28 | 29 | 32 号楼也被称作史塔特科技中心[*] 30 | * (MIT内一栋著名的复合式建筑) 31 | 32 | 这栋楼由两个结构构成 33 | 34 | 分别是 G 塔和 D 塔 [*] 35 | * (G 塔是 Gates Tower,D 塔是 Dreyfoos Tower [维基百科]) 36 | 37 | 我们是在 G 塔的九楼答疑 38 | 39 | 所以如果你坐电梯上去 40 | 41 | 那你一出电梯,休息室就在你右手边 42 | 43 | 所以今天呢 44 | 45 | 我们要讨论下版本控制系统 46 | 47 | 一开始我先想了解下 48 | 49 | 之前有哪些人 50 | 51 | 用过版本控制系统 52 | 53 | 如果你之前用过 Git、Subversion、Mercurial [*] 54 | * 这三个词分别是无用的饭桶、破环颠覆、汞制剂,这代表着人们对版本管理的态度 55 | 56 | 或者其他的版本控制系统 57 | 58 | 那你就举个手 59 | 60 | 啊好,看来用过的人还不少 61 | 62 | 所以我就不讲版本控制系统相关的 63 | 64 | 一些比较烂大街的东西了 65 | 66 | 那么,我们将很快深入 `git` 的细节 67 | 68 | 比如它的数据模型和内在实现 69 | 70 | 但我们先大概讲一下 71 | 72 | 版本控制系统是用来 73 | 74 | 跟踪源代码、文件、文件夹修改的工具 75 | 76 | 正如“版本控制系统”这个名字所说 77 | 78 | 这些工具帮我们追踪 79 | 80 | 我们对一组文件做出的历史更改 81 | 82 | 并且还可以让团队合作更加便捷 83 | 84 | 所以它在一群人一起开发软件项目时 85 | 86 | 非常有用 87 | 88 | 版本控制系统通过(记录)一串快照的方式 89 | 90 | 来追踪对一个文件夹和它的内容的更改 91 | 92 | 所以你就能把整个文件夹的状态 93 | 94 | 还有里面的软件项目之类的玩意 95 | 96 | 像拍照一样定格下来 97 | 98 | 所以你就有一串快照 99 | 100 | 每个快照都描述了 101 | 102 | 你跟踪的这个顶层目录下的 103 | 104 | 所有文件和文件夹的信息 105 | 106 | 然后版本控制系统维护的东西 107 | 108 | 除了你对这些文件内容的实际更改之外 109 | 110 | 还有一堆元数据(Metadata) 111 | 112 | 这是为了让我们弄清楚 113 | 114 | “某个文件的某个修改是谁写的” 115 | 116 | “某个修改是什么时候发生的” 117 | 118 | 所以版本控制系统维护了 119 | 120 | 像是作者、提交时间戳之类的元数据 121 | 122 | 你也可以给这些快照附加其他你想要的额外信息 123 | 124 | 为什么版本控制系统作用很大呢? 125 | 126 | 就算你自己一个人做项目的时候 127 | 128 | 它也很有用 129 | 130 | 你可以用它来看 131 | 132 | 你之前写的旧版本的代码 133 | 134 | 可以通过看提交信息来弄明白 135 | 136 | 为什么当时要做这个更改 137 | 138 | 还可以通过分支来 139 | 140 | 不冲突地同时做多个工作 141 | 142 | 还可以同时修 bug 和开发新功能 143 | 144 | 并使其互不影响 145 | 146 | 所以版本控制系统是无价之宝 147 | 148 | 对于自己一个人写东西或者写小型项目也是如此 149 | 150 | 就比如我认为这门课的讲师 151 | 152 | 除了做研究和写大型软件项目时 153 | 154 | 他们甚至还在安排作业和课程设计 155 | 156 | 这些小型的东西上也用 `git` 157 | 158 | 当然和其他人一起工作的时候 159 | 160 | 版本控制是一个非常强大的工具 161 | 162 | 它可以解决不同人写同一份代码时 163 | 164 | 向代码中更新新内容所引发的内容冲突 165 | 166 | 它就很有用 167 | 168 | 所以在你自己工作 169 | 170 | 或者和其他人一起的时候 171 | 172 | `git` 都是个很强大的工具 173 | 174 | 此外它还有个比较香的功能 175 | 176 | 就是可以让你回答一些在其他情况下 177 | 178 | 比较难回答的问题 179 | 180 | 比如这个软件项目里某个模块是谁写的 181 | 182 | 或者谁改了某个软件项目里的某行代码 183 | 184 | 以及为什么要改这行代码 185 | 186 | 这行代码是什么时候由谁改的 [*] 187 | * 哈哈 原来是自己改的 188 | 189 | 此外版本控制系统还有一些 190 | 191 | 其他的很强大的功能 192 | 193 | 在今天课程的末尾我们会讲 194 | 195 | 或者如果来不及讲了 196 | 197 | 你也可以自己去找课程笔记 [*] 198 | * 在课程官网 199 | 200 | 好 现在我们假设你有个已经 201 | 202 | 搞了好多年的项目 203 | 204 | 然后你注意到这个项目的 205 | 206 | 一些莫名其妙的东西不工作了 207 | 208 | 比如有些单元测试过不了了 209 | 210 | 并且它不是这个时候才挂掉的 211 | 212 | 它可能在之前某个时间挂了 213 | 214 | 但是你并不确定什么时候有的这个毛病 215 | 216 | 好,版本控制系统有个 217 | 218 | 自动查证这种事情的方法 219 | 220 | 就比如你去给它一份单元测试的代码 221 | 222 | 现在的程序会在这个单元测试上挂掉 223 | 224 | 但是你知道过去的某个版本上,它不会挂 225 | 226 | 然后它就可以去二分查找代码历史 [*] 227 | 228 | * 一种算法,可以在值单调的列表(数组)里快速定位元素 229 | 230 | 搞清楚具体是哪一次代码更改让它挂了 231 | 232 | 所以只要你知道该如何正确使用这些工具 233 | 234 | 你就能发现很多又强又炫酷的功能 235 | 236 | 事实上“市面”上有很多版本控制系统 [*] 237 | * 有开源的,也有商用收费的 238 | 239 | 而 `git` 则成为了版本控制的某种事实标准 240 | 241 | 所以这就是为什么 242 | 243 | 今天的课我们主要讲 `git` 244 | 245 | 我想给你们看个梗图 246 | 247 | 这个图刚刚还在屏幕上来着……让我重新找找 248 | 249 | 所以这是个 xkcd 漫画 250 | 251 | 这个漫画因为它的绘画风格而出名 252 | 253 | 我来给你们朗读一下 254 | 255 | “这是 `git`,它能通过一个优美的 256 | 257 | 图论里面的树型模型 [*] 258 | * (此处应是指 Merkle Tree, Git 基于这种数据结构设计,尽管 Git 使用的版本应当被称为 Merkle DAG) 259 | 260 | 来跟踪项目里的分工工作” 261 | 262 | “好,那么我们怎么用呢” 263 | 264 | “不知道,就记住这些 Shell 命令 265 | 266 | 使用它们来同步数据 267 | 268 | 如果出错了就先把你的文件存到别的地方 269 | 270 | 然后删掉项目,再下一份新的” 271 | 272 | 我猜接下来你们有些人不想举手 273 | 274 | 不过还是要说,如果你之前这样用过 `git` 275 | 276 | 请举一下手 277 | 278 | 我在学这个工具的时候肯定这样干过 279 | 280 | 所以你们中的好多人之前也这么干过 281 | 282 | 所以这门课的目的就是 283 | 284 | 让你学点东西 285 | 286 | 以后你就不会这样用 `git` 了 287 | 288 | 不幸的是,就像这张图画的那样 289 | 290 | `git` 的接口设计得很可怕 291 | 292 | 它提供的行为抽象全是坑 293 | 294 | 因为这个原因,我们相信 295 | 296 | 从接口开始,自顶向下学 `git` 的话 297 | 298 | 可能不是个好方法 299 | 300 | 并且这样子可能会让你更加迷惑 301 | 302 | 不过你也可以像这个图里那样 303 | 304 | 记住一大堆命令 305 | 306 | 并且把它们想成魔法咒语 307 | 308 | 至于这些命令为什么能用也不管 309 | 310 | 反正它就是能用 311 | 312 | 但之后你就得像这个图里这样 313 | 314 | 只要有什么东西出问题了 315 | 316 | 你就得删了重下 317 | 318 | 所以尽管 `git` 的接口很丑 319 | 320 | 但它的内在设计和想法事实上都很美 321 | 322 | 这种丑陋的接口只能死记硬背 323 | 324 | 但其内在的优雅设计 325 | 326 | 其实是可以被理解的 327 | 328 | 一旦理解了 `git` 的内部实现 329 | 330 | 也就是它不那么复杂的数据模型 331 | 332 | 然后你就可以学习接口了 333 | 334 | 你可能得记住一些东西 335 | 336 | 但是你可以通过理解 337 | 338 | 命令是怎么操作内在数据结构的 339 | 340 | 去理解这些命令到底在干啥 341 | 342 | 我们今天讲 `git` 的安排 343 | 344 | 第一部分是关于数据模型的 345 | 346 | 几乎都是理论 347 | 348 | 讲一下文件和文件夹,历史快照的模型 349 | 350 | 然后我们会尝试几个 `git` 命令 351 | 352 | 最后在(讲义的)资源和练习里 353 | 354 | 我将会给你一些教程 355 | 356 | 这些教程能教给你所有细节 357 | 358 | 因为实际上你要学很多不同的命令 359 | 360 | 对今天的教学计划 361 | 362 | 有什么不明白的吗? 363 | 364 | 好,所以我们现在开始吧 365 | 366 | 可能有好多比较临时的 367 | 368 | 能拿来做版本控制的手段 369 | 370 | 并且我猜部分同学可能之前这样做过 371 | 372 | 就比如你有些文件 373 | 374 | 或者一个文件夹里有一堆不同的文件 375 | 376 | 它们组成了一个软件项目 377 | 378 | 然后你想跟踪这些文件的更改 379 | 380 | 你显然可以,比如说 381 | 382 | 每天把整个文件夹复制一份 383 | 384 | 然后给它标个时间戳 385 | 386 | 当你需要和其他人合作的时候 387 | 388 | 你可以把把整个文件夹压成一个压缩包 389 | 390 | 然后用电子邮件发给别人 391 | 392 | 然后如果你和你的朋友 393 | 394 | 正在写同一个软件的两个不同的部分 395 | 396 | 你们就可以各写各的 397 | 398 | 然后你们中的一个人要把压缩包 399 | 400 | 拿邮件发给另一个人 401 | 402 | 然后手动把对方给代码做的修改 403 | 404 | 用某种方法复制下来 405 | 406 | 粘贴到自己的代码里 407 | 408 | 然后你的程序里 409 | 410 | 就同时有了你们两个人写的代码 411 | 412 | 如果你们之前这样合作过的话 413 | 414 | 举一下手 415 | 416 | 我之前肯定这样干过的,你们也有好多 417 | 418 | `git` 可以让我们避免干这种 419 | 420 | 并不怎么优美的工作 421 | 422 | 它有个经过仔细考虑的模型 423 | 424 | 这个模型可以让一些 425 | 426 | 你想做的事变得更加简单 427 | 428 | 比如追踪你自己在项目里的更改 429 | 430 | 或者和其他人合作等等 431 | 432 | 所以 `git` 的这个设计得很棒的模型 433 | 434 | 给你提供了像是分支、合作 435 | 436 | 还有从其他人那合并更改 437 | 438 | 诸如此类令人舒适的功能 439 | 440 | `git` 的模型 441 | 442 | 是一组经过抽象,放在某个顶层目录下的 443 | 444 | 文件和文件夹 445 | 446 | 你可能对这种抽象挺熟悉的 447 | 448 | 因为你电脑上也是这样放文件和文件夹的 449 | 450 | 所以我给你们来个例子 451 | 452 | 你有个根目录文件夹 453 | 454 | 我就叫它 root 吧 455 | 456 | 这个目录里面有个叫 `foo` 的文件夹 457 | 458 | 然后 `foo` 里面又有个 459 | 460 | 叫 `bar.txt` 的文件 461 | 462 | 这个文件里可以写点东西 463 | 464 | 比如 `hello world` 465 | 466 | 回头看看这个根目录 467 | 468 | 里面有个目录 469 | 470 | 那它自然也可以有个文件 471 | 472 | 这个文件里也可以 473 | 474 | 写点东西 475 | 476 | 好了,挺简单的 477 | 478 | `git` 用了个术语——树(tree)来表示 479 | 480 | 这些文件和文件夹以及这个最顶上的结构 481 | 482 | 就叫做树 483 | 484 | 所以这是个文件夹 485 | 486 | 然后这些我们平时叫文件的东西被称作 `blob` 487 | 488 | 好,现在我们有个文件和文件夹构成的模型 489 | 490 | 并且这个模型还是递归定义的 491 | 492 | 树里面可以有别的树 493 | 494 | 树里面还可以有文件 495 | 496 | 但显然文件里不能有树 497 | 498 | 好,现在我们有一个由文件和文件夹构成的模型 499 | 500 | 然后最顶上这个我标出来的东西 501 | 502 | 就是我们正在跟踪的 503 | 504 | 被称作为根目录的东西 505 | 506 | 就比如你电脑上的一个文件夹 507 | 508 | 这个文件夹对应着一个软件项目 509 | 510 | 现在我们有了文件和文件夹的模型 511 | 512 | 那么我们用什么模型来维护历史更改呢 513 | 514 | 你可以想想一种比较简单的方式 515 | 516 | 就是你给这整棵树像照相一样“拍”一个快照 517 | 518 | 然后历史更改就是 519 | 520 | 由快照所构成的线性序列 521 | 522 | 你基本上可以把这个东西想成 523 | 524 | 你有一大堆标上了日期和时间的 525 | 526 | 文件夹的拷贝 527 | 528 | 但 `git` 的实现 529 | 530 | 并不是用的这样简单的线性模型 531 | 532 | 它用了一个更有意思的东西 533 | 534 | 你之前可能听说过这个术语 535 | 536 | `git` 有一个用有向无环图 [*] 537 | *(Directed Acylic Graph,一种图论结构) 538 | 539 | 来维护更改历史的模型 540 | 541 | 这个挺像一个听起来比较有意思的数学名词 542 | 543 | 但它实际上完全没有那么复杂 544 | 545 | 在 `git` 里,每个快照都有一些父节点 546 | 547 | 然后我们想知道 548 | 549 | 众多更改之间的前后关系 550 | 551 | 假设我现在用圆圈 552 | 553 | 来表示一个单独的快照 554 | 555 | 这个圈表示整棵树里的所有内容 556 | 557 | 我整个项目的所有文件和文件夹 558 | 559 | 正处在某个状态 560 | 561 | 然后我改了一些文件 562 | 563 | 这样它就在另一个状态了 564 | 565 | 然后用我又加了一些文件,它就又去了另一个状态 566 | 567 | 每个状态都有个指针 568 | 569 | 指向它之前的状态 570 | 571 | 到现在为止,这还是个线性的历史更改 572 | 573 | 但是我们现在做点有意思的事 574 | 575 | 你可以从某个快照分叉你的历史更改 576 | 577 | 也就是说 578 | 579 | 我想要基于这个版本的更改 580 | 581 | 像这样创建一个新的快照 582 | 583 | 通过这种历史更改模型 584 | 585 | 你可以在做一些类似于 586 | 587 | 这是我的开发主线 588 | 589 | 我到这了 590 | 591 | 现在我有两个不同的任务 592 | 593 | 一个是我想给我的项目 594 | 595 | 加点有意思的新特性 596 | 597 | 这个我要写好几天 598 | 599 | 但是除此之外,有人报给了我一个 bug 600 | 601 | 我需要找出来这个 bug,然后赶快修好它 602 | 603 | 我不想在同一条开发流程线里 604 | 605 | 同时干这两个工作 606 | 607 | `git` 有它自己的一套 608 | 609 | 能够在更改历史上创建两个分支的方法 610 | 611 | 通过这种方法,可以临时地并行工作 612 | 613 | 还能让它们不相互影响 614 | 615 | 所以我现在创建一个 616 | 617 | 表示我的项目中 618 | 619 | 某个正常工作的状态的基底快照 620 | 621 | 然后从这,我可以创建一个 622 | 623 | 拿来实现新功能的快照 624 | 625 | 所以这里就有了 626 | 627 | 基底项目加上一个新功能 628 | 629 | 所以我在它上面写个 `+feature` 630 | 631 | 然后类似的,从这里 632 | 633 | 我可以回到最初的快照 634 | 635 | 因为我不想在写新功能的同时修 bug 636 | 637 | 然后可以到这,再去修 bug 638 | 639 | 建一个不同的快照 640 | 641 | 这个快照只包括 bug 修复 642 | 643 | 但是不包括新功能 644 | 645 | 最后等我写完这两块东西后 646 | 647 | 我想把它们一起合并回我正常情况下的代码 648 | 649 | 这样我的代码就既有新功能又有 bug 修复 650 | 651 | 所以最后我可以建一个新快照 652 | 653 | 它合并了这两个快照里的更改 654 | 655 | 然后这个新快照 656 | 657 | 以刚刚的两个快照作为父节点 658 | 659 | 然后这个版本 660 | 661 | 既有新特性,又有 bug 修复 662 | 663 | 所以 `git` 这种历史模型 664 | 665 | 相比于线性的一串快照来说 666 | 667 | 就显得很炫酷 668 | 669 | 我想要能支持同时写多个不同的东西 670 | 671 | 然后也能把我在不同的并行分支里 672 | 673 | 对它们的修改合并起来。有问题吗? 674 | 675 | _[同学的提问]_ 676 | 677 | 好,这是个很不错的点 678 | 679 | 似乎当你合并东西的时候 680 | 681 | 你会遇到一些你没想到的错误 682 | 683 | 你可以设想 684 | 685 | 这个新特性改了一些代码 686 | 687 | 一起把 bug 修了 [*] 688 | * ???这真的可能吗??? 689 | 690 | 或者你想, bug 修复把新特性搞坏了 691 | 692 | 或者一些类似的东西 693 | 694 | -------------------------------------------------------------------------------- /ch1/major.sub: -------------------------------------------------------------------------------- 1 | {1}{1}30.000000 2 | {41}{78}好的,欢迎回来[*] 3 | {103}{227}今天我们要分别谈谈与 Shell 有关的两个话题 4 | {234}{344}首先我们要讲 Shell 脚本,这主要和 bash 有关 5 | {345}{482}这将会是你们大多数人一开始在 macOS 6 | {483}{535}或者大多数 Linux 里接触的 Shell 7 | {536}{565}bash 是它们默认的 Shell 8 | {577}{621}并且其他 Shell ,像是 zsh 9 | {622}{683}对其有良好的向后兼容,这非常棒 [*] 10 | {684}{796}然后我们要谈谈特别方便的其他 Shell 工具 11 | {797}{883}你们可以用它避免重复执行任务 12 | {884}{947}像是寻找一段代码 13 | {948}{1027}或者一些犄角旮旯的文件 14 | {1028}{1101}bash 里也有许多很棒的内置命令 15 | {1102}{1202}它们可以帮你做这些事情 16 | {1257}{1338}昨天我们已经介绍了 Shell 17 | {1339}{1399}和它的一些特性 18 | {1400}{1478}就比如说你怎样执行一个命令 19 | {1479}{1517}或者重定向它们(的输入输出) 20 | {1518}{1569}今天我们将多讲一些 Shell 脚本中的 21 | {1570}{1718}操纵变量的语法,控制流以及函数 22 | {1738}{1840}例如,一旦你接触 Shell 23 | {1859}{1916}说你想要定义一个变量 24 | {1917}{2076}那是你学习编程语言第一个接触的事情[*] 25 | {2081}{2188}你可以执行像是 `foo=bar` 26 | {2209}{2341}并且我们可以通过 `$foo` 操作 `foo` 的值 27 | {2365}{2430}它是 `bar`,完美~ 28 | {2455}{2533}你需要多加注意的一点是 29 | {2534}{2601}当你面对着 bash 的时候[*] 30 | {2602}{2647}空格至关重要[*] 31 | {2648}{2809}主要是因为空格是用于分隔参数的保留字符 32 | {2810}{2926}例如,一些像是 `foo = bar` 的操作不管用 33 | {2939}{3065}Shell 会告诉你它为什么无法生效 34 | {3066}{3118}这是它说因为 `foo` 命令无法生效 35 | {3127}{3188}比如这里提示 `foo` 不存在 36 | {3189}{3284}实际发生的是,我们没有将 `bar` 赋给 `foo` 37 | {3309}{3495}而是用 `=` 和 `bar` 作为参数调用了 `foo` 程序 38 | {3537}{3649}通常,你需要特别关注这类问题 39 | {3657}{3718}比如说一些带有空格的文件名 40 | {3719}{3919}你需要小心地把他们用引号引起来 41 | {3920}{4036}让我们更深入些,探讨一下怎样在 bash 中处理字符串 42 | {4037}{4095}我们有两种定义字符串的方法: 43 | {4096}{4221}可以用双引号定义字符串 44 | {4222}{4345}或者可以用单…… 45 | {4346}{4372}呃,对不起 46 | {4402}{4440}使用单引号(定义) 47 | {4507}{4591}虽然对于纯文本字符串,这两种方式是等价的 48 | {4592}{4669}但是对于其余的字符串,则不相同 49 | {4670}{4899}例如,我们执行 `echo "Value is $foo"` 50 | {4920}{5034}其中 `$foo` 将被展开为字符串 51 | {5035}{5131}并且替换掉 Shell 中 `foo` 变量的值 52 | {5140}{5297}如果我们用单引号来重复实验 53 | {5298}{5368}我们仅仅会得到原样的 `$foo` 54 | {5381}{5456}单引号中的变量将不会被替换 55 | {5460}{5534}脚本真的十分易于编写 56 | {5543}{5646}这个就好比…它有点像你可能更熟悉的 Python 57 | {5658}{5722}你可能没意识到这点 58 | {5738}{5832}这就是给变量赋值的方式 59 | {5833}{5945}我们稍后还会看到 bash 也有控制流技术 60 | {5946}{6008}像是 for 循环、while 循环 61 | {6009}{6131}另一个重点是,我们可以定义函数 62 | {6132}{6257}我们可以访问我在此处定义的函数 63 | {6258}{6379}这里我们已经定义了 `mcd` 函数 64 | {6380}{6483}到目前为止,我们已经了解 65 | {6484}{6567}如何利用管道连接并执行几个命令 66 | {6568}{6627}昨天简要地说过 67 | {6628}{6754}但是很多时候你想先做一件事,然后另一件事 68 | {6755}{6933}有点像我们这里的顺序执行 69 | {6934}{7015}看这里,例如,我们可以调用 `mcd` 函数 70 | {7116}{7207}首先我们调用 `mkdir` 命令 71 | {7208}{7275}它会创建一个目录 72 | {7299}{7384}在这里,`$1` 就像是一个特殊变量 73 | {7385}{7427}这就是 bash 运作的方式 74 | {7428}{7569}类似于其他脚本语言的 `argv` 75 | {7570}{7705}数组 `argv` 的第一项将包含参数[*] 76 | {7706}{7741}在 bash 中同样的东西是 `$1` 77 | {7742}{7871}一般来说,bash 中许多 `$` 开头的东西 78 | {7872}{7893}它们都是被保留的[*] 79 | {7894}{7963}我们之后会看到更多的例子 80 | {7985}{8051}一旦我们创建了文件夹, 81 | {8052}{8115}我们就 `cd` 进去 82 | {8142}{8236}这其实是个挺常见的流程 83 | {8250}{8350}实际上,我们直接将其键入到 Shell 84 | {8351}{8408}它就会起作用,定义这个函数 85 | {8419}{8566}但是有时候,把代码写到文件里更好 86 | {8567}{8696}然后我们就可以 `source` 这个文件 87 | {8697}{8808}这就会在 Shell 中加载脚本并执行 88 | {8827}{8904}虽然现在看起来无事发生 89 | {8905}{9040}但是现在 Shell 中已经定义了 `mcd` 函数 90 | {9057}{9169}因此我们现在能,比如说执行 `mcd test` 91 | {9170}{9289}就从 `tool` 目录移到了 `test` 目录 92 | {9290}{9402}我们创建了文件夹并且进入其中 93 | {9481}{9565}还有什么。结果是... 94 | {9566}{9667}我们可以通过 `$1` 访问第一个参数 95 | {9668}{9789}这里有许多被保留的命令[*] 96 | {9790}{9902}例如 `$0` 将会是脚本的名字 97 | {9903}{9989}`$2` 到 `$9` 是 bash 脚本的 98 | {9990}{10120}第二个到第九个参数 99 | {10121}{10261}有一些保留字可以直接在 Shell 中使用 100 | {10262}{10510}例如 `$?` 能获取上条命令的错误代码(返回值) 101 | {10574}{10612}我会简要解释这些 102 | {10613}{10779}再比如,`$_` 会获取上条命令的最后一个参数 103 | {10780}{10903}因此,我们搞定这个的另一种方式是 104 | {10904}{11023}我们可以执行 `mkdir test` 105 | {11024}{11102}与其重写一遍 `test` 106 | {11103}{11200}不如我们用 `$_` 访问上条命令的一部分 107 | {11201}{11353}也就是最后一个参数 108 | {11354}{11422}它将被替换成 `test` 109 | {11423}{11489}现在我们进去了 `test` 目录 110 | {11568}{11628}像这样的例子很多,你应当熟悉他们 111 | {11629}{11770}另一个我经常用的叫做 `bang bang`(`!!`) 112 | {11771}{11899}每当,比如说,你试着创建某些东西 113 | {11900}{11935}但你没有足够权限的时候 114 | {11936}{11973}正是这个东西的用武之处 115 | {11974}{12031}然后,你可以执行 `sudo !!` 116 | {12045}{12142}`!!` 会被你刚刚尝试的命令取代 117 | {12143}{12203}现在来试一下 118 | {12204}{12259}现在它就提示我输入密码 119 | {12260}{12315}因为我有了 sudo 权限 120 | {12524}{12559}之前我提到了,呃,命令错误什么的 121 | {12560}{12591}昨天我们看过,总体来说…… 122 | {12592}{12632}一个进程有许多方式 123 | {12633}{12741}和其他进程或命令交互 124 | {12756}{12825}我们提到了标准输入(流) 125 | {12826}{12863}它就是好比…… 126 | {12864}{12909}(程序)从标准输入获取各种东西 127 | {12910}{12945}然后把东西输到标准输出里 128 | {12946}{13016}还有些东西更有意思 129 | {13025}{13141}也有一个标准错误(流) 130 | {13157}{13249}如果你程序出错了 131 | {13250}{13284}你想输出错误却不污染标准输出 132 | {13288}{13322}就可以写进这个流 133 | {13323}{13384}也有错误代码(error code)这种东西 134 | {13393}{13493}而且它普遍存在于很多编程语言 135 | {13494}{13606}是一种告诉你整个运行过程 136 | {13610}{13628}结果如何的方式 137 | {13650}{13714}所以,比如我们试试 138 | {13727}{13792}`echo "Hello"` 139 | {13798}{13903}然后查一下错误代码的值,它是 `0` 140 | {13904}{13968}`0` 是因为一切正常,没有出问题 141 | {13969}{14038}这种 `0` 退出码和它在[*] 142 | {14055}{14153}比如 C 这种语言里,代表的意思一样 143 | {14166}{14245}`0` 就代表所有事情正常,没出错误 144 | {14262}{14317}然而,有时候事情会出错 145 | {14332}{14485}比如有时候,我们尝试在 `mcd` 脚本里 146 | {14486}{14545}`grep foobar` 的话 147 | {14553}{14635}现在查一下值,就是 `1` 148 | {14636}{14732}这是因为我们试着在 `mcd` 脚本里 149 | {14733}{14813}搜索 `foobar` 字符串,而它不存在 150 | {14829}{14927}所以 `grep` 什么都没输出 151 | {14928}{14997}但是通过反馈一个 `1` 的错误代码 152 | {14999}{15058}它让我们知道这件事没成功 153 | {15071}{15137}有一些有意思的命令,比如 154 | {15148}{15314}`true` 的错误代码始终是 `0` 155 | {15315}{15445}`false` 的错误代码则是 `1` 156 | {15466}{15578}还有比如这些逻辑运算符 157 | {15579}{15697}你可以用来做条件判断 158 | {15698}{15772}比如……其实你也有 `if-else` 159 | {15773}{15793}之后我们会说 160 | {15800}{15854}但是现在你可以做一些 161 | {15870}{15967}比如 `false`,然后 `echo "Oops fail"` 162 | {15968}{16088}这里有两个被或运算符链接的命令 163 | {16089}{16218}这里 bash 要做的是,执行第一个命令 164 | {16219}{16378}如果第一个命令失败,再去执行第二个[*] 165 | {16379}{16443}这里我们有这个结果 166 | {16444}{16516}因为它尝试做一个逻辑或 167 | {16517}{16588}如果第一个(命令)没有 `0` 错误码 168 | {16589}{16637}它就会去执行第二个(命令) 169 | {16638}{16747}相似地,如果我们把 `false` 170 | {16748}{16786}替换成比如 `true` 171 | {16787}{16860}因为我们有一个 `0` 错误代码 172 | {16861}{16925}所以第二个(命令)会被短路 173 | {16926}{17002}所以就不会打印 174 | {17184}{17269}相似地,我们有与运算符 175 | {17300}{17364}它仅当第一个命令执行无错误时 176 | {17367}{17440}才会执行第二个部分 177 | {17508}{17551}这里也是同样的事情: 178 | {17554}{17642}如果第一个失败,那么第二个命令 179 | {17643}{17684}就不会被执行 180 | {17752}{17910}虽然不是很相关,但是另一个事情是 181 | {18053}{18139}无论你执行什么,你都可以通过 182 | {18140}{18212}在同一行内使用分号来连接命令 183 | {18213}{18272}它就会始终被打印出来 184 | {18310}{18410}在这之后,我们还没学到的是 185 | {18411}{18590}怎样把命令的输出存到变量里 186 | {18591}{18726}我们可以这样做 187 | {18737}{18896}这里我们获取 `pwd` 命令的输出 188 | {18897}{18977}它会打印出当前工作目录 189 | {18978}{19008}也就是我们在哪里 190 | {19019}{19108}然后把这个存进 `foo` 变量 191 | {19130}{19233}然后现在我们询问 `foo` 的值 192 | {19234}{19272}我们就能看到这个字符串 193 | {19273}{19355}更广泛的说,我们可以做一个叫 194 | {19376}{19454}命令替换的事情 195 | {19501}{19540}通过把它放进任意字符串中 196 | {19541}{19636}而且因为我们用的不是单引号 197 | {19637}{19663}而是双引号 198 | {19664}{19714}所以这串东西会被展开 199 | {19715}{19866}告诉我们,现在位于这个文件夹 200 | {19915}{19967}另一个有趣的事情是 201 | {19987}{20086}这个会展开成一个字符串 202 | {20087}{20099}而不是…… 203 | {20100}{20198}呃,它只是展开成一个字符串 204 | {20199}{20294}另一个好用但知名度更低的工具 205 | {20295}{20336}叫做过程替换 206 | {20346}{20423}和之前那个是类似的 207 | {20529}{20571}它会做什么呢……它会 208 | {20608}{20733}比如这里的 `<(` ,接一个命令,再接 `)` 209 | {20734}{20819}它的作用是,内部的命令会被执行 210 | {20820}{20879}其输出将被存储到,大概像一个 211 | {20880}{20950}临时文件内,然后把文件 handle(标识符)[*] 212 | {20951}{20984}交给(最左面的)命令 213 | {20985}{21077}所以这里我们在……`ls` 这个目录 214 | {21078}{21173}把输出放到临时文件内 215 | {21174}{21239}再对父目录如法炮制 216 | {21240}{21311}然后把两个文件连接 217 | {21312}{21393}而这种写法就非常得劲 218 | {21394}{21431}因为有些命令会从 219 | {21432}{21533}某些文件的内容,而不是标准输入 220 | {21534}{21700}获得输入参数 221 | {21747}{21837}所以我们把这两个命令连起来了 222 | {21991}{22072}感觉讲到现在,讲了真不少东西 223 | {22073}{22154}来看一个里面包含这些内容的 224 | {22155}{22326}简单的示例脚本 225 | {22327}{22409}比如说这里我们有个字符串 226 | {22410}{22483}然后有个 `$(date)` 227 | {22484}{22529}这个 `date` 是个程序 228 | {22530}{22591}重复一下,类 UNIX 系统有很多程序 229 | {22592}{22682}你会慢慢都熟悉它们的 230 | {22683}{22792}`date` 就打印出当前的日期 231 | {22793}{22856}你还可以指定各种打印格式 232 | {22875}{23018}然后这里有这个 `$0` 233 | {23019}{23108}是我们运行的这个脚本的文件名 234 | {23122}{23324}然后是这个 `$#`,代表给定的参数个数 235 | {23325}{23458}然后 `$$` 是这个命令的进程 ID[*] 236 | {23484}{23557}强调,这里有很多 `$`+ 什么什么 237 | {23558}{23596}它们(的含义)并不直观 238 | {23597}{23702}因为你找不到一种巧记的方法 239 | {23703}{23764}`$#` 这种大概就是 240 | {23765}{23813}但是……你一直和它们打照面 241 | {23814}{23832}逐渐就能熟络起来 242 | {23833}{23903}这里还有个 `$@` 243 | {23904}{23979}可以展开成所有参数 244 | {23980}{24096}所以比起来……比如有三个参数 245 | {24097}{24174}那我可以键入 `$1 $2 $3` 246 | {24175}{24247}那如果我们不知道有多少参数 247 | {24248}{24295}我们可以用这种方式把这些参数全部放在这里 248 | {24296}{24431}然后这些参数被传给 `for` 循环 249 | {24454}{24578}`for` 循环会创建一个 `file` 变量 250 | {24579}{24837}依次地用这些参数赋值给 `file` 变量 251 | {24838}{25007}下一行我们运行 `grep` 命令 252 | {25012}{25102}它会在一堆文件里搜索一个子串[*] 253 | {25103}{25181}这里我们在文件里搜索字符串 `foobar` 254 | {25200}{25377}这里我们让 `file` 变量展开为它的值 255 | {25394}{25510}昨天说过,如果我们在意程序输出的话 256 | {25511}{25601}我们可以把它重定向到某处 257 | {25602}{25671}到一个文件里保存下来,或者连接组合 258 | {25693}{25775}嘿,但有时候情况恰恰相反 259 | {25776}{25838}有时候,比如说,我们想知道 260 | {25839}{25918}这个脚本的错误代码是什么 261 | {25919}{26041}我想知道 `grep` 能不能成功查找 262 | {26059}{26196}所以,我们甚至能直接扔掉整个输出 263 | {26197}{26336}包括标准输出和标准错误(流) 264 | {26355}{26385}这里我们做的是 265 | {26386}{26491}把两个输出重定向到 `/dev/null` 266 | {26492}{26613}它是 UNIX 系统的一种特殊设备 267 | {26614}{26715}输出到它的内容会被丢弃 268 | {26718}{26801}就是你可以随意乱写乱画 269 | {26802}{26807}然后所有内容都会被丢掉|就是你可以随意乱写乱画 270 | {26808}{26834}然后所有内容都会被丢掉 271 | {26842}{26945}还有这个 `>` 符号 272 | {26946}{27005}昨天说过,用来重定向输出的 273 | {27014}{27086}这里有个 `2>` 274 | {27096}{27167}有些人也许猜到了 275 | {27168}{27226}它是重定向标准错误流的 276 | {27227}{27307}因为这两个流是分立的 277 | {27308}{27424}所以你得告诉 bash 去操作哪个 278 | {27459}{27497}所以这里我们执行命令 279 | {27498}{27551}去检查文件有没有 `foobar` 280 | {27552}{27683}如果有的话,返回一个 `0` 错误码 281 | {27684}{27769}如果没有,就是一个非 `0` 码 282 | {27782}{27839}我们正是要检查这个 283 | {27857}{27938}这部分命令里 284 | {27939}{28012}我们先告诉它:「给我错误代码」 285 | {28013}{28055}这个是用 `$?` 286 | {28056}{28168}然后是一个比较运算符 `-ne` 287 | {28169}{28194}代表不等于(`N`on `E`qual) 288 | {28233}{28316}其他编程语言里有像 289 | {28328}{28449}`==` 和 `!=` 这种符号 290 | {28467}{28580}bash 里有很多预设的比较运算 291 | {28581}{28706}这主要是为了你用 Shell 的时候 292 | {28707}{28753}有很多东西要去做测试 293 | {28762}{28825} 比如我们现在正在对比两个数 294 | {28826}{28909}两个整数,看它们是否相同 295 | {28910}{29042}又比如,`-f` flag 会让我们知道 296 | {29043}{29100}是否存在一个文件 297 | {29111}{29206}这是你以后会频繁用上的 298 | {29307}{29352}回到例子 299 | {29353}{29648}如果文件中没有 `foobar` 会发生什么 300 | {29649}{29743}像之前有非 `0` 的错误代码 301 | {29744}{29774}我们也输出 302 | {29775}{29809}文件中没有 `foobar` 字符串 303 | {29810}{29911}我们将添加一个,而我们所做的是 304 | {29925}{30001}我们输入 `# foobar` 305 | {30002}{30067}蒙一手这 `#` 是个文件注释格式 306 | {30079}{30253}之后我们用 `>>` 操作符把它添在文件末尾 307 | {30254}{30376}这里尽管文件名已经传给了脚本 308 | {30377}{30407}但我们预先并不知道文件名 309 | {30408}{30509}所以我们需要用文件名变量在这里展开 310 | {30553}{30644}我们可以运行这个试试 311 | {30662}{30758}我们已经有这个脚本的正确权限 312 | {30773}{30893}我可以举一些例子,我们在这个夹里有一些文件 313 | {30894}{31003}`mcd` 是我们先前看到的 `mcd` 函数 314 | {31004}{31052}还有其它脚本函数 315 | {31058}{31244}甚至可以把它自己传给它,检查是否有 `foobar` 316 | {31274}{31354}我们运行它,首先我们可以看到 317 | {31355}{31542}我们成功地列出了很多的变量 318 | {31557}{31595}我们有 `date` 命令 319 | {31605}{31690}它成功地被替换成了当前时间 320 | {31698}{31801}接着是这个带着三个参数的程序 321 | {31817}{31924}它的随机的 pid 识别码 322 | {31934}{32015}之后它告诉我们 `mcd` 没有 `foobar` 字符串 323 | {32016}{32076}所以我们新添加了一个 324 | {32077}{32134}并且这个 `script.py` 文件也没有 325 | {32135}{32206}像现在让我们看看 `mcd` 326 | {32207}{32269}它就有我们要找的注释 327 | {32382}{32566}当你在执行脚本时另一个需要知道的是 328 | {32590}{32669}像这里,我们有三个完全不同的参数 329 | {32679}{32716}但通常 330 | {32717}{32887}你会用一些更加简洁的方式输入参数 331 | {32899}{32969}例如这里, 332 | {32985}{33146}如果我想查找所有的 `.sh` 脚本 333 | {33161}{33328}我们只需要键入 `ls *.sh` 334 | {33329}{33487}这是大多数 Shell 都有的一种展开文件名的方式 335 | {33488}{33519}叫做通配 336 | {33520}{33604}这里,如你所想,会显示出 337 | {33605}{33745}所有含有任意字符,且以 `.sh` 为后缀的东西 338 | {33806}{33916}如我们所料,得到了 `example.sh` 和 `mcd.sh` 339 | {33932}{34022}我们也有这些 `project1` 和 `project2` 340 | {34023}{34075}并且如果这里有…… 341 | {34095}{34205}比如,我们可以建一个 `project42` 342 | {34224}{34391}现在如果我只想找有一个特定字符的项 343 | {34392}{34423}而不是两个字符 344 | {34424}{34468}然后,像其它任意的字符 345 | {34469}{34624}我们可以使用 `?` 标记,`?` 标记只会展开一个字符 346 | {34659}{34709}我们得到了列出的 347 | {34710}{34806}先是 `project1` 再是 `project2` 348 | {34830}{35022}总而言之,通配符非常强大,你也可以组合它们 349 | {35185}{35274}一个常用模式是花括号 350 | {35289}{35387}我们在这个文件夹里有一个图片 351 | {35388}{35507}我们想把图片文件格式由 PNG 转为 JPG 352 | {35530}{35587}我们可能会复制它,或者…… 353 | {35588}{35721}这确实是常见情况,有两个或多个挺相似的参数 354 | {35722}{35854}你想把它们当作参数传给命令 355 | {35855}{35963}你可以这样做,但更简洁的做法是 356 | {35964}{36256}你可以只键入 `image.{png,jpg}` 357 | {36309}{36382}这里有一些彩色的反馈…… 358 | {36383}{36523}总之,它会展开成上面的那行 359 | {36524}{36637}实际上,我可以让 zsh 为我做这些 360 | {36638}{36697}也就是这里正进行的 361 | {36744}{36806}这确实很强大,所以比如 362 | {36807}{36893}你可以做一些像……我们可以…… 363 | {36904}{37022}`touch` 一串 `foo`,所有 `foo` 都会被展开 364 | {37091}{37246}你也可以进行多层操作,建立笛卡尔系 365 | {37329}{37366}如果我们有一些像这样的组 366 | {37413}{37506}我们这里有一组 `{1,2}` 367 | {37507}{37577}之后这里又有 `{1,2,3}` 368 | {37578}{37693}这会用使两组展开式形成笛卡尔积 369 | {37694}{37778}而后展开积里的所有表达式 370 | {37826}{37891}我们就可以很快地 `touch` 了 371 | {37911}{38117}你也可以将 `*` 通配符与 `{}` 通配符结合 372 | {38132}{38215}甚至你可以用一些范围表示 373 | {38219}{38303}像,我们可以键入 `mkdir` 374 | {38304}{38400}我们创建 `foo`,`bar` 目录 375 | {38429}{38554}之后可以在这些行里搞事情 376 | {38564}{38678}这将会展开到 `foo/a`,`foo/b` …… 377 | {38679}{38743}像所有的组合,直到 `j` 378 | {38765}{38895}`bar` 同理,虽然说实话我没试…… 379 | {38896}{38993}但是没错,我们得到了我们所能 touch 的所有组合 380 | {39014}{39173}现在,如果我们在两个目录中建一些不同的东西 381 | {39197}{39413}我们可以再次展示…… 382 | {39434}{39491}用我们之前看到的流程代替 383 | {39492}{39611}我们想查看这两个文件夹中有什么不同文件 384 | {39612}{39699}非常显然,我们刚刚看到了,是 `x` 和 `y` 385 | {39700}{39800}但是我们可以用 Shell 去比对 386 | {39801}{39883}一个 `ls` 和另一个 `ls` 输出的不同 387 | {39918}{39970}如我们所料,我们得到了 388 | {39971}{40025}`x` 只在第一个文件夹里 389 | {40026}{40135}`y` 只在第二个文件夹里 390 | {40217}{40360}还有,目前我们只看了 bash 脚本 391 | {40361}{40414}如果你喜欢其它的脚本…… 392 | {40415}{40504}像 bash 对一些工作可能并不是最好的选择 393 | {40517}{40579}它可能会很棘手。事实上你可以 394 | {40580}{40696}用很多语言写和 Shell 工具交互的脚本 395 | {40713}{40802}例如,我们在这里看一个 396 | {40803}{40915}Python 脚本,它的开头有神秘的一行代码 397 | {40916}{40978}我暂且不去解释 398 | {40979}{41051}我们有 `import sys` 399 | {41052}{41284}这很像…… Python 默认不会尝试和 Shell 交互 400 | {41285}{41336}所以你需要导入一些库 401 | {41337}{41381}之后我们在做一个 402 | {41382}{41423}确实很傻的事情 403 | {41435}{41525}就只是迭代 `sys.argv[1:]` 404 | {41568}{41672}`sys.argv` 是一种类似于 405 | {41673}{41762}bash 中 `$0`,`$1` 等等的东西 406 | {41763}{41915}就是一个参数 vector,我们将它倒序输出 407 | {41981}{42071}开始时那神奇的一行叫做 shebang[*] 408 | {42072}{42194}Shell 通过它了解怎么运行这个程序 409 | {42195}{42319}你随时可以键入类似 410 | {42320}{42403}python script.py` 之后是 `a b c` 411 | {42422}{42499}像这样它就会运行 412 | {42500}{42631}但如果我想让它从 Shell 就能执行呢? 413 | {42632}{42805}Shell 是用首行识别到 414 | {42806}{42911}需要用 Python 解释器运行这个程序 415 | {42954}{43000}并且第一行 416 | {43001}{43078}给了这东西所在的路径 417 | {43161}{43205}然而,你可能不知道 418 | {43206}{43251}像不同的设备很可能 419 | {43252}{43334}会把 Python 放在不同的地方 420 | {43335}{43410}最好别假设 Python 装在哪儿 421 | {43412}{43476}其它解释器也是一样 422 | {43565}{43729}所以你可以做的是调用 `env` 命令 423 | {43730}{43848}你也可以在 shebang 中给出参数 424 | {43849}{44002}所以我们现在是在调用 `env` 命令 425 | {44003}{44093}这是对于绝大多数系统而言的,有一些例外 426 | {44094}{44177}但是对于绝大多数系统而言它在 `usr/bin` 427 | {44178}{44220}那儿有很多二进制文件 428 | {44244}{44315}之后用参数 Python 调用它 429 | {44337}{44396}它会使用 430 | {44397}{44521}第一节课提到的 `path` 环境变量 431 | {44522}{44593}`env` 会在那些路径中找 Python 二进制文件 432 | {44594}{44695}接着用它去解释这个脚本 433 | {44696}{44759}这样有更好的可移植性 434 | {44760}{44882}能让它在我的,你的还有其它的设备上运行 435 | {45268}{45425}另一件事是 bash 并不是真正现代化的 436 | {45427}{45485}它好久之前就被开发出来了 437 | {45480}{45553}有些时候调试起来简直要命 438 | {45554}{45685}一般来讲,调试的时候直觉会时不时地失效 439 | {45686}{45854}就像我们之前看到的 `foo` 命令不存在 440 | {45854}{46019}因此我们在讲义里有一个很高效的工具 441 | {46020}{46100}这个工具叫做 shellcheck 442 | {46101}{46134}链接已经放在讲义里了 443 | {46135}{46214}它能给出 warning 和语法错误 444 | {46215}{46303}还能指出哪些地方你没正确引用 445 | {46304}{46398}或者是哪些地方你的空格打错了 446 | {46420}{46485}举个很简单的例子 447 | {46486}{46598}`mcd.sh` 这个文件,我们得到了一些错误提示 448 | {46599}{46684}这些提示说:「嗨!我们惊奇地发现漏掉了一些东西」 449 | {46685}{46814}这可能导致 `mcd.sh` 在别的系统无法解译成功 450 | {46835}{46922}并且, `cd` 后面有一个指令 451 | {46923}{47022}而 `cd` 可能不会被正确执行 452 | {47023}{47057}这里你可能想用 `cd ... || exit` 453 | {47058}{47232}之类的东西来代替它 454 | {47233}{47288}回到这行命令 455 | {47289}{47438}如果 `cd` 命令没有正确结束 456 | {47439}{47487}你就不能进入那个文件夹 457 | {47488}{47586}因为要么你没有权限,要么文件夹不存在 458 | {47587}{47723}之后程序会给你一个非零的错误码 459 | {47724}{47824}然后你就会执行 `exit` 命令,停止脚本的运行 460 | {47825}{47960}而不是在一个不存在的路径继续执行 461 | {48001}{48080}实际上我还没测试 462 | {48081}{48228}但是我想我们可以试一下 `example.sh` 463 | {48229}{48311}这里它告诉我们 464 | {48312}{48422}应该用另外一种方法检查错误码 465 | {48423}{48547}原来写的大概不能很好地达到目的 466 | {48605}{48679}最后一点,我想说的是 467 | {48680}{48819}当你编写这些 bash 脚本或者函数的时候 468 | {48820}{48872}写你要运行的 bash 脚本 469 | {48873}{49025}和写要载入 Shell 的东西 470 | {49026}{49089}这两者是有区别的 471 | {49090}{49277}我们将会在命令行环境那一讲里了解这些差别 472 | {49278}{49403}同时那一讲会用到 `bashrc` 和 `sshrc` 这两种工具 473 | {49418}{49462}但是,总的来说 474 | {49463}{49545}如果你做了一些改动,比如你的路径 475 | {49546}{49618}比方说你 cd 到了一个 bash 脚本 476 | {49619}{49679}并且你直接运行它 477 | {49690}{49765}它就不会 `cd` 到 Shell 当前的路径 478 | {49794}{49911}但是如果你直接通过 Shell 加载 bash 代码 479 | {49912}{50074}比如你的函数,然后你运行这些函数 480 | {50075}{50139}这个操作就有相反的副作用 481 | {50140}{50258}在 Shell 中定义变量也是一样 482 | {50345}{50573}现在我会讲一些和 Shell 搭配干活不累的工具 483 | {50615}{50697}第一个昨天已经着重讲过了 484 | {50698}{50847}怎么去知道 flag 和 command(命令)具体代表什么 485 | {50848}{50954}就像我现在知道 `ls -l` 486 | {50955}{51045}会用列表的形式列出文件 487 | {51046}{51199}或者我运行 `mv -i` 它会给我提示 488 | {51212}{51279}你现在能用的就是 man 命令 489 | {51280}{51455}man 命令会给出很多关于命令的信息 490 | {51456}{51598}比如说在这解释了 `-i` 的作用 491 | {51599}{51671}这些就是你能做的全部操作 492 | {51721}{51944}不仅是系统内封装的简单命令 493 | {51945}{52075}对于一些从网上安装的工具也很方便 494 | {52076}{52216}例如,如果安装完一些工具 495 | {52217}{52291}那么 man 要用的文档也安装好了 496 | {52292}{52455}比如我们要运行这个叫 ripgrep 的工具 497 | {52456}{52537}它可以用 `rg` 调用 498 | {52566}{52611}系统里并没有自带这个工具 499 | {52612}{52714}但是它安装了自己的 man 文档 500 | {52715}{52747}并且我可以查看 501 | {52825}{52913}对有些命令来说, man 命令直截了当 502 | {52914}{53000}但有时,理解 man 调出来的文档也挺头疼 503 | {53001}{53202}因为它涵盖了这个工具所有的文档和描述 504 | {53203}{53400}有的时候会有样例,但有的时候没有 505 | {53401}{53561}比如我经常用的一些优秀工具 506 | {53562}{53622}像 convert 和 ffmpeg 507 | {53623}{53693}虽然他们处理图像或视频很优秀 508 | {53694}{53755}但是他们的 man 文档都是庞然大物 509 | {53756}{53878}然后有个好东西叫 `tldr`,你可以装一下 510 | {53879}{54081}然后就会获得关于你如何调用命令的 511 | {54082}{54152}一些深入浅出的命令示例 512 | {54153}{54208}你也可以上网搜一下 513 | {54209}{54333}但这样你就免得去打开浏览器 514 | {54334}{54404}然后找一堆例子,再返回来 515 | {54405}{54566}`tldr` 是社区贡献的,确实好用 516 | {54567}{54676}比如用它查 `ffmpeg` 517 | {54677}{54772}就有很多经典的例子,格式易于阅读 518 | {54773}{54860}(但我讲课调了特大号字体,打乱了格式所以不明显) 519 | {54920}{54979}甚至如 `tar` 这种简单的命令 520 | {54980}{55058}都有好多 option 要去组合运用 521 | {55059}{55229}比如这里你可以把两三个 flag 结合 522 | {55230}{55337}但结合出的效果可能违反直觉 523 | {55427}{55558}这就是你……要找到更多这样的工具 524 | {55599}{55652}关于查找的主题,我们再来试一下 525 | {55653}{55753}怎么去查找文件 526 | {55754}{55856}你永远可以用 `ls` 527 | {55857}{55941}比如你可以 `ls project1` 528 | {55942}{56055}然后一路 `ls` 下去…… 529 | {56056}{56305}但假设,我们已知要找名为 `src` 的文件夹 530 | {56306}{56406}做这件事有更好的命令 531 | {56407}{56449}它就是 `find` 532 | {56450}{56564}`find` 大概是每个 UNIX 系统都有的工具 533 | {56565}{56704}这个 `find`,我们给他一个…… 534 | {56773}{56887}这里意为,在当前文件夹调用 `find` 535 | {56888}{56975}记住 `.` 代表当前文件夹 536 | {56976}{57058}然后我们找名为 `src` 537 | {57058}{57145}而且类型是个目录的东西 538 | {57158}{57311}键入这些,它就可以在当前目录递归 539 | {57312}{57384}查看所有符合规则的文件 540 | {57385}{57463}或者文件夹,在这个例子里 541 | {57464}{57589}`find` 也有很多有用的 flag 542 | {57606}{57748}比如你甚至可以查询指定格式的文件路径 543 | {57749}{57857}这里(`**`)是指要有几层文件夹 544 | {57858}{57907}我们并不关心具体是多少个[*] 545 | {57908}{58024}然后我们想找所有 Python 脚本 546 | {58025}{58097}也即所有扩展名是 `.py` 的文件 547 | {58098}{58161}然后要求它们在一个 `test` 文件夹内 548 | {58162}{58190}然后我们也在确保 549 | {58191}{58228}虽然确实有点多余,但是 550 | {58229}{58318}我们也检查它是否为 `F` 类型 551 | {58319}{58355}`F` 是代表文件 552 | {58393}{58446}这样就找到了符合的文件 553 | {58496}{58570}也可以针对非路径和非文件名的查找 554 | {58571}{58629}运用不同的 flag 555 | {58644}{58779}比如可以查找被修改过的文件 556 | {58780}{58848}这里 `-mtime` 代表修改时间 557 | {58849}{58926}在最近一天被修改过的东西 558 | {58927}{58984}啊,基本就是这个文件夹的所有东西 559 | {58985}{59063}打印出了我们刚创建的文件 560 | {59064}{59127}和先前就有的文件 561 | {59128}{59195}你甚至可以用其他条件 562 | {59196}{59321}比如大小,所有者,权限,等等 563 | {59358}{59464}更强大的是,`find` 不仅查找东西 564 | {59465}{59574}找到之后还能做别的 565 | {59586}{59847}我们可以查找所有扩展名是 `.tmp` 的文件 566 | {59876}{59930}是代表临时文件的扩展名 567 | {59942}{60083}然后要求 `find` 对于所有这些文件 568 | {60084}{60190}执行 `rm` 命令 569 | {60203}{60296}这会对所有这些文件调用 `rm` 570 | {60312}{60401}我们先不带 `rm` 执行一下 571 | {60425}{60467}再带着它执行一下 572 | {60502}{60592}再次根据命令行的设计哲学 573 | {60627}{60663}看起来无事发生 574 | {60664}{60863}但我们有 `0` 错误代码,就是有事发生 575 | {60864}{60937}那就是所有命令执行成功,一切顺利 576 | {60938}{61014}然后现在再找下这些文件 577 | {61015}{61088}就找不到了 578 | {61156}{61266}总体来说, Shell 的另一个好处 579 | {61267}{61322}就是即便有了这些工具 580 | {61348}{61425}人们也在创造新的方式 581 | {61426}{61509}用别的方法开发这些工具 582 | {61510}{61564}了解一下挺不错的[*] 583 | {61565}{61806}比如你只想找以 `tmp` 结尾的东西 584 | {61807}{61884}做这种挺另类的事情 585 | {61885}{61934}你看这命令其实挺长的 586 | {61935}{62043}有一个工具叫 `fd` 587 | {62044}{62120}举个栗子,这命令更短 588 | {62121}{62181}而且默认使用正则表达式[*] 589 | {62182}{62248}还会忽略你的 gitfile[*] 590 | {62249}{62306}你不会想搜到那堆东西的 591 | {62345}{62456}还有彩色代码和更好的 Unicode 支持…… 592 | {62457}{62536}了解这些工具挺好的 593 | {62537}{62625}但是重申,核心思想是 594 | {62626}{62751}你要是知道这些东西存在 595 | {62752}{62895}就能省去做重复性、无意义工作的时间 596 | {62959}{62991}另一个要记住的命令是 597 | {62992}{63023}呃,就比如 `find` 598 | {63024}{63083}部分同学可能会好奇…… 599 | {63084}{63230}`find` 可能就是遍历目录结构 600 | {63231}{63274}去找匹配的事物 601 | {63287}{63350}那我要是每天高强度 `find` 呢? 602 | {63351}{63434}如果能给整个数据库出来 603 | {63435}{63575}然后建个索引,不断维护它 604 | {63576}{63633}岂不美哉 605 | {63634}{63711}呃,其实大部分 UNIX 系统已经有了 606 | {63712}{63800}可以用 `locate` 命令 607 | {63801}{63969}这个 `locate` 会…… 608 | {63970}{64197}它会查找文件系统中具有指定子串的路径 609 | {64198}{64285}我不知道这行不行…… 610 | {64286}{64316}哦看来可以 611 | {64317}{64498}我来试试找 `missing-semester` 612 | {64590}{64689}得等一会,就能找到这些东西 613 | {64690}{64714}都是在我文件系统里面 614 | {64715}{64874}因为事先建立了索引,它就会快得多 615 | {64877}{64977}然后,如果要更新它的话 616 | {64978}{65080}用这个 `updatedb` 命令 617 | {65081}{65265}通常由 cron 定期执行来更新数据库。[*] 618 | {65310}{65384}另外,查找文件是很有门道的 619 | {65385}{65510}实际上,有时你不关心文件本身 620 | {65511}{65573}而是文件的内容 621 | {65604}{65576}这方面可以用前面见过的 `grep` 命令 622 | {65577}{65919}比如 `grep foobar mcd.sh` 623 | {65920}{65945}找到了 624 | {65964}{66132}如果你还是想递归当前目录结构 625 | {66133}{66177}去查找更多的文件该怎么办 626 | {66178}{66271}你不会愿意亲手干苦活的 627 | {66274}{66331}我们可以用 `find` 命令结合 `-exec` 628 | {66340}{66444}但 `grep` 有一个大写 `-R` 的 flag 629 | {66445}{66604}是可以找遍整个目录的 630 | {66605}{66665}啊,应该是这样儿 631 | {66688}{66723}它告诉我们,噢 632 | {66724}{66830}`example.sh` 中有包含 `foobar` 的行 633 | {66831}{66884}在这三个行的位置都有 634 | {66885}{66948}并且这两个位置也有 `foobar` 635 | {67031}{67078}这个挺省事的 636 | {67079}{67213}主要是当你记得你用一些程序语言 637 | {67214}{67282}写了一些代码的时候 638 | {67283}{67366}你知道它就在你文件系统的某处躺着 639 | {67367}{67410}但你就是想不起来 640 | {67411}{67477}用这招就可以快速搜索 641 | {67486}{67598}比如我可以快速搜索草稿文件夹里 642 | {67674}{67969}所有我用了 `request` 库的 Python 代码 643 | {67981}{68009}如果我执行命令 644 | {68010}{68200}就能查到这些文件,精确到匹配的行 645 | {68201}{68293}比起用 `grep`,虽然它挺好 646 | {68294}{68388}你也可以……我用了 `ripgrep` 647 | {68396}{68527}原理是一样的,但是它也是 648 | {68528}{68583}加了亿点点细节 649 | {68584}{68788}比如代码彩色和文件处理啥的 650 | {68789}{68855}也有 Unicode 支持 651 | {68864}{68914}而且跑的还快 652 | {68915}{69037}所以它没为了这些花招拖慢速度 653 | {69100}{69159}还有很多有用的 flag 654 | {69160}{69318}比如说你想,哦,我想要点上下文 655 | {69395}{69513}这样就是结果附近的五行 656 | {69521}{69623}你就能知道那个 `import` 大概在哪 657 | {69624}{69692}它周围都是什么代码 658 | {69696}{69735}这里找这个 `import` 不怎么实用 659 | {69736}{69825}但是比如,你要查你在哪调用了函数 660 | {69826}{69932}它就很给力 661 | {69963}{70175}我们也可以搜索,比如说 662 | {70176}{70277}一个更高级的用法 663 | {70278}{70484}解释一下,`-u` 是不忽略隐藏文件[*] 664 | {70485}{70673}有时候你想忽略隐藏文件 665 | {70674}{70794}但如果你想查找配置(config)文件 666 | {70795}{70841}它们大多是默认隐藏的,这样子 667 | {70842}{70948}然后,这里不是打印匹配内容 668 | {70961}{71026}而我们要求它,呃,这大概是 669 | {71027}{71129}我觉得 `grep` 做不到的 670 | {71130}{71231}就是,我要你打印出所有 671 | {71232}{71320}不匹配这个模式的内容 672 | {71336}{71408}这么做可能挺奇怪的 673 | {71409}{71452}接着往下看…… 674 | {71453}{71496}这里这个模式(pattern)是一个 675 | {71510}{71554}小巧的正则表达式 676 | {71563}{71697}意思是,匹配行首有 `#!` 的内容 677 | {71721}{71743}这是个 `shebang` 678 | {71744}{71912}也就是说我们在搜索没有 shebang 的文件 679 | {71923}{72039}这里还给了一个 `-t sh` 是说 680 | {72040}{72106}只搜索 `.sh` (后缀名)的文件 681 | {72107}{72233}因为实际来讲 Python 或者文本文件 682 | {72234}{72267}少了 shebang 也没问题 683 | {72281}{72305}这里它告诉我们 684 | {72306}{72377}「哦,`mcd.sh` 明显少了个 shebang」 685 | {72448}{72553}我们还可以……它有一些好用的 flag 686 | {72554}{72633}比如加上这个 `--stats` flag 687 | {72871}{72945}它也会得到这些结果 688 | {72946}{73054}不过它还会告诉我们 689 | {73055}{73148}比如成功匹配了多少行 690 | {73149}{73231}查找了多少行多少文件 691 | {73232}{73277}打印了多少 byte,等等 692 | {73323}{73422}类似 `fd` 这种,有时候单会一个工具 693 | {73465}{73522}其实不是很好 694 | {73523}{73628}实际上有很多类似 `ripgrep` 的工具 695 | {73629}{73751}比如 `ack`,也是 `grep` 一个替代 696 | {73764}{73893}还有 `ag`,那个“银子”搜索器[*] 697 | {73894}{73973}这些基本都是可替换的 698 | {73974}{74026}有可能你用某个操作系统 699 | {74027}{74064}发现它有某一个,没有另一个 700 | {74065}{74201}只要知道你可以用这些工具就行 701 | {74243}{74325}最后我想讲讲,怎么去做一些 702 | {74326}{74381}不是去找文件或者代码 703 | {74382}{74528}而是找一些已经执行过的命令 704 | {74606}{74717}首先,显然可以用上箭头 705 | {74734}{74847}慢慢儿翻你的历史记录 706 | {74848}{74927}你可能也觉得,这不是很有效率 707 | {74928}{75091}所以 bash 有一些更简单的方法 708 | {75101}{75137}有个 `history` 命令 709 | {75138}{75172}它会打印出你的命令历史记录 710 | {75181}{75277}这里我用的 zsh,所以只会打印一部分 711 | {75278}{75407}如果我想从开头全打印出来 712 | {75415}{75550}这就不管是啥,都给打印出来了 713 | {75552}{75622}因为这记录挺多的 714 | {75623}{75745}比如我只关心用了 `convert` 的命令 715 | {75746}{75867}它把某种类型的文件转到另一种 716 | {75868}{75939}呃 抱歉,是图片类型(而非所有文件) 717 | {75940}{76052}这里就是所有的结果 718 | {76053}{76142}所有匹配上这个子字符串的 719 | {76239}{76344}更进一步,基本上所有 Shell 720 | {76345}{76423}默认都会把 `Ctrl`+`R` 这个组合键 721 | {76424}{76486}设成(按执行时间)倒序搜索(backward search) 722 | {76487}{76537}这里我们打开倒序搜索 723 | {76538}{76594}然后输入 `convert` 724 | {76595}{76684}就会找到与之匹配的命令 725 | {76685}{76752}如果我们接着按 `Ctrl`+`R` 726 | {76753}{76839}就会倒着往前搜索匹配的命令 727 | {76840}{76954}也可以重新执行命令 728 | {76978}{77116}另一个相关的是 729 | {77117}{77198}你可以用这个叫 `fzf` 的高级货 730 | {77199}{77279}它就是一个模糊搜索工具 731 | {77280}{77410}像是一个交互式的 `grep` 732 | {77464}{77587}举个栗子,先 `cat` 一下我们这个 733 | {77588}{77677}`example.sh` 734 | {77689}{77745}就会打印到标准输出 735 | {77746}{77805}然后我们用管道连到 `fzf` 上 736 | {77806}{77860}先是显示出所有行 737 | {77861}{77998}然后可以实时地输入要找的字符串 738 | {78030}{78111}`fzf` 有一个好,就是 739 | {78112}{78199}如果你打开默认绑定,它会绑定到 740 | {78200}{78410}Shell 的 `Ctrl`+`R` 执行上 741 | {78411}{78557}然后你就可以动态的查看 742 | {78558}{78645}历史记录里转换 `favicon` 的命令 743 | {78668}{78746}它还是模糊匹配的 744 | {78747}{78806}比起在 `grep` 里默认你得 745 | {78807}{78970}写正则表达式才能搞定这种情况 746 | {78971}{79046}这里就只打 `convert` 和 `favicon` 747 | {79047}{79129}它就能尝试最优的扫描策略 748 | {79130}{79194}在给定的行里匹配出来 749 | {79266}{79343}最后就是这个工具 750 | {79344}{79407}你们已经看到了我一直用的 751 | {79408}{79480}免去打那些又臭又长的命令 752 | {79481}{79575}就是这个历史记录子串查找[*] 753 | {79576}{79677}当我在 Shell 里输入的时候 754 | {79678}{79784}(呃,这个忘记介绍了) 755 | {79785}{79881}(就是 fish,我以为我提到过的)[*] 756 | {79882}{79951}fish 和 zsh 都有很好的实现 757 | {79986}{80060}它们可以,当你打字的时候 758 | {80061}{80157}动态搜索你的历史记录 759 | {80158}{80232}找到前缀相符的一个命令 760 | {80270}{80502}如果匹配的那条不相符了也会变化 761 | {80503}{80560}如果你按一下右箭头 762 | {80561}{80667}就能选中这个命令,就可以重新执行 763 | {81209}{81268}我们已经见识了一大堆东西了 764 | {81269}{81346}我觉得我还剩下几分钟 765 | {81347}{81470}我打算讲几个工具 766 | {81471}{81599}可以快速列出目录和定位目录的 767 | {81600}{81911}确实可以用 `-R` 递归列出目录结构 768 | {81912}{81983}但是这样不是很好受 769 | {81984}{82057}呃 我轻易读不懂这一堆鬼玩意 770 | {82104}{82203}有个叫 `tree` 的工具可以 771 | {82204}{82373}用比较友好的格式打印这些东西 772 | {82374}{82427}它也会用彩色文本,基于…… 773 | {82428}{82478}就比如说 `foo` 是蓝的 774 | {82479}{82549}代表是个目录 775 | {82550}{82646}这个是红的,因为有执行权限 776 | {82671}{82727}我们还可以再深入些 777 | {82728}{82856}有些好用的,比如最近有个 778 | {82857}{82933}`broot`,也是做差不多的事情 779 | {82934}{83051}但是比起列出所有文件 780 | {83052}{83097}比如说在 `bar` 里我们有 781 | {83098}{83152}`a` 一直到 `j` 这些文件 782 | {83153}{83230}它会提示「还有更多文件,未列出」 783 | {83264}{83346}我还可以开始输入,它也会 784 | {83347}{83451}模糊匹配这些文件 785 | {83452}{83542}我可以快速的选择和定位 786 | {83563}{83610}所以还是说 787 | {83611}{83711}知道有这些东西挺好 788 | {83712}{83880}你就不会浪费太多时间 789 | {83947}{84013}还有就是,我记得我装了 790 | {84014}{84150}也是一个,你可能希望你的操作系统该带的 791 | {84151}{84255}比如 Nautilus 或者 mac 的访达[*] 792 | {84256}{84443}有一个交互式的界面 793 | {84444}{84574}你可以用箭头定向,浏览 794 | {84602}{84633}这也许有点过犹不及了 795 | {84634}{84723}但如果在里面走一圈 796 | {84724}{84835}你能够很快速地理解目录结构 797 | {84844}{84899}而且基本所有这些工具 798 | {84900}{84989}去看看选项列表 799 | {84990}{85071}它都可以让你编辑和复制文件什么的 800 | {85137}{85194}最后附加一项就是你怎么 801 | {85195}{85239}去到一个位置 802 | {85240}{85290}我们有 `cd`,挺好用的 803 | {85291}{85445}可以让你进入很多地方 804 | {85446}{85506}但是如果你能快速去到 805 | {85507}{85706}你最近访问的,或者经常访问的地方 806 | {85707}{85748}还是挺美妙的 807 | {85764}{85864}这个有挺多实现方式的 808 | {85865}{85926}你可以考虑,哦,我可以做标签 809 | {85927}{86004}我可以在 Shell 里设置别名 810 | {86005}{86071}这个挑时间会讲 811 | {86072}{86112}还有符号链接…… 812 | {86140}{86191}不过当前来说 813 | {86192}{86264}写了这些工具的程序员们 814 | {86265}{86379}他们搞出了一个特别好的方式 815 | {86416}{86506}有一个是用叫「autojump」的项目…… 816 | {86507}{86570}……也许我这里没有……? 817 | {86848}{86930}呃啊。没事儿,我会在讲到 818 | {86931}{86974}命令行环境的时候再讲 819 | {87064}{87170}我觉得大概是我禁用了 `Ctrl`+`R` 820 | {87171}{87271}影响到了脚本的其他部分 821 | {87285}{87347}我认为现在如果任何人 822 | {87348}{87422}有相关问题的话 823 | {87423}{87478}如果有东西我没讲清楚的话 824 | {87479}{87537}我非常乐于解答 825 | {87538}{87688}没有的话,我们搞了一堆习题 826 | {87689}{87749}差不多都是这些主题的 827 | {87750}{87801}我们鼓励你去做一下 828 | {87802}{87878}以及办公时间来找我们 829 | {87879}{87944}我们可以帮你搞明白习题 830 | {87945}{88023}或者没说清楚的一些 bash 的细节 831 | -------------------------------------------------------------------------------- /ch3/major.sub: -------------------------------------------------------------------------------- 1 | {18}{142} 好,欢迎来到今天的课堂 2 | {144}{197}今天我们讲数据的整理(Data Wrangling) 3 | {208}{306}这个英文名可能听上去有点怪 4 | {307}{373}但它解决的基本问题就是 5 | {374}{488}把一种格式的数据转换成另一种 6 | {495}{553}那这个任务再平常不过了 7 | {554}{616}我说的不仅是图片格式之间的转换 8 | {617}{717}还有可能是你现有的文本文件、日志文件 9 | {718}{803}你想得到它们的其他格式 10 | {804}{896}比如图表或者统计数据 11 | {902}{990}那么我认为的数据处理 12 | {991}{1078}就是像这样把一个数据 13 | {1079}{1148}以另一种方式表达 14 | {1149}{1301}我们在前面几节课已经见过几个例子了 15 | {1310}{1397}例如当你使用管道操作符的时候 16 | {1398}{1522}它会把一个程序的输出喂给另一个程序 17 | {1523}{1604}其实此时,你就在进行某种形式的数据处理 18 | {1617}{1688}那么我们这节课的主要内容就是 19 | {1689}{1778}看看有什么神秘的数据处理魔法 20 | {1779}{1865}以及数据处理的高效方法 21 | {1931}{1990}要处理数据 22 | {1991}{2023}首先你得有数据来源 23 | {2024}{2099}要有能加以实践的数据 24 | {2135}{2236}那优质的数据来源就多了去了 25 | {2239}{2313}那么我们今天的讲义的练习里 26 | {2314}{2353}就会给你许多的样例数据 27 | {2354}{2465}而今天的课呢,我打算用系统日志 28 | {2466}{2580}我在荷兰那地儿跑着个服务器 29 | {2581}{2640}呃,这在当时来说十分合理[*] 30 | {2646}{2753}在那个服务器上呢,跑着一个 31 | {2754}{2836}`systemd` 自带的记录日志的后台进程 32 | {2837}{2930}这是一个挺标准的 Linux 日志机制 33 | {2942}{3014}然后我们可以通过 Linux 的一个 34 | {3015}{3121}`journalctl` 命令来看系统日志 35 | {3129}{3181}那么我要做的事 36 | {3182}{3246}就是对这个日志做一些转换 37 | {3247}{3322}然后看看里面有没有啥有趣的东西 38 | {3345}{3414}你可以看到我跑完这个命令以后 39 | {3415}{3500}获得了这么多的数据 40 | {3503}{3644}因为这个日志文件,它里面有超多东西 41 | {3645}{3704}我的服务器上发生了不少事情 42 | {3705}{3785}你看这一条是一月一日的 43 | {3786}{3898}后面还有更久远的很多东西 44 | {3899}{3991}那我们要做的第一件事就是缩小日志量 45 | {3992}{4054}我们只看一部分的内容 46 | {4055}{4124}此时 `grep` 就是你的最佳伙伴了 47 | {4125}{4240}我们用管道把 `ssh` 的输出接到 `grep` 上 48 | {4247}{4331}我们还没仔细聊过 `ssh`| 49 | {4332}{4438}但它是一种通过命令行,远程访问计算机的方式 50 | {4439}{4562}当你把服务器放到公网上之后 51 | {4563}{4664}世界各地的人都想连接然后登录进去 52 | {4665}{4706}然后控制你的服务器 53 | {4709}{4793}那我就想看看他们是咋整的 [*] 54 | {4799}{4872}那我就 `grep SSH` 55 | {4873}{5056}然后你就能够~~很快的~~看到这会输出很多东西 56 | {5070}{5165}理论上来说是这样的但是实际上很慢... 57 | {5174}{5203}好 58 | {5204}{5275}你可以看到它生成了 59 | {5277}{5335}这么这么这么多的内容 60 | {5336}{5433}这样很难看出发生了什么 61 | {5439}{5527}所以我们只来看看这些人 62 | {5528}{5587}用了什么用户名来尝试登录 63 | {5588}{5767}你可以看到这里有几行写着「无效用户,断开连接」 [*] 64 | {5768}{5804}然后后面是用户名 65 | {5813}{5852}现在我只想要这种日志条目 66 | {5853}{5894}我只关注这些东西 67 | {5900}{5988}那我现在再来点修改 68 | {5989}{6166}我在最后加上个 `Disconnected from`(断开连接) 69 | {6183}{6298}你想想底部的这条命令流水线是如何运作的 70 | {6299}{6415}首先它会通过网络,把整个日志传到这个电脑里 71 | {6416}{6531}然后在本地跑 `grep` 找出所有含 `ssh` 的行 72 | {6532}{6591}然后再在本地更进一步的去筛选 73 | {6592}{6643}这是不是有点浪费 [*] 74 | {6644}{6703}因为我根本不关心其他的条目 75 | {6704}{6766}远程服务器上也有个 Shell 76 | {6767}{6937}那我就把整个命令搬到服务器上运行 77 | {6938}{7028}那么现在,你,SSH 78 | {7029}{7102}你给我在服务器上整这三个活 79 | {7103}{7187}然后拿回来的数据我再接到 `less` 上面 80 | {7223}{7261}那这会发生什么呢 81 | {7262}{7326}其实是一样的数据筛选 82 | {7327}{7375}只是把工作搬到服务器上了 83 | {7376}{7497}而服务器只会回传我想要的行 84 | {7519}{7621}然后我在本地把数据用管道接到了 `less` 上 85 | {7622}{7669}`less` 是个分页显示程序 86 | {7670}{7729}你会看到一些例子… 87 | {7730}{7799}其实当你键入 `man` 88 | {7801}{7835}然后后面接某些命令 89 | {7836}{7879}你实际上已经见过这个程序了 90 | {7880}{7981}使用分页程序可以方便的把长长的内容 91 | {7982}{8041}适配到终端的大小 92 | {8052}{8134}然后让你上下滚动来浏览 93 | {8135}{8214}而不是在你的屏幕上一滚而过 94 | {8215}{8307}执行这个命令的时候还是要花一些时间 95 | {8308}{8374}因为服务器要解析一堆日志文件 96 | {8375}{8483}特别是 `grep` 会先缓存输出 [*] 97 | {8490}{8597}所以它还卡在这 98 | {8600}{8696}让我看看不这样的话会不会好一点 [*] 99 | {8987}{9082}为啥不听我的话... 100 | {9111}{9175}好吧让我搞点小手段 101 | {9176}{9295}你假装没看见 102 | {9549}{9608}也有可能是这个网络差得离谱 103 | {9609}{9643}可能是这两个原因之一 104 | {9644}{9703}还好我有备而来 105 | {9704}{9852}上课前我执行了这个命令 106 | {9878}{10009}它会把前面的这串命令的输出 107 | {10010}{10069}放到我电脑里的这个文件里 108 | {10070}{10129}我在办公室里跑了一次 109 | {10141}{10245}而前面这串命令所做的事就是 110 | {10246}{10305}把所有包含 `disconnect from` 的 111 | {10306}{10370}SSH 日志下载到本地 112 | {10374}{10415}这真是个好东西 113 | {10416}{10501}因为我没有必要每次都传输整个日志 114 | {10508}{10598}我只想要以它开头的行 115 | {10620}{10696}那我们现在来看看 `ssh.log` 116 | {10700}{10758}你可以看到它有这么这么多 117 | {10759}{10856}写着「与无效的用户断开连接」 118 | {10857}{10916}或者「已认证的用户」,等等 119 | {10941}{11005}我们要做的就是在这些日志上整活 120 | {11006}{11044}这也意味着 121 | {11045}{11143}在这之后我们并不需要再走 SSH 的流程 122 | {11144}{11193}我们可以直接 `cat` 这个文件 123 | {11196}{11247}然后在它上面进行操作 124 | {11283}{11353}此外让我来展示一下这个分页器 125 | {11354}{11457}如果我 `cat ssh.log` 126 | {11458}{11495}然后把管道接到 `less` 上 127 | {11496}{11529}它就会给我一个分页器 128 | {11530}{11567}我就可以上下滚动了 129 | {11568}{11617}把字体调小一点点? 130 | {11664}{11741}这样我可以滚动浏览这个文件了 131 | {11742}{11794}那么我还可以用些 132 | {11795}{11848}类似 Vim 的按键操作来浏览 133 | {11849}{11936}`Ctrl+u` 向上翻,`Ctrl+d` 向下翻 134 | {11937}{11979}以及按 `q` 退出 135 | {12037}{12120}这仍然有很多内容 136 | {12121}{12226}里面还是有很多我不感兴趣的垃圾信息 137 | {12227}{12313}我只想看看这些用户名是些啥 138 | {12314}{12398}那么我们就要来用 139 | {12400}{12447}一个叫做 `sed` 的工具了 140 | {12458}{12570}流编辑器 `sed` 是一个更早期的| 141 | {12571}{12661}一个叫做 `ed` 的东西的改版 142 | {12662}{12782}这东西非常之怪,你们肯定不想用 143 | {12788}{12825}诶你有啥问题 144 | {12826}{12877}_抱歉我可能漏听了_ 145 | {12878}{12922}_但是 `tsp` 是个啥 [*]_ 146 | {12923}{13033}哦 `tsp` 是我的远程计算机的名字 147 | {13115}{13177}所以 `sed` 是一个“流”编辑器 148 | {13178}{13350}可以让你修改流(stream)中的内容 149 | {13364}{13450}你可以认为这个命令大概是做文本替换 150 | {13452}{13512}但实际上 `sed` 是一个在输入流上操作的 151 | {13513}{13585}完整的编程语言 [*] 152 | {13586}{13667}那么 `sed` 的一个最常用操作就是 153 | {13668}{13792}在输入流之上执行替换表达式 154 | {13793}{13859}那么这东西长什么样呢 155 | {13860}{13903}让我写给你看看 156 | {13972}{14036}好 现在我要把管道接到 `sed` 上 157 | {14037}{14103}然后我告诉它我想把 158 | {14104}{14182}所有 `Disconnected from` 前面的东西 159 | {14183}{14242}全部丢掉 160 | {14313}{14374}这可能有些奇怪 161 | {14375}{14419}但是你会观察到 162 | {14420}{14577}这些 SSH 里的日期、域名、进程 ID 163 | {14578}{14649}我并不关心,干脆统统把它删掉 164 | {14650}{14821}`Disconnected from` 这几个字每条日志都有 165 | {14822}{14869}也可以删掉 166 | {14870}{14953}那我就要写一个 `sed` 表达式 167 | {14958}{15028}而此处我写的是一个 `s/` 表达式 168 | {15030}{15089}也就是替换表达式(**s**ubstitute) 169 | {15090}{15241}这个表达式接受两个以斜线分隔的参数 170 | {15252}{15328}第一个参数是要找的字符串 171 | {15333}{15406}而第二个是要换成的字符串 172 | {15423}{15510}这里的意思就是,按这个字符串模式搜索 173 | {15511}{15570}然后把它换成空的 174 | {15578}{15641}最后我把它接到 `less` 上 175 | {15657}{15780}看到了吗?它把这些行的开头剪掉了 176 | {15820}{15888}用起来真的爽 177 | {15889}{15932}但是你可能会疑惑 178 | {15933}{16012}我在这写的这玩意是个啥 179 | {16028}{16120}那个 `.*` 是干啥的 180 | {16127}{16205}这实际上是正则表达式的一个例子 181 | {16212}{16348}正则表达式,你之前写程序可能见过 182 | {16358}{16422}但是你一旦用起命令行 183 | {16424}{16465}你会发现这东西用得特别多 184 | {16466}{16525}特别是对于像这样的数据处理 185 | {16541}{16685}正则表达式是一个很有力的文本匹配方式 186 | {16695}{16757}你不一定要把它用在文本上 187 | {16758}{16808}但匹配文本是最普遍的用途 188 | {16817}{16862}在正则表达式里 189 | {16863}{16998}你可以活用一套特殊字符 190 | {16999}{17090}这些字符不会直接匹配它们本身 191 | {17091}{17236}而是匹配某一类的字符或者字符串 192 | {17243}{17320}本质上来说它生成了一段程序 193 | {17321}{17366}来在文本中进行查找 194 | {17367}{17477}例如 `.` 代表「匹配一个任意字符」 195 | {17522}{17621}而如果在某一字符后面加上 `*` 196 | {17622}{17708}那它代表匹配零次或多次该字符 197 | {17744}{17798}那么这个 pattern(模式)所描述的就是 198 | {17799}{17878}任意的、零个或多个字符 [*] 199 | {17888}{17978}然后跟着一个字符串 `Disconnected from` 200 | {18000}{18051}这里就是说,我找到这样的字符串 201 | {18052}{18098}然后把它们换成空的 202 | {18118}{18239}正则表达式有一大把像这样的特殊字符 203 | {18240}{18274}各有各的含义 204 | {18275}{18303}你可以好好运用 205 | {18304}{18338}我们已经讲过了 `*` 206 | {18339}{18371}它匹配零或多个字符 207 | {18377}{18406}还有一个 `+` 208 | {18407}{18455}作用是匹配一次或多次左面的模式 [*] 209 | {18456}{18485}那么这样的意思就是 210 | {18486}{18549}我想要前面那个 pattern 匹配至少一次 211 | {18595}{18655}此外还有方括号 212 | {18659}{18785}可以让你匹配多种字符中的一种 213 | {18786}{18869}好 我现在搞个字符串 214 | {18870}{18929}比如说 `aba` 215 | {18937}{19105}我想把 `a` 和 `b` 换成空的 216 | {19187}{19282}那么我就让 pattern 去把 217 | {19283}{19366}要么是 `a` 要么是 `b` 的字符 218 | {19367}{19426}换成空的 219 | {19463}{19516}就算我把第一个字符换成了 `b` 220 | {19517}{19562}还是会输出 `ba` 221 | {19577}{19602}那你可能就会想了 222 | {19603}{19645}为啥它只替换一次呢 223 | {19646}{19712}这是因为正则表达式 224 | {19713}{19764}在默认模式下 225 | {19765}{19906}每行只匹配一次、替换一次 226 | {19907}{19966}这是 sed **默认**模式下做的事 227 | {19974}{20042}你可以再加个 `g` 修饰符 228 | {20043}{20126}意思是只要能,就尽量多匹配 229 | {20152}{20215}然后整行就没了 230 | {20216}{20291}因为每个字符都是 `a` 或 `b` 之一 231 | {20305}{20348}如果我再加个 `c` 232 | {20349}{20407}它就移除 `c` 之外的所有东西 233 | {20408}{20506}如果再向字符串内加其它字符 234 | {20507}{20537}也都会保留下来 235 | {20538}{20610}但是 `a` 和 `b` 都会被去掉 236 | {20727}{20902}你还可以给他加点修饰符 237 | {21089}{21185}跑这个命令会发生什么呢 238 | {21186}{21260}它的意思是我想要把零个或多个 239 | {21263}{21330}`ab` 这个字符串 240 | {21345}{21393}换成空的 241 | {21409}{21505}单独的一个 `a` 不会被替换掉 242 | {21516}{21577}单独一个 `b` 也不会被替换掉 243 | {21583}{21632}但是 `ab` 连一起 244 | {21633}{21682}它就会被替换掉了 245 | {21872}{21914}`sed` 你好蠢啊 246 | {21957}{22084}这里加上 `-E` 是因为 `sed` 真的很老了 247 | {22089}{22205}它只支持很旧版本的正则表达式 248 | {22206}{22288}一般你要加上 `-E` 开关跑 249 | {22289}{22351}这样他就会用一套支持更多东西的 250 | {22352}{22383}更现代的语法 251 | {22412}{22471}如果它没法使用 `-E` 开关 252 | {22472}{22546}那你就得在括号前面加 `\` 253 | {22547}{22644}来告诉它使用“特殊含义”的括号 254 | {22653}{22723}不然它就只会匹配括号本身 255 | {22724}{22772}那可能不是你想要的 256 | {22798}{22917}注意它把这里的 `ab` 替换掉了 257 | {22923}{22981}把这里的 `ab` 也替换掉了 258 | {22982}{23027}但是把这个 `c` 259 | {23028}{23083}还有末尾的 `a` 留下来了 260 | {23084}{23170}因为它和 pattern 不匹配 261 | {23206}{23284}你可以把 pattern 的任意部分括成一组 262 | {23285}{23362}也有「选择」之类的东西 263 | {23363}{23426}例如你可以让它移除 264 | {23427}{23518}任意匹配 `ab` 或 `bc` 的字符串 265 | {23600}{23674}然后你会注意到这个 `ab` 没了 266 | {23687}{23753}但就算这个 `bc` 和 pattern 相匹配 267 | {23757}{23807}它并没有被删除 268 | {23808}{23881}这是因为 `ab` 已经被删除了 269 | {23901}{23960}这个 `ab` 被删掉了,对吧 270 | {23964}{23993}`c` 还留着 271 | {23994}{24045}这里的 `ab` 被删去了 272 | {24054}{24139}因为这个 `c` 依然不匹配,还留着 273 | {24148}{24232}如果我把这个 `a` 删掉 274 | {24233}{24302}这个 `ab` 的 pattern 275 | {24303}{24348}就不会匹配到这个 `b` 276 | {24349}{24377}然后它就会被留下来 277 | {24378}{24428}然后 `bc` 就会匹配到这个 `bc` 278 | {24429}{24466}随后就会被删掉 279 | {24488}{24570}你刚开始接触的时候 280 | {24571}{24614}正则用起来会很麻烦 281 | {24615}{24670}就算当你熟练之后 282 | {24671}{24718}这东西看起来也很吓人 [*] 283 | {24731}{24781}这也是为什么 284 | {24782}{24877}人们常常会使用正则调试器的原因 285 | {24878}{24920}过一会儿我们会看到 286 | {24926}{24988}但首先让我们编写一个 pattern 287 | {24989}{25141}能够匹配日志条目…呃…匹配我们在处理的条目 288 | {25157}{25280}让我们先从文件里拿几行出来 289 | {25281}{25325}那就前五行吧 290 | {25332}{25408}看,这几行现在是这样一个形式 291 | {25454}{25612}但是我们要做的是,只留用户名 292 | {25632}{25872}那么我们就会想把它写成这样... 293 | {26075}{26144}等下 让我先给你看一个东西 294 | {26145}{26204}我们先整出来 295 | {26205}{26542}写着 _这样一串_ 的一行 296 | {26556}{26632}那么这是一条登录记录 297 | {26646}{26775}有人打算以 `Disconnected from` 作为用户名登录 298 | {26802}{26841}_(学生)少了个 `s`_ 299 | {26842}{26991}少了个 `s` 吗?emmmm... 300 | {26992}{27015}_(学生)第一个 `Disconnected`_ 301 | {27016}{27075}**`Disconnected`** 多谢 302 | {27122}{27221}那么你会发现这个命令连用户名一起移除了 303 | {27222}{27297}这是因为像 `.*` 304 | {27301}{27377}这种匹配一个**范围**的表达式 305 | {27383}{27425}它们是用贪心策略 306 | {27426}{27496}去尽可能多的匹配 307 | {27497}{27633}因此虽然我们在这里想保留用户名 308 | {27644}{27794}但是这个 pattern 会一路匹配到它第二次 309 | {27795}{27835}也就是最后一次出现 310 | {27836}{27900}所以包括用户名在内 311 | {27901}{27960}在这之前出现的文本都会被删掉 312 | {27966}{28003}那么我们就要想一个 313 | {28004}{28053}机智一点的方法来匹配 314 | {28054}{28109}而不仅仅是使用 `.*` 315 | {28119}{28193}这样如果输入比较诡异 316 | {28194}{28253}可能会输出一些诡异的东西 317 | {28279}{28385}好 让我们来看看怎么匹配这些行 318 | {28394}{28466}首先先跑个 `head` (过滤一下) 319 | {28674}{28756}嗯...让我们从头开始构造这个 pattern 320 | {28801}{28881}显然我们不想 `\` 满地跑 321 | {28882}{28990}因此首先我们整个 `-E` 322 | {29004}{29069}这些行是这样一个形式 323 | {29070}{29129}先是 `from` 324 | {29130}{29203}有些写了 `invalid` 325 | {29231}{29348}有些又没有 是吧 326 | {29357}{29431}那这里的问号就是匹配 0 或 1 次 327 | {29442}{29491}那这样写就是说 328 | {29492}{29565}0 或 1 个 invalid 后面跟个空格\ 329 | {29581}{29630}然后是 `user`……? 330 | {29711}{29786}啊——多了个空格 可不敢乱多 331 | {29809}{29892}然后后面有个用户名 332 | {29910}{30008}然后后面是... 333 | {30009}{30093}然后后面是个 IP 地址 334 | {30100}{30188}这里可以用区间匹配的那些语法 335 | {30189}{30269}这个的意思就是 匹配 `0` 到 `9` 或者 `.` 336 | {30287}{30355}这是 IP 地址的特征 337 | {30371}{30424}而且我们要匹配多次 338 | {30472}{30538}然后后面是 `port`(端口) 339 | {30539}{30598}所以我们匹配一个固定的字符串 `port` 340 | {30599}{30754}然后再来一次数字 `0` 到 `9`,匹配多次 341 | {30827}{30904}除此之外我们还要做一件事 342 | {30905}{30950}我们要给表达式打锚点 343 | {30951}{31034}正则表达式里有两个特殊字符 344 | {31035}{31150}`^` 匹配行开头 345 | {31161}{31227}而 `$` 匹配行结尾 346 | {31255}{31293}那我们这样写 347 | {31294}{31391}就代表着这个正则表达式匹配了一整行 348 | {31420}{31458}为什么要这样写呢 349 | {31459}{31532}假设有个人把它的用户名 350 | {31533}{31596}设成了这一整条日志文本 351 | {31624}{31676}那当你匹配的时候 352 | {31677}{31777}就会匹配到用户名 353 | {31788}{31824}坏耶—— 354 | {31842}{31920}一般来说锚点能加尽量加 355 | {31921}{31980}避免这种偶然事件发生 356 | {31992}{32059}现在看看跑这个命令有什么效果 357 | {32064}{32111}这个命令删掉了好多行 358 | {32114}{32146}但还是留下来了一些 359 | {32151}{32249}例如这个 最后有个 `[preauth]` 360 | {32268}{32343}那我们把它炖了吧 361 | {32349}{32486}空格,`preauth`,方括号 362 | {32487}{32545}方括号是特殊字符要转义 363 | {32561}{32577}好耶 364 | {32583}{32669}再多来几行呢 365 | {32691}{32750}啊 还是有奇怪的东西 366 | {32751}{32792}这些行非空 367 | {32793}{32852}那就意味着 pattern 和它不匹配 368 | {32885}{32924}拿这个来说 369 | {32925}{33023}它写的是 `authenticating` 而不是 `invalid` 370 | {33032}{33061}好吧 371 | {33093}{33164}改成 `invalid` 或者 `authenticating` 之一 372 | {33165}{33216}在用户名之前匹配零次或一次 373 | {33248}{33272}现在如何 374 | {33312}{33365}看上去挺稳的 375 | {33386}{33466}但是这个输出没多少用啊 376 | {33467}{33533}它只是成功地 377 | {33534}{33593}把日志的每一行都清空了 378 | {33594}{33642}这不太有用啊 379 | {33650}{33722}相反我们真正想要做的是 380 | {33723}{33802}当我们在这里匹配用户名时 381 | {33809}{33892}我们更想要记录下来用户名是什么 382 | {33893}{33947}因为这是我们想要输出的内容 383 | {33983}{34046}在正则表达式中做这件事的方法 384 | {34056}{34134}是用一个叫「捕获组」的玩意(Capture Groups) 385 | {34161}{34273}「捕获组」用来表示 386 | {34274}{34369}我想要记住这个值 387 | {34377}{34416}并在之后使用 388 | {34423}{34480}在正则表达式中 389 | {34481}{34566}任何圆括号括起来的表达式 390 | {34575}{34624}就是这样一个捕获组 391 | {34629}{34688}所以我们已经在这里用了一个了 392 | {34704}{34794}这是第一组,现在我们在这儿再建第二组 393 | {34810}{34898}注意这些括号并不影响匹配 394 | {34920}{34958}对吧?因为它们仅仅表达了 395 | {34959}{35011}将这个表达式作为一个单元 396 | {35012}{35069}但它的后面没有任何修饰符 397 | {35070}{35111}所以还是只匹配一次 398 | {35156}{35284}然后捕获组有用的原因是 399 | {35289}{35376}你可以在替换式(replacement)中使用它 400 | {35385}{35429}这样,在这里的替换式处 401 | {35430}{35489}我可以键入 `\2` [*] 402 | {35498}{35583}这是你指代捕获组编号的方法 403 | {35590}{35643}这里我写的意思是 404 | {35644}{35683}先匹配整行 405 | {35690}{35866}之后在替换式处放入你在第二个捕获组匹配到的值 406 | {35883}{35936}好的,记住这是第一个捕获组 407 | {35945}{35975}这是第二个 408 | {36023}{36066}现在它给了我所有的用户名 409 | {36083}{36145}现在来看看写出来的表达式 410 | {36146}{36229}它还挺复杂的,对吧 411 | {36235}{36275}当我们一步步地完善它之后 412 | {36276}{36304}你现在可能会明白 413 | {36306}{36353}为什么它必须是它现在这个样子 414 | {36354}{36406}但要搞懂代码执行起来如何 415 | {36407}{36458}其实并不是很直观的事情 416 | {36464}{36619}这就是正则表达式调试器的用武之地了 417 | {36641}{36673}我们这里有一个 418 | {36681}{36795}网上有很多,这个我已经提前填好了 419 | {36796}{36852}我们刚刚用过的表达式 420 | {36853}{36990}注意到,它上面显示了所有的匹配结果 421 | {37007}{37147}这个窗口配这个字体,字太小了 422 | {37162}{37310}但是如果我……这里,这个注释说 423 | {37311}{37450}`.*` 匹配所有字符 0 次到任意次 424 | {37470}{37580}后面是 `Disconnected from` 这几个词 425 | {37589}{37625}后面是一个捕获组 426 | {37626}{37685}下面还有各种别的 427 | {37692}{37741}这是一个功能 428 | {37742}{37803}它还允许你指定测试字符串 429 | {37804}{37929}之后对给定的每个测试串跑正则表达式 430 | {37933}{38030}并且像这样给不同的捕获组着色 431 | {38051}{38158}这里,我们将用户作为一个捕获组,对吧 432 | {38213}{38265}它显示整个串都匹配到了 433 | {38266}{38325}整个串是蓝色的所以匹配完成 434 | {38337}{38389}绿色部分是第一个捕获组 435 | {38395}{38440}红色是第二个捕获组 436 | {38445}{38477}这是第三个 437 | {38487}{38555}因为 preauth 也被括号括起来了 438 | {38577}{38669}这会是一个调试正则表达式的好方法 439 | {38670}{38777}例如如果我放 Disconnected from…… 440 | {38822}{38901}我们这里新添一行 441 | {38993}{39070}如果我把 Disconnected from 作为用户名 442 | {39131}{39216}好吧现在这行已经有这个用户名了 443 | {39231}{39276}我这是未卜先知 444 | {39293}{39346}你会注意到利用这种匹配模式 445 | {39378}{39462}这不再是一个问题 446 | {39465}{39513}因为它正确地匹配了用户名 447 | {39521}{39773}如果我们把这整行或这行变成用户名会发生什么 448 | {39801}{39843}如你所见 449 | {39919}{39955}真让人摸不着头脑 450 | {40008}{40092}将正则表达式调对会很痛苦 451 | {40111}{40175}它现在尝试匹配…… 452 | {40200}{40257}它匹配到的第一个组 453 | {40258}{40361}也就是用户名,似乎是第一个 invalid 454 | {40394}{40429}啊,第二个 invalid 455 | {40430}{40462}因为它是贪心的 456 | {40475}{40517}通过在这里加一个 `?` 457 | {40527}{40569}我可以将它变为非贪心的 458 | {40589}{40721}所以如果你在 `+` 或者 `*` 后加 `?` 459 | {40730}{40790}它会变成非贪心匹配 460 | {40791}{40856}也就是不会尽可能地向后匹配 461 | {40865}{40923}这样你可以看到,这个串被正确地解析了 462 | {40935}{41049}因为 `.*` 匹配会在第一个 Disconnected from 处停止 [*] 463 | {41055}{41143}也就是 SSH 指令固定输出的那个 464 | {41153}{41207}是实际出现在我们记录中的那一个 465 | {41297}{41374}讲到现在,你大概也发现了 466 | {41375}{41452}正则表达式会非常复杂 467 | {41459}{41530}你也很可能会在你写的匹配模式中 468 | {41531}{41589}用到各种各样迷惑的修饰符 469 | {41598}{41651}真正学会它的唯一方式 470 | {41652}{41699}是从简单的表达式开始 471 | {41700}{41721}之后堆砌起来 472 | {41722}{41755}直到它匹配到你想要的 473 | {41767}{41851}通常你仅仅是在做一些一次性工作 474 | {41852}{41897}比如刚才我要提取用户名 475 | {41898}{41988}你不需要去考虑那么多特殊情况,对吧 476 | {41992}{42095}你不需要考虑某人的 SSH 的用户名 477 | {42096}{42149}完美地匹配了登录记录的格式 478 | {42165}{42214}这也不算什么大事 479 | {42215}{42269}因为你只是找用户名而已 480 | {42283}{42326}正则表达式确实很强大 481 | {42327}{42422}处理的内容很重要的时候,记得万分小心 482 | {42435}{42455}你要提问吗? 483 | {42635}{42764}总之正则表达式默认只逐行匹配 484 | {42799}{42878}它不会跨行匹配 485 | {43124}{43199}所以 sed 运行的方式是 486 | {43200}{43255}它逐行处理 487 | {43269}{43399}所以 sed 会对每一行匹配这个表达式 488 | {43493}{43583}好,正则表达式或者模式相关问题到此为止 489 | {43584}{43622}它是一个复杂的模式 490 | {43623}{43709}所以如果感到迷惑,别担心 491 | {43710}{43769}课上完了,回去在调试器中看一看 492 | {44103}{44170}所以记住 493 | {44171}{44204}我们在这里假设 494 | {44205}{44306}用户只能控制他们的用户名,对吗? 495 | {44337}{44377}所以他们能做的最坏的事 496 | {44378}{44468}就是把这种整条记录设为用户名 497 | {44469}{44528}我们看会发生什么 498 | {44627}{44665}好的,这是运行结果 499 | {44666}{44788}它的原因是,`?` 意味着 500 | {44789}{44848}我们一遇到 Disconnected (from) 这个词 501 | {44857}{44908}就立刻匹配后面的模式,对吧 502 | {44950}{45065}第一个 Disconnected 是 SSH 自己输出的 503 | {45066}{45125}一定在用户可编辑的内容之前 504 | {45137}{45181}所以在这个特例下 505 | {45182}{45252}即使这样也不会干扰模式串 506 | {45258}{45274}你要提问吗? 507 | {45297}{45446}_(学生有关模式串的数据安全性的问题)_ 508 | {45483}{45611}啊,如果你在写一个…… 509 | {45612}{45701}这种比较怪的的匹配模式…… 510 | {45708}{45782}总的来说,你在做数据整理的时候 511 | {45783}{45885}一般它不会涉及(信息)安全 512 | {45890}{45969}但你很可能会得到很怪异的数据 513 | {45984}{46036}所以如果你在做一些像 514 | {46037}{46064}绘制图表之类的事 515 | {46065}{46109}你可能会丢掉重要的数据点 516 | {46110}{46169}你可能解析出错误的数值 517 | {46170}{46288}之后你的表突然出现了原始数据中没有的数据点 518 | {46289}{46321}所以重要的是 519 | {46322}{46417}如果你发现你在写一个复杂的正则 520 | {46418}{46459}多检查几下 521 | {46460}{46513}它匹配出来的是不是你想要的 522 | {46514}{46589}即使它与信息安全无关 523 | {46677}{46705}和你想的一样 524 | {46706}{46779}这些模式串可能会非常复杂 525 | {46780}{46855}例如这里有一个讨论 526 | {46856}{46945}关于如何用正则表达式匹配一个 email 地址 527 | {46956}{46999}你可能会想到像这样的 528 | {47001}{47082}这是一个非常直观的表达式 529 | {47083}{47237}只是字母,数字,一些字符,后面一个 `+` 530 | {47238}{47343}因为在 Gmail 里,email 地址里可以有 `+` [*] 531 | {47367}{47530}这里的 `+` 只表示任何这些字符至少出现一个 532 | {47537}{47609}因为你不会有一个 @ 前为空的 email 地址 533 | {47618}{47683}后面域名的规则也差不多,对吧 534 | {47696}{47809}顶级域需要至少两个字符并且不能包括数字 535 | {47816}{47895}你可以是 .com 但是不能是 .7 536 | {47928}{47996}事实上这并不完全正确 537 | {47997}{48097}这里有一堆有效的 email 地址不会被它匹配 538 | {48098}{48175}还有一堆无效的 email 地址会被它匹配 539 | {48188}{48316}所以有很多很多建议 540 | {48317}{48398}还有热心网友写了的完整的测试套件 541 | {48399}{48476}尝试判断哪一个正则表达式是最好的 542 | {48531}{48609}这是一个专门给 URL 的 543 | {48610}{48670}这是类似的给 email 的 544 | {48671}{48731}他们发现最好的就是这个 545 | {48776}{48851}我不建议你去试着理解这个模式串 [*] 546 | {48857}{48982}但这个很明显会几乎完美的匹配到 547 | {48983}{49086}像符合互联网标准的 email 地址 548 | {49087}{49125}就是所说的有效 email 地址 549 | {49132}{49224}它还包含 Unicode 里奇奇怪怪的编码 550 | {49231}{49321}这只是想说明正则表达式可以非常长 551 | {49328}{49373}如果最后你写出像这样的表达式 552 | {49374}{49433}很可能会有更好的方式去做 553 | {49461}{49576}比如,如果你自己在试着解析 HTML 554 | {49577}{49751}或者解析 JSON 格式,对于这种格式来说 555 | {49752}{49793}去用其它工具大概会比较好 556 | {49802}{49879}我们也有这样的练习 557 | {49883}{49940}不是用正则表达式,提醒你 558 | {50039}{50142}这里有各种各样的建议 559 | {50143}{50233}还非常非常深入地展示了它的运行过程 560 | {50234}{50314}如果你想查阅,它在课程笔记里 561 | {50408}{50479}好的,我们有了这些用户名 562 | {50502}{50545}让我们回到数据整理 563 | {50546}{50597}像这列用户名 564 | {50607}{50671}它仍然对我很不友好,对吗? 565 | {50672}{50717}让我们看看总共有几行 566 | {50718}{50782}如果我键入 `wc -l` 567 | {50798}{50831}这有…… 568 | {50903}{50965}一十九万八千行 569 | {50974}{51043}这个 `wc` 是计数程序(**w**ord **c**ount) 570 | {51044}{51100}`-l` 选项是统计行数 571 | {51112}{51164}所以这么多行 572 | {51165}{51266}如果我只是边翻边看,意义也不大 573 | {51270}{51329}对吧,我需要的是统计数据 574 | {51330}{51382}我需要找个方法合计数据 575 | {51476}{51541}虽然 `sed` 这个工具用途很广 576 | {51542}{51587}它支持一个完整的编程语言 577 | {51588}{51673}可以做一些,比如插入文本 578 | {51674}{51739}或者只输出匹配行的操作 579 | {51740}{51831}但它不是应付一切的完美工具,明白吗 580 | {51832}{51884}有时候有更好的选择 581 | {51885}{51944}就比如说,你可以用 `sed` 582 | {51945}{51995}编程实现行数统计 583 | {51996}{52039}但绝对别这么干 584 | {52040}{52096}除了搜索替换之外 585 | {52097}{52144}`sed` 的语法挺烂的 586 | {52194}{52251}但是,还有别的好用的工具 587 | {52252}{52347}比如有个叫 `sort` 的 588 | {52427}{52486}虽然它泛用性不高 589 | {52487}{52565}`sort` 会接受很多行的输入 590 | {52573}{52632}排一个序,然后输出到输出流 591 | {52649}{52759}现在,我有了这个排序后的列表 592 | {52760}{52853}它仍然有二十万行,所以还不是很好 593 | {52868}{52974}但现在我可以把 `uniq` 结合进来 594 | {52985}{53011}这个工具 `uniq` 595 | {53012}{53089}作用在多行有序的输入上 596 | {53099}{53182}输出去重后的输入 597 | {53189}{53277}也即,如果你有重复的行 598 | {53282}{53321}这些行只会被打印一次 599 | {53336}{53408}我可以执行 `uniq -c` 600 | {53417}{53580}意为,对重复的行,计算它们重复的数量 601 | {53581}{53612}然后将其(从输出中)去除 602 | {53628}{53654}这会输出什么呢? 603 | {53662}{53771}呃,如果我执行它,会处理一会 604 | {53780}{53878}里边有 13 个 `zzz` 用户名 605 | {53879}{53973}10 个 `zxvf` 用户名,等等 606 | {53978}{54022}我可以上下翻看 607 | {54023}{54083}这仍是一个很长的表单,对吧 608 | {54084}{54202}但现在,至少比原来稍微条理点了 609 | {54209}{54281}看看现在我们提出来多少行 610 | {54393}{54480}好,两万四千行,仍然很多 611 | {54481}{54530}虽然对我而言,这些信息没用 612 | {54539}{54624}但我可以用更多工具,不断缩减它 613 | {54638}{54703}比如我可能想知道 614 | {54704}{54769}哪个用户名出现的最多 615 | {54789}{54845}我可以再排个序 616 | {54856}{54996}我想要对输入的第一列做数值排序 617 | {55019}{55098}所以 `-n` 意为数值排序 618 | {55099}{55154}`-k` 允许你在输入中 619 | {55155}{55263}选中空白字符分隔的一列,执行排序 620 | {55269}{55351}这里我加了一个 `,1` 的原因是 621 | {55352}{55457}我想要计数第一列到第一列 622 | {55472}{55501}除此之外我也可以要求 623 | {55502}{55586}依据所有的列排序 [*] 624 | {55598}{55663}但这里我只想用第一列 625 | {55720}{55820}然后我只想要最后十列 626 | {55821}{55961}`sort` 默认是以升序输出 627 | {56011}{56093}所以计数最高的一条在最底下 628 | {56094}{56158}然后我就只要最后十列 629 | {56223}{56262}现在再跑的时候 630 | {56270}{56344}我就有比较有用的数据了,对吧 631 | {56345}{56476}它告诉我用户名 `root` 有一万多次登录尝试 632 | {56481}{56633}用户名 `123456` 有四千多次 633 | {56661}{56714}这就很棒了 634 | {56738}{56907}现在这个大日志突然就给我有用信息了 635 | {56912}{56986}这是我真正想从日志里要的信息 636 | {56994}{57046}现在我可能就想,比如 637 | {57047}{57129}快速地禁用一下我机器上 638 | {57130}{57200}比如 SSH 登录的 `root` 用户名 639 | {57223}{57271}顺便我也建议你们这样做 640 | {57364}{57400}其实对于这个情况 641 | {57401}{57460}我们不需要 `sort` 的 `-k` 642 | {57474}{57567}因为 `sort` 默认按(从前到后的)列排序 643 | {57568}{57619}而数字又恰巧是最前面一列 644 | {57625}{57685}但了解这些额外的 flag 是有益的 645 | {57708}{57794}你可能想问,我是怎么知道有这些 flag 的 646 | {57795}{57854}我是怎么了解这些程序的存在的 647 | {57873}{57945}嗯,通常这些程序是 648 | {57946}{58009}在这种课堂上知道的 649 | {58018}{58074}至于这些 flag 650 | {58097}{58154}经常是,我想按照某个基准排序 651 | {58155}{58205}但不是按整行 652 | {58226}{58304}那你的第一反应是键入 `man sort` 653 | {58305}{58343}然后把页面读一遍 654 | {58359}{58407}你很快就能知道怎么能 655 | {58408}{58457}优雅地选中一行 656 | {58458}{58517}怎么能像这样,选这行数字 657 | {58604}{58699}好,如果,我们现在有了这个…… 658 | {58700}{58749}就让它是前 20 的表单 659 | {58779}{58836}假设我并不关心具体数量 660 | {58845}{58938}我只要一个逗号分隔开的用户名表单 661 | {58949}{58989}因为我可能打算通过电邮 662 | {58990}{59065}每天都把它发给自己,之类的 663 | {59066}{59170}像是《震惊!今天攻击者最喜欢的二十个用户名竟是...》 664 | {59188}{59278}嗯,我可以这样—— 665 | {59372}{59431}好,出现了更多怪怪的命令 666 | {59432}{59488}但了解它们都是有意义的 667 | {59499}{59643}这个 `awk` 是基于列的流编辑器 668 | {59656}{59734}我们提到了流编辑器 `sed` 669 | {59741}{59835}它主要是编辑输入进来的文本 670 | {59843}{59960}此外,`awk` 也让你编辑文本 671 | {59961}{60012}也是一个完整的编程语言 672 | {60017}{60083}但它专注于基于「列」的数据 673 | {60093}{60151}所以这里 `awk` 会以默认方式 674 | {60152}{60262}解析空格分隔的输入 675 | {60272}{60343}然后你可以分别处理这些行 676 | {60350}{60415}我这里告诉它只打印第二行 677 | {60422}{60476}就是用户名那行,对吧 678 | {60531}{60568}`paste` 这个程序 679 | {60572}{60659}能借助 `-s` 选项,将一大堆行的输入 680 | {60662}{60725}处理成以 tab 分隔的一行 681 | {60732}{60782}这里 `-d` 使其以 `,` 分隔,而不是 tab 682 | {60819}{60895}这里,这个例子,我想要一个 683 | {60896}{60970}逗号分隔的最靠前的用户名列表 684 | {60989}{61057}然后我就可以物尽其用 685 | {61068}{61132}比如我把它丢进一个配置文件 686 | {61133}{61192}去禁止这些用户名啥的 687 | {61258}{61341}`awk` 值得我多费几句口舌 688 | {61342}{61430}讲白了,对于这样的数据整理 689 | {61431}{61490}它是一个非常有力的语言 690 | {61542}{61657}我简单说了这个 `print $2` 做什么 691 | {61682}{61820}但你可以用 `awk` 施展一些绚丽的魔法 692 | {61821}{61922}比如,我们先回到处理用户名这里 693 | {61932}{62053}然后……我们还是执行 `sort` 和 `uniq` 吧 694 | {62112}{62161}不然这个表单就太长了 695 | {62172}{62254}然后让我们只输出那些 696 | {62260}{62320}和特定模式相符的用户名 697 | {62365}{62459}比如,让我想想…… 698 | {62591}{62643}`uniq -c` 699 | {62666}{62834}我要只出现一次,并且 700 | {62852}{62953}以 c 开头、e 结尾的所有用户名 701 | {62985}{63040}虽然我们在搜索一个奇怪的东西 702 | {63051}{63128}但在 `awk` 里面写出来还挺容易 703 | {63134}{63200}我可以让第一列是 `1` 704 | {63236}{63403}并且第二列匹配这个正则 705 | {63628}{63687}*好像这里只用 `.` 就行* 706 | {63797}{63858}然后我想按整行打印 707 | {63959}{63995}除非我搞错了什么东西 708 | {64008}{64142}不然这就是所有以 c 开头,e 结尾 709 | {64143}{64202}并且只出现了一次的用户名 710 | {64256}{64352}虽然对数据做这种处理没有意义 711 | {64358}{64417}但我在课上想讲的是 712 | {64418}{64466}各种可以运用的工具 713 | {64475}{64566}并且虽然我们举的例子很奇怪 714 | {64567}{64626}但这个 pattern 并不复杂 715 | {64636}{64753}这是因为某些 Linux 的工具 716 | {64754}{64802}以及普遍的命令行工具 717 | {64820}{64942}都是按照以行为单位的输入输出而设计 718 | {64947}{65054}并且这些行经常会分为多列 719 | {65055}{65127}而 `awk` 就是处理列的能手 720 | {65339}{65509}`awk` 不仅能做这种匹配每行的操作 721 | {65546}{65628}而且,比如说…… 722 | {65629}{65688}让我先输出一下行数 723 | {65689}{65768}我想知道多少用户名符合这个模式 724 | {65778}{65855}我可以执行 `wc -l`,这样就挺好 725 | {65901}{65965}有 31 个这样的用户名 726 | {65974}{66025}但 `awk` 是编程语言啊 727 | {66040}{66178}这个黑魔法,你估计不会想去碰它 728 | {66186}{66236}但要知道你可以运用 729 | {66237}{66348}知道这些,对现在和以后都有益 730 | {66426}{66516}在我屏幕上可能不太好读懂 731 | {66518}{66554}我也发现了…… 732 | {66647}{66701}我马上处理一下 733 | {66647}{66727}稍等,让我试着修一下这个锅 734 | {66834}{66935}运行这个试试,啊! 735 | {67008}{67058}显然,fish 不想让我这么做 736 | {67061}{67240}看, `BEGIN` 在第一行的开头被匹配到 737 | {67242}{67389}`END` 在最后一行的末尾被匹配到 738 | {67405}{67514}然后这些是普通的逐行匹配正则 739 | {67516}{67552}所以,我写的这些意思是 740 | {67554}{67651}在第零行开始, `rows` 这个变量被赋值为 0 741 | {67652}{67723}对于能匹配这个规则的文本行 742 | {67724}{67774}`rows` 的值就会增加 743 | {67775}{67859}当你匹配完最后一行的时候 744 | {67860}{67918}就把 `rows` 这个变量的值打印出来 745 | {67932}{68014}这和运行 `wc -l` 差不多 746 | {68016}{68059}但是这些都是用 awk 运行的 747 | {68061}{68161}通常 `wc -l` 就有不错的效果 748 | {68163}{68243}但是如果你想做些别的 749 | {68244}{68343}比如维护一个字典或者 map [*] 750 | {68345}{68399}或者统计一些数据 751 | {68400}{68520}再或者是…我想找第二个符合匹配的结果 752 | {68521}{68576}所以你需要一个有状态的匹配器 753 | {68577}{68623}比方说忽略匹配到的第一个结果 754 | {68624}{68689}从第二个符合条件的结果开始逐个输出 755 | {68690}{68800}这样的话,懂几行 awk 就很有用了 756 | {68897}{68966}实际上,在现在这种情况下 757 | {68967}{69013}我们可以撇掉之前处理文件使用的 758 | {69014}{69167}`sed sort uniq` 和 `grep` 这些命令 759 | {69168}{69209}然后用 awk 取而代之 760 | {69210}{69259}但你大概不愿意这样做 761 | {69260}{69353}这样做不值得,反倒可能让你很痛苦 762 | {69406}{69515}再来说一说命令行里 763 | {69518}{69587}别的非常好用的工具 764 | {69588}{69689}首先是一个很方便的程序,叫做 bc 765 | {69690}{69776}或许 bc 是 **B**erkeley **C**alculator? 766 | {69777}{69802}我想应该是吧 767 | {69803}{69841}`man bc` 768 | {69880}{69956}我想 bc 应该是起源于 Berkeley calculator 吧? 769 | {69966}{70051}无所谓了,它是一个简洁的命令行计算器 770 | {70052}{70115}它并没有给你个提示符,让你输入 771 | {70116}{70165}而是直接从标准输入读数据 772 | {70166}{70304}所以我能这样 `echo "1 + 2" | bc -l` 773 | {70305}{70369}(要加 `-l`)因为好多这样的程序 774 | {70370}{70479}默认的运行模式都很不智能 775 | {70536}{70610}好,它输出了 3 776 | {70611}{70654}哇,太强了 777 | {70670}{70731}同时这也说明它用起来挺方便的 778 | {70732}{70823}想象一下,你有一个有很多行的文件 779 | {70824}{70956}比如说,唔,不知道整啥好了 780 | {70991}{71052}比方说,在这个文件里 781 | {71053}{71230}我想把登录的次数加起来 782 | {71231}{71318}把出现不止一次的名字个数加起来 783 | {71319}{71461}这里写,第一个匹配组的内容不为 1 784 | {71474}{71558}然后只把这个次数输出 785 | {71612}{71665}程序就会告诉我 786 | {71666}{71755}所有登录了不止一次的用户都登录了几次 787 | {71756}{71837}然后我还想了解一下总数是多少 788 | {71838}{71887}注意我不能只数一下有多少行 789 | {71888}{71924}这样是有问题的,对吧 790 | {71925}{72028}因为每一行都有对应的次数,我得把他们都加起来 791 | {72029}{72088}那么,我可以用 `paste` 命令 792 | {72089}{72157}一边粘贴输入,一边附上加号 793 | {72158}{72288}这样就把所有行用 `+` 连接成了一行加法式 794 | {72317}{72388}这就是一个算术表达式 795 | {72389}{72468}这样就可以把它 pipe 到 `bc -l` 796 | {72469}{72610}可以看到,总共有十九万一千多个用户名 797 | {72611}{72703}登录了不止一次 798 | {72704}{72796}你可能并不关心这个结果 799 | {72797}{72926}这只是展示一下你可以轻松提取这些数据 800 | {72994}{73073}你还可以用这些数据做很多别的事 801 | {73074}{73187}有用来计算和统计输入数据的工具 802 | {73206}{73301}比如说,对于刚刚这列数字 803 | {73302}{73393}这样,我们重新只输出数字 804 | {73419}{73481}按顺序输出数字 805 | {73508}{73636}然后我可以用 R 跑一下 806 | {73637}{73683}R 是一门独立的编程语言 807 | {73684}{73761}针对数据的统计分析而设计 808 | {73783}{73833}我可以这样写 809 | {73852}{73915}看看我能不能搞对 810 | {73935}{74093}它也是一个新的编程语言,你要专门去学 811 | {74141}{74250}先假设你会用 R,但也可以 pipe 给别的语言 812 | {74475}{74616}这样我就得到了一个输入数字的统计结果 813 | {74642}{74768}所以各用户名登录次数的中位数是 3 814 | {74784}{74863}最大值是一万多,我们之前看过了,这个是 root 产生的 815 | {74864}{74912}还告诉我平均值是 8 816 | {74947}{75004}这些在目前的这个例子里可能没有意义 817 | {75005}{75053}这些不是什么有意义的数据 818 | {75054}{75148}但是,处理比如统计脚本的输出,或者别的一些 819 | {75157}{75234}会有明显数值分布的数据时 820 | {75235}{75326}如果你想看这种数据,这些工具就有用了 821 | {75381}{75455}我们甚至可以画个简单的图表 822 | {75506}{75556}这里是一堆数字 823 | {75565}{75713}我们回到前面,`sort -nk1,1` 824 | {75723}{75823}然后只保留,就最前面五个吧 825 | {75862}{75934}`gnuplot` 是一个画图表的工具 826 | {75947}{76017}可以接受标准输入 827 | {76126}{76214}我不期望你们都会这些编程语言 828 | {76263}{76345}毕竟他们都是实打实的一门门编程语言 829 | {76356}{76414}只是展示一下你的工具选择 830 | {76502}{76565}现在这就有了一个大直方图 831 | {76566}{76661}是前五个用户,自从 1 月 1 日开始 832 | {76662}{76768}都各被用了多少次的图表 833 | {76818}{76870}这只用了一行命令 834 | {76896}{77011}虽然它特别长特别复杂,但只用一行就可以 835 | {77144}{77224}这节课的最后我再说两句 836 | {77225}{77321}还有两种特别的数据处理 837 | {77334}{77450}首先是命令行参数的处理 838 | {77475}{77646}有时候你会遇到一些情况…… 839 | {77647}{77747}比如上节课讲过的 `find` 命令 840 | {77748}{77816}会产生一连串的文件名 841 | {77817}{77927}或者一些命令可能产生一连串的…… 842 | {77995}{78064}参数,传给你的评测脚本 843 | {78065}{78150}比如你想带上有特定数值分布的参数运行 844 | {78151}{78181}比如你有一个脚本 845 | {78182}{78287}会给一个程序提供它迭代次数的数值 846 | {78288}{78359}然后你想让这组数值呈指数分布之类的 847 | {78360}{78440}然后这个脚本会逐行输出每个迭代次数 848 | {78441}{78527}你想要照此依次运行程序 849 | {78528}{78608}正好,这有个叫 `xargs` 的工具是你的好帮手 850 | {78614}{78800}`xargs` 接受若干行输出,把它们转为参数形式 851 | {78816}{78950}这可能有点怪,我看看有啥好例子吗 852 | {78951}{79014}这样,我会用 Rust 编程[*] 853 | {79015}{79132}Rust 允许你安装前后多个版本的编译器 854 | {79133}{79225}在这里你可以看到稳定版、Beta 版 855 | {79226}{79328}还有几个早期的稳定发布版 856 | {79329}{79390}还有一堆过期了的 nightly 版本 857 | {79400}{79447}这功能还挺不错的 858 | {79448}{79486}但是很长时间过去之后 859 | {79487}{79580}就不需要留着这些 nightly 版本了 860 | {79581}{79650}像这种去年三月份的 861 | {79651}{79682}我可以删掉这些 862 | {79683}{79753}我从今往后可能还想清理一下 863 | {79776}{79855}这是一个多行的列表 864 | {79862}{79904}我可以先找出 nightly 865 | {79940}{80033}我可以去掉……`-V` 是不要匹配 866 | {80080}{80148}我不想匹配最新的 nightly 867 | {80159}{80251}好,这是一些有年头的 nightly 868 | {79647}{79681}那我大概就可以把它们删了 869 | {79682}{79741}那我现在想把这个列表整干净些 870 | {79805}{79849}这是一个多行的列表 871 | {79862}{79897}那我先 `grep nightly` 872 | {79914}{79968}然后我把最新版的 `nightly` 排除掉 873 | {79969}{80028}用 `-v` 来表示不匹配它 874 | {80078}{80135}我不想要匹配当前的`nightly`版本 875 | {80157}{80240}好现在这些 `nightly` 都带着日期 876 | {80250}{80307}我只想留下 2019 年的 [*] 877 | {80379}{80464}那现在我想把这一个个工具链 878 | {80465}{80528}从我的电脑里删掉 879 | {80544}{80597}我固然可以一行行的来回复制粘贴 880 | {80623}{80729}`rustup toolchain remove` 881 | {80748}{80851}哦,好像是 `uninstall` 882 | {80866}{80944}那我可以手动的输入这些文件名 883 | {80945}{80970}也可以复制粘贴 884 | {80971}{81025}但是这不是很烦么 885 | {81026}{81067}我都整出来列表了啊 886 | {81081}{81106}那要不这样吧 887 | {81107}{81361}我用 `sed` 把这些版本号的后缀去掉 888 | {81422}{81461}好,就这个样子 889 | {81474}{81521}然后用 `xargs` 890 | {81522}{81671}它可以把输入的列表转成参数 891 | {81686}{81847}这里我想让它变成这个命令的参数 892 | {81891}{81971}我的习惯是这里加个 `echo` 893 | {81972}{82031}这样就可以看到它将会干些什么 [*] 894 | {82053}{82097}啊这输出没太大用 895 | {82107}{82168}并不是很好读 896 | {82175}{82239}仔细看这个命令 897 | {82247}{82282}删掉 `echo` 之后 898 | {82285}{82342}将会执行 `rustup toolchain uninstall` 899 | {82343}{82380}然后后面是 `nightly` 版本 900 | {82381}{82431}作为**参数**传给程序 901 | {82452}{82487}那我一跑这个命令 902 | {82514}{82556}它就会把这些工具链删了 903 | {82557}{82606}那我就不用一个个复制粘贴了 904 | {82668}{82729}那这就是数据处理 905 | {82730}{82777}在辅助其他工作上的应用 906 | {82778}{82831}在观察数据之外更进一步 907 | {82832}{82858}也就是把一种形式的数据 908 | {82859}{82878}转换成了另一种 909 | {82898}{82959}你还可以处理二进制数据 910 | {82974}{83017}而图像、视频等 911 | {83018}{83077}就是一个很好的例子 912 | {83078}{83221}你可以拿它们来整点有趣的活 913 | {83226}{83283}例如有个工具叫 `ffmpeg` 914 | {83289}{83374}它是用来编解码视频的工具 915 | {83375}{83425}也可以整点图像活 916 | {83448}{83487}我现在把它的日志级别 917 | {83490}{83514}设成 `panic` 918 | {83516}{83557}不然它会输出超多东西 919 | {83583}{83666}我想让它从 `/dev/video0` 920 | {83677}{83768}也就是从我的摄像头读取 921 | {83814}{83902}然后把第一帧拿出来 922 | {83903}{83939}也就是拍个照 923 | {83976}{84035}然后输出成一张图 924 | {84036}{84103}而不是一个单帧的视频 925 | {84136}{84238}我想让它把输出内容,也就是这个图片 926 | {84239}{84265}输出到 `stdout` 927 | {84272}{84338}一般用 `-` 就告诉程序 928 | {84339}{84389}从标准流来输入输出 929 | {84390}{84423}而不要用文件 930 | {84431}{84472}这个参数应该是给出一个文件名 931 | {84473}{84548}而用 `-` 作为文件名就代表 `stdout` 932 | {84554}{84639}然后我想把它用管道 933 | {84644}{84687}接到 `convert` 这个程序 934 | {84699}{84779}`convert` 是一个图像处理软件 935 | {84789}{84873}我想让 `convert` 从 `stdin` 读入 936 | {84912}{85027}然后把图片转成灰度的 937 | {85028}{85172}然后把结果写到 `-` 文件里 938 | {85173}{85204}也就是标准输出 939 | {85244}{85319}然后我想接给 `gzip` 940 | {85328}{85388}它可以压缩这个图片文件 941 | {85433}{85529}它也会用标准流来输入输出 942 | {85554}{85620}然后我会把它接到 943 | {85621}{85675}我的远程服务器上 944 | {85705}{85793}在那上面解码(解压缩) 945 | {85830}{85902}然后把图像存个副本 946 | {85920}{85953}复习一下 947 | {85954}{85985}`tee` 会从 `stdin` 输入 948 | {85986}{86024}然后输出到文件和 `stdout` 949 | {86025}{86057}那这样我们就得到了一份 950 | {86064}{86146}名字是 `copy.png` 的 951 | {86160}{86201}解码过的图像副本 952 | {86256}{86323}继续沿着管道推流 953 | {86335}{86437}我再把数据流导向本地 954 | {86466}{86605}我想让它在一个图片查看器里显示出来 955 | {86606}{86639}看看能不能用 956 | {86676}{86699}瓦! 957 | {86736}{86841}好 这张图经过我的服务器 958 | {86853}{86935}然后又由管道传回来了 959 | {86952}{87064}此外我的服务器理论上 960 | {87065}{87163}有这张图解码后的副本 961 | {87164}{87194}来看看有没有 962 | {87203}{87299}`scp copy.png` 到我这里 963 | {87320}{87342}啊这 964 | {87343}{87382}这样呢 965 | {87507}{87611}看!完全一致!这个命令生效了 966 | {87648}{87711}这是个简单的例子 967 | {87712}{87812}但是你会见识到这样构建管道的强大之处 968 | {87813}{87874}它不一定要是文本 969 | {87875}{87931}而是会把任意格式的数据 970 | {87932}{87950}转换成另一种格式 971 | {87962}{88022}如果想要的话 972 | {88023}{88081}我可以 `cat /dev/video0` 973 | {88082}{88135}然后把它用管道接到 974 | {88136}{88206}Anish 的服务器上 975 | {88207}{88307}他就可以把它 `pipe` 到视频播放器上 976 | {88308}{88400}然后在他的机器上看视频了 977 | {88443}{88498}只要知道有这么个操作就好 978 | {88575}{88646}今天有许多的练习可以做 979 | {88647}{88720}有些呢会用到一些数据源 980 | {88721}{88820}像是 macOS 和 Linux 的日志 981 | {88821}{88844}然后我们会告诉你用哪些命令 982 | {88845}{88877}你可以自己玩一下 983 | {88886}{88923}但是记住 984 | {88924}{89018}用什么数据源并不重要 985 | {89019}{89083}更重要的是找些 986 | {89084}{89147}你觉得有意思的数据源 987 | {89148}{89214}然后从里面找出有意思的东西 988 | {89215}{89274}这些练习的重点在这里 989 | {89306}{89407}星期一马丁路德金日不上课 990 | {89408}{89480}下一节课周二 991 | {89481}{89519}我们讲命令行环境 992 | {89531}{89593}现在讲的这些有些什么疑问吗 993 | {89594}{89651}管道啊 正则啊 994 | {89706}{89815}正则这东西真的值得好好看看 995 | {89816}{89868}它太好用了 996 | {89869}{89930}在编程方面也很好用 997 | {89945}{89980}如果有问题 998 | {89981}{90001}~~办公室找我~~ 999 | {90002}{90061}我会帮你的 1000 | --------------------------------------------------------------------------------