Linux开发—Makefile工具使用
一,背景
- 一个项目通常有多个源文件,如果只修改其中一个,就对所有源文件重新执行编译、链接步骤,就太浪费时间了。因此十分有必要引入 Makefile 工具:Makefile 工具可以根据文件依赖,自动找出那些需要重新编译和链接的源文件,并对它们执行相应的动作。
二,认识
1,make与makefile
- Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
- Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。
- 而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。
- makefile 文件是许多编译器–包括 Windows下的编译器–维护编译信息的常用方法,只是在集成开发环境中,用户通过友好的界面修改 makefile 文件而已。
- 在 UNIX 系统中,习惯使用 Makefile 作为 makefile 文件。如果要使用其他文件作为 makefile,则可利用类似的 make 命令选项指定 makefile 文件。
- 一个文件,指示程序如何编译和链接程序。makefile文件的默认名称是名副其实的Makefile,但可以指定一个命令行选项的名称。
- make程序有助于您在开发大型程序跟踪整个程序,其中部分已经改变,只有那些编译自上次编译的程序,它已经改变了部分。
2,关于编译
编译一个小的C程序至少需要一个单一的文件.h文件(如适用)。虽然命令执行此任务只需CC file.c中,有3个步骤,以取得最终的可执行程序,如下所示:
-
编译阶段:所有的C语言代码.c文件中被转换成一个低级语言汇编语言;决策.s文件。
-
汇编阶段:前阶段所作的汇编语言代码,然后转换成目标代码的代码片段,该计算机直接理解。目标代码文件.o 结束。
-
链接阶段:编译程序涉及到链接的对象代码的代码库,其中包含一定的“内置”的功能,如printf的最后阶段。这个阶段产生一个可执行程序,默认情况下,这是名为a.out。
3,编译案例:
假定有以下的源文件。
main.cpp
hello.cpp
factorial.cpp
functions.h
- main.cpp 文件的内容
#include <iostream.h>
#include "functions.h"
int main(){
print_hello();
cout << endl;
cout << "The factorial of 5 is " << factorial(5) << endl;
return 0;
}
- hello.cpp 文件的内容
#include <iostream.h>
#include "functions.h"
void print_hello(){
cout << "Hello World!";
}
- factorial.cpp 文件的内容
#include "functions.h"
int factorial(int n){
if(n!=1){
return(n * factorial(n-1));
}
else return 1;
}
- functions.h 内容
void print_hello();
int factorial(int n);
琐碎的方法来编译的文件,并获得一个可执行文件,通过运行以下命令:
CC main.cpp hello.cpp factorial.cpp -o hello
- CC:代表编译器(gcc),编译C++文件,
- 这上面的命令将生成二进制的Hello。在我们的例子中,我们只有四个文件,我们知道的函数调用序列,因此它可能是可行的,上面写的命令的手,准备最后的二进制。但对于大的项目,我们将有源代码文件成千上万的文件,就很难保持二进制版本。
- make命令允许您管理大型程序或程序组。当开始编写较大的程序,你会发现,重新编译较大的程序,需要更长的时间比重新编译的短节目。此外会发现通常只能在一小部分的程序(如单一功能正在调试),其余的程序不变。
三,Makefile 宏
make程序允许您使用宏,这是类似的变量。 = 一对一个Makefile中定义的宏。例如:
MACROS= -me
PSROFF= groff -Tps
DITROFF= groff -Tdvi
CFLAGS= -O -systype bsd43
LIBS := "-lncurses -lm -lsdl"
LIBS += -lstdc++
MYFACE = ":*)"
PROJ_HOME = /home/moon/projects
$(MACROS)
$(MYFACE) :*)
1,特殊的宏($@
,$?
, $<
, $*
,$^
)
1,$@
,$?
目标规则集发出任何命令之前,有一些特殊的预定义宏。
- $@
表示目标文件。
- $?
表示比目标还要新的依赖文件列表。
因此,举例来说,我们可以使用一个规则:
hello: main.cpp hello.cpp factorial.cpp
$(CC) $(CFLAGS) $? $(LDFLAGS) -o $@
alternatively:
hello: main.cpp hello.cpp factorial.cpp
$(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@
- 在这个例子中$@代表 hello, ? 或 ? 或 ?或@.cpp将拾取所有更改的源文件。
2,$<
, $*
有两个比较特殊的隐含规则中使用的宏。它们是:
$<
表示第一个依赖文件。$*
这个变量表示目标模式中“%”及其之前的部分。.o
文件 等效于%.o
文件- 对于
$*
如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*
”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*
”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*
”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*
”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make
,所以,你应该尽量避免使用“$*
”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*
”就是空值。。
常见的隐含规则的构造 .o(对象)文件,.cpp(源文件)。
- 对于
.o:.cpp
$(CC) $(CFLAGS) -c $<
alternatively
.o:.cpp
$(CC) $(CFLAGS) -c $*.cpp
3,$^
$^
表明所有的依赖项:
LDFLAGS = -lstdc++
hello:main.o factorial.o hello.o
$(CC) $^ $(LDFLAGS ) -o $@
2,传统宏
有很多默认的宏(输入“make -p”打印出来的默认值)。大多数使用它们的规则是很明显的:
这些预定义变量,即。在隐含规则中使用的宏分为两大类:那些程序名(例如CC)和那些含有参数的程序(如CFLAGS)。
下面是一些比较常见的变量用作内置规则:makefile文件的程序名称的表。
- AR Archive-maintaining program; default `ar’.
- AS Program for compiling assembly files; default `as’.
- CC Program for compiling C programs; default `cc’.
- CO Program for checking out files from RCS; default `co’.
- CXX Program for compiling C++ programs; default `g++'.
- CPP Program for running the C preprocessor, with results to standard output; default `$(CC) -E’.
- FC Program for compiling or preprocessing Fortran and Ratfor programs; default `f77’.
- GET Program for extracting a file from SCCS; default `get’.
- LEX Program to use to turn Lex grammars into source code; default `lex’.
- YACC Program to use to turn Yacc grammars into source code; default `yacc’.
- LINT Program to use to run lint on source code; default `lint’.
- M2C Program to use to compile Modula-2 source code; default `m2c’.
- PC Program for compiling Pascal programs; default `pc’.
- MAKEINFO Program to convert a Texinfo source file into an Info file; default `makeinfo’.
- TEX Program to make TeX dvi files from TeX source; default `tex’.
- TEXI2DVI Program to make TeX dvi files from Texinfo source; default `texi2dvi’.
- WEAVE Program to translate Web into TeX; default `weave’.
- CWEAVE Program to translate C Web into TeX; default `cweave’.
- TANGLE Program to translate Web into Pascal; default `tangle’.
- CTANGLE Program to translate C Web into C; default `ctangle’.
- RM Command to remove a file; default `rm -f’.
这里是一个变量,其值是上述程序的额外的参数表。所有这些的默认值是空字符串,除非另有说明。
- ARFLAGS Flags to give the archive-maintaining program; default `rv’.
- ASFLAGS Extra flags to give to the assembler (when explicitly invoked on a
.s' or
.S’ file). - CFLAGS Extra flags to give to the C compiler.
- CXXFLAGS Extra flags to give to the C compiler.
- COFLAGS Extra flags to give to the RCS co program.
- CPPFLAGS Extra flags to give to the C preprocessor and programs that use it (the C and Fortran compilers).
- FFLAGS Extra flags to give to the Fortran compiler.
- GFLAGS Extra flags to give to the SCCS get program.
- LDFLAGS Extra flags to give to compilers when they are supposed to invoke the linker, `ld’.
- LFLAGS Extra flags to give to Lex.
- YFLAGS Extra flags to give to Yacc.
- PFLAGS Extra flags to give to the Pascal compiler.
- RFLAGS Extra flags to give to the Fortran compiler for Ratfor programs.
- LINTFLAGS Extra flags to give to lint.
注:您可以取消-R
或--no-builtin-variables
选项隐含规则使用的所有变量。
如,也可以在命令行中定义的宏
make CPP=/home/moon/projects
四,Makefile定义依赖性
新版的gcc编译器引入头文件不再需要后缀名,eg:# include<iostream>,不再需要.h后缀。
这是很常见的,最终的二进制文件将依赖于各种源代码和源代码的头文件。依存关系是重要的,因为他们告诉对任何目标的源。请看下面的例子
hello: main.o factorial.o hello.o
$(CC) main.o factorial.o hello.o -o hello
- 在这里,我们告诉目标文件hello 它依赖main.o,factorial.o和hello.o,所以每当有任何变化,这些目标文件将采取行动,也可使用编译规则规则:
$(CC) $? -o $@
来替代$(CC) main.o factorial.o hello.o -o hello
。
同时我们会告诉如何准备 .o文件,所以我们必须定义这些依赖也如下:
main.o: main.cpp functions.h
$(CC) -c main.cpp
factorial.o: factorial.cpp functions.h
$(CC) -c factorial.cpp
hello.o: hello.cpp functions.h
$(CC) -c hello.cpp
如果依赖项多时,就在LDFLAGS里面 += 来连接
五,Makefile定义规则
一个Makefile目标规则的一般语法
target [target...] : [dependent ....]
[ command ...]
- 方括号中的项是可选的,省略号是指一个或多个。注意标签,每个命令前需要。
下面给出一个简单的例子,定义了一个规则使您的目标从 hello 其他三个文件。
hello: main.o factorial.o hello.o
$(CC) $? -o $@
- 注:在这个例子中,你必须放弃规则,使所有对象从源文件读文件
语义是相当简单的。当"make target"发现目标规则适用,如有眷属的新目标,使执行的命令一次一个(后宏替换)。如果有任何依赖进行,即先发生(让您拥有一个递归)。
如果有任何命令返回一个失败状态,MAKE将终止。这就是为什么看到规则,如:
clean:hello
-rm *.o *~ core paper
- Make忽略一个破折号开头的命令行返回的状态。例如。如果没有核心文件,谁在乎呢?,定义完clean后,执行:
make clean
,清理所有文件
Make 会 echo 宏字符串替换的命令后,告诉发生了什么事,因为它发生。有时可能想要把它们关掉。例如:
install:hello
@echo You must be root to install
- @加在echo命令前,表示静默输出,不会打印出跟信息无关的东西,比如不会打印出echo,只会打印出You must be root to install。
- 大家所期望的Makefile的 某些目标。应该总是先浏览,但它的合理预期的目标(或只是做),安装,清理。
- make all - 编译一切,让你可以在本地测试,之前安装的东西,all可以把规则一起使用,eg:
all:hello install
- make install - 应安装在正确的地方的东西。但看出来的东西都安装在正确的地方为系统。
- make clean - 应该清理的东西。摆脱的可执行文件,任何临时文件,目标文件等。
- make all - 编译一切,让你可以在本地测试,之前安装的东西,all可以把规则一起使用,eg:
六,Makefile的隐含规则
1,该命令应该在所有情况下,我们建立一个可执行x的的源代码x.cpp的作为一个隐含的规则,这可以说:
.cpp:
$(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@
- 这种隐含的规则说,如果make c, x.c 运行x.c 调用输出x。规则是隐式的,因为没有特定的目标提到。它可用于在所有的情况下。
2,另一种常见的隐含规则的构造 .o(对象)文件和 .cpp (源文件)。
.o:.cpp
$(CC) $(CFLAGS) -c $<
alternatively
.o:.cpp
$(CC) $(CFLAGS) -c $*.cpp
3,$^ 表明所有的依赖项
#LDFLAGS 表示依赖库
LDFLAGS = -lstdc++
hello:main.o factorial.o hello.o
$(CC) $^ $(LDFLAGS) -o $@
.o:.cpp functions.h
$(CC) -c $<
clean:
-rm *.o hello
install:hello
@echo "You need build hello!"
echo "no @ used"
all:hello install
七,Makefile 自定义后缀规则
就其本身而言,make已经知道,为了创建一个 .o文件,就必须使用 cc-c 相应的c文件。 建成MAKE这些规则,可以利用这一点来缩短Makefile。如果仅仅只是表示 .h 文件的 Makefile依赖线,依赖于目前的目标是,MAKE会知道,相应的文件已规定。你甚至不需要编译器包括命令。
这减少了我们的Makefile更多,如下所示:
OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
cc $(OBJECTS) -o hello
hello.o: functions.h
main.o: functions.h
factorial.o: functions.h
1, .SUFFIXES
允许自定义后缀
Make 使用一个特殊的目标,故名 .SUFFIXES
允许你定义自己的后缀。例如,依赖项:
.SUFFIXES: .foo .bar
- 告诉make ,将使用这些特殊的后缀,以使自己的规则。
如何让 make 已经知道如何从 .c 文件生成 .o文件。类似的可以定义规则以下列方式:
.foo:.bar
tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' < $< > $@
.c:.o
$(CC) $(CFLAGS) -c $<
- 第一条规则允许你创建一个 .bar 文件从 .foo文件。 (不要担心它做什么,它基本上打乱文件)
- 第二条规则 .c文件创建一个 .o 文件中使用的默认规则。
2,*
是一个通配符
*
用来表示任意的词,可以极大的简化makefile:
LDFLAGS = -lstdc++
all:hello install
hello:*.cpp *.h
$(CC) $^ $(LDFLAGS) -o $@
clean:
-rm *.o hello
install:hello
@echo "You need build hello!"
echo "no @ used"
八,Makefile指令
有好几种指令以不同的形式。让程序可能不支持所有指令。因此,请检查make是否支持指令,这里我们探索GNU make支持的一些指令
1,条件的指令
条件指令:
- ifeq 指令开始的条件,指定的条件。它包含两个参数,用逗号分隔,并用括号括起。两个参数进行变量替换,然后对它们进行比较。该行的makefile继IFEQ的服从如果两个参数的匹配,否则会被忽略。是否相等
- ifneq 指令开始的条件,指定的条件。它包含两个参数,用逗号分隔,并用括号括起。两个参数进行变量替换,然后对它们进行比较。makefile ifneq 遵守如果两个参数不匹配,否则会被忽略。是否不等
- ifdef 指令开始的条件,指定的条件。它包含单参数。如果给定的参数为真,则条件为真。是否定义
- ifndef 指令开始的条件,指定的条件。它包含单参数。如果给定的是假的,那么条件为真。是否没定义
- else 指令会导致以下行如果前面的条件未能被遵守。在上面的例子中,这意味着第二个选择连接命令时使用的第一种选择是不使用。它是可选的,在有条件有一个else。
- endif 指令结束条件。每一个条件必须与endif结束。
1,条件式指令的语法
一个简单的条件,
没有其他的语法如下:
conditional-directive
text-if-true
[else
text-if-false]
endif
- 文本如果真可以是任何行文字,被视为makefile文件的一部分,如果条件为真。如果条件是假的,没有文字来代替。
- 中括号
[]
表示忽略相应内容
一个复杂的条件
语法如下:
conditional-directive
text-if-true
else
text-if-false
endif
- 如果条件为真时,文本,如果真正的使用,否则,如果假文本来代替。的文本,如果错误的数量可以是任意的文本行。
有条件的指令的语法是相同的,无论是简单或复杂的条件。有四种不同的测试不同条件下的指令。
这里是一个表:
ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"
与上述条件相反的指令如下:
ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2"
2,条件式指令示例
条件指令,跨平台编译。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
2,include 指令
include指令告诉make暂停读取当前makefile文件和读取一个或多个其它的makefile,然后再继续。该指令是一行在makefile中,看起来像这样:
include filenames...
文件名可以包含shell文件名模式。允许额外的空格开头的行被忽略,但不允许一个标签。例如,如果有三个.mk'文件,
a.mk’, b.mk', and
c.mk’, 和一个 $(bar) 扩展到bash中,然后下面的表达式。
include foo *.mk $(bar)
is equivalent to
include foo a.mk b.mk c.mk bash
当MAKE处理包括指令,它包含的makefile暂停读取,并从各列出文件中依次读取。当这个过程完成,使读取指令出现在其中的makefile的恢复。
3,override 指令
如果一个变量已经设置的命令参数,在makefile中被忽略的普通任务。如果要设置makefile的变量,即使它被设置的命令参数,可以使用一个override指令,这是一行看起来像这样:
override variable = value
or
override variable := value
4,=、+=、:=、?=
= 直接赋值
:= 忽略掉之前的赋值
+= 追加
?= 如果前面没有定义,则定义生效
九,Makefile文件重新编译
make 程序是一个智能的实用程序和工作根据在源文件中的变化。如果有四个文件main.cpp,hello.cpp,factorial.cpp和functions.h。这里所有reamining文件是依赖functions.h,main.cpp的是依赖于hello.cpp,factorical.cpp。因此,如果做任何改变functions.h然后将重新编译所有源文件来生成新的对象文件。但是,如果做任何改变main.cpp,因为这是不依赖任何其他的过滤,那么在这种情况下,只有main.cpp文件将被重新编译而hellp.cpp factorial.cpp将无法重新编译。
1,重新编译的条件
虽然编译一个文件时,MAKE检查目标文件和比较时间表,如果源文件有更新的时间戳比目标文件新,然后将生成新的对象文件,假设源文件已被改变。
2,避免重新编译
有可能是项目包括成千上万的文件。有时候可能已经改变了一个源文件,但不想重新编译所有依赖于它的文件。例如,假设添加宏到一个头文件或声明,许多其他文件依赖。假设在头文件中的任何变化需要重新编译所有相关文件,但要知道,他们并不需要重新编译,宁可不要浪费时间等待他们的编译。
-
如果预期改变头文件的问题之前,可以使用
-t
标志位。这个标志告诉make命令不运行的规则,而是来标记目标,迄今为止,通过改变它的最后修改日期。遵循以下步骤:- 1.使用命令’make’来重新编译真的需要重新编译源文件。
- 2.在头文件中进行更改。
- 3.使用命令
-t
来记录所有的目标文件为最新。下一次运行make,在头文件中的变化不会引起任何重新编译。
-
如果已经改变了头文件的时候,有一些文件就需要重新编译,做到这一点已经太晚了。相反,可以使用
-o
文件的标志,这标志着一个指定的文件作为old
。这意味着该文件本身不会被重制并没有别的其交代将被重制。遵循以下步骤:- 1.重新编译源文件,需要编制独立的特定头文件的原因,
make -o headerfile
。如果涉及几个头文件,使用一个单独的-o
选项,每个头文件。 - 2.touch所有目标文件使用
make -t
,避免重新编译.
- 1.重新编译源文件,需要编制独立的特定头文件的原因,
3,touch
touch更新库为最新时间,避免项目由于时间间隔过短不更新问题。
比如:
touch xxx.cpp
十,Makefile 其他功能
1,make 递归使用
递归使用的手段使用,make在makefile作为命令。这种技术是非常有用的,当你想要的makefile各种子系统组成一个更大的系统。例如,假设你有一个子目录,子目录都有其自己的makefile,并且您希望所在目录的makefile中运行make子目录。可以做到这一点如以下:
#方式一:先切换目录再去make编译
subsystem:
cd subdir && $(MAKE)
or, equivalently
#方式二:编译时去寻找切换目录
subsystem:
$(MAKE) -C subdir
可以编写递归复制这个例子只是通过make命令,但有很多事情,了解他们是如何和为什么工作的,以及如何子涉及到顶层make。
2,通信变量到子make
顶层make变量的值可以被传递到子通过环境,通过显式请求。这些变数定义子作为默认值,但不会覆盖子的makefile使用makefile中所指定的,除非使用`-e’开关
向下传递,或导出,一个变量,变量和其值的环境中运行每个命令添加。子make反过来,make使用环境变量值来初始化它的表格
特殊变量SHELL和MAKEFLAGS总是导出(除非取消导出)。 MAKEFILES导出,如果把它设置到任何东西。
如果想导出特定变量的一个子制造,使用导出指令,像这样:
export variable ...
如果想阻止一个变量被导出的,使用撤消导出的指令,像这样:
unexport variable ...
3,MAKEFILES 变量
MAKEFILES,环境预定义的宏,如果环境变量的定义,make额外的makefile 名称列表(由空格分隔)之前被读取别人认为其值。这很像include指令:不同的目录中查找这些文件。
MAKEFILES的主要用途是MAKE递归调用之间的通信没有报错,易把项目变得复杂,但没有必要,最好不用定义和使用该变量
LDFLAGS=-lstdc++
all:hello install
@echo "MAKEFILES=$(MAKEFFILES)"
4,头文件包含在不同的目录
如果已经把你的头文件在不同的目录,在不同的目录中运行make,那么它需要告诉头文件的路径。这是可以做到的makefile中使用-I
选项。假设该functions.h文件可在/home/yidaoyun/header头和其他文件/home/yidaoyun/src/然后进行文件将被写入如下。
INCLUDES = -I "/home/yidaoyun/header"
CC = gcc
LIBS = -lm
CFLAGS = -g -Wall
OBJ = main.o factorial.o hello.o
hello: ${OBJ}
${CC} ${CFLAGS} ${INCLUDES} -o $@ ${OBJS} ${LIBS}
.o:.cpp
${CC} ${CFLAGS} ${INCLUDES} -c $<
5,追加更多的文本变量
通常,它用于添加更多的文字,已定义的变量的值。
make 这行包含+=
,像这样:
objects += another.o
这需要值的变量对象,并添加文字another.o
(前面由一个单一的空间)。因此:
objects = main.o hello.o factorial.o
objects += another.o
设置文件main.o hello.o factorial.o another.o
的对象。
使用+=
是类似于:
objects = main.o hello.o factorial.o
objects := $(objects) another.o
6,Makefile中的续行
如果不喜欢太长的行,在Makefile中,然后你可以使用反斜杠\
,但反斜杠后面不能有任何字符(空格,tab建,字母,下划线等)
,如下所示
OBJ = main.o factorial.o \
hello.o
is equivalent to
OBJ = main.o factorial.o hello.o
7,从命令提示符下运行的Makefile
如果已经准备好Makefile的名称为“Makefile”文件,然后简单地写在命令提示符下,它将运行Makefile文件。但是,如果有任何其他的名字的Makefile,然后使用以下命令
make -f your-makefile-name
十一,Makefile案例
这是一个例子编译hello程序Makefile。此程序包含三个文件main.cpp,factorial.cpp,hello.cpp。
# Define required macros here
SHELL = /bin/sh
OBJS = main.o factorial.o hello.o
#CFLAG编译参数,-Wall:开启所有警告,-g:开启调试信息
CFLAG = -Wall -g
CC = gcc
#INCLUDES:依赖文件,没有就空,-I:当前文件
INCLUDES = -I .
#LIBS 依赖库
LIBS = -lstdc++
hello:${OBJS}
${CC} ${CFLAGS} -o $@ $^ ${LIBS}
clean:
-rm -f *.o hello
.o:.cpp
${CC} ${CFLAGS} ${INCLUDES} -c $<
- 建立hello 程序使用“make”hello项目。如果发出命令“make clean”,则它会删除所有的对象可在当前目录中的生成文件。
总结
1,vim命令模式下快捷键:
u:撤销最后保存前的所有输入。
control+r:恢复 最后保存前的所有输入。
按下两次d:删除某行。