grepのさまざまな出力を取得する方法は?

grepのさまざまな出力を取得する方法は?

私はgrepが多くの答えに現れるのを見ましたが、実際にそれについて考えたことはありません。

これで、インターネット上でローカルに保存されたページのHTMLタグの内部コンテンツを取得しようとしました。 grepを使用して目的の出力を識別できますが、それを使用できるように区別することはできません。

これは私のtest.shファイルの内容です。

a=$(awk '/<div class="power-bar-text">/,/<\/div>/' 'Acid Fast.html')
b=$(echo $a | grep -PzTo [0-9\.]+)
echo $a
echo $b

結果は次の端末出力です。

test.sh: line 4: warning: command substitution: ignored null byte in input
<div class="power-bar-text"> 9 </div> <div class="power-bar-text"> 8 </div> <div class="power-bar-text"> 11.25 </div> <div class="power-bar-text"> 10 </div> <div class="power-bar-text"> 6 </div> <div class="power-bar-text"> 5 </div> <div class="power-bar-text"> 2 (1s) </div> <div class="power-bar-text"> 3 </div> <div class="power-bar-text"> 2.50 </div>
9811.2510652132.50

以下は、人がもう少し読みやすい前の繰り返しです。

$ awk '/<div class="power-bar-text">/,/<\/div>/' 'Acid Fast.html' | grep -Pzn -C1 [0-9\.]+ -
1:            <div class="power-bar-text">
                9
            </div>
            <div class="power-bar-text">
                8
            </div>
            <div class="power-bar-text">
                11.25
            </div>
            <div class="power-bar-text">
                10
            </div>
            <div class="power-bar-text">
                6
            </div>
            <div class="power-bar-text">
                5
            </div>
            <div class="power-bar-text">
                2 (1s)
            </div>
            <div class="power-bar-text">
                3
            </div>
            <div class="power-bar-text">
                2.50
            </div>

上記のコードボックスで色を設定する方法はわかりませんが、端末はデフォルトの一致フォントの色である赤色を使用して各数字とピリオドをエンコードします。

(クラス名がピリオドと一致するため、「power.bar.text」の場合は機能しない可能性があります。ピリオドが数値であるかどうかを確認するのに役立ちますか?正規表現に適用できるようです。[0-9]+\.?[0-9]*

しかし、Bashのコードを使用して最初のコードブロックに戻ると、それが提供する最終出力は9811.2510652132.50。しかし、私は次のようなものが欲しい9,8,11.25,10,6,5,2,1,3,2.50

grepコードを書くと、-d,出力でコマンドに区切り文字を設定することを選択できます。残念ながら、私が試したとき、そのアイデアはうまくいきませんでした。

私が持っているひどいアイデアの1つは、-mパラメータ出力を使用してそれを繰り返し処理し、許可されている一致の数を増やし、各出力間で新しいものを見つけることです。繰り返しますが、これはひどいようです。 (例えば、-m1は9を取得し、-m2は98を取得し、-m3は9811.25を取得すると予想し、m1の出力からm2の出力を「減算」して8を取得します。m3、は11.25を取得します。)

私は実際に試してみましたが、awkが1行になっているように見えて動作しません。そのため、一致の数に関係なく、文字列全体が最初で唯一の一致である9811.2510652132.50ため、文字列全体を出力します。

確かにより良い方法がありますか?

答え1

コメントで述べたようにgrep(構造化されていないテキスト文書から行を抽出するユーティリティ)は、通常、HTMLや構造化文書を解析するために使用したいツールではありません。理想的には、構造化クエリをドキュメントに適用し、データを抽出、変更、または処理できるツールを使用することをお勧めします。 XML文書の場合、これらのコマンドラインツールの1つは、xmlstarletそれを使用して適用できることです。XPathクエリXML文書として。

divHTML文書が正しいXHTMLであると仮定すると、class値が属性であるノードの内容を抽出し、power-bar-text両方のスペースを削除できます。

xmlstarlet select --template \
    --match '//div[@class="power-bar-text"]' \
    --value-of 'normalize-space()' -nl file.xml

これは最初にdiv関心のあるノードを一致させ、次にこれらの一致normalize-space()するノードに適用された関数の結果を抽出します。最後に、-nl各出力を改行文字で区切ります。

または短いオプションを使用してください。

xmlstarlet sel -t \
    -m '//div[@class="power-bar-text"]' \
    -v 'normalize-space()' -n file.xml

表示した文書の一部を考慮すると、次のように出力されることがあります。

9
8
11.25
10
6
5
2 (1s)
3
2.50

これは、カンマで区切られた1行に渡すことによって実行できます。

paste -d , -s -

...このように:

$ xmlstarlet sel -t -m '//div[@class="power-bar-text"]' -v 'normalize-space()' -n file.xml | paste -d , -s -
9,8,11.25,10,6,5,2 (1s),3,2.50

コマンドの各出力行にある最初のスペースの前にのみ操作を実行するには、いくつかの追加処理を追加しますxmlstarlet

$ xmlstarlet sel -t -m '//div[@class="power-bar-text"]' -v 'normalize-space()' -n file.xml | sed 's/ .*//' | paste -d , -s -
9,8,11.25,10,6,5,2,3,2.50

ファイルがXHTMLでない場合は、次を使用して利用可能なファイルに変換できます。

xmlstarlet format --recover --html file.html >file.xml

答え2

クサラナンダに追加回答、より一般的なHTMLがある場合は、XMLに変換するのではなくBeautifulSoupを使用することをお勧めします(他のXMLパーサー自体を使用しないという意味ではなく、構文解析の処理方法がユースケースにとってよりエレガントです)。 。

スクリプトを作成します。しかし、bashスクリプトではありませんが、Pythonスクリプト(最初から直接作成し、表面的にテストしました)を作成します。

#! /usr/bin/env python3

from bs4 import BeautifulSoup
import sys

if not len(sys.argv) == 2:
    print(
        f"expected one argument, got {len(sys.argv) - 1}:\n {' '.join(sys.argv)}",
        file=sys.stderr,
    )
    sys.exit(-1)

with open(sys.argv[1]) as inputfile:
    soup = BeautifulSoup(inputfile)

hits = soup.find_all("div", class_="power-bar-text")

for hit in hits:
    content = hit.contents[0].strip()
    print(f"found value {content}")

たとえば、ファイルに保存してmyparser.py実行可能にし()、chmod 755 myparser.pyHTMLファイル名を引数()として使用します/path/to/myparser.py /path/to/input.html

美しく説明が必要なコードはこんな感じです。これをシェルで実行する必要があると思われる場合は、1行に圧縮できます。 (これをしないことをお勧めします。上記の完全な読み取り可能で合理的なエラーを生成するPythonコードをbashスクリプトの複数行文字列/ HEREDOCに含めることができます):

infile="foo.html"
python3 -c "from bs4 import BeautifulSoup as BS;soup=BS(open('${infile}'));print('\n'.join(tag.contents[0].strip() for tag in soup.find_all('div', class_='power-bar-text')))"

関連情報