BBS-complangawk/39

トップ 差分 一覧 Farm ソース 検索 ヘルプ RSS ログイン

Quoted fields containing field separator - Jonny (2005年09月24日 17時42分20秒)

1 つのフィールドとして、ダブルクォートで挟まれたものを認識することはできるでしょうか?

here are "some words"
"here are" some "more words"
and "yet more" words

http://groups.google.co.jp/group/comp.lang.awk/browse_thread/thread/5910b7f9ba0a9656/18d992801346a84d?hl=ja#18d992801346a84d


  • 解のひとつで以下のようなものがあります。 - Janis Papanagnou (2005年09月24日 21時51分39秒)
{ for (i=1; i<=NF; i++) {
      f = $i
      if ($i ~ /^".*[^"]$/) {
         do { f = f FS $++i }
         while ($i !~ /"$/)
      }                                                                                      
      # print "Handle field nr " ++j " with value '" f "' here"     
  }                                                                                         
  # print "End of record"; j = 0
}                                                                                            
  • スペースやタブを気にしないのであれば、SUBSEP を用いる方法があります。 - Ed Morton (2005年09月24日 21時53分54秒)
awk '{while ($0!=($0=gensub(/("[^ \t]*)[ \t]/,"\\1"SUBSEP,"")));}1'

まず、スペースとタブを SUBSEP に変換します。

$ printf "here are \"some \twords   in quotes\"\n" | awk '{while (                           
$0 != ($0=gensub(/("[^ \t]*)[ \t]/,"\\1_","")));print NF,$0}'
3 here are "some????words??????in??quotes"

次に、スペースに戻します。

$ printf "here are \"some \twords   in quotes\"\n" |
awk '{while ($0!=($0=gensub(/("[^ \t]*)[ \t]/,"\\1"SUBSEP,"")));
for (i=1;i<=NF;i++){gsub(SUBSEP," ",$i);print $i}}'
here
are
"some  words   in quotes"
  • Ed のやり方を参考にして、以下のようにしました。 - Jonny (2005年09月25日 20時29分08秒)
BEGIN{FS="[ \t]"}
{ for (i=1; i<=NF; i++) {
      f = $i
      if ($i ~ /^".*[^"]$/) {
          do { f = f OFS $++i }
          while ($i !~ /"$/)
      }                                                                                      

      print "field:" ++j " value:" f
   }                                                                                         
   print "End of record"; j = 0
}                                                                                            
  • gawk on Windows で RS がなぜか効かないので、以下のようにしました。 - Jonny (2005年09月25日 20時31分35秒)
awk '{while ($0!=($0=gensub(/("[^ \t]*)[ \t]/,"\\1"SUBSEP,"")));for
(i=1;i<=NF;i++){gsub(SUBSEP," ",$i);print $i}}'

ダブルクォートは外すのが正しいのでしょうか?

  • gawk on Windows なら、クォートするのではなく、gawk -f yourprogram で指定した方が確実です。 - Janis Papanagnou (2005年09月25日 20時33分45秒)
  • Windows であれば、ダブルクォートした方が確実でしょう。 - Ed Morton (2005年09月25日 20時34分53秒)
  • 以下のような 2 つのプログラムを作りました。 - Jonny (2005年09月25日 20時35分52秒)
{
       while ($0!=($0=gensub(/([^ ]*)[ ]/,"\\1"SUBSEP,"")));
       while ($0!=($0=gensub(/([\t]*)[\t]/,"\\1""\x07","")));
}1                                                                                           

{
       while ($0!=($0=gensub(/([^ ]*)[ ]/,"\\1"SUBSEP,"")));
       while ($0!=($0=gensub(/([\t]*)[\t]/,"\\1""\x07","")));
       for (i=1;i<=NF;i++)
       {
               gsub(SUBSEP," ",$i);
               gsub("\x07","\t",$i);
               print $i
       }                                                                                     
}                                                                                            

です。

  • なんでこれ (↑) で動くかが分かりません。 - Ed Morton (2005年09月26日 00時56分01秒)

"SUBSEP" の代わりに "X" を、"\x07" の代わりに "Y" を使ってみます。

$ cat tst1.awk
{        while ($0!=($0=gensub(/([^ ]*)[ ]/,"\\1X","")));
        while ($0!=($0=gensub(/([\t]*)[\t]/,"\\1Y","")));
        for (i=1;i<=NF;i++)
        {
                print i, $i
        }                                                                                    
}                                                                                            
$ cat tst2.awk
{        while ($0!=($0=gensub(/("[^ ]*)[ ]/,"\\1X","")));
         while ($0!=($0=gensub(/("[^\t]*)[\t]/,"\\1Y","")));
         for (i=1;i<=NF;i++)
         {
                print i, $i
         }                                                                                    
}                                                                                            
$ printf "a  \"b \t c\"" | gawk -f tst1.awk
1 aXX"bXYXc"
$ printf "a  \"b \t c\"" | gawk -f tst2.awk
1 a
2 "bXYXc"
$ printf "a \t \"b \t c\"" | gawk -f tst1.awk
1 aXYX"bXYXc"
$ printf "a \t \"b \t c\"" | gawk -f tst2.awk
1 a
2 "bXYXc"
  • データは以下のようなものも含まれています。 - Jonny (2005年09月28日 02時05分17秒)
here are "some         words"
"here  are" some "more         words"
and "yet       more" words

tst2.awk スクリプトを実行すると以下のようになります。

1 here
2 are
3 "someXYwords"
1 "hereXYare"XsomeX"moreXYwords"
1 and
2 "yetXYmore"Xwords

tst1.awk スクリプトを実行すると以下のようになります。

1 hereXareX"someXYwords"
1 "hereXYare"XsomeX"moreXYwords"
1 andX"yetXYmore"Xwords

これは望んでいるものではないと思います。

  • 私の gawk の getQField 関数を使います。 - Steffen Schuler (2005年10月26日 23時24分14秒)
#!/usr/bin/gawk -f

function getQFields(record, field,    rec, i, ready, p, q)
{
  delete field
  rec =3D record
  i =3D 0
  OLDFS =3D FS
 =20
  if (FS =3D=3D " ") {
    sub(/^[ \t\n]*/, "", rec)
    sub(/[ \t\n]*$/, "", rec)
    FS =3D"[ \t\n]+"
  }
 =20
  ready =3D (rec =3D=3D "")

  while (!ready) {
    p =3D index(rec, "\"")
    match(rec, FS)
   =20
    if (RSTART =3D=3D 0) {
      field[++i] =3D rec
      ready =3D 1
    } else if (p =3D=3D 0) {
      field[++i] =3D substr(rec, 1, RSTART-1)
      rec =3D substr(rec, RSTART+RLENGTH)
    } else if (p <=3D RSTART) {
      p =3D p + index(substr(rec, p+1), "\"")
      q =3D match(substr(rec, p+1), FS)
     =20
      if (q =3D=3D 0) {
        field[++i] =3D rec
	ready =3D 1
      } else {
        field[++i] =3D substr(rec, 1, p+q-1)
	rec =3D substr(rec, p+q+RLENGTH)
      }
    } else {
      field[++i] =3D substr(rec, 1, RSTART-1)
      rec =3D substr(rec, RSTART + RLENGTH)
    }
  }

  FS =3D OLDFS
  return i
}

BEGIN {FS=3D" "; RS=3D"\n"; OFS=3D"=A7"; ORS=3D"@\n"}

{
  getQFields($0,field)
  $0 =3D ""
  for (i=3D1; i in field; ++i) {
    $i =3D field[i]
  }
  print
}
  • 以下のようにできます。 - trexx (2005年10月28日 22時04分06秒)

FS と FPAT を使えばできます。

quote-space.awk は以下のようなものです。

BEGIN{
  FS = 0
  FPAT = /"[^"]*"|[^ "]*/
}
(1){
  print "1:",$1,"2:",$2,"3:",$3
}

インプットファイルの quote-space.txt は以下のとおりです。

here are "some         words"
"here  are" some "more         words"
and "yet       more" words

gawk 3.1.3 だと全て $1 になってしまいます。

c:\research\awk\gawk>gawk -f quote-space.awk quote-space.txt
1: here are "some         words" 2:  3:
1: "here  are" some "more         words" 2:  3:
1: and "yet       more" words 2:  3:

Tawk だと正しいようです。

c:\research\awk\gawk>awkw -f quote-space.awk quote-space.txt
1: here 2: are 3: "some         words"
1: "here  are" 2: some 3: "more         words"
1: and 2: "yet       more" 3: words

なので、以下のようにします。

#!/usr/bin/gawk -f

function getQFields(record, field,    rec, i, ready, p, q)
{
  delete field
  rec = record
  i = 0
  OLDFS = FS

  if (FS == " ") {
    sub(/^[ \t\n]*/, "", rec)
    sub(/[ \t\n]*$/, "", rec)
    FS ="[ \t\n]+"
  }

  ready = (rec == "")

  while (!ready) {
    p = index(rec, "\"")
    match(rec, FS)

    if (RSTART == 0) {
      field[++i] = rec
      ready = 1
    } else if (p == 0) {
      field[++i] = substr(rec, 1, RSTART-1)
      rec = substr(rec, RSTART+RLENGTH)
    } else if (p <= RSTART) {
      p = p + index(substr(rec, p+1), "\"")
      q = match(substr(rec, p+1), FS)

      if (q == 0) {
        field[++i] = rec
ready = 1
      } else {
        field[++i] = substr(rec, 1, p+q-1)
rec = substr(rec, p+q+RLENGTH)
      }
    } else {
      field[++i] = substr(rec, 1, RSTART-1)
      rec = substr(rec, RSTART + RLENGTH)
    }
  }

  FS = OLDFS
  return i
}

BEGIN {FS=" "; RS="\n"; OFS="ァ"; ORS="@\n"}

{
  getQFields($0,field)
  $0 = ""
  for (i=1; i in field; ++i) {
    $i = field[i]
  }
  print
}
  • FPAT って何ですか? - Ed Morton (2005年10月28日 22時10分39秒)
  • FPAT はフィールドがマッチする特定のパターンの変数です。 - trexx (2005年10月28日 22時11分50秒)
  • FPAT が gawk で動作すると思ってスクリプトを書いているのですか? - Ed Morton (2005年10月28日 22時13分24秒)

{{comment}}


  • あえて最後のあたりは書いていませんが、Ed と trexx の言い争いになっていますが、trexx が議論を放棄しましたね。なんだかなぁ。 - hi_saito (2005年10月28日 22時15分23秒)