在 Linux 命令行上使用数据流

学习如何使用 STDIO 将数据流从一个实用程序连接到另一个实用程序。
224 位读者喜欢这个。

作者注:本文的大部分内容摘自我的新书《面向系统管理员的 Linux 哲学》第 3 章:数据流,并进行了一些重大编辑以适应 Opensource.com 的文章格式。

Linux 中的一切都围绕数据流——尤其是文本流。数据流是 GNU 实用程序、Linux 核心实用程序和许多其他命令行工具执行其工作的原材料。

顾名思义,数据流是从一个文件、设备或程序传递到另一个文件、设备或程序的数据流——尤其是文本数据——使用 STDIO。本章介绍如何使用管道将来自一个实用程序的数据流连接到另一个实用程序,使用 STDIO。您将了解到这些程序的功能是以某种方式转换数据。您还将学习如何使用重定向将数据重定向到文件。

我将“转换”一词与这些程序结合使用,因为每个程序的主要任务是以系统管理员期望的特定方式转换来自 STDIO 的传入数据,并将转换后的数据发送到 STDOUT,以供另一个转换程序可能使用或重定向到文件。

标准术语“过滤器”暗示了一些我不同意的东西。根据定义,过滤器是一种设备或工具,用于去除某些东西,例如空气过滤器去除空气中的污染物,以防止汽车的内燃机因这些微粒而磨损致死。在我的高中和大学化学课上,滤纸被用来去除液体中的微粒。我家 HVAC 系统中的空气过滤器去除了我不希望吸入的微粒。

尽管它们有时确实会从数据流中过滤掉不需要的数据,但我更喜欢“转换器”这个术语,因为这些实用程序的功能远不止于此。它们可以向数据流添加数据、以惊人的方式修改数据、对其进行排序、重新排列每行中的数据、根据数据流的内容执行操作等等。您可以随意使用您喜欢的术语,但我更喜欢转换器。我预计只有我一个人这样想。

可以通过使用管道将转换器插入数据流来操作数据流。系统管理员使用每个转换器程序对数据流中的数据执行某些操作,从而以某种方式更改其内容。然后可以在管道末端使用重定向将数据流定向到文件。如前所述,该文件可以是硬盘驱动器上的实际数据文件,也可以是设备文件,例如驱动器分区、打印机、终端、伪终端或连接到计算机的任何其他设备。

使用这些小型但功能强大的转换器程序来操作这些数据流的能力是 Linux 命令行界面强大功能的核心。许多核心实用程序都是转换器程序,并使用 STDIO。

在 Unix 和 Linux 世界中,流是文本数据的流动,它起源于某个源;该流可以流向一个或多个程序,这些程序以某种方式转换它,然后可以将其存储在文件中或显示在终端会话中。作为系统管理员,您的工作与操作这些数据流的创建和流动密切相关。在这篇文章中,我们将探讨数据流——它们是什么、如何创建它们,以及如何使用它们。

文本流——通用接口

使用标准输入/输出 (STDIO) 进行程序输入和输出是 Linux 工作方式的关键基础。STDIO 最初是为 Unix 开发的,此后已应用于包括 DOS、Windows 和 Linux 在内的大多数其他操作系统。

这就是 Unix 哲学:编写只做一件事并做好一件事的程序。编写程序以协同工作。编写程序来处理文本流,因为这是一个通用接口。”

—— Doug McIlroy,《Unix 哲学基础》

STDIO

STDIO 是由 Ken Thompson 开发的,它是早期版本的 Unix 上实现管道所需的基础架构的一部分。实现 STDIO 的程序使用标准化的文件句柄进行输入和输出,而不是存储在磁盘或其他记录介质上的文件。STDIO 最好描述为缓冲数据流,其主要功能是将数据从一个程序、文件或设备的输出流式传输到另一个程序、文件或设备的输入。

有三个 STDIO 数据流,每个数据流在程序启动时都会自动打开为一个文件——嗯,那些使用 STDIO 的程序。每个 STDIO 数据流都与一个文件句柄相关联,文件句柄只是一组描述文件属性的元数据。文件句柄 0、1 和 2 由约定和长期实践明确定义为 STDIN、STDOUT 和 STDERR。

STDIN,文件句柄 0,是标准输入,通常是来自键盘的输入。STDIN 可以从任何文件(包括设备文件)而不是键盘重定向。不需要重定向 STDIN 的情况并不常见,但可以做到。

STDOUT,文件句柄 1,是标准输出,默认情况下将数据流发送到显示器。通常将 STDOUT 重定向到文件或通过管道将其传输到另一个程序以进行进一步处理。

STDERR,文件句柄 2。STDERR 的数据流通常也发送到显示器。

如果 STDOUT 重定向到文件,则 STDERR 继续显示在屏幕上。这确保了当数据流本身未显示在终端上时,STDERR 会显示,从而确保用户将看到程序执行产生的任何错误。STDERR 也可以重定向到同一程序或传递到管道中的下一个转换器程序。

STDIO 以 C 库 stdio.h 的形式实现,该库可以包含在程序的源代码中,以便可以将其编译到生成的​​可执行文件中。

简单流

您可以在 Linux 主机的 /tmp 目录中安全地执行以下实验。以 root 用户身份,将 /tmp 作为 PWD,创建一个测试目录,然后将新目录作为 PWD。

# cd /tmp ; mkdir test ; cd test

输入并运行以下命令行程序,以在驱动器上创建一些包含内容的文件。我们使用 dmesg 命令只是为了提供文件要包含的数据。内容不如每个文件都包含一些内容这一事实重要。

# for I in 0 1 2 3 4 5 6 7 8 9 ; do dmesg > file$I.txt ; done 

验证 /tmp/ 中现在至少有 10 个文件,名称为 file0.txtfile9.txt

# ll
total 1320
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file0.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file1.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file2.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file3.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file4.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file5.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file6.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file7.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file8.txt
-rw-r--r-- 1 root root 131402 Oct 17 15:50 file9.txt

我们使用 dmesg 命令生成了数据流,该命令被重定向到一系列文件。大多数核心实用程序都使用 STDIO 作为其输出流,而那些生成数据流而不是充当转换数据流的实用程序可以用来创建我们将用于实验的数据流。数据流可以短至一行甚至一个字符,也可以根据需要长。

探索硬盘驱动器

现在是时候进行一些探索了。在这个实验中,我们将研究一些文件系统结构。

让我们从一些简单的东西开始。您应该至少对 dd 命令有所了解。正式名称为“磁盘转储”,许多系统管理员将其称为“磁盘破坏者”,这是有充分理由的。我们中的许多人都不小心使用 dd 命令破坏了整个硬盘驱动器或分区的内容。这就是为什么我们将停留在 /tmp/test 目录中以执行其中一些实验的原因。

尽管 dd 声名狼藉,但它在探索各种类型的存储介质、硬盘驱动器和分区方面非常有用。我们还将使用它作为探索 Linux 其他方面的工具。

如果您尚未以 root 身份登录终端会话,请登录。我们首先需要使用 lsblk 命令确定硬盘驱动器的设备特殊文件。

[root@studentvm1 test]# lsblk -i
NAME                                 MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                                    8:0    0   60G  0 disk 
|-sda1                                 8:1    0    1G  0 part /boot
`-sda2                                 8:2    0   59G  0 part 
  |-fedora_studentvm1-pool00_tmeta   253:0    0    4M  0 lvm  
  | `-fedora_studentvm1-pool00-tpool 253:2    0    2G  0 lvm  
  |   |-fedora_studentvm1-root       253:3    0    2G  0 lvm  /
  |   `-fedora_studentvm1-pool00     253:6    0    2G  0 lvm  
  |-fedora_studentvm1-pool00_tdata   253:1    0    2G  0 lvm  
  | `-fedora_studentvm1-pool00-tpool 253:2    0    2G  0 lvm  
  |   |-fedora_studentvm1-root       253:3    0    2G  0 lvm  /
  |   `-fedora_studentvm1-pool00     253:6    0    2G  0 lvm  
  |-fedora_studentvm1-swap           253:4    0   10G  0 lvm  [SWAP]
  |-fedora_studentvm1-usr            253:5    0   15G  0 lvm  /usr
  |-fedora_studentvm1-home           253:7    0    2G  0 lvm  /home
  |-fedora_studentvm1-var            253:8    0   10G  0 lvm  /var
  `-fedora_studentvm1-tmp            253:9    0    5G  0 lvm  /tmp
sr0                                   11:0    1 1024M  0 rom 

我们可以从中看到,此主机上只有一个硬盘驱动器,与其关联的设备特殊文件是 /dev/sda,并且它有两个分区。/dev/sda1 分区是启动分区,/dev/sda2 分区包含一个卷组,主机其余的逻辑卷都在该卷组上创建。

以终端会话中的 root 身份,使用 dd 命令查看硬盘驱动器的启动记录,假设它已分配给 /dev/sda 设备。bs= 参数不是您可能认为的那样;它只是指定块大小,count= 参数指定要转储到 STDIO 的块数。if= 参数指定数据流的来源,在本例中为 /dev/sda 设备。请注意,我们没有查看分区的第一个块,而是查看硬盘驱动器的第一个块。

[root@studentvm1 test]# dd if=/dev/sda bs=512 count=1
�c�#�м���؎���|�#�#���!#��8#u
                            ��#���u��#�#�#�|���t#�L#�#�|���#�����€t��pt#���y|1��؎м ��d|<�t#��R�|1��D#@�D��D#�##f�#\|f�f�#`|f�\
                                      �D#p�B�#r�p�#�K`#�#��1��������#a`���#f��u#����f1�f�TCPAf�#f�#a�&Z|�#}�#�.}�4�3}�.�#��GRUB GeomHard DiskRead Error
�#��#�<u��ܻޮ�###��� ������ �_U�1+0 records in
1+0 records out
512 bytes copied, 4.3856e-05 s, 11.7 MB/s

这会打印启动记录的文本,该文本是磁盘(任何磁盘)上的第一个块。在这种情况下,有关于文件系统的信息,尽管它是以二进制格式存储的而无法读取,但还是分区表。如果这是一个可启动设备,则 GRUB 或其他一些引导加载程序的第 1 阶段将位于此扇区中。最后三行包含有关已处理记录和字节数的数据。

/dev/sda1 的开头开始,让我们一次查看几个数据块,以找到我们想要的内容。该命令与前一个命令类似,只是我们指定了要查看的更多数据块。如果您的终端不够大,无法一次显示所有数据,则可能需要指定较少的块,或者您可以将数据通过 less 实用程序进行管道传输并使用它来分页浏览数据——这两种方法都可以。请记住,我们正在以 root 用户身份执行所有这些操作,因为非 root 用户没有所需的权限。

输入与您在上一个实验中输入的命令相同的命令,但将要显示的块计数增加到 100,如下所示,以便显示更多数据。

[root@studentvm1 test]# dd if=/dev/sda1 bs=512 count=100
##33��#:�##�� :o�[:o�[#��S�###�q[#
                                  #<�#{5OZh�GJ͞#t�Ұ##boot/bootysimage/booC�dp��G'�*)�#A�##@
     #�q[
�## ##  ###�#���To=###<#8���#'#�###�#�����#�'  �����#Xi  �#��`  qT���
  <���
      �  r����  ]�#�#�##�##�##�#�##�##�##�#�##�##�#��#�#�##�#�##�##�#��#�#����#	�	�#	�#	�#
�
�#
�#
�#
  �
   �#
     �#
       �#
         �
          �#
            �#
              �#100+0 records in
100+0 records out
51200 bytes (51 kB, 50 KiB) copied, 0.00117615 s, 43.5 MB/s

现在尝试这个命令。我不会在此处重现整个数据流,因为它会占用大量空间。使用 Ctrl-C 中断并停止数据流。

[root@studentvm1 test]# dd if=/dev/sda

此命令生成的数据流是硬盘驱动器 /dev/sda 的完整内容,包括启动记录、分区表以及所有分区及其内容。此数据可以重定向到文件,用作可以执行裸机恢复的完整备份。它也可以直接发送到另一个硬盘驱动器以克隆第一个硬盘驱动器。但不要执行此特定实验。

[root@studentvm1 test]# dd if=/dev/sda of=/dev/sdx

您可以看到 dd 命令对于探索各种类型文件系统的结构、查找有缺陷的存储设备上的数据等等非常有用。它还会生成一个数据流,我们可以在其上使用转换器实用程序来修改或查看。

这里的真正重点是 dd,像许多 Linux 命令一样,将其输出生成为数据流。可以使用其他工具以多种方式搜索和操作该数据流。它甚至可以用于类似幽灵的备份或磁盘复制。

随机性

事实证明,随机性在计算机中是一件令人向往的事情——谁知道呢?系统管理员可能希望生成随机数据流的原因有很多。随机数据流有时可用于覆盖完整分区(例如 /dev/sda1)甚至整个硬盘驱动器(如 /dev/sda)的内容。

以非 root 用户身份执行此实验。输入此命令以将无休止的随机数据流打印到 STDIO。

[student@studentvm1 ~]$ cat /dev/urandom

使用 Ctrl-C 中断并停止数据流。您可能需要多次使用 Ctrl-C

随机数据也用作程序的输入种子,这些程序生成随机密码和随机数据和数字,用于科学和统计计算。我将在第 24 章:一切皆文件 中更详细地介绍随机性和其他有趣的数据源。

管道梦想

管道对于我们在命令行上执行令人惊叹的操作的能力至关重要,以至于我认为重要的是要认识到它们是由 Douglas McIlroy 在 Unix 的早期发明的(感谢 Doug!)。普林斯顿大学网站有一个 McIlroy 采访片段,他在其中讨论了管道的创建和 Unix 哲学的开端。

请注意下一个显示的简单命令行程序中管道的使用,该程序列出每个登录用户一次,无论他们有多少个活动登录。以学生用户身份执行此实验。输入如下所示的命令

[student@studentvm1 ~]$ w | tail -n +3 | awk '{print $1}' | sort | uniq
root
student
[student@studentvm1 ~]$

此命令的结果生成两行数据,显示用户的 root 和 student 都已登录。它不显示每个用户登录了多少次。您的结果几乎肯定与我的不同。

管道——用竖线 ( | ) 表示——是将这些命令行实用程序连接在一起的句法粘合剂,即运算符。管道允许将一个命令的标准输出“管道传输”,即从一个命令的标准输出流式传输到下一个命令的标准输入。

|& 运算符可用于将 STDERR 以及 STDOUT 通过管道传输到下一个命令的 STDIN。这并非总是可取的,但它确实提供了记录 STDERR 数据流以进行问题确定的灵活性。

用管道连接的一系列程序称为管道线,使用 STDIO 的程序在官方上称为过滤器,但我更喜欢术语“转换器”。

想想如果我们不能将数据流从一个命令通过管道传输到下一个命令,这个程序将如何工作。第一个命令将对其数据执行其任务,然后需要将该命令的输出保存在文件中。下一个命令将必须从中间文件读取数据流并执行其对数据流的修改,将其自身的输出发送到新的临时数据文件。第三个命令将必须从第二个临时数据文件获取其数据并执行其自身对数据流的操作,然后将生成的数据流存储在另一个临时文件中。在每个步骤中,数据文件名都必须以某种方式从一个命令传输到下一个命令。

我什至无法忍受考虑这一点,因为它太复杂了。记住:简单性至上!

构建管道线

当我做一些新的事情,解决一个新问题时,我通常不会从头开始凭空键入完整的 Bash 命令管道线。我通常从管道线中的一个或两个命令开始,并通过添加更多命令来进一步处理数据流,从而从那里构建。这使我可以在管道线中每个命令之后查看数据流的状态,并在需要时进行更正。

可以构建非常复杂的管道线,这些管道线可以使用许多不同的实用程序来转换数据流,这些实用程序与 STDIO 一起工作。

重定向

重定向是将程序的 STDOUT 数据流重定向到文件而不是默认目标显示器的功能。“大于”(>) 字符,也称为“gt”,是重定向 STDOUT 的句法符号。

重定向命令的 STDOUT 可用于创建包含该命令结果的文件。

[student@studentvm1 ~]$ df -h > diskusage.txt

除非出现错误,否则此命令没有终端输出。这是因为 STDOUT 数据流被重定向到文件,而 STDERR 仍然定向到 STDOUT 设备,即显示器。您可以使用以下下一个命令查看刚刚创建的文件的内容

[student@studentvm1 test]# cat diskusage.txt 
Filesystem                          Size  Used Avail Use% Mounted on
devtmpfs                            2.0G     0  2.0G   0% /dev
tmpfs                               2.0G     0  2.0G   0% /dev/shm
tmpfs                               2.0G  1.2M  2.0G   1% /run
tmpfs                               2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/mapper/fedora_studentvm1-root  2.0G   50M  1.8G   3% /
/dev/mapper/fedora_studentvm1-usr    15G  4.5G  9.5G  33% /usr
/dev/mapper/fedora_studentvm1-var   9.8G  1.1G  8.2G  12% /var
/dev/mapper/fedora_studentvm1-tmp   4.9G   21M  4.6G   1% /tmp
/dev/mapper/fedora_studentvm1-home  2.0G  7.2M  1.8G   1% /home
/dev/sda1                           976M  221M  689M  25% /boot
tmpfs                               395M     0  395M   0% /run/user/0
tmpfs                               395M   12K  395M   1% /run/user/1000

当使用 > 符号重定向数据流时,如果指定的文件尚不存在,则会创建该文件。如果该文件确实存在,则其内容将被来自命令的数据流覆盖。您可以使用双大于号 >> 将新数据流附加到文件中的任何现有内容。

[student@studentvm1 ~]$ df -h >> diskusage.txt

您可以使用 cat 和/或 less 查看 diskusage.txt 文件,以验证新数据是否已附加到文件末尾。

<(小于)符号将数据重定向到程序的 STDIN。您可能希望使用此方法将文件中的数据输入到不将文件名作为参数但确实使用 STDIN 的命令的 STDIN。尽管可以将输入源重定向到 STDIN,例如用作 grep 输入的文件,但通常没有必要,因为 grep 也将文件名作为参数来指定输入源。大多数其他命令也将文件名作为其输入源的参数。

只是 grep'ing 周围

grep 命令用于从数据流中选择与指定模式匹配的行。grep 是最常用的转换器实用程序之一,可以以一些非常有创意和有趣的方式使用。grep 命令是少数可以正确称为过滤器的命令之一,因为它确实过滤掉了数据流中您不需要的所有行;它只留下您想要的行在剩余的数据流中。

如果 PWD 不是 /tmp/test 目录,请将其设为该目录。让我们首先创建一个随机数据流以存储在文件中。在这种情况下,我们希望稍微少一些的随机数据,这些数据将仅限于可打印字符。一个好的密码生成器程序可以做到这一点。以下程序(您可能需要安装 pwgen,如果它尚未安装)创建一个包含 50,000 个密码的文件,这些密码长 80 个字符,使用每个可打印字符。首先尝试不重定向到 random.txt 文件来查看它的外观,然后再执行一次,将输出数据流重定向到文件。

$ pwgen -sy 80 50000 > random.txt

考虑到有这么多密码,它们中的某些字符串很可能相同。首先,cat random.txt 文件,然后使用 grep 命令在屏幕上的最后十个密码中找到一些短的、随机选择的字符串。我在其中一个密码中看到了单词“see”,所以我的命令如下所示:grep see random.txt,您可以尝试一下,但您也应该选择一些您自己的字符串进行检查。两到四个字符的短字符串效果最佳。

$ grep see random.txt 
	R=p)'s/~0}wr~2(OqaL.S7DNyxlmO69`"12u]h@rp[D2%3}1b87+>Vk,;4a0hX]d7see;1%9|wMp6Yl.
	bSM_mt_hPy|YZ1<TY/Hu5{g#mQ<u_(@8B5Vt?w%i-&C>NU@[;zV2-see)>(BSK~n5mmb9~h)yx{a&$_e
	cjR1QWZwEgl48[3i-(^x9D=v)seeYT2R#M:>wDh?Tn$]HZU7}j!7bIiIr^cI.DI)W0D"'vZU@.Kxd1E1
	z=tXcjVv^G\nW`,y=bED]d|7%s6iYT^a^Bvsee:v\UmWT02|P|nq%A*;+Ng[$S%*s)-ls"dUfo|0P5+n

总结

正是管道和重定向的使用,使得可以在 Linux 命令行上使用数据流执行许多令人惊叹且强大的任务。正是管道将 STDIO 数据流从一个程序或文件传输到另一个程序或文件。通过一个或多个转换器程序管道传输数据流的能力支持对这些流中的数据进行强大而灵活的操作。

实验中演示的管道线中的每个程序都很小,并且每个程序都做得很好。它们也是转换器;也就是说,它们接受标准输入,以某种方式对其进行处理,然后将结果发送到标准输出。将这些程序实现为转换器,以将其自身标准输出中的处理数据流发送到其他程序的标准输入,这与管道作为 Linux 工具的实现是互补的,并且是必需的。

STDIO 不过是数据流。此数据几乎可以是任何内容,从列出目录中文件的命令的输出,到来自 /dev/urandom 等特殊设备的无休止的数据流,甚至是可以包含来自硬盘驱动器或分区的所有原始数据的数据流。

Linux 计算机上的任何设备都可以像数据流一样处理。您可以使用 ddcat 等普通工具将设备中的数据转储到 STDIO 数据流中,该数据流可以使用其他普通 Linux 工具进行处理。

标签
David Both
David Both 是一位开源软件和 GNU/Linux 倡导者、培训师、作家和演讲者。自 1996 年以来,他一直从事 Linux 和开源软件方面的工作,自 1969 年以来一直从事计算机方面的工作。他是“面向系统管理员的 Linux 哲学”的坚定拥护者和传播者。

1 条评论

非常感谢,信息很好..

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.