Linux 开发者应该知道的十个命令

作者:John Fusco

本文列出了您应该能够在任何 Linux 安装上找到的命令列表。这些工具可以帮助您改进代码并提高工作效率。该列表来自我作为程序员的经验,包括我反复依赖的工具。有些工具帮助创建代码,有些工具帮助调试代码,还有一些工具帮助逆向工程您收到的代码。

1. ctags

你们这些沉迷于集成开发环境 (IDE) 的人可能从未听说过这个工具,或者即使听说过,也可能认为它已经过时了。但是,一个支持标签的编辑器是一个高效的编程工具。

标记您的代码允许像 vi 和 Emacs 这样的编辑器像处理超文本一样处理您的代码(图 1)。代码中的每个对象都超链接到其定义。例如,如果您正在 vi 中浏览代码,并想知道变量 foo 在哪里定义的,请输入:ta foo。如果您的光标指向该变量,只需使用 Ctrl-右方括号。

Ten Commands Every Linux Developer Should Know

图 1. 使用标签的 gvim 工作界面

对于 vi 使用障碍者来说,好消息是 ctags 不仅适用于 C 和 vi 了。GNU 版本的 ctags 生成的标签可以与 Emacs 和许多其他识别标签文件的编辑器一起使用。此外,ctags 识别除 C 和 C++ 之外的多种语言,包括 Perl 和 Python,甚至硬件设计语言,例如 Verilog。它甚至可以生成人类可读的交叉引用,这对于理解代码和执行指标非常有用。即使您对在编辑器中使用 ctags 不感兴趣,您也可能想通过输入以下命令来查看人类可读的交叉引用ctags -x *.c*.

我喜欢这个工具的原因是,无论您输入一个文件还是一百个文件,您都可以获得有用的信息,这与许多 IDE 不同,除非它们可以看到您的整个应用程序,否则它们是无用的。它不是程序检查器,因此垃圾进,垃圾出 (GIGO) 规则适用。

2. strace

当您没有调试器也没有源代码时,strace 可以让您解读正在发生的事情。我最讨厌的事情之一是程序无法启动并且不告诉你原因。也许缺少必需的文件或权限错误。strace 可以告诉你程序正在做什么,直到它退出的那一刻。它可以告诉你程序正在使用哪些系统调用以及它们是否通过或失败。它甚至可以跟踪 fork。

strace 通常比调试器更快地给我答案,尤其是在代码不熟悉的情况下。有时,我必须在没有调试器的实时系统上调试代码。快速运行 strace 有时可以避免修补系统或用 printfs 污染我的代码。这是一个我作为非特权用户尝试删除受保护文件的简单示例

strace -o strace.out rm -f /etc/yp.conf

输出显示了问题出在哪里

lstat64("/etc/yp.conf", {st_mode=S_IFREG|0644,
st_size=361, ...}) = 0
access("/etc/yp.conf", W_OK) = -1 EACCES
(Permission denied)
unlink("/etc/yp.conf") = -1 EACCES (Permission
denied)

strace 还允许您附加到进程以进行即时调试。假设一个进程似乎花费大量时间无所事事。找出正在发生的事情的快速方法是输入strace -c -p mypid。一两秒钟后,按 Ctrl-C,您可能会看到类似这样的转储

% time    seconds  usecs/call     calls    errors  syscall
------ ----------- ----------- --------- --------- ----------------
 91.31    0.480456        3457       139           poll
  6.66    0.035025         361        97           write
  0.91    0.004794          16       304           futex
  0.52    0.002741          14       203           read
  0.31    0.001652           3       533           gettimeofday
  0.26    0.001361           4       374           ioctl
  0.01    0.000075           8        10           brk
  0.01    0.000064          64         1           clone
  0.00    0.000026          26         1           stat64
  0.00    0.000007           7         1           uname
  0.00    0.000005           5         1           sched_get_priority_max
  0.00    0.000002           2         1           sched_get_priority_min
------ ----------- ----------- --------- --------- ----------------
100.00    0.526208                  1665           total

在这种情况下,它的大部分时间都花在了 poll 系统调用中——可能是在等待套接字。

3. fuser

名称是 file user 的助记符,它告诉你哪些进程打开了给定的文件。它也可以为您向所有这些进程发送信号。假设您想删除一个文件,但由于某个程序打开了它并且不关闭它而无法删除。与其重启,不如输入fuser -k myfile。这会向每个打开 myfile 的进程发送 SIGTERM。

也许您需要杀死一个有意或无意地到处 fork 自己的进程。一个不明智的程序员可能会输入类似这样的内容ps | grep myprogram。这不可避免地会伴随几次用鼠标进行的剪切和粘贴操作。更简单的方法是输入fuser -k ./myprogram,其中myprogram是可执行文件的路径名。fuser 通常位于 /sbin 中,/sbin 通常保留给系统管理工具。您可以将 /usr/sbin 和 /sbin 添加到 $PATH 的末尾。

4. ps

ps 用于查找进程状态,但很多人没有意识到它也可以是一个强大的调试工具。要使用这些功能,请使用 -o 选项,该选项允许您访问进程的许多详细信息,包括 CPU 使用率、虚拟内存使用率、当前状态等等。许多这些选项都在 POSIX 标准中定义,因此它们可以在跨平台工作。

要按 pid 和进程状态查看正在运行的命令,请输入ps -e -o pid,state,cmd。输出看起来像这样

4576 S /opt/OpenOffice.org1.1.0/program/soffice.bin -writer
4618 D dd if /dev/cdrom of /dev/null
4619 S bash
4645 R ps -e -o pid,state,cmd

在这里你可以看到我的 dd 命令处于不可中断的睡眠状态(状态 D)。基本上,它在等待 /dev/cdrom 时被阻塞。我的 OpenOffice.org Writer 在我输入示例时处于睡眠状态(状态 S),而我的 ps 命令正在运行(状态 R)。

要了解正在运行的程序的性能,请输入

ps -o start,time,etime -p mypid

这显示了稍后讨论的 time 命令的基本输出,除非您不必等到程序完成。

ps 生成的大部分信息都可以从 /proc 文件系统获得,但是如果您正在编写脚本,使用 ps 更具可移植性。您永远不知道内核的哪个小版本会破坏所有挖掘 /proc 文件系统的脚本。请改用 ps。

5. time

time 命令对于理解代码的性能很有用。最基本的输出由实际时间、用户时间和系统时间组成。直观地,实际时间是代码开始和退出之间的时间量。用户时间和系统时间分别是执行应用程序代码与内核代码所花费的时间量。

有两种 time 命令可用。shell 有一个内置版本,只告诉您调度器信息。/usr/bin 中的版本包含更多信息,并允许您格式化输出。您可以通过在内置 time 命令前加上反斜杠来轻松覆盖它,如下面的示例所示。

对 Linux 调度器的基本了解有助于解释输出,但此工具也有助于了解调度器的工作原理。例如,进程的实际时间通常大于用户时间和系统时间之和。在系统调用中阻塞所花费的时间不计入进程,因为调度器可以在此期间自由调度其他进程。以下 sleep 命令需要一秒钟才能执行,但不占用可测量的系统或用户时间

\time -p sleep 1
real 1.03
user 0.00
sys 0.00

下一个示例显示了任务如何将其所有时间都花在用户空间中。在这里,Perl 在循环中调用 log() 函数,这不需要内核的任何东西

\time perl -e 'log(2.0) foreach(0..0x100000)'
real 0.40
user 0.20
sys 0.00

此示例显示了一个使用大量内存的进程

\time perl -e '$x = 'a' x 0x1000000'

0.06user 0.12system 0:00.22elapsed 81%CPU
(0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (309major+8235minor)pagefaults
0swaps

此处的有用信息列为 pagefaults。虽然 GNU time 命令宣传了很多信息,但 2.4 系列的 Linux 内核仅存储主要和次要页面错误信息。主要页面错误是需要 I/O 的错误;次要页面错误则不需要。

6. nm

此命令允许您检索对象文件或可执行文件内部的符号名称信息。默认情况下,输出为您提供符号名称及其虚拟地址。这有什么用呢?假设您正在编译代码,并且编译器抱怨您有一个未解析的符号 _foo。您搜索了所有源代码,但找不到任何使用此符号的地方。也许它是从某个模板或宏中拉入的,该模板或宏埋藏在与您的代码一起编译的数十个包含文件中。命令

nm -guA *.o | grep foo

显示所有引用 foo 的模块。如果您想找出哪个库定义了 foo,只需使用

nm -gA /usr/lib/* | grep foo

nm 命令还了解如何解构 C++ 名称,这在混合使用 C 和 C++ 时可能很方便。例如,忘记用以下命令声明 C 函数extern"C"会产生类似这样的链接时错误

undefined reference to `cfunc(char*)'

在一个具有定义不明确的头文件的大型项目中,您可能很难跟踪到有问题的模块。在这种情况下,您可以查找每个对象文件中所有未解析的符号,并启用解构,如下所示

nm -guC *.o
extern-c.o:cfunc
no-extern-c.o:cfunc(char*)

第一个模块是正确的;第二个模块不正确。

7. strings

此命令查找嵌入在二进制文件中的 ASCII 字符串。它可以用于好的方面,也可以用于邪恶的方面。好的用途包括试图弄清楚哪个库偶尔会在 stdout 上生成那个神秘的字符串,例如

strings -f /usr/lib/lib* | grep "cryptic message"

在邪恶方面,字符​​串可以用来探测您的格式字符串,寻找线索和漏洞。这就是为什么您永远不应该将密码和登录名放在程序中的原因。使用此工具检查您自己的程序,看看聪明的程序员可以看到什么,这可能是明智之举。GNU binutils 附带的 strings 版本具有许多有用的选项。

8. od, xxd

这两个命令基本上做同样的事情,但每个命令都提供略有不同的功能。od 用于将二进制文件转换为您喜欢的任何格式。当处理生成原始二进制文件的程序时,od 是必不可少的。虽然名称代表八进制转储,但它也可以以十进制和十六进制转储数据。od 转储整数、IEEE 浮点数或纯字节。当查看多字节整数或浮点数时,主机字节顺序会影响输出。

xxd 也转储二进制文件,但不尝试将它们解释为整数或浮点数,因此主机字节顺序不会影响输出,这可能会令人困惑或有所帮助,具体取决于文件。让我们在 Intel 机器上创建一个四字节文件

$ echo -n abcd > foo.bin
$ od -tx4 foo.bin
0000000 64636261
0000004

$ xxd -g4 foo.bin
0000000: 61626364         abcd

od 的输出是字节交换的 32 位整数,而 xxd 的输出是以与它们在文件中出现的相同字节顺序排列的四个字节组。如果您正在寻找字符串 abcd,xxd 就是适合您的命令。但是,如果您正在寻找 32 位数字 0x64636261,则 od 是正确的命令。

xxd 还知道 od 不知道的一些很酷的技巧,包括以二进制格式化输出以及将二进制文件转换为 C 数组的能力。假设您有一个二进制文件,您想将其编码到 C 程序中的数组中。一种方法是创建一个文本文件,如下所示

$ xxd -i foo.bin

unsigned char foo_bin[] = {
  0x61, 0x62, 0x63, 0x64
};

unsigned int foo_bin_len = 4;
9. file

UNIX 和 Linux 从未强制执行任何文件名扩展名策略。命名约定已经发展,但它们是指导原则,而不是策略。如果您想将您的数字图片命名为 image00.exe,请随意。您的 Linux 照片应用程序会很乐意接受该文件,无论名称是什么,尽管可能很难记住。

当您必须从一个笨拙的 Web 浏览器中检索文件时,file 命令可以提供帮助,该浏览器会破坏名称——例如,一个本应命名为 foo.bar.hello.world.tar.gz 的文件变成了 foo.bar。file 命令可以像这样提供帮助

$ file foo.bar

foo.bar: gzip compressed data,
was "foo.bar.hello.world.tar", from Unix

也许您收到了一个带有 bin 目录的发行版,该目录充满了数十个文件,其中一些是可执行文件,一些是脚本。假设您想挑出所有 shell 脚本。试试这个

$ file /usr/sbin/*  | grep script

/usr/sbin/makewhatis:  a /bin/bash script text
executable

/usr/sbin/xconv.pl:    a /usr/bin/perl script
text executable

file 命令识别 bin 目录中的所有文件,grep 命令过滤掉所有非脚本文件。以下是一些更多示例

file core.4867

core.4867: ELF 32-bit LSB core file Intel 80386,
version 1 (SYSV), SVR4-style, from 'abort'

file /boot/initrd-2.4.20-6.img

/boot/initrd-2.4.20-6.img: gzip compressed data,
from Unix, max compression

file -z /boot/initrd-2.4.20-6.img

/boot/initrd-2.4.20-6.img: Linux rev 1.0 ext2
filesystem data (gzip compressed data, from Unix,
max compression)

正如你不应该以貌取人一样,你不应该根据文件名来推断文件的内容。

10. objdump

这是一个更高级的工具,不适合胆小的人。它有点像对象文件的数据挖掘工具。您的对象代码内部编码了大量信息,此工具可让您看到它。此工具可以做的一件有用的事情是转储与源代码行混合的汇编代码,这gcc -S由于某种原因没有做到。您的对象代码必须使用调试 (-g) 进行编译才能使其工作

objdump --demangle --source myobject.o

当您无法访问调试器时,objdump 还可以帮助从核心文件中提取二进制数据以进行事后调试。一个完整的示例对于本文来说太长了,但是您需要来自nmobdump -t的虚拟地址。然后,您可以使用以下命令转储每个虚拟地址的文件偏移量objdump -x。最后,objdump 能够读取 gdb 和其他工具无法触及的非 ELF 文件格式。

本文并非旨在作为权威参考,而是作为帮助您提高工作效率的起点。这些命令中的每一个都在 Linux man 和 info 页面中得到了充分的文档记录。查阅它们以获取更多信息和更多想法。

本文资源: /article/7658

John Fusco 是通用电气医疗(前身为 GE 医疗系统)的软件开发人员,他在那里为 GE Lightspeed 系列计算机断层扫描仪设计 Linux 软件和设备驱动程序 (www.gemedicalsystems.com/rad/ct/products/light_series/index.html)。

加载 Disqus 评论