如何使用 wget 仅下载压缩 (bgz) 文件的某些列?

如何使用 wget 仅下载压缩 (bgz) 文件的某些列?

我们需要下载一个 182 GB 的压缩文件(未压缩为硅通孔)。但是,我们只需要文件的前五列,大约相当于 1 GB。

是否有一些奇特的 shell 魔法可以用来下载文件的子集?

下载整个文件只是为了删除 99% 的内容,这确实会耗尽我们服务器的存储空间。

正在下载什么: gnomad.genomes.v3.1.2.sites.chr1.vcf.bgzgnomad.broadinstitute.org

欢迎任何替代解决方案。我对也适用于非压缩文件的解决方案感兴趣。

要点是,当我只需要该文件的子集时,如何避免下载大量文件?

答案1

您可以下载该文件,对其进行过滤,然后将结果写入本地磁盘

curl https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz |
    bgzip -d |
    cut -f1-5

这仍然需要您下载完整的文件,但只有过滤后的文件才会写入磁盘。

在 Debian 上,该bgzip命令由软件包提供tabix。但如果未安装,您也可以使用gzipzcat读取bgzip压缩文件 ( curl … | zcat | cut -f1-5)。


我对该管道所需的数据存储量有一些疑问。这是一次真正的跑步。请注意,在此系统上我总共只有 2GB 可用存储空间;即使压缩,下载和保存文件所需的 182GB 也相差甚远:

# How much disk space available in my current directory?
df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       7.9G  5.6G  2.0G  75% /

# Download and filter the file, saving only the result
curl https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz |
bgzip -d |
cut -f1-5 > bigfile

# What did we get, and how much disk space remains?
ls -lh bigfile
-rw-r--r-- 1 roaima roaima 1.7G Feb  7 05:09 bigfile

df -h .
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       7.9G  7.2G  347M  96% /

有趣的是,我注意到该文件是不严格TSV(制表符分隔值)格式。在其 59,160,934 行中,942 行不包含制表符分隔的数据。

file bigfile
bigfile: Variant Call Format (VCF) version 4.2, ASCII text, with very long lines

答案2

根本不存在。

我查看了数据,正如你所说,文件很大,所以我没有下载它。

你说的第二件事是 TSV 文件:

...我们只需要二进制 TSV 格式文件的前 5 列...

如果我正确解释您的缩写,您的意思是TSVas in Tab Separated Valuefile,在这种情况下它是常规文本文件而不是二进制文件。当然,从某些角度来看,即使文本文件也是二进制文件 - 但在这种情况下我们讨论的是文本文件。

然而该文件确实被压缩了:

: curl -s -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | file -
/dev/stdin: Blocked GNU Zip Format (BGZF; gzip compatible), block length 4462

正如有人已经回答的那样,这意味着,您可以使用流技术动态解包文件,通过管道将其发送到解压缩器。bgzcat我的发行版中似乎不存在,但是zcat,它非常通用,在我的发行版中并且知道如何读取此压缩流:

: curl -s -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | zcat | head -n 1              
##fileformat=VCFv4.2

进一步调查,文件开头有注释:

curl -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | zcat | head -n 80
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0##fileformat=VCFv4.2
##hailversion=0.2.77-684f32d73643
##FILTER=<ID=AC0,Description="Allele count is zero after filtering out low-confidence genotypes (GQ < 20; DP < 10; and AB < 0.2 for het calls)">
##FILTER=<ID=AS_VQSR,Description="Failed VQSR filtering thresholds of -2.7739 for SNPs and -1.0606 for indels">
##FILTER=<ID=InbreedingCoeff,Description="InbreedingCoeff < -0.3">
##FILTER=<ID=PASS,Description="Passed all variant filters">
##INFO=<ID=AC,Number=A,Type=Integer,Description="Alternate allele count">
##INFO=<ID=AN,Number=1,Type=Integer,Description="Total number of alleles">
##INFO=<ID=AF,Number=A,Type=Float,Description="Alternate allele frequency">
##INFO=<ID=popmax,Number=A,Type=String,Description="Population with maximum allele frequency">
##INFO=<ID=faf95_popmax,Number=A,Type=Float,Description="Filtering allele frequency (using Poisson 95% CI) for the population with the maximum allele frequency">
##INFO=<ID=AC_non_v2_XX,Number=A,Type=Integer,Description="Alternate allele count for XX samples in non_v2 subset">
##INFO=<ID=AN_non_v2_XX,Number=1,Type=Integer,Description="Total number of alleles in XX samples in non_v2 subset">
##INFO=<ID=AF_non_v2_XX,Number=A,Type=Float,Description="Alternate allele frequency in XX samples in non_v2 subset">
##INFO=<ID=nhomalt_non_v2_XX,Number=A,Type=Integer,Description="Count of homozygous individuals in XX samples in non_v2 subset">
##INFO=<ID=AC_non_cancer_fin_XX,Number=A,Type=Integer,Description="Alternate allele count for XX samples of Finnish ancestry in non_cancer subset">
##INFO=<ID=AN_non_cancer_fin_XX,Number=1,Type=Integer,Description="Total number of alleles in XX samples of Finnish ancestry in non_cancer subset">
##INFO=<ID=AF_non_cancer_fin_XX,Number=A,Type=Float,Description="Alternate allele frequency in XX samples of Finnish ancestry in non_cancer subset">

最后@943行可能标题开始:

: curl -s -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | zcat | tail -n+943 | head -n 1 
#CHROM  POS ID  REF ALT QUAL    FILTER  INFO

我懒得确定第一个数据行中哪个实际上是列分隔符,但似乎“\t”确实是列分隔符:

: curl -s -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | zcat | tail -n+944 | head -n 1 
chr1    10031   .   T   C   .   AC0;AS_VQSR AC=0;AN=56642;AF=0.00000;AC_non_v2_XX=0;AN_non_v2_XX=23674;AF_non_v2_XX=0.00000;nhomalt_non_v2_XX=0;AC_non_cancer_fin_XX=0;AN_non_cancer_fin_XX=1060;AF_non_cancer_fin_XX=0.00000;nhomalt_non_cancer_fin_XX=0;AC_non_neuro_nfe=0;AN_non_neuro_nfe=24462;AF_non_neuro_nfe=0.00000;nhomalt_non_neuro_nfe=0;AC_non_neuro_afr_XY=0;AN_non_neuro_afr_XY=5226;AF_non_neuro_afr_XY=0.00000;nhomalt_non_neuro_afr_XY=0;AC_non_neuro_nfe_XY=0;AN_non_neuro_nfe_XY=9974;AF_non_neuro_nfe_XY=0.00000;nhomalt_non_neuro_nfe_XY=0;AC_controls_and_biobanks_eas_XY=0;AN_controls_and_biobanks_eas_XY=392;AF_controls_and_biobanks_eas_XY=0.00000;nhomalt_controls_and_biobanks_eas_XY=0;AC_non_neuro_sas_XX=0;AN_non_neuro_sas_XX=260;AF_non_neuro_sas_XX=0.00000;nhomalt_non_neuro_sas_XX=0;AC_non_v2=0;AN_non_v2=44696;AF_non_v2=0.00000;nhomalt_non_v2=0;AC_non_topmed_nfe_XX=0;AN_non_topmed_nfe_XX=2800;AF_non_topmed_nfe_XX=0.00000;nhomalt_non_topmed_nfe_XX=0;AC_non_v2_mid=0;AN_non_v2_mid=192;AF_non_v2_mid=0.00000;nhomalt_non_v2_mid=0;AC_non_topmed_sas=0;AN_non_topmed_sas=1114;AF_non_topmed_sas=0.00000;nhomalt_non_topmed_sas=0;AC_non_cancer_eas_XX=0;AN_non_cancer_eas_XX=746;AF_non_cancer_eas_XX=0.00000;nhomalt_non_cancer_eas_XX=0;AC_amr_XY=0;AN_amr_XY=3650;AF_amr_XY=0.00000;nhomalt_amr_XY=0;AC_non_v2_nfe_XX=0;AN_non_v2_nfe_XX=12970;AF_non_v2_nfe_XX=0.00000;nhomalt_non_v2_nfe_XX=0;AC_controls_and_biobanks_XY=0;AN_controls_and_biobanks_XY=6960;AF_controls_and_biobanks_XY=0.00000;nhomalt_controls_and_biobanks_XY=0;AC_non_neuro_asj_XY=0;AN_non_neuro_asj_XY=722;AF_non_neuro_asj_XY=0.00000;nhomalt_non_neuro_asj_XY=0;AC_oth=0;AN_oth=782;AF_oth=0.00000;nhomalt_oth=0;AC_non_topmed_mid_XY=0;AN_non_topmed_mid_XY=82;AF_non_topmed_mid_XY=0.00000;nhomalt_non_topmed_mid_XY=0;AC_non_cancer_asj_XX=0;AN_non_cancer_asj_XX=770;AF_non_cancer_asj_XX=0.00000;nhomalt_non_cancer_asj_XX=0;AC_sas_XY=0;AN_sas_XY=860;AF_sas_XY=0.00000;nhomalt_sas_XY=0;AC_non_neuro_fin=0...

这就是你的数据。

现在的问题是:

  • 因为文件是硅通孔,它基本上只是更一般的CSV格式,不幸的是,这意味着,它是面向行的格式,不是面向列,这意味着:
    • 传入的数据单位是行
    • 线路传入被分成列,反之亦然
    • 即要“仅”获取前 5 列,您仍然需要访问文件中的所有行
    • 由于行具有意外/不可预测的长度,因此数据结构不规则,因此您无法以任何有意义的方式计算所需的字节范围
  • 因为文件被bgzip压缩,所以无论如何都不可能使用字节范围
    • 由于在 HTTP 服务器中使用即时 gzip 压缩时大小计算错误,此类文件的字节范围经常被破坏
    • 我们不知道文件是否被预压缩(并放入下载目录)(并且由 httpd/nginx 动态压缩)
    • 给定大小的文件,它可能是预压缩的,因此理论上字节范围可以工作,但是您仍然需要索引将各个未压缩的行映射到相应的 BGZIP 块中,这可能是高于您级别的任务(假设您问这个问题 - 它即使在我的水平上,考虑到时间投入比率,也不会盈利(我需要几周/几个月))
    • 即使您的索引工作正常,您仍然需要下载/解析整个文件,即使文件在上游更改时索引也会中断
  • 对您来说最简单的方法是希望谷歌下载服务器的设置方式不会终止您的连接,即使它持续数小时/数天。

鉴于最后一点,你可以按照@user10489所说的去做:

  1. 开始从谷歌服务器流式传输文件并将curl其通过管道传输到zcat
  2. 用管道将输出的末尾附加zcat到一些快速组合在一起的脚本的输入:perl、python、php、lua
  3. 在脚本中解析用制表符分隔的前五列的每个传入行,并将这些列存储到本地单独的数据文件中(TSV,或者我建议sqlite3) - 您还必须忽略上述数据集开头的注释。

最终的用法看起来像这样:

: curl -s -L https://storage.googleapis.com/gcp-public-data--gnomad/release/3.1.2/vcf/genomes/gnomad.genomes.v3.1.2.sites.chr1.vcf.bgz | zcat | myscript my-outfile.tsv

你明白了。

这在几分钟/几小时内就可以完成,具体取决于您的能力,但现在您将遇到以下问题:

  • 你需要以某种方式设置你的管道,以便它可以在无人值守的情况下运行几个小时(tmux,屏幕)
  • 即使您实现行计数并将最后处理的行存储在哨兵文件中,如果您的脚本在处理过程中崩溃,您将需要从头开始重新下载,并且要么等到到达存储的行,要么,在这种情况下,重新启动时重新处理所有内容会更容易
  • 您不知道提取的“仅前 5 列”将大大小于压缩后的 128GB - 无论如何您可能会在处理过程中耗尽空间
  • 最后,我们不知道谷歌服务器是否没有一些“下载连接花费太长时间”的保护,因此它们可能会curl过早地切断与管道头的链接 - 这意味着它仍然对您有效不可能的将整个文件向下传输。

因此,对于你的情况,我会去找项目/研究负责人或管理人员并请求资源:驱动器,可能是虚拟机/节点,按预期下载整个文件并在本地进行预处理。分析预处理后的 5 列文件长度/大小,然后仅将其上传到服务器。

您不会依赖谷歌服务器不切断您的管道,并且您可以根据需要多次重新运行和调整管道。您不必担心“存储空间‘只能’容纳 5 列吗?”也有疑问。

希望有帮助。

答案3

  • 如果文件未压缩,则可以在 http 协议中下载字节范围。然而,如果没有索引,即使有可能,这也可能没有用。
  • 如果文件是通过流压缩进行压缩的,则必须下载整个文件才能解压缩其中的任何内容。许多流压缩器实际上会执行顺序压缩块,但同样,如果没有这些块的索引,它基本上是不可行的,并且如果您试图获取单个列而不是一系列大块,这是不可能的。
  • 如果您遇到的问题不是网络带宽而是磁盘空间,那么有很多选择。将内容下载到内存中,在内存中流解压缩它,将未压缩的数据发送到提取所需列并将其写出的进程。这一切都可以在单个管道中完成wget -O - | bgzcat |,并使用基于流的工具,这些工具可以在标准输入上获取输入并将所需内容写入标准输出或文件。

答案4

正如其他人所指出的,从技术上讲,使用 wget 只下载文件的某些列是不可能的。

您需要在下载数据之前对其进行过滤,这意味着数据应该在服务器端进行过滤。 HTTPS 服务器上的简单压缩文件不提供此功能。

其他替代方案:

  • 如果您可以通过不同的方式访问数据,那么可能值得研究。也许制表符分隔值 (TSV) 文件实际上是数据库转储的输出,您可以使用应用程序编程接口 (API) 直接查询数据库。也许存在一种基于表述性状态传输 (REST) API 或其他方式来定制数据的方法。您可能希望与数据提供商确认此类接口是否公开可用。如此巨大的 TSV 文件是他们内部存储数据的方式,这似乎出乎意料。

  • 您也许能够将代码引入数据,而不是将数据引入代码。显然,数据存储在多个云中。如果您在其中任何一个中拥有租户,则可以更快地访问数据,也许可以将其过滤到足够小以便您在下载到本地系统之前进行存储。

相关内容