如何使用 bash 仅拆分文本文件的某些元素?

How can I use bash to split only some elements of a text file?

我正在尝试弄清楚如何制作 ID 和基因的 .txt 文件 (myGeneFile.txt),如下所示:

Probe Set ID    Gene Symbol
1007_s_at       DDR1 /// MIR4640
1053_at RFC2
117_at  HSPA6
121_at  PAX8
1255_g_at       GUCA1A
1294_at MIR5193 /// UBA7

进入这个:

DDR1
MIR4640
RFC2
HSPA6
PAX8
GUCA1A
MIR5193
UBA

首先我尝试这样做:

cat myGeneFile.txt | tail -n +2 | awk '{split(,a,"///"); print a[1] "\t" a[2] "\t" a[3] "\t" a[4] "\t" a[5];}' > test.txt

(即,我删除了文件的顶部 (header) 行,我尝试沿分隔符 /// 拆分第二行,然后打印可能出现的任何基因)

然后,我尝试这样做:

cat myGeneFile.txt | tail -n +2 | awk '{print }' | grep -o -E '\w+' > test.txt

(字面上列出了第二列中的所有单词)

我在这两种情况下得到了相同的输出——每行中只有第一个基因的一长串(例如,MIR4640 和 UBA7 丢失了)

有什么想法吗?


编辑:感谢@CodeGnome 的帮助。我最终使用了该代码并对其进行了修改,因为我发现我的文件每一行都有 1 到 30 个不同的基因名称。所以,我用了:

awk 'NR == 1 {next}                                                                                                                                    
       {                                                                                                                                               
           sub("///", "")                                                                                                                              
           print  }                                                                                                                                  
           { for (i=3; i<=30; i++)                                                                                                                     
             if ($i) {print $i}                                                                                                                        
       }' myGeneFile.txt > test2.txt

@GlenJackson 也有一个非常有效的解决方案:

awk 'NR>1 {for (i=2; i<=NF; i++) if ($i != "///") print $i}' file

这会起作用:

tail -n+2 tmp | sed -E 's/ +/ /' | cut -d' ' -f2- | sed 's_ */// *_\n_'

这是正在发生的事情:

  1. tail -n+2 去掉 header
  2. sed -E 's/ +/ /'凝白space
  3. cut -d' ' -f2- 使用 cut 到 select 除第一个字段之外的所有字段,使用单个 space 作为分隔符
  4. sed 's_ */// *_\n_' 将所有 ///(以及任何周围的白色space)转换为换行符

您不需要初始 cat,通常最好将输入文件作为参数传递给第一个命令。如果你想把文件名放在一个容易更改的地方,这是一个更好的选择,因为它避免了额外的过程(而且我发现如果它在最后更改文件更容易):

(tail -n+2 | sed -E 's/ +/ /' | cut -d' ' -f2- | sed 's_ */// *_\n_') < tmp

在 AWK 操作中使用条件打印语句

下面通过使用 sub() 删除不需要的字符来提供所需的输出,然后使用多个打印语句创建换行符。第二个打印语句是有条件的,只有当第三个字段不为空时才会触发;这避免了在输出中创建无关的空行。

$ awk 'NR == 1 {next}
       {
           sub("///", "")
           print 
           if () {print }
       }' myGeneFile.txt
DDR1
MIR4640
RFC2
HSPA6
PAX8
GUCA1A
MIR5193
UBA7

鉴于现有的输入和修改后的要求(来自对 Morgen 回答的评论),以下内容应该可以满足您的要求(对于任意数量的基因列)。

awk 'NR > 1 {
    p=0
    for (i = 2; i <= NF; i++) {
        if ($i == "///") {
            p=1
            continue
        }
        printf "%s%s\n", p?"n":"", $i
    }
}' input.txt

我的 awk 采取:

awk 'NR>1 {for (i=2; i<=NF; i++) if ($i != "///") print $i}' file

或 sed

sed '
    1d                   # delete the header
    s/[[:blank:]]\+/ /g  # squeeze whitespace
    s/^[^ ]\+ //         # remove the 1st word
    s| ///||g            # delete all "///" words
    s/ /\n/g             # replace spaces with newlines
' file

您选择输出哪些字符串的标准并不完全清楚,但这里有另一个命令至少会产生您预期的输出:

tail -n +2 myGeneFile.txt | grep -oE '\<[A-Z][A-Z0-9]*\>'

它基本上只是 1) 跳过第一行和 2) 找到所有其他完全由大写字母或数字组成的单词(由行的非单词字符 and/or start/end 分隔),第一个是字母。