用于“cat”文件中所有行的成对扩展的命令行工具

用于“cat”文件中所有行的成对扩展的命令行工具

假设我有一个如下所示的文件(称为 sample.txt):

Row1,10
Row2,20
Row3,30
Row4,40

我希望能够处理该文件中的流,该流本质上是所有四行的成对组合(因此我们最终应该总共有 16 行)。例如,我正在寻找一个流式(即高效)命令,其输出为:

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row1,20 Row2,20
...
Row4,40 Row4,40

我的用例是,我想将此输出流式传输到另一个命令(如 awk)中,以计算有关此成对组合的一些指标。

我有办法在 awk 中执行此操作,但我担心的是,我使用 END{} 块意味着我基本上在输出之前将整个文件存储在内存中。示例代码:

awk '{arr[$1]=$1} END{for (a in arr){ for (a2 in arr) { print arr[a] " " arr[a2]}}}' samples/rows.txt 
Row3,30 Row3,30
Row3,30 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row1,10 Row1,10
Row1,10 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20

是否有一种有效的流式处理方法可以做到这一点,而不必将文件存储在内存中,然后在 END 块中输出?

答案1

以下是如何在 awk 中执行此操作,以便不必将整个文件存储在数组中。这与 terdon 的算法基本相同。

如果您愿意,您甚至可以在命令行上给它多个文件名,它将独立处理每个文件,将结果连接在一起。

#!/usr/bin/awk -f

#Cartesian product of records

{
    file = FILENAME
    while ((getline line <file) > 0)
        print $0, line
    close(file)
}

在我的系统上,它的运行时间大约是 terdon 的 Perl 解决方案的 2/3。

答案2

我不确定这比在内存中执行更好,但是用一个sedr出其内文件中每一行的内文件,另一个在管道的另一侧H用输入行交替旧空间......

cat <<\IN >/tmp/tmp
Row1,10
Row2,20
Row3,30
Row4,40
IN

</tmp/tmp sed -e 'i\
' -e 'r /tmp/tmp' | 
sed -n '/./!n;h;N;/\n$/D;G;s/\n/ /;P;D'

输出

Row1,10 Row1,10
Row1,10 Row2,20
Row1,10 Row3,30
Row1,10 Row4,40
Row2,20 Row1,10
Row2,20 Row2,20
Row2,20 Row3,30
Row2,20 Row4,40
Row3,30 Row1,10
Row3,30 Row2,20
Row3,30 Row3,30
Row3,30 Row4,40
Row4,40 Row1,10
Row4,40 Row2,20
Row4,40 Row3,30
Row4,40 Row4,40

我用另一种方式做到了这一点。它确实存储一些在内存中 - 它存储一个字符串,例如:

"$1" -

...文件中的每一行。

pairs(){ [ -e "$1" ] || return
    set -- "$1" "$(IFS=0 n=
        case "${0%sh*}" in (ya|*s) n=-1;; (mk|po) n=+1;;esac
        printf '"$1" - %s' $(printf "%.$(($(wc -l <"$1")$n))d" 0))"
    eval "cat -- $2 </dev/null | paste -d ' \n' -- $2"
}

它非常快。cat文件中的行数与文件中的行数一样多|pipe。在管道的另一侧,输入与文件本身合并的次数与文件中的行数相同。

这些case东西只是为了可移植性 -yash两者zsh都在拆分中添加一个元素,而mkshposh两者都丢失一个元素。kshdashbusyboxbash全部拆分为与 打印的零一样多的字段printf。正如所写,上面提到的每一个 shell 在我的机器上都会呈现相同的结果。

如果文件是非常很长,可能会出现$ARGMAX参数过多的问题,在这种情况下,您也需要引入xargs或类似的。

考虑到我在输出之前使用的相同输入,输出是相同的。但是,如果我要变大的话……

seq 10 10 10000 | nl -s, >/tmp/tmp

这会生成一个与我之前使用的几乎相同的文件(无“行”)- 但在 1000 行。您可以亲眼看看它有多快:

time pairs /tmp/tmp |wc -l

1000000
pairs /tmp/tmp  0.20s user 0.07s system 110% cpu 0.239 total
wc -l  0.05s user 0.03s system 32% cpu 0.238 total

在 1000 行时,shell 之间的性能存在一些细微的差异 -bash总是最慢的 - 但因为它们所做的唯一工作就是生成 arg 字符串(1000份filename -效果微乎其微。zsh如上所述,这里的性能差异bash是百分之一秒。

这是另一个适用于任意长度文件的版本:

pairs2()( [ -e "$1" ] || exit
    rpt() until [ "$((n+=1))" -gt "$1" ]
          do printf %s\\n "$2"
          done
    [ -n "${1##*/*}" ] || cd -P -- "${1%/*}" || exit
    : & set -- "$1" "/tmp/pairs$!.ln" "$(wc -l <"$1")"
    ln -s "$PWD/${1##*/}" "$2" || exit
    n=0 rpt "$3" "$2" | xargs cat | { exec 3<&0
    n=0 rpt "$3" p | sed -nf - "$2" | paste - /dev/fd/3
    }; rm "$2"
)

它使用半随机名称创建到其第一个参数的软链接,/tmp这样它就不会被奇怪的文件名挂起。这很重要,因为cat的 args 是通过管道传递给它的xargscat的输出被保存到<&3while sed prints 第一个参数中的每一行,次数与该文件中的行数一样多 - 并且它的脚本也通过管道提供给它。再次paste合并其输入,但这次它-的标准输入和链接名称再次只需要两个参数/dev/fd/3

最后一个 - 链接/dev/fd/[num]- 应该适用于任何 Linux 系统以及更多系统,但如果它不创建命名管道mkfifo并使用它也应该可以工作。

它所做的最后一件事是rm在退出之前创建软链接。

这个版本其实是还更快在我的系统上。我想这是因为虽然它执行了更多的应用程序,但它开始立即向它们传递参数 - 而之前它首先将它们全部堆叠起来。

time pairs2 /tmp/tmp | wc -l

1000000
pairs2 /tmp/tmp  0.30s user 0.09s system 178% cpu 0.218 total
wc -l  0.03s user 0.02s system 26% cpu 0.218 total

答案3

好吧,你总是可以在 shell 中执行此操作:

while read i; do 
    while read k; do echo "$i $k"; done < sample.txt 
done < sample.txt 

它比你的解决方案慢很多awk(在我的机器上,1000 行花费了约 11 秒,而 中的约 0.3 秒awk),但至少它在内存中永远不会保存超过几行。

上面的循环适用于示例中非常简单的数据。它会被反斜杠阻塞,并且会吃掉尾随和前导空格。同一件事的更强大的版本是:

while IFS= read -r i; do 
    while IFS= read -r k; do printf "%s %s\n" "$i" "$k"; done < sample.txt 
done < sample.txt 

另一种选择是使用perl

perl -lne '$line1=$_; open(A,"sample.txt"); 
           while($line2=<A>){printf "$line1 $line2"} close(A)' sample.txt

上面的脚本将读取输入文件 ( -ln) 的每一行,将其另存为$lsample.txt再次打开,并与 一起打印每一行$l。结果是所有成对组合,而内存中只存储了 2 行。在我的系统上,1000 行只花费了大约0.6几秒钟。

答案4

你可以编译这个代码以获得相当快的结果。
对于 1000 行文件,它在大约 0.19 - 0.27 秒内完成。

它目前将10000行读入内存(以加快打印到屏幕的速度),如果1000每行有字符,则使用的内存会少于10mb内存,我认为这不会成为问题。不过,您可以完全删除该部分,如果确实引起问题,只需直接打印到屏幕即可。

您可以使用以下命令进行编译:g++ -o "NAME" "NAME.cpp"
Where NAMEis the name of the File to save it to and NAME.cppis the file this code is save to

CTEST.cpp:

#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <sstream>
int main(int argc,char *argv[])
{

        if(argc != 2)
        {
                printf("You must provide at least one argument\n"); // Make                                                                                                                      sure only one arg
                exit(0);
   }
std::ifstream file(argv[1]),file2(argv[1]);
std::string line,line2;
std::stringstream ss;
int x=0;

while (file.good()){
    file2.clear();
    file2.seekg (0, file2.beg);
    getline(file, line);
    if(file.good()){
        while ( file2.good() ){
            getline(file2, line2);
            if(file2.good())
            ss << line <<" "<<line2 << "\n";
            x++;
            if(x==10000){
                    std::cout << ss.rdbuf();
                    ss.clear();
                    ss.str(std::string());
            }
    }
    }
}
std::cout << ss.rdbuf();
ss.clear();
ss.str(std::string());
}

示范

$ g++ -o "Stream.exe" "CTEST.cpp"
$ seq 10 10 10000 | nl -s, > testfile
$ time ./Stream.exe testfile | wc -l
1000000

real    0m0.243s
user    0m0.210s
sys     0m0.033s

相关内容