Hatena::ブログ(Diary)

日本 GNU AWK ユーザー会 0.2 このページをアンテナに追加 RSSフィード

2007-11-30

[]異様な盛り上がりの Netnews

なぜか comp.lang.awk が盛り上がっているわけですが、先週帰省のため追従してない部分を含めて紹介したいと思います。

[]マッチした特定の場所を抜き出す

f:id:Rocco:20071005231714p:image:w100:right

以下のような場合にマッチした数値だけを抜き出したいと言うものです。

$echo "test4325363test" | awk "/(.*)([0-9]+)(.*)/ {print NUMBER HERE!}"

代表的なものに match() を使うのと gensub() を使うものがあります。

以下は match() を使ったものです。

echo "test4325363test" | awk '
    match($0,/[0-9]+/) {
        print substr($0,RSTART,RLENGTH)
    }
'

そして gensub() を使ったものです。

gawk '{print gensub(/(.*)([0-9]+)(.*)/,"\\2","")}'

[]範囲内の使っていない数字を求める

f:id:Rocco:20071005231714p:image:w100:right

MIN_VALUE=800
MAX_VALUE=810

という数字の範囲で、以下のファイルから使っていない数字を求めるというものです。

#> cat all_values
100
200
800
801
803
900

以下のようなコードを書いたので、最適化してくれまいかということです。

#> nawk -v MIN_VALUE=$MIN_VALUE -v MAX_VALUE=$MAX_VALUE '{
   if(($1 >= MIN_VALUE) && ($1 <= MAX_VALUE)) {
    VAL[$1] = $1 ;
   }
  } END {
   for (i=MIN_VALUE ; i<=MAX_VALUE ; i++) {
    if (VAL[i] == "") {
     print "result: " i ;
     exit ;
    }
   }
  }' all_values
result: 802

つまり変数の使用を確認するということに落とし込めば以下のような awk の使い方にできます。

if (VAL[i] == "") {

とか

if (i in VAL) {

といったものです。

あと範囲内の計算をするライブラリとして以下のようなものもあるそうです。awk でも最大級のスクリプトかも?

全体をまとめたものが以下のものです。

 $ awk -v MIN_VALUE=$MIN_VALUE -v MAX_VALUE=$MAX_VALUE '
BEGIN{
        for(i=MIN_VALUE;i<MAX_VALUE;i++){a[i]}
}

MIN_VALUE>$1{next}
MAX_VALUE<$1{next}
{delete a[$1]}
END{
        for(f in a){
                print f;
                exit}
        }
' all_values
802
nawk -v MIN_VALUE=$MIN_VALUE -v MAX_VALUE=$MAX_VALUE '

   $1 >= MIN_VALUE && $1 <= MAX_VALUE { VAL[$1] }

   END {
     for (i = MIN_VALUE; i in VAL; i++)
         ;                 # nothing to do here, just iterate

     if (i <= MAX_VALUE)   # or simply print i without if(),
       print i             # depending on requirements if
                           # all positions already occupied
   }
'

[]テキストにある文字列ブロックを挿入する

f:id:Rocco:20071005231714p:image:w100:right

1 test1
1 test2
1 test3
2 test1
2 test2
2 test3
2 test4
3 test1
3 test2
3 test3

というファイルを以下のようにしたいというものです。

1 beg
1 test1
1 test2
1 test3
1 end
2 beg
2 test1
2 test2
2 test3
2 test4
2 end
3 beg
3 test1
3 test2
3 test3
3 end
||<<

そこで無理して作ったのか以下のようなもので、これを直して欲しいというものです。

>|awk|
awk '{
      old=new
      new=$1
     }
    $1 != old {
         if (oline) print oline
         newl=$0
         $2="beg"
         print $0
         print newl
         old=$1
         next
     }
    $1 == old { print; $2="end"; oline=$0 }
     END {print oline}
    '

回答は以下のようなものです。

$1 != prev { print (prev ? prev " end" ORS : "") $1 " beg"; prev=$1 }
1
END { print prev " end" }
$1 != prev { pre() }
$1 != prev { print $1, "beg"; prev = $1 }
1
END { pre() }

function pre() {
  if (prev) print prev, "end"

}
$1 != prev { pre(); print $1, "beg"; prev = $1 }
1
one!=(now=$1){print (NR==1?"":"end"ORS)"beg"ORS $0;one=now;next}
1
END{print "end"}

[]awk できちんとマッチしない

f:id:Rocco:20071005231714p:image:w100:right

awk '/^----/,/^____/{next}{print}'

というものが正常に動作していないというものです。具体的には、以下のようなファイルを使っています。

From - Sun Sep 18 12:55:25 2005
(...)
Some text I want to keep
Some text I want to keep

-------------------------------------------------------
SF.Net email is sponsored by:
Tame your development.....
________________________________________
this text should stay
email@address
https://address/should/stay

 From - Sun Sep 18 12:58:18 2005
(...)
2 Some text I want to keep 2
2 Some text I want to keep 2

A cool diagram which needs to stay:

回答は以下のようなものです。

$ awk '/^-+$/,/^_+$/{next}{print}'
$ awk '/^-+$/,/^_*$/{next}{print}'
{a[NR]=$0}
/^-+$/{from=NR}
/^_+$/{to=NR}
END{ if(from < to) {
         for(i=1;i<from;i++) print a[i]
         for(i=to+1;i<NR;i++) print a[i]
    }

}
awk 1 RS="-+\n[^-]**_+\n" filename
awk 1 RS="-+[^-]*_+" filename

しかし、awker という種族は Golf が好きですね。

[]tac の代用

f:id:Rocco:20071005231714p:image:w100:right

最後から先頭へ向かって処理したいので、tac の代用はないかということですが、sed で以下のようなものを使っているそうです。

sed -e '1!G;h;$!d'

以下の Juergen Kahrs の回答はどうかと思いますけど・・・。

awk '{print NR, $0}' data_file.txt | sort -n -r | awk '{$1=""; print $0}'

ついでに Ed Morton も・・・。

awk '{print NR, $0}' data_file.txt | sort -n -r | awk 'sub(/[[:digit:]]+ /,"")'

awk の中でクローズするなら以下のように書きますが、tac の代用は Perl が良いかと思います。

gawk '{a[NR]=$0}END{for(i=NR;i>0;i--){print a[i]}}' infile

[]変数のセットに awk を用いる

f:id:Rocco:20071005231714p:image:w100:right

set myvariable = `awk '{print $1}' cool`

としても動作しないのは何故かというものですが・・・結果が読めないぞ・・・shell が bash 系というオチでいいのかな?

myvariable=`awk '{print $1}' cool`

で動作したようですが、質問者が「空気読めよ。バカ」(意訳。オリジナルは "The rest of you are a bunch of narrow minded suckers.") といったのを機に盛り上がっていたのか・・・。

でもって、Ed が「その空気の読めないバカが藻前のために悩んでやってんじゃないか」(意訳。オリジナルは "You got exactly what us "narrow minded suckers" were trying to warn you about - something that seems to work but is in fact a poor "solution" to your actual problem") と微妙な大人の意見をしているのに噴いた。

[]csv のヘッダーをどう扱うか

f:id:Rocco:20071005231714p:image:w100:right

awkcsvフィールドの位置ではなく、ヘッダーで一致させることができるかというものです。

awk '( $4 ~ /ASPAC/) {do something;}' < input_file

ではなく

awk '( region_name ~ /ASPAC/) {do something;}' < input_file

のようなものです。

回答は以下のようなもんが上がっていますが、csv という部分が最後以外欠落してないかい?

awk 'NR==1 {for (i=1;i<=NF;i++) f[$i]=i; next}
$f["region_name"] ~ /ASPAC/) {do something}' input_file
awk 'NR==1
    for (i=1;i<=NF;i++) {
        if ($i == "region_name") region_name = i
        else if ($i == "some_other_field") some_other_field = i
        else if ....
        next
    }
}
BEGIN { FS=","}
NR==1 { for(f=1;f<=NF;f++) m[$f]=f; next }
$m["region_name"] ~ /ASPAC/

[]フォーマットの問題

f:id:Rocco:20071005231714p:image:w100:right

2007-10  14,807 1,604  29,600
2007-09   15,173  521  35,853
2007-08  12,799 1,236   516
2007-07   5,780  416  37,135

というファイルがあり、以下のように出力したいそうです。

2007-10    14,807 1,604    29,600
2007-09    15,173   521    35,853
2007-08    12,799 1,236       516
2007-07     5,780   416    37,135

そのためにやったことが以下のものです。

cat file| awk '{printf "%-11s%-7s%-9s%s\n",$1,$2,$3,$4}'

回答は以下のとおり。

$ awk '{printf "%7s%9s%9s%9s\n",$1,$2,$3,$4}' file
2007-10   14,807    1,604   29,600
2007-09   15,173      521   35,853
2007-08   12,799    1,236      516
2007-07    5,780      416   37,135

これを UUOC だと Ed は言っていますが、個人的には UUOC は悪くないと教えられてきたので、先頭に cat があっても悪いとあまり思っていなかったりします。先頭の cat は不要ですが、処理のオーバーヘッドMS-DOS よりも少なく済む、つまりパイプを使うことは Unix醍醐味のひとつだと師匠の一人に教わった記憶があります。既に 12 年前ですが・・・。

[]数学問題

f:id:Rocco:20071005231714p:image:w100:right

計算結果が awk だけ異なると言うものです。

$ echo "111111111 * 111111111" | bc -l
12345678987654321

$ perl -le 'print 111111111 * 111111111'
12345678987654321

$ python -c "print 111111111 * 111111111"
12345678987654321

(bash/zsh - same results)
$ echo $((111111111 * 111111111))
12345678987654321

$ awk 'BEGIN {printf "%.0f", '"111111111 * 111111111"'}'
12345678987654320

これは awk が倍精度浮動小数点で扱うことが原因です。

いろいろ試した結果が載っていて興味深いです。

on cygwin on Windows XP:

$ gawk 'BEGIN {print (111111111*111111111); exit}'
12345678987654320

on Solaris:

$ /usr/bin/awk 'BEGIN {print (111111111*111111111); exit}'
12345678987654320
$ nawk 'BEGIN {print (111111111*111111111); exit}'
12345678987654320
$ /usr/xpg4/bin/awk 'BEGIN {print (111111111*111111111); exit}'
12345678987654321
$ gawk 'BEGIN {print (111111111*111111111); exit}'
1.23457e+16
$ gawk 'BEGIN {printf "%.0f\n", (111111111*111111111); exit}'
12345678987654320

mawk, nawk もダメなので、Linux 上だと xgawk だけ?

[]ファイルパース

f:id:Rocco:20071005231714p:image:w100:right

10 abc
20 abcc
30 abcd
40 xycz
42 wxy
5 abc
20 abcd
30 abcc

というファイルがあり、最初のステップで以下のようにします。

"abc" - 10
"abcc" - 20
"abcd" - 30
...

次のステップ

"abc"  10 - 5 = "abc" 5
"abcc"  20 - 30 = "abcc" -10

のようにしたいそうです。

回答は以下のとおりです。

awk '{a[$2]=(a[$2]=="" ? $1 : a[$2] - $1)} END{for (i in a) print i,a[i] }
 { if (a[$2]=="")      {
        a[$2] = $1
   }
   else {
        a[$2] = a[$2] - $1
        print $2, a[$2]
        a[$2]=""}
}
{ if (a[$2]=="")      {
        a[$2] = $1
   }
   else {
        a[$2] = a[$2] - $1
        print $2, a[$2]
        a[$2]=""}

}

END {
        for (i in a) {
                 if(a[i]!=""){ print i,a[i] }
        }

}
$2 in a { print $2, a[$2] - $1; delete a[$2]; next }
{ a[$2] = $1 }
END { for (i in a) print i, a[i] }

[]並べ替え

f:id:Rocco:20071005231714p:image:w100:right

***record***
TAG1 A
TAG2 B
irrelevant------------------
TAG2 C
TAG3 D
TAG4 E
#
***record***
TAG1 f
TAG2 g
TAG2 h
irrelevant------------------
TAG3 i
TAG4 j
#

というファイルがあり、これを以下のようにしたいというものです。

A TAB B C TAB D TAB E
f TAB g h TAB i TAB j

回答は以下のようなものが上がっています。

/^\*\*\*record/ { tags="" }
/^#/ { print tags }
/^irrelevant/ { next }
{ sep = (t!=$1 ? (tags=="" ? "" : "\t") : " ") }
{ tags = tags sep $2 ; t = $1}

なんという無駄無駄遣い

当然、違うところに萌えているわけですが、Linux のみなさん、jfbterm + vlc 256 color くらいで誰か作ってくれないかなぁ。

といいつつ、ちょっと作ってみたい。Excel に負けるわけにはいかない。(w

中の人という立場って難しい

「第82回カーネル読書会」で話が出るかもしれない アレは私も設計を担当していた (微妙ではあるが、嘘ではない) ので、参加したい気もあるが、どうしようかな。中の人立ち位置が難しい。

アレとか伏字とかより ppencode

awk って ppencode のようなものって作れるんだっけ!? (括弧は必要だけどね)

以下は、例によって Perl

#!/usr/bin/perl -w
length q bind glob and print chr ord uc qw q sin q and print chr ord uc q exp le and print chr oct oct ord uc q lc eval and print chr oct ord uc qw q gt q