在最开始学习向终端输入命令时总是感觉很神奇,终端究竟是怎样理解我的命令的?
终端如何加载进程
举个例子,当我输入cat test.txt
之后,bash会通过一次fork复制出一模一样的子进程bash,然后再通过execve覆盖掉自己,重新加载新的程序cat,并把test.txt作为参数传递给cat。
这样子的来回反复,效率岂不是奇低?一个fork函数就不知道要费多少时间呢 ,更何况还要再重新加载另一个程序。
但是事实上,linux系统非常聪明,fork和execve都是通过内存映射来完成的,也就是说,当bash使用fork复制了一个一模一样的自己时,他其实并没有实际的拷贝,而只是通过映射,将新的子进程的所有代码区域、数据区域等都指向了自己。直到子进程需要修改某个变量的值时,才会真正的复制出来内容,而且只复制所修改的内容所在的页块这一小部分。也就是利用写时复制(CopyOnWrite)的惰性操作。
而execve稍微麻烦一些,需要先重置进程内容,再重新映射一遍各种区域到磁盘的相应位置,等到真正用到的时候在缓存到内存上来。总之二者都不会傻乎乎的浪费时间搬些没用的砖。
终端如何理解命令
别看终端支持的命令这么多、那么复杂,其实上,终端完成的绝大多数工作都是调用其他程序。偶尔会用一下内置的命令以及一些内置语法结构(function, if, while, &&, ||, >, <, |, &
等等)。
在用户按下enter键输入命令后,终端先解析命令行,也就是通过空格将整个输入的字符串分割开来,成为一连串的单词,如果包含某些语法关键字,就进行相应的处理,比如| < > if && || while &
等等。
举几个例子就明白了:
cat text.txt &
:终端需要加载名为cat的程序,参数为test.txt,后台运行。
which which
:终端需要加载名为which的程序,参数为which。which会搜索并输出名为which的文件的路径。
if ls; then echo 'exit normally'; fi
:if
为关键字,当终端发现if
时,会吞掉if
,然后运行后边的ls
程序,如果ls
的返回值为0,也就是正常退出,则终端运行echo
后面的内容,最后读到fi
结束判断语句。
[ 1 = 1 ]
:这句话真的很奇葩,我最开始一直以为[]
是bash支持的关键字,表示判断,但实际上:输入which [
之后,输出的结果是:/usr/bin/[
,也就是说,[
其实是一个程序,他的名字就是[
,它读入后边的四个参数1
=
1
]
,如果这几个参数的数学含义正确,就正常退出(返回值为0),否则就设置退出值为1,表示错误。然后再读入结尾的]
,如果没有读到结尾的]
,也会异常退出。最后由于[
程序判断1 = 1正确,因此正常退出。这也解释了为什么必须要加空格,因为不加空格的话bash的分割就会出错。
[ 1 = 1 ] && echo ah!
:前面的部分已经说过了,再说后面的。&&
为终端的内置语法,具有短路(short-circuiting)的性质。a && b
,a的正确并不能保证且的结果正确,因此要继续运行后面的部分,也就是b,判断b是否也正确。而如果a错误(也就是返回值非0),那么就不用在判断b了,此时b就不会被运行。
jobs
:需要注意的是,jobs是内置命令,而非外部命令,因为job是bash自己内部保持的一个结构,其他外部命令是访问不了的。通过which jobs
可以发现jobs是不存在的文件。
ls | grep '*.txt' | xargs cat
:这个命令有点复杂,用到了管道操作,其实管道就是把前一个文件的输出定向到后一个文件的输入,也就是在fork后,execve前修改了各个进程的输入输出描述符。最后的xargs是一个程序,它读取cat作为自己的参数,并且用输入流的内容作为cat的参数调用cat。
实用的linux终端操作!
说了一大堆虚的东西,来点干货!
- 找到当前目录下(不包含子目录)的所有非.c源文件的文件并删除。
find -not -path './*/* -type f -not name '*.cpp' | xargs rm
- 编译c或cpp文件并运行
新建一个名为run的文件,输入以下内容:
#!/usr/bin/env bash
prefix=$(echo $1 | cut -d. -f1)
suffix=$(echo $1 | cut -d. -f1)
if [ $suffix = 'cpp' ]; then
g++ $1 -o $prfix && ./$prefix
elif [ $suffix = 'c' ]; then
gcc $1 -o $prfix && ./$prefix
fi
最后通过chmod a+x run
赋予run运行权限,就可以在当前目录下输入./run test.cpp
来代替g++ test.cpp -o test && ./test
了。当然不change mode也是可以的,直接bash run test.cpp
即可,不过要麻烦一些。
最后还能进一步把run加入到系统路径中,比如:
leehyukshuai@HES:~/code$ ./run test.cpp
hello
leehyukshuai@HES:~/code$ sudo cp run /usr/bin/
[sudo] password for leehyukshuai:
leehyukshuai@HES:~/code$ run test.cpp
hello
leehyukshuai@HES:~/code$
一些解释:开头的#!
为shebang
符,表示应该调用什么解释器解释该脚本文件。env
是一个程序,会调用bash(仅是为了增加可移植性,并非必要)。
一些其他有趣的操作各位自己探索趴···