Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/bash/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/visual-studio-2008/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Bash 大文件的条件拆分_Bash_Split - Fatal编程技术网

Bash 大文件的条件拆分

Bash 大文件的条件拆分,bash,split,Bash,Split,我有一个非常大的文件(>5亿行),我想根据其中一列的前3个字符将其拆分为几个较小的文件 看起来是这样的,其中第1列和第2列的每个元素都是唯一的: A0A023GPI8 A0A023GPI8.1 232300 1027923628 A0A023GPJ0 A0A023GPJ0.2 716541 765680613 A0A023PXA5 A0A023PXA5.1 559292 728048729 A0A023PXB0 A0A023PXB0.1 559292 72

我有一个非常大的文件(>5亿行),我想根据其中一列的前3个字符将其拆分为几个较小的文件

看起来是这样的,其中第1列和第2列的每个元素都是唯一的:

A0A023GPI8  A0A023GPI8.1    232300  1027923628
A0A023GPJ0  A0A023GPJ0.2    716541  765680613
A0A023PXA5  A0A023PXA5.1    559292  728048729
A0A023PXB0  A0A023PXB0.1    559292  728048786
A0A023PXB5  A0A023PXB5.1    559292  728048524
A0A023PXB9  A0A023PXB9.1    559292  728048769
A0A023PXC2  A0A023PXC2.1    559292  728050382
我使用了下面的脚本,认为它会非常快,因为在我看来,它涉及到对整个文件的一次读取。然而,它已经运行了好几天,还远远没有完成。有什么想法可以解释原因,并提出解决方案吗

while read line
do
    PREFIX=$(echo "$line" | cut -f2 | cut -c1-3)
    echo -e "$line" >> ../split_DB/$PREFIX.part
done < $file
读取行时
做
前缀=$(回显“$行”|切割-f2 |切割-c1-3)
echo-e“$line”>>../split_DB/$PREFIX.part
完成<$file

阅读
效率不高;它必须一次读取一个字符,以避免读取超过下一个换行符。然而,这里的一大开销来源是在每行上调用两次
cut
。我们可以通过再次使用
read
进行拆分,并使用参数展开提取第二列的第一个字符来避免这种情况

while read -r line; do
    read -r _ col2 _ <<< "$line"
    prefix=${col2:0:3}
    # If the first column has a fixed width, you can forgo the
    # previous two lines and use
    #   prefix=${line:12:3}
    printf '%s\n' "$line" >> ../split_DB/$prefix.part
done < "$file"

这可能与以下内容一样简单:

$ awk '{s=substr($2,1,3); print >> s}' file
>
将打印重定向到按给定名称追加文件。名称由第二列的前3个字母组成

while read -r line; do
    read -r _ col2 _ <<< "$line"
    prefix=${col2:0:3}
    # If the first column has a fixed width, you can forgo the
    # previous two lines and use
    #   prefix=${line:12:3}
    printf '%s\n' "$line" >> ../split_DB/$prefix.part
done < "$file"
这将比Bash处理这个文件快得多


注: 通常操作系统对同时打开的文件数量有限制。这可能是一个问题,具体取决于第二列前3个字符中可能的字符组合数量。这将影响任何解决方案,其中这些名称的文件在处理给定文件时保持打开状态,而不仅仅是awk

如果您有
000
999
,则可能打开999个文件;如果你有
AAA
ZZZ
那是17575;如果您有三个大写和小写字母数字,即238327个可能打开的文件。。。如果您的数据只有几个唯一的前缀,您可能不需要担心这一点;如果您陈述了数据的细节,这里建议的解决方案可能会有所不同

(您可以根据3个字符中允许的字母长度,通过将
'ZZZ'
转换为十进制来计算可能的组合。
('0'..'9','a'..'Z')
是32基
('0'..'9','a'..'Z','a'..'Z')
是62基,依此类推。)

如果需要(在合理范围内),您可以提高大多数Unix风格OSs的限制,或者根据需要打开和关闭新文件。将文件限制提高到238327是不切实际的。您还可以对数据进行排序,并在上一个文件停止使用时关闭它。

为什么shell脚本速度慢 速度慢的原因是,对于5亿行中的每一行,您都在强制shell创建3个进程,因此您的内核正在努力生成15亿个进程。假设它每秒可以处理10000个进程;你仍然在看15万秒,也就是2天。每秒10k进程速度快;可能比你得到的要好十倍或更多。在我2016年运行macOS High Sierra 10.13.1的15英寸MacBook Pro上,我使用了2.7 GHz Intel Core i7、16 GB 2133 MHz LPDDR3和500 GB闪存(大约150 GB免费),每秒大约有700个进程,因此该脚本名义上需要大约25天才能运行5亿条记录

加速的方法 有一些方法可以加快代码的速度。您可以使用纯shell、Awk、Python或Perl。请注意,如果使用Awk,它需要是GNU Awk,或者至少不是BSD(macOS)Awk—BSD版本只是认为它没有足够的文件描述符

我使用随机数据生成器创建了一个包含100000个随机条目的文件,这些条目与问题中的条目有些类似:

E1E583ZUT9  E1E583ZUT9.9    422255  490991884
Z0L339XJB5  Z0L339XJB5.0    852089  601069716
B3U993YMV8  B3U993YMV8.7    257653  443396409
L2F129EXJ4  L2F129EXJ4.8    942989  834728260
R4G123QWR2  R4G123QWR2.6    552467  744905170
K4Z576RKP0  K4Z576RKP0.9    947374  962234282
Z4R862HWX1  Z4R862HWX1.4    909520  2474569
L5D027SCJ5  L5D027SCJ5.4    199652  773936243
R5R272YFB5  R5R272YFB5.4    329247  582852318
G1I128BMI2  G1I128BMI2.6    359124  404495594
(使用的命令是一个即将进行重写的自制生成器。)前两列在模式
X#X###XXX#
X
表示字母,
表示数字)中具有相同的10个前导字符;唯一的区别在于后缀
。这在脚本中没有被利用;这一点都不重要。也不能保证第二列中的值是唯一的,也不能保证如果出现
.2
项,键就会出现
.1
项,等等。这些细节对per来说基本上是无关紧要的性能测量。由于文件名使用字母-数字-字母前缀,26*10*26=6760个可能的文件前缀。对于100000个随机生成的记录,这些前缀中的每一个都存在

我编写了一个脚本来计时处理数据的各种方式。有4个shell脚本变体——一个是由发布者,OP;两个是由发布者(一个作为注释),还有一个是我创建的。还有由创建的Awk脚本,chepner发布的Python3脚本的稍微修改版本,以及我编写的Perl脚本

结果 此表总结了结果(以秒为单位的运行时间或挂钟时间):

原始的shell脚本比Perl慢2.5个数量级;当有足够的可用文件描述符时,Python和Awk的性能几乎相同(如果没有足够的可用文件描述符,Python就会停止;Perl也会停止)。shell脚本的速度大约是Python或Awk的一半

7000表示需要打开的文件数(
ulimit-n7000
)。这是因为在生成的数据中有26*10*26=6760个不同的3字符起始代码。如果您有更多的模式,您将需要更多的打开文件描述符以获得保持它们全部打开的好处,或者您将需要编写一个文件描述符缓存算法,有点像GNU Awk必须使用的算法,并使用conseq大量性能损失。请注意,如果数据是按排序顺序显示的,因此每个文件的所有条目都是按顺序显示的,那么您可以调整算法,以便
╔═════════════════╦════╦═════════╦═════════╦═════════╦═════════╗
║  Script Variant ║  N ║    Mean ║ Std Dev ║     Min ║     Max ║
╠═════════════════╬════╬═════════╬═════════╬═════════╬═════════╣
║   Lucas A Shell ║ 11 ║ 426.425 ║  16.076 ║ 408.044 ║ 456.926 ║
║ Chepner 1 Shell ║ 11 ║  39.582 ║   2.002 ║  37.404 ║  43.609 ║
║         Awk 256 ║ 11 ║  38.916 ║   2.925 ║  30.874 ║  41.737 ║
║ Chepner 2 Shell ║ 11 ║  16.033 ║   1.294 ║  14.685 ║  17.981 ║
║   Leffler Shell ║ 11 ║  15.683 ║   0.809 ║  14.375 ║  16.561 ║
║     Python 7000 ║ 11 ║   7.052 ║   0.344 ║   6.358 ║   7.771 ║
║        Awk 7000 ║ 11 ║   6.403 ║   0.384 ║   5.498 ║   6.891 ║
║       Perl 7000 ║ 11 ║   1.138 ║   0.037 ║   1.073 ║   1.204 ║
╚═════════════════╩════╩═════════╩═════════╩═════════╩═════════╝
cat "$@" |
while read line
do
    PREFIX=$(echo "$line" | cut -f2 | cut -c1-3)
    echo -e "$line" >> split_DB/$PREFIX.part
done
cat "${@}" |
while read -r line; do
    read -r _ col2 _ <<< "$line"
    prefix=${col2:0:3}
    printf '%s\n' "$line" >> split_DB/$prefix.part
done
cat "${@}" |
while read -r line; do
    prefix=${line:12:3}
    printf '%s\n' "$line" >> split_DB/$prefix.part
done
sed 's/^[^ ]*  \(...\)/\1 &/' "$@" |
while read key line
do
    echo "$line" >> split_DB/$key.part
done
exec ${AWK:-awk} '{s=substr($2,1,3); print >> "split_DB/" s ".part"}' "$@"
import fileinput

output_files = {}
#with open(file) as fh:
#    for line in fh:
for line in fileinput.input():
    cols = line.strip().split()
    prefix = cols[1][0:3]
    # Cache the output file handles, so that each is opened only once.
    #outfh = output_files.setdefault(prefix, open("../split_DB/{}.part".format(prefix), "w"))
    outfh = output_files.setdefault(prefix, open("split_DB/{}.part".format(prefix), "w"))
    print(line, file=outfh)

# Close all the output files
for f in output_files.values():
    f.close()
#!/usr/bin/env perl
use strict;
use warnings;

my %fh;

while (<>)
{
    my @fields = split;
    my $pfx = substr($fields[1], 0, 3);
    open $fh{$pfx}, '>>', "split_DB/${pfx}.part" or die
        unless defined $fh{$pfx};
    my $fh = $fh{$pfx};
    print $fh $_;
}

foreach my $h (keys %fh)
{
    close $fh{$h};
}
#!/bin/bash
#
# Test suite for SO 4747-6170

set_num_files()
{
    nfiles=${1:-256}
    if [ "$(ulimit -n)" -ne "$nfiles" ]
    then if ulimit -S -n "$nfiles" 
         then : OK
         else echo "Failed to set num files to $nfiles" >&2
              ulimit -HSa >&2
              exit 1
         fi
     fi
}

test_python_7000()
{
    set_num_files 7000
    timecmd -smr python3 pyscript.py "$@"
}

test_perl_7000()
{
    set_num_files 7000
    timecmd -smr perl jlscript.pl "$@"
}

test_awk_7000()
{
    set_num_files 7000
    AWK=/opt/gnu/bin/awk timecmd -smr sh awkscript.sh "$@"
}

test_awk_256()
{
    set_num_files 256   # Default setting on macOS 10.13.1 High Sierra
    AWK=/opt/gnu/bin/awk timecmd -smr sh awkscript-256.sh "$@"
}

test_op_shell()
{
    timecmd -smr sh opscript.sh "$@"
}

test_jl_shell()
{
    timecmd -smr sh jlscript.sh "$@"
}

test_chepner_1_shell()
{
    timecmd -smr bash chepner-1.sh "$@"
}

test_chepner_2_shell()
{
    timecmd -smr bash chepner-2.sh "$@"
}

shopt -s nullglob

# Setup - the test script reads 'file'.
# The SOQ global .gitignore doesn't permit 'file' to be committed.
rm -fr split_DB
rm -f file
ln -s generated.data file

# Ensure cleanup
trap 'rm -fr split_DB; exit 1' 0 1 2 3 13 15

for function in \
    test_awk_256 \
    test_awk_7000 \
    test_chepner_1_shell \
    test_chepner_2_shell \
    test_jl_shell \
    test_op_shell \
    test_perl_7000 \
    test_python_7000
do
    mkdir split_DB
    boxecho "${function#test_}"
    time $function file
    # Basic validation - the same information should appear for all scripts
    ls split_DB | wc -l
    wc split_DB/* | tail -n 2
    rm -fr split_DB
done

trap 0
time (ulimit -n 7000; TRACEDIR=. Trace bash test-script.sh)