web-dev-qa-db-ja.com

ファイルを1行ずつ読み取り、条件が満たされた場合は次の条件まで読み取りを続行します

ファイルfoo.txtがあります

test
qwe
asd
xca
asdfarrf
sxcad
asdfa
sdca
dac
dacqa
ea
sdcv
asgfa
sdcv
ewq
qwe
a
df
fa
vas
fg
fasdf
eqw
qwe
aefawasd
adfae
asdfwe
asdf
era
fbn
tsgnjd
nuydid
hyhnydf
gby
asfga
dsg
eqw
qwe
rtargt
raga
adfgasgaa
asgarhsdtj
shyjuysy
sdgh
jstht
ewq
sdtjstsa
sdghysdmks
aadfbgns,
asfhytewat
bafg
q4t
qwe
asfdg5ab
fgshtsadtyh
wafbvg
nasfga
ghafg
ewq
qwe
afghta
asg56ang
adfg643
5aasdfgr5
asdfg
fdagh5t
ewq

qweewqの間のすべての行を別のファイルに出力したい。これは私がこれまでに持っているものです:

#!/bin/bash

filename="foo.txt"

#While loop to read line by line
while read -r line
do
    readLine=$line
    #If the line starts with ST then echo the line
    if [[ $readLine = qwe* ]] ; then
        echo "$readLine"
        read line
        readLine=$line
        if [[ $readLine = ewq* ]] ; then
            echo "$readLine"
        fi
    fi
done < "$filename"
4
gkmohit

スクリプトに変更を加える必要があります(順不同)。

  • 使用する IFS=の前にreadを付けると、先頭と末尾のスペースが削除されなくなります。
  • なので $lineはどこでも変更されません。変数readLineは必要ありません。
  • ループの途中でreadを使用しないでください!!。
  • ブール変数を使用して印刷を制御します。
  • 印刷の開始と終了を明確にします。

これらの変更により、スクリプトは次のようになります。

#!/bin/bash

filename="foo.txt"

#While loop to read line by line
while IFS= read -r line; do
    #If the line starts with ST then set var to yes.
    if [[ $line == qwe* ]] ; then
        printline="yes"
        # Just t make each line start very clear, remove in use.
        echo "----------------------->>"
    fi
    # If variable is yes, print the line.
    if [[ $printline == "yes" ]] ; then
        echo "$line"
    fi
    #If the line starts with ST then set var to no.
    if [[ $line == ewq* ]] ; then
        printline="no"
        # Just to make each line end very clear, remove in use.
        echo "----------------------------<<"
    fi
done < "$filename"

これはこのように凝縮することができます:

#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
    [[ $line == qwe* ]]       && printline="yes"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == ewq* ]]       && printline="no"
done < "$filename"

これにより、開始行と終了行が含まれます(両端を含みます)。
それらを印刷する必要がない場合は、開始テストと終了テストを入れ替えます。

#!/bin/bash
filename="foo.txt"
while IFS= read -r line; do
    [[ $line == ewq* ]]       && printline="no"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == qwe* ]]       && printline="yes"
done < "$filename"

ただし、readarrayを使用して配列要素でループするのは(bashバージョン4.0以降の場合)非常に優れています。

#!/bin/dash
filename="infile"

readarray -t lines < "$filename"


for line in "${lines[@]}"; do
    [[ $line == ewq* ]]       && printline="no"
    [[ $printline == "yes" ]] && echo "$line"
    [[ $line == qwe* ]]       && printline="yes"
done

これにより、readの使用に関する問題のほとんどが回避されます。


もちろん、推奨される(コメント;ありがとう、@ costas)sed行を使用して、処理する行のみを取得できます。

    #!/bin/bash
filename="foo.txt"

readarray -t lines <<< "$(sed -n '/^qwe.*/,/^ewq.*/p' "$filename")"

for line in "${lines[@]}"; do

     : # Do all your additional processing here, with a clean input.

done 
7
user79743

@Costasが指摘したように、このジョブに使用する正しいツールはsedです。

sed '/qwe/,/ewq/ w other.file' foo.txt

印刷する行に他の処理が必要な場合があります。それはいいです;次のようにしてください:

sed -e '/qwe/,/ewq/{w other.file' -e 'other processing;}' foo.txt

(もちろん、「その他の処理」は実際のsedコマンドではありません。)上記は、処理を行う必要がある場合に使用するパターンですafter行を印刷します。他の処理を行い、変更されたバージョンの行を出力する場合(これは可能性が高いと思われます)、以下を使用します。

sed -e '/qwe/,/ewq/{processing;w other.file' -e '}' foo.txt

}を独自の引数に入れる必要があることに注意してください。そうしないと、other.file名の一部として解釈されます。)


あなた(OP)は、あなたがライン上でしなければならない「その他の処理」を述べていません、あるいは私はより具体的かもしれません。しかし、その処理が何であれ、sedで確実に実行できます。または、扱いにくい場合は、上記のコードをほとんど変更せずにawkで実行できます。

awk '/qwe/,/ewq/ { print > "other.file" }' foo.txt

そうすれば、awkステートメントを実行する前に、printプログラミング言語のすべての機能を利用して、行を処理することができます。そしてもちろん、awkとは異なり、sed(およびbash)は設計済みであり、テキスト処理用です。

4
Wildcard