コンテキスト: GNU/Linux Ubuntu。
数千行のファイルがあり、2つの特定のキーワードの間にあるいくつかの行を削除するスクリプトが必要です。
初期ファイルは次のとおりです。
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
keyword1
との間の部分を除くすべてのファイルを維持したいと思いますkeyword2
。
考えてみて、keyword1
ファイルkeyword2
に一度だけ表示できます。これらのキーワードには、行の前後にスペースや<
その他の文字を含めることができます。>
キーワードを含む行は次のとおりです(実際にはXMLベースのファイルです)。
<keyword2>
キーワードはファイルに残ったり、添付のテキストと共に削除したり、両方の結果が満足です。
使い続ける方法がわかりませんgrep
。よくわかりませんawk
。動作しますか?
答え1
サンプルテキストをファイルに入れ、file
キーワード<>
を使用してテストしました。
このコマンドはsed
キーワードを削除します
$ < file sed '/keyword1/,/keyword2/d'
bla bla
...
bla bla
bla bla
...
bla bla
このコマンドはsed
キーワードを予約します
$ < file sed -n -e '1,/keyword1/p' -e '/keyword2/,$p'
bla bla
...
bla bla
<keyword1>
<keyword2>
bla bla
...
bla bla
答え2
Raku(以前のPerl_6)の使用
raku -ne '.put unless /keyword1/ ^fff^ /keyword2/;'
入力例:
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
出力例:
bla bla
...
bla bla
keyword1
keyword2
bla bla
...
bla bla
つまり、Rakuの-ne
コマンドラインフラグは、Rakuに自動的に印刷せずにコードを実行するように指示します。印刷は.put
最初のコマンド(改行文字「print-using-terminator」)で行われます。.
前の点はこれの略語put
で、ターゲット変数(この場合は入力行のデータを含む)を表します。$_.put
$_
このfff
ディレクティブは、2つの周囲の正規表現に基づいてオンまたはオフにするRakuのsedに似た「トリガー」演算子です。 Raku(およびPerl5)ではunless
yesですif not
。最後に、^
周辺キャレットはRakuにエンドポイントを除外するようにfff
指示します。^fff^
unless
否定であるため、エンド^fff^
ポイント除外を無効にして出力に合計を維持しますkeyword1
。出力から合計を削除する代わりにkeyword2
使用してください。fff
^fff^
keyword1
keyword2
(実際にファイルを解析するには、XML
Rakuのモジュールを使用して1行のRakuソリューションを作成できますXML
。)
https://unix.stackexchange.com/search?q=Raku+%5BXML%5D
https://github.com/raku-community-modules/XML
https://raku.org
答え3
sedの以前の提案は、「キーワード」が行の唯一の単語ではない場合、予想される結果を提供しません。場所に関係なく、任意の段落から2つの単語の間でテキストを抽出するには、特にPerlが必要です。Perlファイルを読む
たとえば、次のようなテキストがあるとします。
Sir Arthur Conan Doyle was born on May 22, 1859, in Edinburgh.
He studied medicine at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock Holmes, who he introduced in his first novel, A Study in Scarlet (1887).
This was followed in 1889 by an historical novel, Micah Clarke.
ここで重要な言葉はそれぞれ「医学」と「シャーロック・ホームズ」です。
sed の結果は、段落の最初の行と最後の行を正確に削除します。そして予想される結果は、文の前部分と埋め込まれた部分medicine
、そして後ろと埋め込まれた部分も削除しなければなりませんHolmes
。
PerlのFile Slurpを試してみましょう:
perl -0777 -i -pe 'push @a,/medicine(.*?)Holmes/s;END{print "@a"}' myparagraph.txt
出力:
at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock
答え4
作業する実際のXML文書がないので、関連文書が次のようになるとします。
<?xml version="1.0"?>
<root>
<entry>
<name>Joe</name>
<number>133</number>
</entry>
<entry>
<name>Mary</name>
<number>123</number>
</entry>
<entry>
<name>Stan</name>
<number>233</number>
</entry>
</root>
作業も少し不明ですので、方法をお見せしましょう。
entry
指定された値を持つノードの1つを削除しますname
。- 値が与えられたら、
number
ノードの値を変更します。entry
name
entry
値が与えられると、ノードの1つの内容を削除しますname
。
これは最初にかなり一般的なコマンドラインXMLパーサーを使用して行われ、次にあまり知られていないxmlstarlet
パーサーを使用して行われましたxq
(https://kislyuk.github.io/yq/)、有名なJSONパーサーのラッパーですjq
。
まずXPath構文を使用してくださいxmlstarlet
。
スタンの取り外し:
xmlstarlet ed \ --var name '"Stan"' \ --delete '//entry[name = $name]' file.xml
これはXPath文字列を取得して
"Stan"
内部変数に割り当て、それを使用して特定の値を持つノードを$name
選択します。ノードを見つけるために特定のパスの代わりに使用するため、ノードは文書のどこにでも存在できます。entry
name
entry
//entry
/root/entry
見つかったノードが削除さ
xmlstarlet
れ、結果のXML文書が標準出力に書き込まれます。生成された文書:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> </root>
Stanの番号を455に変更します。
xmlstarlet ed \ --var name '"Stan"' \ --var value '455' \ --update '//entry[name = $name]/number' \ --expr '$value' file.xml
entry
これは、目的のノードを選択するためにXPath文字列を含む内部変数を使用するという点で、最初のコマンドと似ています。$name
見つかったノードは削除しませんが、number
内部変数に指定された値で子ノードを更新します$value
。生成された文書:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry> <name>Stan</name> <number>455</number> </entry> </root>
Stanの記録を消去する:
xmlstarlet ed \ --var name '"Stan"' \ --update '//entry[name = $name]' \ --value '' file.xml
これは、その値を空の文字列に更新してノードをクリアできることを再度示しています。
生成された文書:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry/> </root>
xq
ラッパーはjq
XML文書を解析し、それをJSONにトランスコードします。次に、jq
結果のJSON文書に式を適用し、オプションでそれをXMLに変換します。
この回答の冒頭にある文書を見ると、入力がXML文書であっても、次のxq
JSON文書が内部的に使用されます。
{
"root": {
"entry": [
{
"name": "Joe",
"number": "133"
},
{
"name": "Mary",
"number": "123"
},
{
"name": "Stan",
"number": "233"
}
]
}
}
スタンの取り外し:
xq --xml-output \ --arg name 'Stan' \ 'del(.root.entry[] | select(.name == $name))' file.xml
この
del()
関数を使用してjq
指定されたパスを削除します。パスは、コマンドラインで設定した内部変数の値を.root.entry
キーとする配列から要素を選択することによって検索されます。.name
$name
Stanの番号を455に変更します。
xq --xml-output \ --arg name 'Stan' \ --arg value 455 \ '(.root.entry[] | select(.name == $name)).number |= $value' file.xml
これは前の式と似ていますが、選択したノードを削除するのではなく、内部変数を使用してキー
del()
にアクセスしてその値を更新します。.number
$value
Stanの記録を消去する:
xq --xml-output \ --arg name 'Stan' \ '(.root.entry[] | select(.name == $name)) |= null' file.xml
もう一度同様の式を使用して関心のあるノードを選択し、それを空にするように
null
更新します。empty
代わりにinを使用するとnull
ノードが削除されるため、これは上記の最初の点と同じ結果を得るもう1つの方法です。
xmlstarlet
これらと式xq
の主な違いjq
は、絶対パスにwithを使用し、xq
関心のあるノードを再帰的に検索するために//
XPath式にwithを使用することです。xmlstarlet
再帰検索を使用することもできますが、xq
これは少しトリッキーであり、ここで使用することを選択した例ではこれを必要としません。