我想要分割并压缩一个大文件,然后这个答案它似乎就是我所寻找的,而且它似乎是一种我从未想过的非常有用的方法,所以我想将它概括出来;唯一的问题是:它似乎不起作用。
假设我想拆分我的输入并进一步处理它(我知道,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
这里的问题并不完全是head
“awk
关闭输入”。他们别无选择;任何程序在终止时都会关闭其输入,这是操作系统强制执行的。
问题是标准输入是管道,而程序正在进行缓冲读取。没有办法从管道中取消读取,因此预读中的任何数据都消失了。如果您不使用管道而是使用文件,您可能会发现它工作正常:
#!/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 在这里不是一个优选的解释器。