awk 在 .gz 文件中追加列,如使用 cat -A 所示,但更改常规输出中的列名称

awk 在 .gz 文件中追加列,如使用 cat -A 所示,但更改常规输出中的列名称

使用awk, 在此表中,我想添加一列,其中第一行是“INFO”,其余行都是“1”。

$ gunzip -c foo.gz | head
SNPID   CHR BP  Allele1 Allele2 Freq1   Effect  StdErr  P.value TotalN
rs1000033   1   226580387   t   g   0.8266  -0.0574 0.0348  0.09867 17310
rs1000050   1   162736463   t   c   0.8545  0.0654  0.0461  0.1564  10864

在哪里

gunzip -c foo.gz | head | cat -A
SNPID^ICHR^IBP^IAllele1^IAllele2^IFreq1^IEffect^IStdErr^IP.value^ITotalN^M$
rs1000033^I1^I226580387^It^Ig^I0.8266^I-0.0574^I0.0348^I0.09867^I17310^M$
rs1000050^I1^I162736463^It^Ic^I0.8545^I0.0654^I0.0461^I0.1564^I10864^M$

因为这是.gz我使用过的文件

gunzip -c foo.gz | \
  awk 'BEGIN {FS="\t"; OFS="\t"} NR == 1 {print $0 OFS "INFO"} NR > 1 {print $0 OFS "1"}' | \
  gzip > foo.V2.gz

由于某种原因,这似乎改变了我的列名,但没有改变最后的预期列。

$ gunzip -c foo.V2.gz | head   
SNPID   INFO    BP  Allele1 Allele2 Freq1   Effect  StdErr  P.value TotalN
--------^
rs1000031   1   226580387   t   g   0.8266  -0.0574 0.0348  0.09867 17310
rs1000051   1   162736463   t   c   0.8545  0.0654  0.0461  0.1564  10864

奇怪的是,当我cat -A查看该列时,该列似乎位于其应在的位置。

$ gunzip -c foo.V2.gz | head | cat -A                                      
SNPID^ICHR^IBP^IAllele1^IAllele2^IFreq1^IEffect^IStdErr^IP.value^ITotalN^M^IINFO$
----------------------------------------------------------------------------^ 
rs1000033^I1^I226580387^It^Ig^I0.8266^I-0.0574^I0.0348^I0.09867^I17310^M^I1$
rs1000050^I1^I162736463^It^Ic^I0.8545^I0.0654^I0.0461^I0.1564^I10864^M^I1$

我想知道,

  1. 这里发生了什么事?
  2. 我可以相信gunzip -c foo.V2.gz | head还是gunzip -c foo.V2.gz | head | cat -A现在?
  3. 如何使用获得我的预期输出gunzip -c foo.V2.gz | head

SNPID   CHR BP  Allele1 Allele2 Freq1   Effect  StdErr  P.value TotalN INFO
rs1000033   1   226580387   t   g   0.8266  -0.0574 0.0348  0.09867 17310 1
rs1000050   1   162736463   t   c   0.8545  0.0654  0.0461  0.1564  10864 1

笔记,我正在使用一个配置脚本来定义SNPID=1; CHR=2; ...我所在的位置,具体取决于我指定的列号对于后续分析是否正确。

答案1

正如已经提到的,你有 DOS 行结尾。看为什么我的工具输出会覆盖自身以及如何修复它有关问题的描述和可能的解决方案,例如使用任何 awk:

gunzip -c foo.gz |
  awk -v OFS='\t' '{sub(/\r$/,""); print $0, (NR>1 ? 1 : "INFO")}' |
  gzip > foo.V2.gz

您可以使用RS="\r\n"多字符 RS,它是一个 GNU awk 扩展,最近被 1 或 2 个其他 awk 变体采用。对于任何其他符合 POSIX 的 awk 设置,RS="\r\n"将被视为与您设置的设置相同,RS="\r"因为每个 POSIXRS只能是单个文字字符。在底层 C 原语\r在 awk 看到行尾之前将其剥离的系统上,它也会失败,因此RS="\r?\n"更加健壮。对于任何 awk,您都可以保留RS其默认值\n并添加{sub(\r$/,"")}为脚本的第一条语句。

我还整理了脚本中的其他一些内容,例如删除不需要或已经具有该值的代码设置变量,将 2 个打印语句更改为 1 个,按设计使用 OFS,并消除不必要的转义在管道符号之后的行尾。

答案2

您的输入似乎是某种带有 Microsoft 行结尾的 TSV 文件。

然后你可以使用mlr相反,awk它支持 2 种 tsv 并且可以指定记录分隔符。

  1. --tsv v值是tabs分隔的,但\\, \t, \r,\n可用于\在字段中嵌入 , TAB, CR 和 LF 字符。
  2. --tsvlite vtabs分隔的,并且不可能在字段值中嵌入行分隔符或制表符。

在这里,由于您只想添加一个额外的列,其标题和值均不包含任何这些字符,因此使用其中之一不会产生任何影响。

默认情况下,mlr接受 CRLF (Microsoft) 或 LF (Unix) 行分隔符并输出用 LF (Unix) 分隔的行。但您可以将--rs crlf其传递给以 CRLF (Microsoft) 分隔的输出行。

所以:

< foo.gz gunzip |
  mlr --tsvlite put '$INFO = 1' |
  gzip > foo.V2.gz

在输出上获取 Unix tsv 并在输入上接受 Microsoft 或 Unix tsv。

< foo.gz gunzip |
  mlr --tsvlite --rs crlf put '$INFO = 1' |
  gzip > foo.V2.gz

在输出上获取 Microsoft tsv 并在输入上接受 Microsoft 或 Unix tsv。

从6.0.0版本开始,mlr内置了读取gzip压缩文件的支持,所以你还可以这样做:

mlr --tsvlite put '$INFO = 1' foo.gz |
  gzip > foo.V2.gz

--gzin(如果文件路径不以 结尾,则 传递该选项.gz)。

mlr(磨坊主,通常以miller包裹形式运送)是专门处理表格数据的工具。这可能需要一些动词作为对记录执行不同操作的参数,例如sort, cut, join, filter...

put是用来使用 a 对记录进行修改的简单的领域特定语言与 的情况并不完全不同awk

在那种语言中,比如awk$用于引用记录中的字段除了这些字段是命名的1.对于$INFO = 1,我们为每条记录的INFO字段赋予一个 numeric² 值1。如果该字段尚不存在,则会添加该字段并将其显示为额外列。


1 不过也可以像使用--implicit-csv-header.

² 您可以$INFO = "1"将其设置为字符串,这会对 JSON 等输出格式产生不同的影响,但对于没有类型指示的 tsv 则不然。

答案3

作为@steeldriver注释中指出,该文件似乎是 Windows 污染了回车 (CR) 和换行 (LF) 字符,我们可以删除设置记录分隔符RS="\r\n",但ORS="\n"不会再次引入相同的问题。

gunzip -c foo.gz | \
  awk 'BEGIN {FS=OFS="\t";RS="\r\n";ORS="\n"} NR == 1 {print $0 OFS "INFO"} NR > 1 {print $0 OFS "1"}' | \
  gzip > foo.V2.gz

答案4

<tab>INFO在行分隔符之前添加第一行或<tab>1后续行,无论该行分隔符是 LF (Unix) 还是 CR 后跟 LF (Microsoft),您可以执行以下操作:

<foo.gz gunzip |
  perl -pe 's/(?=\r?$)/$. == 1 ? "\tINFO" : "\t1"/e' |
  gzip > foo.V2.gz

要就地编辑文件,理论上您应该能够-iPerlIO::gzipIO 层一起使用该选项(可能需要单独安装):

perl -Mopen=IO,gzip -i -pe 's/(?=\r?$)/$. == 1 ? "\tINFO" : "\t1"/e' foo.gz

虽然我发现我的版本失败了无法在 foo.gz 上进行就地编辑:无法创建临时名称:设备的 ioctl 不合适。这对我来说听起来像是一个错误。

相关内容