在Linux的世界里,命令行不仅仅是执行命令的地方,它更像一个强大的工作室,你可以把各种工具连接起来,完成复杂的任务。而实现这一切的魔法,就是我们今天要讲的“输入输出重定向”(I/O Redirection)。
假设你执行的每个命令都像一个处理数据的机器。这台机器需要有“原料”输入,也会产生“产品”输出。通常情况下,它的“原料”来自你的键盘输入,这叫做“标准输入”(Standard Input, stdin); 它的“产品”会显示在你的屏幕上,这叫做“标准输出”(Standard Output, stdout)。

但有时候,机器在运行过程中可能会发出一些提示或者警报,比如“原料不够了”或者“某个零件出错了”。这些信息并不能算是“产品”,它们是“状态和错误消息”,会被送到另一个地方,叫做“标准错误”(Standard Error, stderr)。 默认情况下,标准错误和标准输出一样,也会显示在屏幕上。
这就是每个命令都有的三个默认“管道”:标准输入、标准输出和标准错误。而重定向,就是让我们能够把这些“管道”从默认的设备(键盘、屏幕)上拔下来,然后插到我们想让它去的地方,比如一个文件。
每个Linux命令都有三个默认的数据流:
标准输出通常用于正常的结果输出,而标准错误用于错误信息和警告。在重定向时,我们需要明确指定要重定向的是哪个流。
有时候,命令的输出结果太长,刷满了屏幕,我们想把它存下来慢慢看,或者作为另一个程序的输入。这时,就可以把标准输出重定向到一个文件里。我们用 > 这个符号来实现。
比如,ls -l /usr/bin 命令会列出 /usr/bin 目录下所有的文件,内容非常多。我们可以把它存到一个叫 file-list.txt 的文件里:
|$ ls -l /usr/bin > file-list.txt
执行这个命令后,屏幕上不会有任何输出,因为输出结果已经像水流一样,通过 > 这个管道,流进了 file-list.txt 文件里。
你可以用 less 或者 cat 命令查看这个文件的内容,会发现 ls 的结果都在里面。
使用 > 时要特别小心,它会从头开始重写目标文件。如果 file-list.txt 已经存在,原来的内容会被完全覆盖。如果你只是想在文件末尾追加内容,而不是覆盖它,应该使用 >> 这个符号。
比如,我们想把 /bin 目录的内容也追加到刚才的文件里:
|$ ls -l /bin >> file-list.txt
这样,file-list.txt 就会包含 /usr/bin 和 /bin 两个目录的内容了。
如果我们尝试列出一个不存在的目录,ls 命令会报错。
|$ ls -l /no/such/directory > file-list.txt ls: cannot access /no/such/directory: No such file or directory
你会发现,错误信息还是显示在了屏幕上,而 file-list.txt 文件被清空了。这是因为错误信息是通过标准错误(stderr)管道输出的,而 > 只重定向了标准输出(stdout)。
如果我们想把错误信息也存起来,就需要单独重定向标准错误。在Linux中,这三个标准管道可以用数字代号表示:0代表标准输入,1代表标准输出,2代表标准错误。所以,我们可以用 2> 来重定向标准错误。
|$ ls -l /no/such/directory 2> error-log.txt
这样,错误信息就会被保存到 error-log.txt 文件中,屏幕上将一片寂静。
有时候,我们希望把所有输出——无论是正确的“产品”还是“错误警报”——都收集到同一个文件里。可以用 &> 这个方便的符号:
|$ ls -l /no/such/directory &> all-output.txt
这条命令会把标准输出和标准错误都重定向到 all-output.txt 文件。
还有一种情况,我们不关心任何输出,只想让命令安安静静地执行,不产生任何信息。Linux提供了一个神奇的地方,叫做 /dev/null,它像一个黑洞,任何被重定向到这里的数据都会永远消失。
|$ ls -l /no/such/directory 2> /dev/null
这样,错误信息就被“扔进黑洞”了,不会在任何地方出现。
说完了输出,我们再来看看输入。有些命令不仅可以从命令行参数接收数据,还可以从标准输入读取数据。cat 命令就是一个典型的例子。
cat 的本意是“连接”(concatenate)文件,但如果你不给它任何文件名,它就会从标准输入——也就是你的键盘——读取内容,然后把它显示到标准输出——也就是屏幕上。
|$ cat hello world hello world (按下 Ctrl-D 结束输入)
你可以输入任何东西,cat 都会原样复述一遍。Ctrl-D 是告诉它“输入结束了”的信号。
利用这个特性,我们可以快速创建一个文件:
|$ cat > my-note.txt 这是我写的第一行笔记。 这是第二行。 (按下 Ctrl-D 保存并退出)
现在,my-note.txt 文件里就有了我们刚才输入的内容。
既然 cat 可以从标准输入读取数据,我们就可以用 < 符号,把一个文件重定向为它的标准输入。
|$ cat < my-note.txt 这是我写的第一行笔记。 这是第二行。
这看起来和 cat my-note.txt 的效果一样,但背后的原理完全不同。前者是 cat 从标准输入读取数据,而我们把 my-note.txt 接到了它的标准输入管道上;后者则是 cat 直接打开文件进行读取。
重定向最强大的地方,在于它可以把多个命令像乐高积木一样拼接起来,形成一个“处理流水线”。我们用 | 这个符号来实现,它被称为“管道”。
管道的作用是把前一个命令的标准输出,直接连接到后一个命令的标准输入。
比如,我们想看看 /usr/bin 目录里有多少个文件。我们可以分两步:
用 ls /usr/bin 列出所有文件,存到一个文件里。
用 wc -l 命令统计这个文件的行数。wc (word count) 是一个计数工具,-l 选项表示只计行数。
但有了管道之后,我们可以一气呵成:
|$ ls /usr/bin | wc -l
ls 的输出没有显示在屏幕上,而是通过管道 |,直接流进了 wc 命令的“嘴”里(标准输入),wc 统计完行数后,把结果显示在屏幕上。
这条流水线可以无限延长。比如,我们想在 /usr/bin 目录中,找出所有包含 “zip” 这个词的文件,并按字母顺序排序,然后分页查看。
我们可以这样组合:
ls /usr/bin:列出所有文件。
grep zip:grep 是一个文本搜索工具,它会从标准输入里筛选出包含 “zip” 的行。
sort:对筛选结果进行排序。
less:分页显示最终结果。
如果我们用管道把它们串起来,就可以这样:
|$ ls /usr/bin | grep zip | sort | less
这条命令展示了Linux命令行的精髓:每个命令只做一件事,并把它做到最好。然后通过管道,把这些简单的小工具组合起来,就能完成非常复杂的任务。
|# 统计文件数量 $ ls | wc -l # 查找包含特定内容的文件 $ ls | grep pattern # 排序并去重 $ cat file.txt | sort | uniq # 查找进程并杀死 $ ps aux | grep process_name | awk '{print $2}' | xargs kill # 监控日志文件 $ tail -f log.txt
Linux中还有一个有趣的工具叫 tee。它就像管道上的一个三通接头。数据流过它的时候,它会把数据复制一份,一份送到指定的文件里保存,另一份继续顺着管道流向下一个命令。
比如,我们想把 /usr/bin 的完整列表存到 full-list.txt 文件里,同时又想在屏幕上看到经过 grep 筛选后的结果:
|$ ls /usr/bin | tee full-list.txt | grep zip
执行后,full-list.txt 文件里保存了 ls 的全部输出,而屏幕上只显示了包含 “zip” 的那些行。
进程替换是重定向的高级用法,它允许我们把命令的输出当作文件来处理:
|# 比较两个命令的输出 $ diff <(ls /usr/bin) <(ls /bin) # 将命令输出作为另一个命令的输入 $ grep “pattern” <(find . -name “*.txt”)
重定向的顺序很重要,特别是在同时重定向多个流时:
|# 正确的顺序:先重定向标准输出,再重定向标准错误 $ command > output.txt 2>&1 # 错误的顺序:这样不会达到预期效果 $ command 2>&1 > output.txt
现在让我们来做一些实际的练习,巩固刚才学到的知识。
|# 1. 创建并查看文件列表 $ ls -la /etc > etc_files.txt $ cat etc_files.txt # 2. 统计文件数量 $ ls /usr/bin | wc -l # 3. 查找特定文件 $ ls /usr/bin | grep python # 4. 创建错误日志 $ ls /nonexistent 2> error.log $ cat error.log # 5. 同时保存输出和错误 $ ls /usr/bin
通过灵活运用输入输出重定向和管道,你可以把简单的命令组合成强大的自动化脚本,记住,实践是最好的老师,多尝试不同的组合,你会发现命令行世界的无限可能!