将标准输入传递给多个程序而无需关闭

将标准输入传递给多个程序而无需关闭

我想要分割并压缩一个大文件,然后这个答案它似乎就是我所寻找的,而且它似乎是一种我从未想过的非常有用的方法,所以我想将它概括出来;唯一的问题是:它似乎不起作用。

假设我想拆分我的输入并进一步处理它(我知道,split但我想直接在我的脚本中传输它!)

这用于read将一行读入变量

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
  echo "<< $i >>"
  for ((j = 0 ; j < 2 ; j++)) ; do
    read l
    echo "$l"
  done
done

它打印

<< 0 >>
a
b
<< 1 >>
c
d

这几乎就是我想要的,除了它从开始和结束处修剪空格(并且可能以其他方式修改行?它可以用于任意 UTF-8 编码内容吗?)编辑 解决了

我想它可能会很慢。编辑对其进行了基准测试:至少慢了 3000 倍。

所以我尝试通过管道传输它head(我按照答案所建议的方式得到了结果awk,它似乎没有做任何不同的事情)

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
  echo "<< $i >>"
  head -n 2
done

打印

<< 0 >>
 a 
 b 
<< 1 >>

并停止,因为head退出时显然关闭了输入。我还没有找到不这样做的程序,也许它实际上是由系统强制执行的?(我在 OS X 上)

使用head -n 2 <&0哪个(根据 bash 文档)首先复制文件描述符也不起作用。

我必须使用命名管道吗? 有什么咒语可以让它工作吗?

答案1

这里的问题并不完全是headawk关闭输入”。他们别无选择;任何程序在终止时都会关闭其输入,这是操作系统强制执行的。

问题是标准输入是管道,而程序正在进行缓冲读取。没有办法从管道中取消读取,因此预读中的任何数据都消失了。如果您不使用管道而是使用文件,您可能会发现它工作正常:

#!/bin/bash
printf " %s \n" a b c d > /tmp/abcd
for ((i = 0 ; i < 2 ; i++)) ; do
    echo "<< $i >>"
    for ((j = 0 ; j < 2 ; j++)) ; do
        read
        echo "$REPLY"
    done
done < /tmp/abcd

至少,这在 Ubuntu 上可以正常工作。如果关闭缓冲,您可以使用管道使其工作 - 但这可能会使速度变得非常慢。这是一个小 C 程序,它关闭缓冲,然后逐个字符地回显其输入,直到它消耗了请求的行数:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  int n = 1000;
  if (argc > 1) n = atoi(argv[1]);
  setvbuf(stdin, NULL, _IONBF, 0);
  for (int ch = getchar(); ch != EOF; ch = getchar()) {
    putchar(ch);
    if (ch == '\n' && --n <= 0) break;
  }
  return n > 0;
}

对我来说,这很好(在 Ubuntu 上,同样如此——您需要使用-std=c99或编译它-std=c11,以便编译器不会抱怨)。程序确实不会调用fclose(stdin),但添加不会有任何区别。另一方面,删除对的调用setvbuf可能会让您回到您在使用时观察到的症状head。(并且它还会使程序运行很多快点。)

如果您拥有 GNUsplit版本而不是随 OS X 附带的 BSD 版本,您将能够使用有用的--filter=COMMAND语法,它可以很好地完成您的要求;它不会创建拆分文件,而是将每个文件部分通过管道传输到指定 COMMAND 的调用中(并将环境变量设置$FILE为预期的文件名)。

答案2

通过指定一个变量,read您可以命令它执行单词拆分。 如果不这样做,空格将保持不变:

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
    echo "<< $i >>"
    for ((j = 0 ; j < 2 ; j++)) ; do
        read
        echo "$REPLY"
    done
done

输出:

<< 0 >>
 a  
 b  
<< 1 >>
 c  
 d  

这看起来很简单,但实际上你问了一个很好的问题,因为这个功能在手册中没有清楚地解释。

PS 我也会使用一个-r标志(不要将其\视为转义字符)read

答案3

但是如果你想编写一个独立的脚本来操作大文件,从效率的角度来看,AWK 比 Bash 更合适。一行代码:

$ awk 'NR%2 { print "<< " int(NR/2) " >>" }; 1' <<< $' a \n b \n c \n d '
<< 0 >>
 a 
 b 
<< 1 >>
 c 
 d 

与脚本相同:

#!/usr/bin/awk -f

# where (number of line) mod 2 == 1, i. e. every odd line
NR%2 == 1 {
    # print (number of line) div 2
    print "<< " int(NR/2) " >>"
}

{  
    # print input stream
    print
} 

与 Bash 脚本完全相同:

#!/bin/bash

while read; do
    let lnum++
    ((lnum % 2 == 1)) && \
        echo "<< $((lnum / 2)) >>"
    echo "$REPLY"
done

一百万行的基准测试:

$ awk 'BEGIN { for (i=1; i<=10^6; i++) print i }' >> 1e6

$ time ./pascal.awk < 1e6 > /dev/null

real    0m0.663s
user    0m0.656s
sys     0m0.004s

$ time ./pascal.sh < 1e6 > /dev/null

real    0m31.293s
user    0m29.410s
sys     0m1.852s

您会看到,为什么 Bash 在这里不是一个优选的解释器。

相关内容