我有一个脚本,其中通过相当大的管道流式传输数据来处理数据。管道的几个部分实际上是“总机”功能,它们根据某些外部参数执行不同的操作。下面给出了一个人为的例子。
#! /bin/bash
switchboard() {
# Select the appropriate command depending on input.
case "$1" in
1)
sort
;;
2)
awk '{ print $5 }' | sort
;;
*)
cat # <= Is there something more optimal here?
;;
esac
}
# The data processing pipeline.
<"$1" tr '[:upper:]' '[:lower:]' | switchboard "$2" | head -n 10
在“switchboard”功能中,后备只是用于cat
将输入直接发送到输出。这工作得很好,但在我的管道中,我可能有很多“总机”,如果可能的话,我想避免创建一堆无所事事的cat
进程。
是否有某种 bash 内置(或替代方案)可用于指定管道的给定部分应将 STDOUT 直接连接到 STDIN,而无需使用子进程? (我尝试过:
,但只是吃掉了数据)或者,是否cat
使用了如此少量的资源,这不是问题?
答案1
首先,使用另一个cat
并没有多大区别,你不应该为此烦恼。
其次,组成管道的命令无论如何都在单独的进程中执行,无论它们是外部命令还是内置命令:
$ a=0
$ a=1 | a=2 | a=3
$ echo $a
0
至于您的确切问题,不可能简单地将“stdin”连接到“stdout”;即使 shell 有一些nop
内置函数在管道中使用时会崩溃(例如| nop |
-> |
),shell 在设置管道时也无法提前知道您的“总机”将切换到nop
而不是awk
或者sort
。
您还可以通过自己构建管道,然后调用 eval 来运行它,从而达到与“总机”相同的效果。例子:
$ cat test.sh
type=`file -zi "$1"`
case $type in
*application/gzip*) mycat='zcat "$1"';;
*) mycat='cat "$1"';;
esac
case $type in
*charset=utf-16le*) mycat="$mycat | iconv -f utf16le";;
esac
# highlight comments in blue
esc=`printf '\033'`;
mycat="$mycat | sed 's/^#.*/$esc[34m&$esc[m/'"
echo >&2 "$mycat" # show the built pipeline
eval "$mycat" # ... and run it
$ iconv -t utf16 test.sh > test16.sh; gzip test16.sh
$ sh test.sh test16.sh.gz
这有点偏离主题,但在 Linux 上,有一种更快的方法将标准输入复制到标准输出(如果其中任何一个是管道)——系统splice(2)
调用,它不涉及将数据移入和移出用户区:
$ cat splice_cat.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <err.h>
int main(int ac, char **av){
ssize_t r;
size_t block = ac > 1 ? strtoul(av[1], 0, 0) : 0x20000;
for(;;)
if((r = splice(0, NULL, 1, NULL, block, 0)) < 1){
if(r < 0) err(1, "splice");
return 0;
}
}
$ cc -Wall splice_cat.c -o splice_cat
$ dd if=/dev/zero bs=1M count=100 status=none | (time cat >/dev/null)
real 0m0.153s
user 0m0.012s
sys 0m0.056s
$ dd if=/dev/zero bs=1M count=100 status=none | (time ./splice_cat >/dev/null)
real 0m0.100s
user 0m0.004s
sys 0m0.020s
但是(据我所知), shell 或cat
,dd
等都没有使用它。