0%

其他工具

精通调试代码并不是说学会使用GDB这样的调试器就行了—这只是开端。

为了增强调试技能,初学者程序员最好学会其中的集中调试工具,了解那种工具适合于调试那种程序错误,并识别发生程序错误时使用其中那个工具可以节省时间和精力。

充分利用文本编辑器

最好的调试方法是一开始就不要有编程错误,而简单地充分利用一种支持编程的编辑器就可以达到这个效果。因为:

  • 精通强大的编辑器可以缩短编写代码所需的时间,具有自动缩排、单词补充和全文代码查询等特殊功能的编辑器对程序员是非常有利的;
  • 优秀的编辑器确实可以帮到编码者在编写代码时捕获某些类型的程序错误。

Mark:Makefile中可以使用patsubst关键字来进行文本查找和替换命令。

从vim中调用make非常方便,不需要退出编辑器,直接:make 即可,同时vim可以捕获编译器发出的所有消息,编辑器理解GCC输出内容的语法,知道何时发生编辑器警告或错误。发生错误时可以直接定位到第一个错误,然后使用:cnext或者:cprevious来显示下一个或上一个错误或者警告。

在Vim中可以使用K来查询man页面中的函数。

充分利用编译器

如果说编辑器是对抗程序错误的第一个武器,那么编译器就是第二个武器了,所有编译器都有能力扫描代码并查找常见错误,但是通常必须通过调用适当的选项来启用这种错误检查。

对于GCC而言,如果不适用-Wall,就几乎没有必要使用GCC了,所以任何编译最好都加上-Wall选项。

C语言中的错误报告

C语言中的错误报告是使用名为errno的老式机制完成的。系统与库调用的失败通常是由于设置了名为errno的全局定义整数变量,在大多数GNU/Linux系统上,error是在/usr/include/errno.h上声明的,因此,只要包含了这个头文件,就不必在源代码中声明extern int errno了。

当一个系统或库调用失败时,它将errno设置为一个指示失败类型的值。检查errno的值,并采取适当的动作可以判断错误类型。比如可以使用函数perror或者strerror。

errno可以通过任何库函数或系统调用设置,无论它是成功还是失败。因为即便成功的函数调用能够设置errno,让人不能依赖errno告诉你是否发生了错误。因此使用errno最安全的方式为:

  • 执行对库或者系统函数的调用;
  • 使用函数的返回值判断时候发生了某个错误;
  • 如果发生了某个错误,使用errno确定为什么发生这个错误。

库函数和系统调用的区别

库函数是更高级别的,完全在用户空间里运行,并未程序员提供了更方便的做实际工作的函数接口。

系统调用代表用户以内核模式工作,由操作系统本身的内核提供。

库函数printf看上去类似于一般输出函数,但是它实际上只是格式化你提供给字符串的数据,并用低级系统调用write编写字符串数据,然后将数据发送到一个与终端的标准输出关联的文件中。

更好地使用strace和ltrace

strace跟踪系统调用而ltrace跟踪库调用,这两个使用程序,有时比深度调试代码还快。

静态代码检查器:lint与其衍生

静态代码检查器:扫描代码的工具,不编译代码,仅仅警告错误、可能的错误和与严格C语言编码标准的差距。

衍生版本为splint:该软件的目标是帮助编写大部分有防御性、安全和尽可能少出错的程序,当然,改程序对组成有效代码的内容非常挑剔。

很多程序员将splint没有报告警告看做一种极大的荣誉。当出现这种情况是,代码被声明为无lint的。

调试动态分配的内存

动态分配的内存(Dynamically Allocated Memory, DAM)是程序用malloc和calloc这样的函数从堆中请求的内存。

查找DAM问题分麻烦,分为以下几类:

  • 没有释放动态分配的内存;—内存泄漏
  • 对malloc的调用失败;
  • 想DAM段之外的地址执行读写操作;—访问错误
  • 释放DAM段之后对DAM区域中的内存进行读写操作;—访问错误
  • 对动态内存的同一段调用两次free。—重复释放double free

Electric Fence

Bruce Perens在1988年写的,当EFence链接到代码中时,导致程序在发生下列情况之一时立即发生段错误并转出核心:

  • 在DAM边界之外执行读写操作;
  • 对已经释放的DAM执行读写操作;
  • 对没有指向malloc分配的DAM的指针执行free,包括重复释放的特殊情况。

比如,

1
$ gcc –g3 –Wall –std=c99 test.c –o test_with_efence outOfBound_with_efence  –lefence

and

1
$ gcc –g3 –Wall –std=c99 test.c –o test_with_efence outOfBound_without_efence  -lefence

可以对一个内存泄露但是没有编译失败的程序做测试。

用GNU C库工具调试DAM问题

  • 在调用任何与堆有关的函数钱调用函数mcheck;
  • 使用mtrace函数捕获内存泄露和重复释放;

程序崩溃处理

有人说C语言是低级语言,这有一部分原因是因为应用程序的内存管理大部分需要由程序员来实现。虽然这种方法非常有用,但是也给程序员添加了很多的麻烦。

也有人说,C语言是相对较小且容易学习的语言,然而,只有不考虑标准C语言库的典型实现时,C语言才比较小,这个库相当庞大,很多程序员认为C语言是易用语言,那是因为他们还没有遇到指针。

一般而言,程序错误会导致下面两件事情的发生:

  • 导致程序做一些程序员没有打算做的事情;
  • 导致程序崩溃

相信很多调试过程序的兄弟都碰到过段错误即segmentation fault,则合格主要原因是试图在未经允许的情况下访问一个内存单元。硬件会感知这件事并执行对操作系统的跳转。

堆区域

调用malloc函数分配的内存;

栈区域

用来动态分配数据的空间,函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。

查看程序在Linux上的精确内存布局情况

可以通过使用info proc mappings来详细查看该程序在Linux上的精确内存布局情况,例如:

此时我们还可以看到这个进程号为14455,所以我们还可以通过文件/proc/14455/maps来查看该信息。通过这些信息,我们有可能看到文本和数据区域,以及堆和栈。

分配页策略

操作系统不会将不完整的页分配给程序,例如,如果要运行的程序总共大约有10000字节,如果完全加载,会占用3个内存页(一个页占4096个字节),它不会仅占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元,这是调试时要着重了解的情况,这也导致了程序的一些错误内存访问不会触发段错误,换言之,在调试会话期间,没有引起段错误并不能直接说明代码是没有问题的。

页的角色细节

当程序执行时,它会连续访问程序中的各个区域,导致硬件按照以下几种情况所示处理页表:

  • 每次程序使用全局变量时,需要具有对数据区域的读写访问权限;
  • 每次程序访问局部变量时,程序会访问栈,需要对栈区域具有读写访问权限;
  • 每次程序进入或离开函数时,对该栈进行一次或多次访问,需要对栈区具有读写访问权限;
  • 每次程序访问通过调用malloc或者new创建的存储器时,都会发生堆访问,也需要读写访问权限;
  • 程序执行的每个机器指令是从文本区域取出的,因此需要具有读和执行文件;

信号

这里需要注意的是,进程抛出的信号,实际上没有任何内容发送给进程。所发生的事情只不过是操作系统将信号记录到进程表中,以便下次进程接收信号时得到CPU上的时间片,执行恰当的信号处理程序。

自定义信号的复杂性

使用GDB/DDD/Eclipse调试时,自定义信号处理程序可能会使程序变得复杂,无论是直接使用还是通过DDD GUI,每当发出任何信号时,GDB都会停止进程,所以,有可能意味着GDB会因为与调试无关的工作而频繁的停止,此时可以使用handle命令告诉GDB在某些信号发生时不要停止。

总线错误的原因

  • 访问不存在的物理地址;
  • 在很多架构上,要求访问32位量的机器指令要求字对齐,而导致视图在奇数号地址上访问具有4字节的数的指针错误可能引起总线错误。

总线错误是处理器层的异常,导致在Unix系统上发出SIGBUS信号,默认情况下,SIGBUS会导致转储内存并终止。

核心文件

有些信号表示让某个进程继续是不妥当的,甚至是不可能的,在这些情况中,默认动作是提前终止进程,并编写一个名为核心文件core file的文件,俗称转储核心。

核心文件包含程序崩溃时对程序状态的详细描述:栈的内容、CPU寄存器的内容、程序的静态分配变量的值。

我们可以通过file命令来查看文件的详细信息。

为什么需要核心文件

  • 只有在运行了一段长时间后才发生段错误,所以在调试器中无法重新创建该错误;
  • 程序的行为取决于随机的环境事件,因此再次运行程序可能不会再现段错误;
  • 当新手用户运行程序时发生的段错误,需要发送核心文件给开发人员。

重载功能

当GDB注意到重新编译了程序后,它会自动加载新的可执行文件,因此不需要退出和重启GDB。

调试设计的内容

  • 确认原则;
  • 使用核心文件进行崩溃进程的“死后”分析;
  • 纠正、编译并重新运行程序后,甚至不需要退出GDB;
  • Printf()风格调试的不足之处;
  • 利用你的智慧,这是无可替代的;
  • 如果你过去使用printf风格调试的,就会发现使用printf跟踪这些程序错误中的部分错误原来有多难,虽然在调试中使用printf诊断代码有一定的好处,但是作为一种通用目的的工具,它远远不足以用来跟踪实际代码中发生的大部分程序错误。

调试器不仅能够运行程序,还可以通知它暂停程序的运行,暂停以后,调试器提供了检查变量、跟踪执行路径的机会。

暂停机制

有3中方式可以通过GDB暂停程序的执行:

  • 断点:通知GDB在程序中的特定位置暂停执行;
  • 监视点:通知GDB当特定内存位置(或者设计一个或多个位置的表达式)的值发生变化时暂停执行;
  • 捕获点:通知GDB当特定事件发生时暂停执行;

在GDB中删除断点

  • delete breakpoint_list
  • delete
  • clear
  • clear function、clear filename:function、clear line_number、clear filename:line_number

在GDB中禁用断点

在调试会话期间,会遇到大量断点,对于经常重复的循环结构或函数,这种情况使得调试极不方便。如果要保留断点以便以后使用,暂时又不希望GDB停止执行,可以禁用它们,在以后需要时再启用。此时我们可以使用disable/enable breakpoint_list来禁用和启用断点。

DDD

  • 可以直接拖拽断点,很方便;

  • 还有一个优秀的功能Undo/Redo;

GDB中恢复执行的方法

  • 使用step和next单步调试程序;
  • 使用continue使GDB无条件地恢复程序的执行,知道它遇到另一个断点或者程序结束;
  • 用finish或until命令恢复。

next和step的区别

next执行函数,不会在其中暂停,然后在调用之后的第一条语句处暂停;

step在函数中的第一个语句处暂停;

使用continue恢复程序执行

continue与执行一行代码的step和next相反,这个命令使GDB恢复程序的执行,直到触发断点或者程序结束。

continue命令可以接受一个可选的整数参数n,这个数字要求GDB忽略下面n个断点。

使用finish恢复程序执行

命令finish指示GDB恢复执行,直到恰好在当前栈帧完成之后位置,这意味着如果你在一个不是main的函数中,finish命令会导致GDB恢复执行,直到恰好在函数返回之后为止,例如:

如果在一个递归函数中,finish只会将你带到递归的上一层,这是因为每次调用都被看做在它自己权限内的函数调用,因为每个函数都有自己的栈帧,如果要在递归层次较高时完全退出递归函数,那么更适合使用临时断点及continue,或者使用until命令。

使用until恢复程序执行

命令until执行程序,直到到达当前循环体外的下一行源代码。

设置条件断点的方法

break break-args if (condition)

监视点

监视点是一种特殊类型的断点,它类似于正常断点,是要求GDB暂停程序执行的指令。区别在于监视点是没有“住在”某一行源码中,取而代之的是,监视点是指示GDB每当某个表达式改变了值就暂停执行的指令。

预备知识

处于TUI模式的GDB

加上-tui选项可以在调用GDB时使用TUI模式运行,或者当处于非TUI模式时在GDB中使用Ctrl+A+X组合键就可以在TUI模式和非TUI模式中跳转。

在GUI模式中,GDB窗口划分为两个子窗口:

  • 用于输入GDB命令的窗口
  • 用于查看源码的窗口

通过使用上下方向键可以在TUI模式中移动到代码的其他部分。如果没有处于TUI模式中,就可以使用箭头键来浏览以前的GBD命令,从而修改或重复执行这些命令。

在TUI模式中,箭头键用于滚动源代码子窗口,可以使用Ctrl+P和Ctrl+N组合键来浏览以前的GDB命令。

常用的命令

  • backtrace:显示程序的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)。
  • breakpoint:在程序中设置一个断点。
  • cd:改变当前工作目录。
  • clear:删除刚才停止处的断点。
  • commands:命中断点时,列出将要执行的命令。
  • continue:从断点处开始继续执行。
  • delete:删除一个断点或监测点,也可与其他命令一起使用。
  • display:程序停止时显示变量和表达式。
  • down:下移栈帧,使得另一个函数成为当前函数。
  • frame:选择下一条continue命令的帧。
  • info:显示与该程序有关的各种信息。
  • info break:显示当前断点清单,包括到达断点处的次数等。
  • info files:显示被调试文件的详细信息。
  • info func:显示所有的函数名称。
  • info local:显示当前函数中的局部变量信息。
  • info prog:显示被调试程序的执行状态。
  • info var:显示所有的全局和静态变量名称。
  • jump:在源程序中的另一点开始运行。
  • kill:异常终止在gdb 控制下运行的程序。
  • list:列出相应于正在执行的程序的源文件内容。
  • next:执行下一个源程序行,从而执行其整体中的一个函数。
  • print:显示变量或表达式的值。
  • pwd:显示当前工作目录。
  • pype:显示一个数据结构(如一个结构或C++类)的内容。
  • quit:退出gdb。
  • reverse-search:在源文件中反向搜索正规表达式。
  • run:执行该程序。
  • search:在源文件中搜索正规表达式。
  • set variable:给变量赋值。
  • signal:将一个信号发送到正在运行的进程。
  • step:执行下一个源程序行,必要时进入下一个函数。
  • undisplay display:命令的反命令,不要显示表达式。
  • until:结束当前循环。
  • up:上移栈帧,使另一个函数成为当前函数。
  • watch:在程序中设置一个监测点(即数据断点)。
  • whatis:显示变量或函数类型。

CGDB

CGDB是一个很友好的GDB界面,当然是相对于GDB的TUI界面,CGDB也是GDB的前端,虽然CGDB类似于基于终端的TUI的概念,但其在色彩方面特别有吸引力,而且可以浏览源代码子窗口,并直接在子窗口中设置断点,并且CGDB处理屏幕刷新的能力似乎也比GDB的TUI强。

CGDB的基本命令与约定:

  • 按下ESC键可以从基本命令转到源代码窗口,按下i键返回;
  • 当光标在源代码窗口中时,可以使用箭头键或者类似vi的操作在源码中随意移动,j下k上/查找;
  • 要执行的下一行用箭头标记;
  • 为了在通过光标突出显示的当前代码行上设置断点,只要按下空格键即可;
  • 断点行的行号用红色突出显示

编译:gcc -g -Wall -o gdb_app test.c

Attention:其中的-g选项可以让编译器将符号表(即对应程序的变量和代码行的内存地址列表)保存在生成的可执行文件。这是一个绝对必要的步骤,这样才能在调试会话过程中引用源代码中的变量名和行号。

预备知识

正确使用恰当的调试工具可以提高发现和改正错误的效率,GDB用于逐行跟踪程序、设置断点、检查变量以及查看特定时间程序的执行情况,DDD是流行的GDB的GUI前端,而eclipse提供完整的集成开发环境。

gdb

GNU调试器(GNU Debugger,缩写:GDB),是GNU软件系统中的标准调试器,此外GDB也是个具有移携性的调试器,经过移携需求的调修与重新编译,如今许多的类UNIX操作系统上都可以使用GDB,而现有GDB所能支持除错的编程语言有C、C++、Pascal以及FORTRAN。

DDD

GNU DDD(Data Display Debugger)是命令行调试程序,如GDB、DBX、WDB、Ladebug、JDB、XDB、Perl Debugger或Python Debugger的可视化图形前端。它特有的图形数据显示功能(Graphical Data Display)可以把数据结构按照图形的方式显示出来。

DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C/C++ 、Ada、Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并由此将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。

Eclipse

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

为什么要用调试工具,使用printf或者cout不是很好嘛

对于打印输出要求我们有策略地持续添加跟踪代码,重新编译程序,运行程序并分析跟踪代码的输出,在修正程序错误之后删除跟踪代码,并且针对发现的每个新的程序错误重复上述这些步骤,很费事,很费力,很容易将我们的注意力转移到排查错误的过程上而不是实际的任务上。

相反,使用调试工具,比如ddd或者eclipse的GUI,我们只需要使用鼠标指针就可以检查变量的值,并显示该变量的当前值,and调试器还可以指出程序错误所在的大概位置。例如,段错误即内存访问错误,调试器会立即指出段错误所在的位置,and调试器还可以设置监视点watchpoint,持续监视某个变量的值。

调试原则:

  • 从简单工作开始调试;
  • 使用自顶向下的方法;
  • 使用调试工具确定段错误的位置;
  • 通过发出中断确定无限循环的位置;
  • 使用二分搜索;

命令行调试 vs GUI调试

GUI的优点: GUI界面比GDB提供的GUI界面的外观更加形象,使用起来更加方便;

GDB的优点

  • GDB的启动速度比DDD快很多;
  • 在某些情况下,通过来自于公共中断的SSH连接远程执行调试,如果没有安装X11,就完全不能使用GUI了,即使有X11,GUI的屏幕刷新操作也会非常缓慢;
  • 当调试彼此之间协同操作的多个程序时,就需要针对每个程序的独立调试窗口,对于GUI窗口操作就比较麻烦;

Linux rsync 命令

rsync也是远程(或本地)复制和同步文件最常用的命令。

scp类似。

官方定义为:

rsync - a fast, versatile, remote (and local) file-copyint tool

从定义看,比scp要强一些。

借助rsync命令,可以跨目录,跨磁盘和跨网络远程与本地数据进行复制和同步。比如最常用的就是在两台Linux主机之间进行数据的备份。

语法

语法相对而言,比较简单,不过用法其实挺多的。

1
$ rsync [OPTION...] SRC... [DEST]

常用的参数为:

  • -v : 详细模式输出
  • -r : 递归拷贝数据,但是传输数据时不保留时间戳和权限
  • -a : 归档模式, 归档模式总是递归拷贝,而且保留符号链接、权限、属主、属组时间戳
  • -z : 压缩传输
  • -h : human-readable
  • --progress: 显示传输过程
  • --exclude=PATTERN 指定排除传输的文件模式
  • --include=PATTERN 指定需要传输的文件模式

无参数传输

默认情况下,传输一个文件不需要任何参数:

1
$ rsync user@192.168.100.123:~/dest_file dir/

命令执行后,会提示输入远程机器的密码,不过成功后不会显示任何信息,需要自行确认。

常用传输

所以默认情况下,会使用rv参数,不仅可以传输一个目录,也可以是文件:

1
2
3
4
5
6
$ rsync -rv user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
a
b
c

显示详细进度

对于小文件而言,没有问题,但是如果文件比较大,比如有几个GB,那么此时--progress参数就会比较有帮助:

1
2
3
4
5
6
7
$ rsync -rv --progress user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
30 files to consider
a 100% 278.25MB/s 0:02:00 (xfer#1, to-check=28/30)
b 100% 289.25MB/s 0:02:00 (xfer#1, to-check=27/30)
c 100% 277.45MB/s 0:02:00 (xfer#1, to-check=26/30)

会实时更新传输的进度。

此时对比scp,可以看到多了一些提示信息,比如会提示:

receiving file list … done
30 files to consider

另外,在实时更新的进度里面也有了一些多出来的信息。

不传输一些文件

比如做软件开发,不希望传输一些编译过程中产生的.o文件,测试的--exclude参数就很完美,如下:

1
2
3
4
5
6
7
$ rsync -rv --progress --exclude "*.o" user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
25 files to consider
a 100% 278.25MB/s 0:02:00 (xfer#1, to-check=28/30)
b 100% 289.25MB/s 0:02:00 (xfer#1, to-check=27/30)
c 100% 277.45MB/s 0:02:00 (xfer#1, to-check=26/30)

此时就看到,本来该传输30组的数据,去掉了部分.o文件。

Linux rsync 命令

rsync也是远程(或本地)复制和同步文件最常用的命令。

scp类似。

官方定义为:

rsync - a fast, versatile, remote (and local) file-copyint tool

从定义看,比scp要强一些。

借助rsync命令,可以跨目录,跨磁盘和跨网络远程与本地数据进行复制和同步。比如最常用的就是在两台Linux主机之间进行数据的备份。

语法

语法相对而言,比较简单,不过用法其实挺多的。

1
$ rsync [OPTION...] SRC... [DEST]

常用的参数为:

  • -v : 详细模式输出
  • -r : 递归拷贝数据,但是传输数据时不保留时间戳和权限
  • -a : 归档模式, 归档模式总是递归拷贝,而且保留符号链接、权限、属主、属组时间戳
  • -z : 压缩传输
  • -h : human-readable
  • --progress: 显示传输过程
  • --exclude=PATTERN 指定排除传输的文件模式
  • --include=PATTERN 指定需要传输的文件模式

无参数传输

默认情况下,传输一个文件不需要任何参数:

1
$ rsync user@192.168.100.123:~/dest_file dir/

命令执行后,会提示输入远程机器的密码,不过成功后不会显示任何信息,需要自行确认。

常用传输

所以默认情况下,会使用rv参数,不仅可以传输一个目录,也可以是文件:

1
2
3
4
5
6
$ rsync -rv user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
a
b
c

显示详细进度

对于小文件而言,没有问题,但是如果文件比较大,比如有几个GB,那么此时--progress参数就会比较有帮助:

1
2
3
4
5
6
7
$ rsync -rv --progress user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
30 files to consider
a 100% 278.25MB/s 0:02:00 (xfer#1, to-check=28/30)
b 100% 289.25MB/s 0:02:00 (xfer#1, to-check=27/30)
c 100% 277.45MB/s 0:02:00 (xfer#1, to-check=26/30)

会实时更新传输的进度。

此时对比scp,可以看到多了一些提示信息,比如会提示:

receiving file list … done
30 files to consider

另外,在实时更新的进度里面也有了一些多出来的信息。

不传输一些文件

比如做软件开发,不希望传输一些编译过程中产生的.o文件,测试的--exclude参数就很完美,如下:

1
2
3
4
5
6
7
$ rsync -rv --progress --exclude "*.o" user@192.168.100.123:~/dest_file dir/
user@192.168.100.123's password:
receiving file list ... done
25 files to consider
a 100% 278.25MB/s 0:02:00 (xfer#1, to-check=28/30)
b 100% 289.25MB/s 0:02:00 (xfer#1, to-check=27/30)
c 100% 277.45MB/s 0:02:00 (xfer#1, to-check=26/30)

此时就看到,本来该传输30组的数据,去掉了部分.o文件。

rsync特性

高效地复制同步数据到对端,或者对端到本地
支持复制链接、设备、属主、属组、权限
比scp(Secure Copy)更快。rsync使用远程更新协议( remote-update protocol ),这允许仅仅传输两组文件之间的差异。对于首次传输,它将文件或目录的全部内容从源复制到目标,但是从下次起,它仅将变化部分复制到目标。
Rsync消耗较少的带宽,因为它使用压缩和解压缩方法,同时发送和接收数据两端。HTTP压缩技术

基本语法

1
2
3
4
5
6

--delete 同步时,删除那些DST中有,而SRC没有的文件
--max-size:限定传输文件大小的上限
--dry-run:显示那些文件将被传输,并不会实际传输
--bwlimit:限制传输带宽
-W:拷贝文件,不进行增量检测

使用范例:

本地拷贝到远程

1
$ rsync -avz localfile user@ip:/the/path/

传输数据时显示传输过程
使用–progress参数

使用–exclude和–include

传输dat开头的文件、目录,排除其他情况的文件、目录

1
$ rsync -avz --include 'dat*' --exclude '*' /local/path/ user@ip:/the/path

使用–delete
–delete用于同步时,删除那些DST中有,而SRC没有的文件

传输完毕后自动删除源文件、目录, 可以使用–remove-source-files选项完成此自动删除。

使用–dry-run
如果你对rsync不熟悉,贸然使用rsync可能会搞乱对端文件、目录。借助–dry-run可以让你知道会传输些什么东西,但实际上并没有传输任何东西。如果输出结果与你的预期吻合,可以去掉–dry-run,进行实际的传输工作。

同步整个文件
rsync由于采用远程更新协议( remote-update protocol ),默认是同步变化的字节或块。使用-W可以取消这种机制,整个文件同步

指定端口

下个示例通过指定12345端口进行数据传输。

1
rsync -e 'ssh -p 12345'  -avz localfile user@ip:/the/path/

ubuntu删除多余内核

如果升级到了一个新的内核,并且还比较稳定,那么老的内核就可以清理了,放在电脑里也占位置。方法(命令行比较通用)如下:

1.查看系统内存在的内核版本列表:

1
sudo dpkgget-selections |grep linux

结果:

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
28
29
30
31
32
33
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
libselinux1                              install

linux-firmware install

linux-generic install

linux-headers-3.2.0-33 install

linux-headers-3.2.0-33-generic install

linux-headers-3.2.0-33-generic-pae install

linux-headers-3.2.0-34 install

linux-headers-3.2.0-34-generic install

linux-headers-3.2.0-34-generic-pae install

linux-headers-3.2.0-35 install

linux-headers-3.2.0-35-generic install

linux-headers-3.2.0-35-generic-pae install

linux-headers-3.2.0-37 install

linux-headers-3.2.0-37-generic install

linux-headers-3.2.0-37-generic-pae install

linux-headers-3.2.0-38 install

linux-headers-3.2.0-38-generic install

linux-headers-3.2.0-38-generic-pae install

linux-headers-3.2.0-40 install

linux-headers-3.2.0-40-generic install

linux-headers-3.2.0-40-generic-pae install

linux-headers-generic install

linux-headers-generic-pae install

linux-image-2.6.32-21-generic deinstall

linux-image-2.6.32-40-generic deinstall

linux-image-2.6.32-41-generic deinstall

linux-image-2.6.32-42-generic install

linux-image-3.2.0-33-generic install

linux-image-3.2.0-34-generic install

linux-image-3.2.0-35-generic install

linux-image-3.2.0-37-generic install

linux-image-3.2.0-38-generic install

linux-image-3.2.0-40-generic install

linux-image-generic install

linux-libc-dev install

linux-sound-base install

pptp-linux install

syslinux install

syslinux-common install

syslinux-legacy install

util-linux install

2.查看当前Ubuntu系统使用的内核版本

1
uname -a

结果:

1
Linux linux 3.2.0-40-generic #64-Ubuntu SMP Mon Mar 25 21:22:26 UTC 2013 i686 i686 i386 GNU/Linux

3.删除多余内核:

1
2
3
sudo apt-get purge
linux-image-2.6.32-21-generic linux-image-2.6.32-40-generic linux-image-2.6.32-41-generic linux-image-2.6.32-42-generic linux-image-3.2.0-33-generic linux-image-3.2.0-34-generic linux-image-3.2.0-35-generic linux-image-3.2.0-37-generic linux-image-3.2.0-38-generic
linux-headers-3.2.0-33 linux-headers-3.2.0-34 linux-headers-3.2.0-35 linux-headers-3.2.0-37 linux-headers-3.2.0-38

更新grub:

1
sudo update-grub

再次查看一下内核列表,就发现旧版本已经不存在了!

vim 插入操作

vim如何倒序文件

执行下面的命令

1
:g/.*/mo0

或者

1
:g/^/mo0 

从第一行开始,匹配每一行,然后执行mo(move)操作移动到第0行。如此处理每一行,直到文本末行。执行完毕文本逆序化成功!

比如,原来的文件为:

1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9

经过操作后,会更新为:

1
2
3
4
5
6
7
8
9
9
8
7
6
5
4
3
2
1

软件管理利器 - Debian系的apt

.. note::
当年不肯嫁春风,无端却被秋风误。
贺铸《芳心苦·杨柳回塘》

对于最常用的命令而言,apt可能排不上号,但是,在新安装的系统中,apt 命令绝对应该是排在前十位的存在,所以apt是管理 Debian 系列系统中软件包的必备工具。

apt - command-line interface

apt是Advanced Package Tool的缩写,恰如字面描述高级包工具apt 命令是用于 Debian 系列 Linux 发行版的强大工具,比如广为人知的Ubuntu,还有超赞桌面的Linux Mint。

apt使得处理软件包,比如安装、更新和删除软件包的过程特别丝滑,也结合了较早的工具如 apt-getapt-cache 的功能,提供了更友好的交互体验。

更新软件包列表

在安装或升级软件包之前,可以更新软件包列表,以确保拥有可用软件包的最新信息。

使用以下命令:

1
$ sudo apt update

此命令从配置的仓库中获取最新的软件包信息。

升级软件包

要将所有已安装的软件包升级到最新版本,可以使用:

1
$ sudo apt upgrade

要进行更全面的升级,包括删除旧软件包和安装新依赖项,请使用:

1
$ sudo apt full-upgrade # 特别留意,这个会把老版本给删除

安装软件包

apt 命令使得软件包安装变得非常简单。要安装一个软件包,只需要使用:

1
$ sudo apt install <软件包名称>

例如,要安装文本编辑器瑞士军刀 vim,您可以运行:

1
$ sudo apt install vim

删除软件包

卸载软件包同样简单。要删除一个软件包,使用:

1
$ sudo apt remove <软件包名称>

如果您想删除软件包及其配置文件,使用:

1
$ sudo apt purge <软件包名称>

搜索软件包

要查找某个软件包,可以使用关键词进行搜索:

1
$ apt search <关键词>

例如,要搜索与 “python” 相关的软件包,您可以运行:

1
$ apt search python

显示软件包信息

要查看特定软件包的详细信息,使用:

1
$ apt show <软件包名称>

此命令提供软件包描述、依赖关系和版本信息等详细信息。

清理

随着时间的推移,积累过时的软件包和缓存文件。要清理不必要的软件包,使用:

1
$ sudo apt autoremove

要清除本地存储库中获取的包文件,使用:

1
$ sudo apt clean

管理仓库

apt 获取软件包信息的仓库列表存储在 /etc/apt/sources.list/etc/apt/sources.list.d/ 目录下的文件中。

要添加新的仓库,可以直接编辑这些文件或使用 add-apt-repository 命令:

1
$ sudo add-apt-repository ppa:<仓库名称>

添加仓库后,需要更新软件包列表才能使用:

1
$ sudo apt update