// 本文源自以前在知乎上的一个回答,https://www.zhihu.com/question/19655399/answer/101900080
// 此处重新整理,待改写。
典型使用场景
- 服务器远程登陆,只有命令行界面,此时不得不用命令行工具处理日常工作任务,所以就用,边用边查手册,用久了所以熟悉。
- 自己的电脑上经常使用命令行处理日常编程/文件管理工作。
- 自己编写命令行程序。
分类随记
文件上传、下载:
- 一般来说就是sz,rz,wget,curl,scp等。
- for循环wget,分分钟多进程下载。
- wget -c 分分钟断点续传。
压缩、解压:
所以得会用tar,zip等命令。我一开始的时候,问一个老手tar cvzf,tar xvzf后面一排参数啥意思,老手说:”早就忘记了,天天都这么用“。所以我认为它和一个按钮一个菜单没什么区别,命令行参数的选项和参数一般是经常用就熟悉,久不用就容易忘记,此时复查是正常的事情。顺便吐槽下windows下7z的菜单里直接解压tar.gz要解压两次真是麻烦事,命令行当然可以一次搞定。
目录和文件的创建、移除、拷贝、移动:
基本命令是:mkdir,rm,mv等。这是最基本的常规操作了,一般来说需要注意的有:
- 非空目录的移除,
- 递归删除时拼接路径的安全隐患(递归删除根目录就一点都不好玩了)
- 删除时文件被占用
- 如果可能只是短时被占用,可以sleep几十毫秒再删,重试n次退出。
- 考虑采用”删除xxx.old, 移动xxx到xxx.old, 拷贝yyy到xxx“的方式代替”直接删除xxx,移动yyy到xxx“的方案、
- 理解硬盘驱动,冗余磁盘阵列,inode,文件和目录,缓存策略等。
文本显示、查找、替换、统计:
在命令行下分析程序的日志,必备的技能就是文本操作。基本的命令有cat,tail,less,head,grep,sed,awk,wc,xargs等,这组命令行基本要通过管道来配合使用。我个人觉的awk的设计有点古老,awk的脚本我是一点都不觉得写起来流畅和爽快,sed的稍微好点,但也一般。如果是在windows下,这种流式过滤的工作,我是宁可开个LinqPAD写Linq来做,其实很多时候写脚本我都实际上觉的用正式点的高级语言做会更好。很多shell脚本写的人天马行空,别人读起来就不好玩了,毕竟写shell的人不写注释,遍地意大利面条。吐槽归吐槽,现实上我们不得不妥协,该用还得用。毕竟一切数据都可以输出成文本,所以文本的流式分析是基本技能。
我认为这组命令的组合使用能让人体会到管道的好处。管道让多进程之间的流式操作流畅进行。顺便我认为shell的一个特点是,它天生是多进程程序,一个shell里随便一堆命令就是多进程操作,可能很多人写着的时候并没有意识到这点哦。进一步,多主机之间ssh授权后,shell里的ssh远程命令的组织又天生带有点分布式属性,也许这能带来一些启发,例如ssh 信任就是非对称加密。
- A机留私钥; B机在.ssh目录放公钥,在.ssh/authorized_keys里填上公钥内容。
- 则A机执行ssh user@B cmd 的时候,B机会用公钥和A机之间建立ssh连接,然后执行命令。
- git 的客户端向git的仓库之间建立ssh授权也是这个过程,实际上任何依赖于ssh建立授权的都是这个过程。
- 这实际上是一种“反连”协议,思考下私钥在哪里就知道了。
理解管道的另一个要素是,需要理解fork,vfork、clone,exec;需要理解标准输入输出。fork之后,在exec子进程之前重定向父进程的标准输出,重定向子进程的标准输入,这样它们就被流式串在一起。
文本编辑:
vi基本技能。shell下编辑文本只能用这个了,我认为vi或者vim里最重要的技能就三个:导航、查找、替换,带着这个思维去记忆那些命令会明确很多。我觉的任何一个程序的核心功能就几个,每个核心功能的核心命令也就几个,其他的就按需查用即可。
理解模式的概念,同样的键盘按钮,在不同vim模式下,操作代表不同的语义。想象一下相机是如何由有限的几个按钮做到多组不同功能的支持。
我认为快捷键并不是vim/emacs之类的最大特点,任何一个支持快捷键的编辑器,你都可以通过熟练掌握常见的快捷键使得操作快捷高效,比如notepad、visual studio、word等,理解这点有助于我们看清事实,驾驭工具。
资源监控:
输出的信息都可以配合grep等使用,比如ps aux|grep processname.
- 任务管理器型:vmstat, top, htop,
- 进程:ps
- cpu的虚拟化。
- 理解OS对process的调度:Scheduling (computing)
-FIFO,SJF,STCF,RR,MLFQ- 理解os调度process的话,再去看协程,用户态协程需要自己做调度:)
- IO密集和CPU密集?
- 磁盘:du, iotop, iostat
- 文件句柄:lsof,比如查看一个进程打开的文件描述符,反向查看哪些进程打开了某个文件。
- 内存:物理内存的虚拟化(进程的内存分布,内部浪费和外部浪费),分页,页错误。
调试和Hook:
开发的话会需要调试程序和Hook程序等。
通过ulimit -c unlimited让程序崩溃时产生coredump,然后用gdb调试,看崩溃堆栈,变量、单步等,这是gdb的常规操作。linux下开发必备技能了。ulimit 还可以限制其他资源,比如-n限制文件句柄个数,-s限制堆栈大小等,具体查文档。
Debugging with GDB
每个线程的堆栈,判断出多线程问题、内存问题,这两个基本占大头,所以gdb里主要就是:
- 查看线程->切换线程->查看堆栈->切换函数帧和代码行->查看变量信息,再结合源码合理怀疑临界区问题、状态机问题、内存问题等。
- 定位就是:查看->切换->怀疑->[重现]->确定->修复,疑难问题可能只能靠肉眼codereview确定。
系统调用、堆栈等的Hook:strace,dtrace、pstack等,这组算是lowlevel的神器,在很多时候能起到一针见血的效果。http://jvns.ca/blog/2016/09/07/new-zine-linux-debugging-tools-youll-love/
顺便,可以理解下系统调用的原理,中断的实现原理,这样你在使用strace监控一个程序的系统调用的时候,脑子里会有更清晰的思路。系统调用并不是一个简单的函数调用这件事么..
Things UNIX can do atomically 多进程之间可以对文件和文件夹加锁
- 说起锁,想起Dijkstra,锁、信号量、等并发编程同步的机制最早都是他搞清楚的。
- 什么是”原子“的,在CS里,这是一个及其重要的概念。
- 锁应该保护的是数据还是行为?
- 锁的尽量局部化。
- 常见的有添加路径到环境变量: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
网络:
tcpdump抓包是一定要掌握的,抓tcp、udp包是必备的技能。输出pcap格式文件也是要的。如果是有图形界面的话,wireshake在windows和linux下都能用,如果是windows上还可以用microsoft networkmonitor。说实话,我觉的microsoft networkmonitor做的比wireshake好用。
一个tcp连接,三次握手、四次挥手;建立在tcp连接之上的http连接,http request包在什么时候发,seq号是怎样变动的;诸如此类,都可以在这个地方查看。
为什么要三次握手。syn、ack、ackack,它们是原子不可分的么,在更复杂的协议里,一个syn,一个ack、一个ackack本身可能就是一组子协议。三者中任何一个环节被打破(Break),都会导致握手失败。
为什么要四次挥手。自己设计流式协议的话,如果你不需要这种Feature,就可以暴力Close了。知其然,知其所以然。
- P2P打洞的原理:NAT,限ip,限port,限ip和port,随机ip和port
- DNS的原理,host,DNS污染
Http协议说到底是一个Tcp协议,这点很重要。SPDY->HTTP2->QUIC
拥塞控制算法,根据历史数据记录估算未来数据,这是一种学习(统计):Reno,Cubic,Ledbat,BBR。
Deep Learning,NN:问题是,它和大数据一样,广告词的成分居多,只有少数人在做和它相关的事情,从工程上来说,它应该是被搭建出来的算法模块,而非广告词,然而满大街的AI广告词让人讨厌,正经的程序员和工程师要对那些满口广告词的文章带有警惕性。
http://neuralnetworksanddeeplearning.com 这个6章的教程主要讲神经网络最基本的一个算法(误差逆传播算法,也就是所谓的bp算法)的基本原理(一部分在推导bp过程如何被用来算bp误差函数的微分,这里主要用到了微分的链式法则,求微分的目的是加速递归下降法的收敛速度),作者也对AI的未来有一番自己的见解,你会发现bp本质上就是用来做连续函数的逼近(利用递归下降法,让逼近函数的权重参数“快速”收敛)。后面的CNN只是一种用来减少bp权重参数的分层策略,本质上还是bp,不过RNN只是简单提了一下。
一台机子有多个网卡(ifconfig),或者单网卡,多ip,绑定到0.0.0.0的时候,收发包是怎样的情况?
拆开ip包头,tcp和udp包头,以及做checksum校验的拟包头,理解协议包是如何被Stack的。话说最早我想当然认为tcp包头里面才是ip包包头,然而事实上这个鸡蛋黄是上层的包头在下层协议包的包体里。不知道是否有同类人有过这种直觉的错觉。
数据库操作:
- 命令行下,操作mysql和sqlite是常见的事情,这个基本就是常规的sql操作语句。
- 将数据库dump出SQL文本格式,到别的机子上导入什么的。。
- 以前有个人打开了这么一份文本,惊讶说:“哇,谁写的SQL创建代码,这么整洁,连注释啥的都很规整...”
- 理解关系型数据库的范式,事务,一致性。
- 理解B+树、Block。
- 基于行存储Block的数据库,基于列存储Block的数据库,各自的优缺点
- 局部性原理,行Block存储是行记录读写偏好,列的则是部分字段读写偏好
- trad off,没有最好,只有最合适
版本管理等:
- svn、git,这个本来就要会的,只是很多人用图形界面的工具多了后,可能命令行使用并不熟悉,但是在Linux shell下,这也算基本技能。
- 理解“围绕数据结构设计接口”,git是关于commit的链表的数据结构的一组api。
- 元数据分布式同步,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的向导,让你一步一步选择和填写必要的设置。同理,在命令行下,也有类似的方式:交互式命令行。通过交互式命令,一步一步的提问让你选择选项和填写必要的配置,最后一次性执行。或者不执行,只是生成合并后的命令,让你自己拷贝到脚本里执行等,相当于一种交互式的文档,读文档你还是要自己根据文档写命令,交互式的方式直接通过分支裁剪你的需求生成你要的命令。这样,命令行可以通过交互式的方式提供一种更友好的「文档」,类似于游戏里的新手练习区。