【问题标题】:How can I use bash to split only some elements of a text file?如何使用 bash 仅拆分文本文件的某些元素?
【发布时间】:2015-01-28 19:26:22
【问题描述】:

我正在尝试弄清楚如何制作一个包含 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($2,a,"///"); print a[1] "\t" a[2] "\t" a[3] "\t" a[4] "\t" a[5];}' > test.txt

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

然后,我尝试这样做:

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

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

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

有什么想法吗?


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

awk 'NR == 1 {next}                                                                                                                                    
       {                                                                                                                                               
           sub("///", "")                                                                                                                              
           print $2 }                                                                                                                                  
           { 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

【问题讨论】:

  • 您假设awk 理解您对“列”的定义。它没有。默认情况下,awk 按空格分割字段,因此1007_s_at DDR1 /// MIR4640 行是四个 字段而不是两个。 1007_s_atDDR1///MIR4640。如果您的输入是制表符分隔的,那么告诉awk 只在制表符上拆分,您的第一次尝试会更好。
  • @EtanReisner 您可以在操作中使用 sub() ,然后 awk 将只看到 2-3 个字段。 OP 的部分问题是他的数据具有可变数量的字段。我在下面的my answer 中解决了这个问题。
  • @CodeGnome 可变数量的字段不是问题,我知道 awk 可以做什么。您的解决方案是特定于字段计数的,不需要如此。
  • 太棒了!很高兴您在@GlenJackman 的回答和我的回答中都找到了一些有用的想法。通常有不止一种方法可以解决大多数问题,看到多种方法并从其他方法中综合出您自己的解决方案会非常有帮助。祝你的基因研究好运!

标签: bash unix split


【解决方案1】:

我的 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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 感谢您的帮助!这对我来说就像一个魅力。
    • @KM 乐于助人。不要忘记为所有对您有帮助的答案投票,并在最能帮助您解决问题的答案旁边打勾。
    • 会的!对于未来的谷歌员工,我最终使用了这段代码并对其进行了修改,因为我发现我的文件有 1 到 30 个不同的基因名称。所以:
    【解决方案3】:

    这将起作用:

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

    这是发生了什么:

    1. tail -n+2 去掉标题
    2. sed -E 's/ +/ /'压缩空格
    3. cut -d' ' -f2- 使用 cut 选择除第一个以外的所有字段,使用单个空格作为分隔符
    4. sed 's_ */// *_\n_' 将所有 ///(以及任何周围的空格)转换为换行符

    您不需要初始的cat,通常最好将输入文件作为参数传递给第一个命令。如果您希望将文件名放在易于更改的位置,这是一个更好的选择,因为它避免了额外的过程(我发现如果文件在末尾更容易更改):

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

    【讨论】:

    • 非常感谢!这与我想要的非常接近,但它在之前在 /// 分隔符之后的任何基因前面添加了一个“n”。因此,例如,它将在新行上打印 nMIR4640 和 nUBA7。我还发现 /// 之前的基因名称消失了,例如DDR1 不再出现。
    • @KM 如果你想要领先的n,你的示例输出应该包括它。
    • 澄清:我不想要前导 n - 我想打印 'MIR4640' 和 'UBA7',但是当我尝试代码时,它会打印 'nMIR4640' 和 'nUBA7' 。我无法弄清楚如何在没有 n 的情况下打印换行符。非常奇怪
    • @KM 我没有在我的机器的输出中看到领先的n。您可能需要检查以确保在复制/粘贴期间没有丢失任何反斜杠。
    【解决方案4】:

    鉴于现有的输入和修改后的要求(来自对 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
    

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-05
        • 1970-01-01
        相关资源
        最近更新 更多