如果我有一个脚本,可以为它处理的每个文件发送不同的数据类型到 STDOUT。如何分离每种数据类型,以便另一个读取 STDIN 的脚本知道哪个数据类型是什么?
例如。我有一个脚本,它为它处理的每个文件生成两个不同的(未知)字符串和两个不同的(未知)数字。然后还有另一个脚本,它从 STDIN 读取数据来处理每个给定的字符串、数字。如何格式化第一个脚本的输出,以便第二个脚本可以正确识别每种类型?
我习惯于通过 JSON 序列化网络数据,但我想知道 STDIN/STDOUT 是否有更轻量级或内置的解决方案?也许是一些独特的分隔符或者我缺少的东西?
答案1
您没有解释您的标准输出是什么以及它去哪里! (例如a的标准输出计算机图形图像处理应用程序间接地访问某些浏览器)。
这Unix哲学和Unix 管道希望 stdin 和 stdout 是纯文本,但这只是一个习俗。在某些情况下,您可能有其他约定(例如lpr
或lp
通常更喜欢 stdin 上的 PDF)。
如果您正在编写某个程序,您可能会使其具有某种模式(例如由某些程序参数指定)来输出更结构化的文本,例如 JSON 或 XML 或CSV或者YAML。请注意杰克可以处理JSON。
有些程序检测(使用伊萨蒂和/或统计数据)当他们的标准输出(或他们的标准输入)是一个终端并进行相应的操作(也许通过使用恩诅咒,特尔米奥斯, 或者ANSI 转义码)。
许多文本格式(特别是 XML)都有传统的起始字符或标题,因此在某些情况下需要猜测它们(即找出文件格式)是可能的。也看看哑剧 和libmagic(和文件(1))。
我有一个脚本,它为它处理的每个文件生成两个不同的(未知)字符串和两个不同的(未知)数字。然后还有另一个脚本,它从 STDIN 读取数据来处理每个给定的字符串、数字。如何格式化第一个脚本的输出,以便第二个脚本可以正确识别每种类型?
如果您知道(并记录)未知字符串足够好,不包含控制字符,例如换行符(因此它们每个都是单身的行)你可以决定某种格式,例如
FIRSTSTRING:
第一弦
FIRSTNUMBER:
第一个数字
例如
FIRSTSTRING: foo bar is nice!
FIRSTNUMBER: 42
然后为你的第二个脚本编写并使用一些简单的awk
脚本(或者可能使用)sed
换句话说,决定并文档简单的临时格式;在你的情况下可能比 JSON 更容易处理。如果您愿意,您可以拥有自己的临时会议文档他们(也许使用一些EBNF符号)。
许多工具已经做好了一切准备;例如ps
,ls
、df
、 和ifconfig
具有临时但有据可查的输出格式和约定。同样对于过程(5)。因此许多脚本可以解析此类输出。
然而,JSON 被设计为简单、灵活、可伸缩、可扩展……并且它能够表示随意的字符串(即使有控制字符、多行等......)。如果这对您很重要,请使用它。
人们可以重新发明和重新实现所有 Unix 实用程序来输出 JSON 或 XML(但这样做是一种很多工作的)。例如,有些人重新发明了过程(5)和制成一个具有伪文件系统的内核模块/xmlproc/
,而不是以/proc/
某种 XML 形式输出系统内核数据的文件系统。但那并没有成功!社会习俗非常重要(这就是为什么如此重要文档您的输出格式,至少在长评论中)。
(即使您使用 JSON 或 XML,您也需要记录如何使用它们)
顺便说一句,许多现有的 Unix 工具可能会在约定中添加微妙之处。例如,文件路径中可能存在空格、制表符或回车符(请参阅路径分辨率(7))但可能会被人皱眉(所以我从不这样做)。$HOME
理论上,某些用户的目录可以包含返回字符或冒号,但如果您这样做,大多数工具都会受到影响(并且您可能会破坏密码(5)...)。以破折号开头的文件路径是不友好的,非常长的路径也是如此(例如,$HOME
理论上您的长度可能是 3000 个字符,但这确实是不明智的)。
答案2
您有两个脚本,一个生成两个字符串和两个数字。您可以在第二个脚本中将其解析为两个字符串和两个数字。
由于两个脚本都在您的控制之下,因此您可以以任何方便第二个脚本读取数据的方式将数据从一个脚本发送到另一个脚本。
您可以决定在两个脚本之间通过管道传输的数据的格式、顺序和解释。如果您认为有意义,您甚至可以发送二进制编码数据。
除了您通过决定两个脚本或程序之间的特定“合同”或“协议”而为自己编写的数据之外,没有任何数据类型可以应用于数据。
一些标准 Unix 工具喜欢sort
并cut
假定输入采用特定格式,但可以通过使用命令行选项来更改其对输入数据的解释。
人为的例子:
#!/bin/sh
echo 'first string'
echo 'second string'
echo '1.1'
echo '3.14'
第二个脚本是这样的:
#!/bin/sh
IFS= read -r string1
IFS= read -r string2
read number1 number2
或者,使用 JSON:
#!/bin/sh
echo '{ "string1": "hello", "string2": "world", "numbers": [1.1,3.14] }'
第二个脚本:
#!/bin/sh
jq -r '"A number: \(.numbers[])"'
答案3
stdin
只是stdout
进程的文件描述符 0 和 1 等等打开文件描述它们指出,它们本身是由例如使用系统调用打开文件、使用命名管道或在命名管道上open()
创建管道、或使用//等创建套接字而产生的。pipe()
open()
connect()
accept()
socketpair()
至少,其中大多数能够以流的形式读取和写入任何字节序列。对于那些通常用于进程间通信的对象,例如管道和溪流套接字中,一般不保留消息边界。
例如,在 shell 命令行中,如下所示:
writer | reader
其中,写入器的标准输出是管道的一端(或套接字对,具体取决于外壳),而读取器的标准输出是另一端
如果writer
a 这样做write(1, "foo", 3); write(1, "bar", 3)
,则将reader
无法知道有两条消息传入,除非它恰好在的两次写入read()
之间执行。writer
有一些文件类型,例如数据报套接字(至少是 UDP、SCTP 或 unix 域),或者在某些保留消息边界的系统上,但是如果您想允许空消息SOCK_SEQPACKET
,则需要不同的 API ,并且读者会必须提前知道这些消息的最大可能大小,并分配一个足够大的缓冲区来接收它。您仍然需要使用某种形式的编码来指定这些消息内容的性质。read()
write()
例子:
$ strace -e write dd bs=2 count=3 if=/dev/zero status=none | strace -fe read cat
write(1, "\0\0", 2) = 2
write(1, "\0\0", 2) = 2
write(1, "\0\0", 2) = 2
read(0, "\0\0\0\0\0\0", 131072) = 6
read(0, "", 131072) = 0
3 次写入大小为 2,1 次读取大小为 6,但根据时间的不同,您可能会看到 3 次大小为 2 的读取,或者 1 次读取大小为 4 的内容和 1 次读取大小为 2 的内容。那是使用管道或 SOCK_STREAM 套接字对。
使用 SOCK_SEQPACKET 套接字对:
$ perl -MSocket -e '
socketpair(my $rdr, my $wtr, AF_UNIX, SOCK_SEQPACKET, PF_UNSPEC);
shutdown($rdr, 1); shutdown($wtr, 0);
if (fork) {
open STDIN, "<&", $rdr; close $wtr; close $rdr; sleep 1;
exec qw(strace -e read cat)
} else {
open STDOUT, ">&", $wtr; close $rdr; close $wtr;
exec qw(strace -e write dd count=3 bs=2 status=none if=/dev/zero)
}'
write(1, "\0\0", 2) = 2
write(1, "\0\0", 2) = 2
write(1, "\0\0", 2) = 2
+++ exited with 0 +++
read(0, "\0\0", 131072) = 2
read(0, "\0\0", 131072) = 2
read(0, "\0\0", 131072) = 2
read(0, "", 131072) = 0
+++ exited with 0 +++
这 3 次写入需要 3 次读取,尽管我们在写入完成很久之后才将读取延迟了一秒。
因此,最后,最好是以一种或另一种方式对类型和长度进行编码。一种紧凑的形式,允许读者在数据到来时立即处理数据(但作者提前知道消息的长度)是使用TLV(类型、长度、值)编码。您仍然需要编写者和读者就“type”和“length”字的长度和类型(例如,32 位小端整数)以及如何解释类型值达成一致。
或者您可以使用任何可以生成的序列化格式文本这对于跨具有不同字节顺序的系统或不允许 NUL 字节或进行某些行分隔符转码的通道进行交换也更安全。像 json、XML、perl Data::Dumper
、php一样serialize()
,某些 shell 的输出typeset -p
取决于您使用的语言......