web-dev-qa-db-ja.com

2つのキャラクター間のすべてのパターンを見つける方法は?

二重引用符のペアの間のすべてのパターンを見つけようとしています。次のような内容のファイルがあるとします。

first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".

出力として単語の下にしたい:

One
Two
Three
Four

ご覧のとおり、出力内のすべての文字列は引用符のペアの間にあります。

私が試したのはこのコマンドです:

grep -Po ' "\K[^"]*' file

"マークの最初のペアの前にスペースがある場合、上記のコマンドは正常に機能します。たとえば、入力ファイルに次が含まれている場合に機能します。

first matched is "One". the second is here "Two "
and here are in second line " Three " "Four".

複数のコマンドの組み合わせでこれを実行できることを知っています。しかし、私は1つのコマンドを探していますが、それを何度も使用することはありません。例:以下のコマンド

grep -oP '"[^"]*"' file | grep -oP '[^"]*'

1つのコマンドを使用してすべてのパターンを達成/印刷するにはどうすればよいですか?

コメントへの返信:一対の引用符内の一致したパターンの周りの空白を削除することは重要ではありませんが、コマンドがそれをサポートしている方が良いでしょう。また、私のファイルには"foo "bar" Zoo"のようなネストされた引用符が含まれています。そして、引用された単語はすべて別々の行にあり、複数行に展開されません。

前もって感謝します。

5
αғsнιη

まず、grepgrep -Po '"\K[^"]*' file"One"の両方を引用符の内側にあると見なしているため、". the second is here"アイデアは失敗します。個人的には、おそらくただ

$ grep -oP '"[^"]+"' file | tr -d '"'
One
Two 
 Three 
Four

しかし、それは2つのコマンドです。単一のコマンドでこれを行うには、次のいずれかを使用できます。

  1. Perl

    $ Perl -lne '@F=/"\s*([^"]+)\s*"/g; print for @F' file 
    One
    Two 
    Three 
    Four
    

    ここで、@F配列は正規表現のすべての一致を保持します(引用符、次の"まで可能な限り多くの"が続く)。 print for @Fは、単に「@Fの各要素を印刷する」という意味です。

  2. Perl

    $ Perl -F'"' -lne 'for($i=1;$i<=$#F;$i+=2){print $F[$i]}' file 
    One
    Two 
     Three 
    Four
    

    各マッチから先頭/末尾のスペースを削除するには、これを使用します:

    Perl -F'"' -lne 'for($i=1;$i<=$#F;$i+=2){$F[$i]=~s/^\s*|\s$//; print $F[$i]}' file 
    

    ここでは、Perlはawkのように動作しています。 -aスイッチは、入力行を-Fで指定された文字のフィールドに自動的に分割します。 "を指定したため、フィールドは次のとおりです。

    $ Perl -F'"' -lne 'for($i=0;$i<=$#F;$i++){print "Field $i: $F[$i]"}' file 
    Field 0: first matched is 
    Field 1: One
    Field 2: . the second is here
    Field 3: Two 
    Field 0: and here are in second line
    Field 1:  Three 
    Field 2: 
    Field 3: Four
    Field 4: .
    

    2つの連続したフィールドセパレーターの間のテキストを探しているため、1つおきのフィールドが必要であることがわかります。したがって、for($i=1;$i<=$#F;$i+=2){print $F[$i]}は重要なものを出力します。

  3. 同じ考えですが、awk

    $ awk -F'"' '{for(i=2;i<=NF;i+=2){print $(i)}}' file 
    One
    Two 
     Three 
    Four
    
7
terdon

重要なのは、式で引用符を使用することです。単一のgrepコマンドでそれを行うのは困難です。 Perlのワンライナーは次のとおりです。

Perl -0777 -nE 'say for /"(.*?)"/sg' file

それは入力全体を丸lurみし、マッチのキャプチャされた部分を印刷します。引用符の内側に改行があっても機能しますが、改行がある要素とない要素を分けるのは難しくなります。これを支援するために、出力レコードの区切り文字として別​​の文字を使用します。たとえば、ヌル文字

Perl -0777 -lne 'print for /"(.*?)"/sg} BEGIN {$\="\0"' <<DATA | od -c
blah "first" blah "second
quote with newline" blah "third"
DATA
0000000   f   i   r   s   t  \0   s   e   c   o   n   d  \n   q   u   o
0000020   t   e       w   i   t   h       n   e   w   l   i   n   e  \0
0000040   t   h   i   r   d  \0
0000046
2
glenn jackman

これは、以下のgrep one linerで可能になる可能性があり、バランスの取れた引用符があると仮定しました。

grep -oP '"\s*\K[^"]+?(?=\s*"(?:[^"]*"[^"]*")*[^"]*$)' file

例:

$ cat file
first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".
$ grep -oP '"\s*\K[^"]+?(?=\s*"(?:[^"]*"[^"]*")*[^"]*$)' file
One
Two
Three
Four

PCRE動詞 *SKIP)(*F)

$ grep -oP '[^"]+(?=(?:"[^"]*"[^"]*)*[^"]*$)(*SKIP)(*F)|\s*\K[^"]+(?=\b\s*)' file
One
Two
Three
Four
1
Avinash Raj

sedを使用:

sed 's/[^"]*"\([^"]\+\)"[^"]*/\1\n/g' file

[^"]*

^の先頭にある[^"]* ...は、文字クラスにリストされている文字が一致しないことを意味します(単一の"のみに一致)。 *は、"が0回以上発生する可能性があることを意味します。

"\([^"]\+\)"

\(...\)内のすべてが一致するグループです。一致するグループの外側の最初の文字は、開始一致です。文字クラス[^"]が続きます("を除くすべての文字に一致します)。量指定子\+は、入力ファイルの引用符("...")の間に少なくとも1つの文字が必要であることを意味します。次に、\)、一致するグループの終わり。この一致するグループは、\1を介してインデックスによってアクセスできます。

最後の部分[^"]*は、次の"までのすべてに一致する最初の部分と同じです。

0
αғsнιη

正規表現を必要としないPythonの代替アプローチ(厳密には堅牢ではありませんが)は、テキストファイルの各行を文字ごとに処理することです。

これがどのように機能するかの基本的な考え方:二重引用符が表示され、フラグが立てられていない場合-フラグを上げ、再度表示されてフラグが立てられている場合-フラグを下げます。フラグが立てられると、二重引用符で囲まれていることがわかります。したがって、後続の文字を格納できます。フラグが下がったら、読んだものを印刷します。

#!/usr/bin/env python
from __future__ import print_function
import sys

flag=False
quoted_string=[]
for line in sys.stdin:
    for char in line.strip():
        if char == '"':
           if flag:
               flag=False
               if quoted_string:
                  print("".join(quoted_string))
                  quoted_string=[]
           else:
               flag=True
               continue 
        if flag:
           quoted_string.append(char)

そして、テスト実行:

$ cat input.txt
first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".

$ ./get_quoted_words.py < input.txt                                                                                      
One
Two 
 Three 
Four
0