その他
    ホーム技術発信DoRuby要素検索してパラメータを取得するsed

    要素検索してパラメータを取得するsed

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    今回はsedのお話。findも最後に組み合わせます。

    はじめに

    前回の記事からパラメータ加工について調べてみたり、いじってみたり。
    ※利用しているOSによってはバックスラッシュをつける文字があります。

    sedで取り出し

    正規表現で抽出して置換したり削除したりできます。

    xml内(something.xmlとします)
    <title>DoRuby</title>
    <page>30</page>
    <pageTitle>What is DoRuby?</pageTitle>
    
    # 置換で擬似削除
    $ grep "<page>" something.xml | sed -e "s/<[^<>]+>//g"
    
    # 置換で抽出
    $ grep "<page>" something.xml | sed -e "s/^.*>\([^<>]+\)<.*$/\1/g"
    
    いずれも結果は30
    
    s/{検索する正規表現}/{置換後}/{gをつけると検索対象全てに反映}
    

    1つ目はタグ部分を削除する書き方です。 grep で該当行を抽出し、その行についてタグ部分を削除(値なしへ置換)しています。
    sed の部分を sed -e "/<[^<>]+>/d" とすると、その行自体が削除されてしまい、抽出されません。

    2つ目はパラメータが > と < に挟まれていることを利用した書き方です。ただ、これだとタグが1行に複数存在すると対応できませんので、その場合は grepで検索したパラメータを置換条件に含めます。

    something.xml内
    <title>DoRuby</title><page>30</page><pageTitle>What is DoRuby?</pageTitle>
    
    # 〆る用のタグは検索しなくても良い(.*$ に全て含まれる)
    $ grep "<page>" something.xml | sed -e 's/^.*<page>\([^<>]\+\).*$/\1/g'
    結果:30
    

    () はエスケープ必須。\1 は () で括られたマッチ部分を返します。() を複数使うと \2 以降も使用可能です。(詳しくは正規表現について調べてみてください。)

    最後を </page> にする場合はエスケープ必須なため、 <\/page> となります。他のタグも紛れてしまう可能性がありそうですが、内部の値を取るために [^<>] で除外しているため、置換後の値にタグは入ってきません。
    本記事はパラメータを取得する前提で話を進めていますが、もしタグを含む値や、 <>を含む値を取る場合は

    $ grep "<page>" something.xml | sed -e 's/^.*<page>\(.*\)<\/page>.*$/\1/g'
    

    となります。
    またパラメータの検索で、抽出する部分以外の記載( ^.* 、 .*$ )もしていますが、これは前後の不要な部分も置換しなければそのまま残ってしまうためです。もしつけない場合は以下のようになります。

    $ grep "<page>" something.xml | sed -e 's/<page>\(.*\)<\/page>/\1/g'
    
    結果
    <title>DoRuby</title>30<pageTitle>What is DoRuby?</pageTitle>
    

    <page>タグのみ削除され、残りはそのまま残ってしまいました。

    ファイル名リストを取得して全検索

    複数ファイル指定をする場合は、他のファイルにあらかじめまとめておくか、シェルの配列を用意すると言った方法があります。

    ファイルに記載しておく場合

    >settings.txt内
    something.xml
    something2.xml
    something3.xml
    
    >something.xml内
    <title>DoRuby</title><page>30</page><pageTitle>What is DoRuby?</pageTitle>
    
    >something2.xml内
    <pagea>something2-500</page>
    
    >something3.xml内
    <page>something3-100</page>
    <page>something3-200</page>
    
    # findで特定のファイル群を取得して行う場合はcatの部分のみ書き換えr
    $ cat settings.txt | xargs grep "<page>" | sed -e 's/^.*<page>\(.*\)<\/page>.*$/\1/g'
    
    結果 # something2.xmlはpageaになっているため取得しない
    30
    something3-100
    something3-200
    

    xargs で左側のコマンド処理の結果を引数として受け取ります。(つけない場合は左側のコマンド結果に対して grep します)
    find を利用したファイル名取得にもそのまま応用できます。

    # *.xmlで実行できない場合は"*.xml"
    $ find ./ -name *.xml | xargs grep "<page>" | sed -e 's/^.*<page>\(.*\)<\/page>.*$/\1/g'
    または
    $ ls *.xml | xargs grep "<page>" | sed -e 's/^.*<page>\(.*\)<\/page>.*$/\1/g'
    
    結果
    30
    something3-100
    something3-200
    

    シェルスクリプトの場合

    #!/bin/bash
    set -eu
    array=("something.xml" "something2.xml" "something3.xml")
    grep "<page>" ${array[@]} | sed -e 's/^.*<page>\(.*\)<\/page>.*$/\1/g'
    

    ubuntuだとshのシンボリックリンクの初期値が dash になっているため、変更していない場合は配列型が利用できませんので実行に注意してください。(実行権限が付与されている場合はシェルスクリプト名で実行できます。付与していない場合は bash {シェルスクリプト名} )

    おわりに

    sedで簡単な置換は作ったことがありますが、そもそも正規表現使えるよねってことで、ちょっとだけ複雑な探索と置換を紹介しました。マッチした場所以外は出てこないようなコマンドがあればいいのですが、あることに気づいていないだけかもしれませんね。(そもそもこういう使い方を想定しているのか、という話ですが)
    awkだと区切り文字の指定なのですよね…その代わりに制御しやすいのではという印象を受けました。


    参考:
    grepとawkで値検索

    モバイルバージョンを終了