工具(-1): 命令行
典型使用场景
- 服务器远程登陆,只有命令行界面,此时不得不用命令行工具处理日常工作任务,所以就用,边用边查手册,用久了所以熟悉。
- 自己的电脑上经常使用命令行处理日常编程/文件管理工作。
- 自己编写命令行程序。
使用环境
- windows:
- cmd/powershell, 最新的好像默认直接用powershell
- msys2: http://www.msys2.org/
- linux bash on windows: https://docs.microsoft.com/en-us/windows/wsl/install-win10
- mac:
- 默认终端(可搭配fish)
- iterm2
- linux:
- 任意终端
- android:
分类随记
文件上传、下载:
**命令**:sz,rz,scp,rsync,wget,curl
评注:
- for循环wget,分分钟多进程下载。
- wget -c 分分钟断点续传。
压缩、解压:
命令:tar,zip,unzip
评注:
所以得会用tar,zip等命令。我一开始的时候,问一个老手tar cvzf,tar xvzf后面一排参数啥意思,老手说:”早就忘记了,天天都这么用“。所以我认为它和一个按钮一个菜单没什么区别,命令行参数的选项和参数一般是经常用就熟悉,久不用就容易忘记,此时复查是正常的事情。顺便吐槽下windows下7z的菜单里直接解压tar.gz要解压两次真是麻烦事,命令行当然可以一次搞定。
目录和文件的创建、移除、拷贝、移动:
命令:mkdir,rm,mv,cp
评注:
文件拷贝,主要是要考虑很多意外情况,例如
- 非空目录的移除,
- 递归删除时拼接路径的安全隐患(递归删除根目录就一点都不好玩了)
- 删除时文件被占用
- 如果可能只是短时被占用,可以sleep几十毫秒再删,重试n次退出。
- 考虑采用”删除xxx.old, 移动xxx到xxx.old, 拷贝yyy到xxx“的方式代替”直接删除xxx,移动yyy到xxx“的方案、
- 理解硬盘驱动,冗余磁盘阵列,inode,文件和目录,缓存策略等。
文本显示、查找、替换、统计:
命令:cat,tail,less,head,grep,sed,awk,wc,xargs
- awk: https://coolshell.cn/articles/9070.html
- awk-workflow: https://www.tutorialspoint.com/awk/awk_workflow.htm
- sed: https://coolshell.cn/articles/9104.html
评注:
在命令行下分析程序的日志,必备的技能就是文本操作。这组命令行基本要通过管道来配合使用。我个人觉的awk的设计有点古老,awk的脚本我是一点都不觉得写起来流畅和爽快,sed的稍微好点,但也一般。有好多次,我都尝试把awk学下来,不过都没成功,简单说规则一致性太差了。
如果是在windows下,这种流式过滤的工作,可以开LinqPAD写Linq来做,高级语言的代码可读性更好,也更利于维护,shell脚本的可读性差很多。考虑到夸平台,选择python脚本或者nodejs脚本都是不错的选择,python基本上什么库都有,nodejs 可能库不如python那么全,这两个工具链也适合把常用工具库发布到社区仓库上。
很多shell脚本写的人天马行空,别人读起来就不好玩了,毕竟写shell的人不写注释,遍地意大利面条。吐槽归吐槽,现实上我们不得不妥协,该用还得用。毕竟一切数据都可以输出成文本,所以文本的流式分析是基本技能。
我认为这组命令的组合使用能让人体会到管道的好处。管道让多进程之间的流式操作流畅进行。顺便我认为shell的一个特点是,它天生是多进程程序,一个shell里随便一堆命令就是多进程操作,可能很多人写着的时候并没有意识到这点。进一步,多主机之间ssh授权后,shell里的ssh远程命令的组织又天生带有点分布式属性。
**[非对称加密&反连协议] **:
ssh也许这能带来一些启发,例如ssh 信任就是非对称加密。
- A机留私钥; B机在.ssh目录放公钥,在.ssh/authorized_keys里填上公钥内容。
- 则A机执行ssh user@B cmd 的时候,B机会用公钥和A机之间建立ssh连接,然后执行命令。
- git 的客户端向git的仓库之间建立ssh授权也是这个过程,实际上任何依赖于ssh建立授权的都是这个过程。
- 这实际上是一种“反连”协议,思考下私钥在哪里就知道了。
[fork&exec]:
理解管道的另一个要素是,需要理解fork,vfork、clone,exec;需要理解标准输入输出。fork之后,在exec子进程之前重定向父进程的标准输出,重定向子进程的标准输入,这样它们就被流式串在一起。
文本编辑:
命令:vi,vim,emacs,cat,tail
评注:
cat,tail 是命令行下快速查看文本文件的快捷命令,cat一般配合grep、head使用更佳,而tail一般用来快速查看文件末尾或者通过tail -f
来监听文件的变更。
vi基本技能。shell下编辑文本只能用这个了,我认为vi或者vim里最重要的技能就三个:导航、查找、替换,带着这个思维去记忆那些命令会明确很多。我觉的任何一个程序的核心功能就几个,每个核心功能的核心命令也就几个,其他的就按需查用即可。同时,要理解vim的一个重要概念:模式。同样的键盘按钮,在不同vim模式下,操作代表不同的语义。想象一下相机是如何由有限的几个按钮做到多组不同功能的支持。
我认为快捷键并不是vim/emacs之类的最大特点,任何一个支持快捷键的编辑器,你都可以通过熟练掌握常见的快捷键使得操作快捷高效,比如notepad、visual studio、word等,理解这点有助于我们看清事实,驾驭工具。
资源监控:
**命令**: ps,du,top,htop,iotop,iostat,vmstat,lsof
评注:
输出的信息都可以配合grep等使用,比如ps aux|grep processname.
- 任务管理器型:vmstat, top, htop,
- 进程:ps
- 磁盘:du, iotop, iostat
- 文件句柄:lsof,比如查看一个进程打开的文件描述符,反向查看哪些进程打开了某个文件。
理解背后的知识:
- CPU的虚拟化,理解OS对process的调度(Scheduling):
- 调度算法:FIFO,SJF,STCF,RR,MLFQ
- 理解os调度process的话,再去看协程,用户态协程需要自己做调度:)
- IO密集和CPU密集?
- 内存:
- 物理内存的虚拟化
- 进程的内存分布,内部浪费和外部浪费
- 分页,页错误
调试和Hook:
**命令**: ulimit,gdb,strace,pstack,dtrace
评注:
开发和运维中,调试程序和Hook程序是必备的技能。
例1: coredump生成,运行时资源限制。
通过ulimit -c unlimited让程序崩溃时产生coredump,然后用gdb调试,看崩溃堆栈,变量、单步等,这是gdb的常规操作。linux下开发必备技能了。ulimit 还可以限制其他资源,比如-n限制文件句柄个数,-s限制堆栈大小等,具体查文档。
例2: C/C++开发中,每个线程的堆栈,判断出多线程问题、内存问题,这两个基本占大头,最常用的是GDB :Debugging with GDB,GDB操作的基本原则如下:
- 查看线程->切换线程->查看堆栈->切换函数帧和代码行->查看变量信息,再结合源码合理怀疑临界区问题、状态机问题、内存问题等。
- 定位就是:查看->切换->怀疑->[重现]->确定->修复,疑难问题可能只能靠肉眼codereview确定。
例3: 系统调用、堆栈等的Hook:strace,dtrace、pstack等。
这组算是lowlevel的神器,在很多时候能起到一针见血的效果。这里,可以理解下系统调用的原理,中断的实现原理,这样你在使用strace监控一个程序的系统调用的时候,脑子里会有更清晰的思路。系统调用并不是一个简单的函数调用这件事么..
例4: 常见的有添加路径到环境变量:export LD_LIBRARY_PATH=$PWD; 理解系统查找共享库的搜索路径顺序。
- readelf: 都说了任何东西都可以输出成文本来看了,看个依赖库路径还是容易的。
- https://en.wikipedia.org/wiki/Portable_Executable
- https://cs.gmu.edu/~setia/cs365-S02/assembler.pdf
- http://www.cs.cornell.edu/courses/cs3410/2012sp/lecture/15-linkers-i.pdf
- http://www.iecc.com/linker/
- https://en.wikipedia.org/wiki/COFF
- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
例5: 多线程程序调试中一个重要的关注点是关于锁的问题:
- Things UNIX can do atomically 多进程之间可以对文件和文件夹加锁
- 说起锁,想起Dijkstra,锁、信号量、等并发编程同步的机制最早都是他搞清楚的。
- 什么是”原子“的,在CS里,这是一个及其重要的概念。
- 锁应该保护的是数据还是行为?
- 锁的尽量局部化。
- 感兴趣?:Operating Systems: Three Easy Pieces
网络:
**命令**: tcpdump
评注:
tcpdump抓包是一定要掌握的,抓tcp、udp包是必备的技能。输出pcap格式文件也是要的。如果是有图形界面的话,wireshake在windows和linux下都能用,如果是windows上还可以用microsoft networkmonitor。说实话,我觉的microsoft networkmonitor做的比wireshake好用。
一个TCP连接,三次握手、四次挥手;
- 为什么要三次握手。syn、ack、ackack,它们是原子不可分的么,在更复杂的协议里,一个syn,一个ack、一个ackack本身可能就是一组子协议。三者中任何一个环节被打破(Break),都会导致握手失败。
- 为什么要四次挥手。自己设计流式协议的话,如果你不需要这种Feature,就可以暴力Close了。知其然,知其所以然。
- 拥塞控制算法(Congestion Ctrol, CC),根据历史数据记录估算未来数据,这是一种学习(统计):Reno,Cubic,Ledbat,BBR。
理解网络相关的必要知识:
- P2P打洞的原理:NAT,限ip,限port,限ip和port,随机ip和port
- DNS的原理,host,DNS污染
建立在TCP连接之上的Http连接,HTTP request包在什么时候发,seq号是怎样变动的,要自己通过tcpdump、wireshake、microsoft-networkmonitor等实际观察过至少一次。HTTP协议说到底是一个TCP协议,这点很重要。
理解这些协议之间的演化路径:SPDY->HTTP2->QUIC,它们的基本原理和目标都是一致的。
解决复杂链路问题,一台机子有多个网卡(ifconfig),或者单网卡,多ip,绑定到0.0.0.0的时候,收发包是怎样的情况?
理解协议栈,拆开ip包头,tcp和udp包头,以及做checksum校验的拟包头,理解协议包是如何被Stack的。话说最早我想当然认为tcp包头里面才是ip包包头,然而事实上这个鸡蛋黄是上层的包头在下层协议包的包体里。不知道是否有同类人有过这种直觉的错觉。
联想:
关于拥赛控制,本质上是一个对历史数据做filter,实际上Deep Learning也是一个这样的过程:问题是,它和大数据一样,广告词的成分居多,只有少数人在做和它相关的事情,从工程上来说,它应该是被搭建出来的算法模块,而非广告词,然而满大街的AI广告词让人讨厌,正经的程序员和工程师要对那些满口广告词的文章带有警惕性。
http://neuralnetworksanddeeplearning.com 这个6章的教程主要讲神经网络最基本的一个算法(误差逆传播算法,也就是所谓的bp算法)的基本原理(一部分在推导bp过程如何被用来算bp误差函数的微分,这里主要用到了微分的链式法则,求微分的目的是加速递归下降法的收敛速度),作者也对AI的未来有一番自己的见解,你会发现bp本质上就是用来做连续函数的逼近(利用递归下降法,让逼近函数的权重参数“快速”收敛)。后面的CNN只是一种用来减少bp权重参数的分层策略,本质上还是bp,不过RNN只是简单提了一下。
数据库操作:
**命令**:mysql,sqlite3,mongo,redis
评注:
- 命令行下,操作mysql和sqlite是常见的事情,这个基本就是常规的sql操作语句。
- 可以通过这个在线网站练习sql里窗口函数的使用:windowfunctions.com/questions/0
- 理解关系型数据库的范式,ACID,事务(Transaction)。
- 基于行存储Block的数据库,基于列存储Block的数据库,各自的优缺点
- B+树
- 局部性原理,行Block存储是行记录读写偏好,列的则是部分字段读写偏好
- trad off,没有最好,只有最合适
- 理解数据库元数据的读写,核心是bin日志文件的读写。
- 理解复式记账的本质
版本管理等:
**命令**:svn,git
评注:
- svn、git,这个本来就要会的,只是很多人用图形界面的工具多了后,可能命令行使用并不熟悉,但是在Linux shell下,这也算基本技能。
- 理解“围绕数据结构设计接口”,git是关于commit的链表的数据结构的一组api:http://www.cnblogs.com/math/p/git.html
- 元数据分布式同步,ZooKeeper
- Fast PaxOS协议。Leslie Lamport原作: http://research.microsoft.com/pubs/64624/tr-2005-112.pdf
- Raft protocol: http://thesecretlivesofdata.com/raft/
视角和启发
如果你觉的这么多东西真啰嗦,很简单,不理会此处的所有链接,只看个大概,我要强调的只是如何看待命令行的一种视角,命令则应该每个人自己使用中自己累积,正如一个人对自己写的代码,无论多复杂都能了然于胸,而对别人写的代码,则要花非常多心思才能做到同样熟悉,所以具体的操作应该是自己使用出来的。因为命令行作为操作系统的shell,每个命令行侧重的是操作系统的某一方面的知识,我提供的只是把命令行作为一个索引,去归类某些知识,这是我的目的。
在Mac下用了一年多fish,感觉很好用,大部分命令敲过一遍之后,输入前面几个单词,fish就会自动灰色显示备选历史命令,方向键选择下就好了,很方便。个人电脑的终端上装fish这种带自动补全功能的工具是很方便的。我们使用命令行,首先是为了效率。
以及,命令行的option,其实就像一级菜单/二级菜单一样的概念,只不过在命令行下你不能方便的使用鼠标就看清楚这些菜单是什么,就只能靠大脑去记忆,所以显得能记住命令的好像多高级,你想想如果给你VisualStudio,你会去记住那些一级菜单/二级菜单都是什么吗?不会,你只会每次都用鼠标一个个点出来而已,习惯了就记住了。命令行的option,也大概是这样的,通过查文档/常用示例,经常用的就记住了,没怎么用的估计就用一次忘记一次了。从这个角度来说,命令行的option设计上,应该满足:最少但是足够完备 的方式,最少使得你仅仅只需要记住这些必要的option就能使用,足够完备使得需要的大部分功能都有。
类比GUI,还有一种常见的GUI是应付选项特别多的情况:向导。例如IDE创建Project一般就是会有一个step by step的向导,让你一步一步选择和填写必要的设置。同理,在命令行下,也有类似的方式:交互式命令行。通过交互式命令,一步一步的提问让你选择选项和填写必要的配置,最后一次性执行。或者不执行,只是生成合并后的命令,让你自己拷贝到脚本里执行等,相当于一种交互式的文档,读文档你还是要自己根据文档写命令,交互式的方式直接通过分支裁剪你的需求生成你要的命令。这样,命令行可以通过交互式的方式提供一种更友好的「文档」,类似于游戏里的新手练习区。
命令行程序案例
A few years ago, we chose Go over Node.js for writing the wolkenkit CLI. A few months ago we rewrote everything from scratch, this time using Node.js again. We've learned that you can't dance at two weddings.