ryuki999
@ryuki999

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Bash】ファイルの文字列を分割し、条件を満たす文字列のみを出力する方法

解決したいこと

題名の通りです。具体的には、下記のようなファイルがあったときに>で分割し、ヘッダー部分(>America/NICD-N20214/2021)が一致するものを出力したいです。

示したのは一例で、使うときには任意のヘッダー部分に対して出力ができるようなものであると助かります。

>South Africa/NICD-N23184/2021
AGATCTGTTCTCTAAACGAA
CTTTAAAATCTGTGTGGCTG
TCACTCGGCTGCATGCTT
>America/NICD-N20213/2021
AAATCTGTGTGGCTGAGATC
TGTTCTCTAAACGAACTTTA
TCACTCGGCTGCATGCTTAG
TGTTCTCTAA
>America/NICD-N20214/2021
CTTTAAAAGATCTGTTCTCT
AAACGAAATCTGTGTGGCTG
TCACTCGGCTGCATGC
>Oceania/NICD-A21344/2021
TGTTCTCTAAACGAACTTTA
AAAGATCATCTGTGTGGCTG
TCAC
...(200万件程度)

>America/NICD-N20214/2021
CTTTAAAAGATCTGTTCTCT
AAACGAAATCTGTGTGGCTG
TCACTCGGCTGCATGC

自分で試したこと

pythonのwith openで読み込もうとしたのですが、ファイルが69GBあり、メモリ128GBのサーバで読み込むこともきませんでした。bashでやれば動くかなと思い、質問させていただきました。

0

3Answer

bash では簡単には書けないと思います。 Python でも1行ずつ読み込めばメモリは足りますよ(1行が何GBもあれば別ですが)。

filter.py
import re
import sys

input_file = sys.argv[1]
header = sys.argv[2]
header_pat = re.compile('^>(.*)')
is_target_section = False

with open(input_file) as f:
    for line in f:
        if m := header_pat.match(line):
            is_target_section = m[1] == header
        if is_target_section:
            print(line, end='')
% python3 filter.py input.txt 'America/NICD-N20214/2021'
>America/NICD-N20214/2021
CTTTAAAAGATCTGTTCTCT
AAACGAAATCTGTGTGGCTG
TCACTCGGCTGCATGC

@HalHarada さん、 それだと出力されるのはヘッダ行だけで本文が出ませんね。 -v RS=">" のことを忘れていました。つければ正しく動きますね。失礼しました。

2Like

Comments

  1. @ryuki999

    Questioner

    先日に引き続きご回答ありがとうございます。
    確かに一行ずつ読み込めました。私の場合`f.readlines()`で読みこんでいたため、動かなかったようです。
    書いて頂いたもの私のものより読みやすいので参考にさせて頂きます!

bashでもできますが、@uasi さんが書いているように1行ずつ読み込めば問題ないはずです。

#!/bin/bash

readonly INPUT=./input.txt
readonly OUTPUT=./output.txt
readonly TARGET_HEADER=America/NICD-N20214/2021

cat /dev/null >${OUTPUT}

in_section=0
while read line; do
  if [ "$(echo ${line} | cut -c 1)" = ">" ]; then
    if [ ${in_section} -eq 1 ]; then
      break
    elif [ "$(echo ${line} | cut -c 2-)" = "${TARGET_HEADER}" ]; then
      in_section=1
      echo ${line} >>${OUTPUT}
    fi
  else
    if [ ${in_section} -eq 1 ]; then
      echo ${line} >>${OUTPUT}
    fi
  fi
done <${INPUT}
1Like

Comments

  1. @ryuki999

    Questioner

    ご回答ありがとうございます!
    丁寧に書いて頂いて非常に参考になります。実は最近BashでできることはBashでやってしまおうと考えていたところで、やりたいことのScript例を貰えて非常に助かります。

    動作確認等しながら今後のためにも確認しようと思います!

69GB読めるかな? 遅いと思う!
pythonならRay並列分散処理一択と思いますが?

さて、

cat 69GB.log | awk -v RS=">" -f 69GB.awk > print.log
awk -v RS=">" -f 69GB.awk 69GB.log > print.log
69GB.awk
/^¥/America¥/NICD¥-N20214¥/2021.*$/ {
   printf(">%s", $0)
   # or print(">" $0)
   next
}
/正規表現/{
  printf(">%s", $0)
  next
}

-v RS=">" は入力レコードセパレータの指定です。
暇人x in 電車

1Like

Comments

  1. @ryuki999

    Questioner

    ご回答ありがとうございます!
    非常に簡単にできるScript例頂けてとてもたすかります。
    pythonにRay分散処理というものがあるんですね😳
    一つ一つのコマンド調べつつ参考にさせて頂きます!

Your answer might help someone💌