Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2013-12-09

なるべく書かないawkの使い方

awkという、古くからのスクリプト言語がある。(1977年生まれ。読み方は「オーク」である。エイ・ダブリュ・ケイではない)man awkをPDFに変換してみると、たったの3ページ強しかない。

$ man -t awk|pstopdf -i -o ~/Downloads/awk.pdf

とてもシンプルな言語仕様ではあるが、awkには必要十分な表現力がある。特にテキストを処理する場面においては、最小限のシンプルな記述で、気の利いた処理を素早くこなす。無駄のないawkワンライナーを見ると、ある種の感動を覚える。awk以降に生まれたスクリプト言語は、少なからずawkの影響を受けていると思われる。

awkを知ることで、間違いなく幸福度は上がると思う。いつかきっと「知ってて良かった」と思える時が来るはず。もっともっと、awkを知りたくなってきた。

基本動作

awkの基本動作は、とってもシンプルである。

  1. テキストを1行読み込んで、
  2. 読み込んだ行を空白区切りのデータと解釈して、
  3. 何らかの処理を行う。
  • 以上の動作を、行末まで繰り返す。
  • 1、2の動作は、awkが自動的に処理する。
  • 3の部分を、自分が書くことになる。

  • 例えば以下のようなテキストファイルを作って...
$ cat <<EOS > abc.txt
> a b c
> d e f
> g h i
> EOS

$ cat abc.txt
a b c
d e f
g h i

  • awkスクリプトにパイプで渡してみると...
$ cat abc.txt | awk '{print $0}'
a b c
d e f
g h i

つまり...

  • a b cを読み込んで、print $0して、
  • d e fを読み込んで、print $0して、
  • g h iを読み込んで、print $0しているのだ。
  • awkスクリプト全体はシングルクォートで囲って、何らかの処理はさらに{ }で囲っておく必要がある。

  • スペース区切りを実感するために、真ん中の列(2列目)だけ出力してみる。
$ cat abc.txt | awk '{print $2}'
b
e
h

つまり...

  • $0、$2は、awkが用意した変数である。
    • $1には、1列目のデータが入っている。
    • $2には、2列目のデータが入っている。
    • $nには、n列目のデータが入っている。
    • そして、$0にはすべての列、つまり1行全体のデータが入っているのだ。

  • ところで、print $1 $2 $3と、print $1,$2,$3は、違う。
$ cat abc.txt | awk '{print $1 $2 $3}'
abc
def
ghi

$ cat abc.txt | awk '{print $1,$2,$3}'
a b c
d e f
g h i
  • awkにおいて、print $0 = print $1,$2,$3 である。
  • awkのスペースには、文字列を連結する役目がある。

条件と処理

  • 条件を付加することによって、その条件が満たされた時だけ、何らかの処理を行うようになる。
    • 条件は、何らかの処理の直前に書く。
  • "e"を含む行の時だけ、print。
$ cat abc.txt | awk '/e/{print $0}'
d e f

  • 1列目が"a"の時だけ、print。
$ cat abc.txt | awk '$1=="a"{print $0}'
a b c

  • 3行目の時だけ、print。
$ cat abc.txt | awk 'NR==3{print $0}'
g h i
  • NRは、awkが用意した変数である。
    • 現在処理している行番号が代入されている。

  • awkでは、この条件と何らかの処理の組み合わせによって、あらゆることを処理していく。
    • 条件 = パターン と呼ばれている。
    • 何らかの処理 = アクション と呼ばれている。
  • 言い換えれば、 パターンとアクションの組み合わせによって、あらゆることを処理していく。
  • そして、'パターン{アクション}'の定義は、複数書くことができる。
  • つまり、awkスクリプトとは 'パターン{アクション}' の集合なのだ。

  • 例えば、aで始まる行と、gで始まる行を取り出すなら...
$ cat abc.txt | awk '
> /^a/{print $0}
> /^g/{print $0}
> '
a b c
g h i
  • 複数行に分けても、1行にまとめても、どちらでもOK。
$ cat abc.txt | awk '/^a/{print $0}/^g/{print $0}'
a b c
g h i

grep+cutとの違い

  • ここまでの処理は、別にawkを使わなくても、grepとcutを組み合わせれば実現できる。
# 2列目を取り出す
$ cat abc.txt | cut -d' ' -f2
b
e
h

# aかgで始まる行を取り出す
$ cat abc.txt | grep -E '^a|^g'
a b c
g h i

# aかgで始まる行の2列目を取り出す
$ cat abc.txt | grep -E '^a|^g' | cut -d' ' -f2
b
h
  • ところが、その性能は微妙に違っている。
  • その違いが、使い勝手に大きく影響する。
  • 例えばlsの出力を加工しようと思った場合...
$ ls -l
total 16
drwx------+  31 bebe  staff   1054 12  4 16:21 Desktop
drwx------+ 450 bebe  staff  15300 12  5 10:37 Documents
drwx------+ 332 bebe  staff  11288 12  5 14:24 Downloads
drwx------@  72 bebe  staff   2448 10 30 17:44 Library
drwx------+   9 bebe  staff    306 10 30 06:42 Movies
drwx------+   9 bebe  staff    306  2 28  2013 Music
drwx------+  14 bebe  staff    476 11  5 15:51 Pictures
drwxr-xr-x+  12 bebe  staff    408 12  2 16:14 Public
drwxr-xr-x+   5 bebe  staff    170 12  2 16:14 Sites
...中略...
  • cutで5列目のバイト数の列だけ取り出すのは、ちょっと苦労する。
  • 区切り記号をスペースに変更して、5列目を指定しただけでは、以下のような意図しない結果になってしまう...。
$ ls -l | cut -d' ' -f5


staff
staff

bebe
bebe


bebe
...中略...
  • この原因は、cutが指定された区切り記号であるスペースを使って、1文字ずつ厳格に区切ろうとした結果である。
  • スペースが連続する部分でも、それぞれのスペースを区切り記号と解釈して、無駄な区切りを増やしているのだ。
  • よって、もしcutでバイト数の列だけ取り出すなら、連続するスペースを1つにまとめる処理が必要になる。
    • sed 's/ \{1,\}/ /g' によって、連続するスペースを1つにまとめている。
$ ls -l | sed 's/ \{1,\}/ /g' | cut -d' ' -f5

1054
15300
11288
2448
306
306
476
408
170
...中略...

  • 一方、awkなら何の苦労もなく、バイト数の列を取り出せる。
$ ls -l | awk '{print $5}'

1054
15300
11288
2448
306
306
476
408
170
...中略...
  • awkではデフォルトの区切り記号が、1文字以上の連続する、スペースかタブになっている模様。
    • ちなみに、cutのデフォルトの区切り記号は、1文字のタブである。
    • そして残念なことに、cutの区切り文字には、1文字以上という正規表現が設定できないのだ...。

awkには、人の感覚に合った区切り文字がデフォルトで設定されている。だから使いやすい!

  • コマンド出力されたテキストデータを加工するなら、断然awkを使いたくなる。

集計の技

そして、awkの処理は列を取り出すだけに留まらない。

  • 取り出した列は、素早く集計できる。
    • ENDは、すべての行を読み込み完了後、1回だけ実行される特殊なパターン(=条件)である。
    • ちなみに、最初の行を読み込む前に、1回だけ実行されるBEGINというパターン(=条件)もある。
$ ls -l | awk '{sum+=$5; print $5} END{print "--------\n" sum}'

1054
15300
11288
2448
306
306
476
408
170
136
136
18
343
136
136
340
--------
33001

  • 行列の集計だって簡単にできる。
    • -vオプションは、スクリプト実行前に変数に値を設定する。
    • OFSはawkが用意した組込み変数で、awkが出力する時に、列と列を繋ぐ区切り文字を保存する。
$ cat <<EOS > 123.txt
> 1 2 3
> 4 5 6
> 7 8 9
> EOS

$ cat 123.txt
1 2 3
4 5 6
7 8 9

$ cat 123.txt | awk -v OFS="\t" '{rt=$1+$2+$3; c1+=$1; c2+=$2; c3+=$3; c4+=rt; print $1,$2,$3,"",rt} END{print ""; print c1,c2,c3,"",c4}'
1	2	3		6
4	5	6		15
7	8	9		24

12	15	18		45

  • Fizz Buzz問題もワンライナーで。
    • seqコマンドは、指定した数値までの数列を出力する。
$ seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"} {print $1 $3 $5}'
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...中略...

これはもう、表計算アプリと同じ感覚である。

  • テキスト全体が区切り文字によって区切られた行列データと解釈され、自在に集計できる。
  • 単なる行列データではなく、指定した条件にマッチしたデータのみ抽出して、集計できる。
  • はるか昔の1977年の頃から、まだ表計算アプリなど存在しない時代から、awkはデータベースの検索と集計をシンプルな仕組みで実現していたのだ。

awkとは、表計算コマンドなのかもしれない。

省略の技

awkの心地よさは、省略可能な表現力だと思う。

  • 例えば、ここまでprint $0と書いてきたが、多くの場合、$0は省略できる。
    • よって、print $0は、printのみでOK。
$ #cat abc.txt | awk '{print $0}'
$ cat abc.txt | awk '{print}'
a b c
d e f
g h i

  • また、パターンのみでアクションが存在しない場合、パターンがマッチした時、awkは{print}を処理してくれる。
    • よって、検索はパターンの指定のみでOK。
$ #cat abc.txt | awk '/e/{print $0}'
$ #cat abc.txt | awk '/e/{print}'
$ cat abc.txt | awk '/e/'
d e f

  • さらに、パターンがマッチするとは、条件式の戻り値が0でない状態である。
    • 条件式が成立すると、1が返る。
    • 条件式が成立しないと、0が返る。
  • この仕組みを知ると、パターンとは特に条件式である必要はないことに気付く。
    • つまり、何らかの式が0を返せば、続くアクションは実行されず、
    • 何らかの式が0以外を返せば、続くアクションが実行されるのだ。
  • 式とは、数値のみでも式である。
    • よって、すべての行を出力したいのであれば、1と書くだけでもOK。
      • 2でも3でも、0以外なら何でもOKなのだけど、一般的によく1が使われるようだ。
    • 1に続くアクションがないので{print}が実行されるのだ。
$ #cat abc.txt | awk '{print $0}'
$ #cat abc.txt | awk '{print}'
$ cat abc.txt | awk '1'
a b c
d e f
g h i

  • awk '1'だけのスクリプトでは、その恩恵をほとんど感じないが、
  • awkでは各列のデータを加工して、最後にすべてをprintしたくなることがよくある。
  • その場合、'{print}'の省略記法として、'1'が使われることが多い。
    • 例えば、Fizz Buzz問題の最後の{print $1 $3 $5}は、1に置換えても、許せる範囲の書式で出力されると思う。
$ #seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"} {print $1 $3 $5}'
$ seq 100 | awk '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"}1'
1
2
  Fizz
4
    Buzz
  Fizz
7
8
  Fizz
    Buzz
11
  Fizz
13
14
  Fizz  Buzz
...中略...
    • OFS=""を設定しておけば、書式も乱れないはず。
$ seq 100 | awk -v OFS="" '{n=$1} n%3==0{$1="";$3="Fizz"} n%5==0{$1="";$5="Buzz"}1'

  • 文字列を置換えるgsubという関数も、すべての行で置き換えが発生するなら、printは不要になる。
    • 小文字のaを大文字のAにするには、特定の行しか対象にならないので、printが必要である。
    • gsub関数の最後の引数は、置き換え対象の文字列を指定するのだが、省略すると行全体($0)に対して置き換え処理が行われる。
$ #cat abc.txt | awk '{gsub(/a/,"A",$0);print}'
$ cat abc.txt | awk '{gsub(/a/,"A");print}'
A b c
d e f
g h i
    • もし行頭に#を付加する場合なら、すべての行が対象になるので、アクションではなくパターンに書くことで{print}を省略できる。
    • 置き換えが発生した場合、gsub関数の戻り値には置き換えした回数が返るので。(必ず置き換えが発生すれば、常に1以上が返る)
$ #cat abc.txt | awk '{gsub(/^/,"#");print}'
$ cat abc.txt | awk 'gsub(/^/,"#")'
#a b c
#d e f
#g h i

省略しても、awkが良きに計らい解釈してくれる所が嬉しい。

省略の落とし穴

但し、省略のルールによって、awkに予想外の解釈をされることもある。

$ echo apple orange melon | awk '{if($0 ~ /apple/ || $0 ~ /orange/)print "Hit!"}'
Hit!
  • 上記と下記($0 ~ を省略)は、意味的にはまったく同じである。
$ echo apple orange melon | awk '{if(/apple/ || /orange/)print "Hit!"}'
Hit!

  • その省略のルールによって、予想外の結果が導かれてしまう...。
$ echo apple orange melon | awk '{if(/apple/ ~ $1)print "Hit!";else print "Unmatched"}'
Unmatched
  • $1は"apple"なはずなのに、なぜUnmatchedになってしまうのか?
  • awkは、条件の先頭に/正規表現/を見つけると、($0 ~ /正規表現/) と解釈してしまうのだ。
  • よって、上記スクリプトは以下のように解釈されていると思われる。
$ echo apple orange melon | awk '{if(($0 ~ /apple/) ~ $1)print "Hit!";else print "Unmatched"}'
    • ($0 ~ /apple/)の計算結果は1となり、
    • その後(1 ~ $1)が評価されているのだ。

  • ならば、appleの前に1を追記すれば、Hit!するはず。思ったとおり!
$ echo 1 apple orange melon | awk '{if(/apple/ ~ $1)print "Hit!";else print "Unmatched"}'
Hit!

気の利いたawkのお節介が誘発する唯一のマイナス面と思われる。気をつけよう!

  • 比較対象を明示する時は、正規表現は後に書く必要があるのだ。
  • うっかり先に書いてしまうと、相当悩み続けることになりそう。

文字と数値と変数

awkは必要に応じて、文字や数値を良きに計らい自動変換してくれる。

  • 文字の足し算も、ちゃんと計算してくれる。
$ echo | awk '{print 1 + 2}'
3

$ echo | awk '{print "1" + "2"}'
3

  • 数値に変換できない文字は0と解釈される。
$ echo | awk '{print "a" + "b"}'
0

  • 数値同士でも、文字列として結合できる。
$ echo | awk '{print 1 2}'
12

  • パターンの戻り値は、0または""がfalse。
$ echo | awk '0{print "Trueです。"}'

$ echo | awk '""{print "Trueです。"}'
  • 上記以外は、すべてtrue。"0"もtrue。
$ echo | awk '"0"{print "Trueです。"}'
Trueです。

  • 変数には最初から、""が代入されている。(と考えることにしている)
  • よって、変数の初期化なしで、いきなり数値計算できるのだ!
    • 1から10まで足し算してみた。
$ seq 10 | awk '{sum+=$1; print sum}'
1
3
6
10
15
21
28
36
45
55

プログラマーが面倒だと思うことは、ことごとくawk側でうまく取り計らってくれるのだ!

  • 文字と数値を区別することなく、ゆるい気分でコードを書けて嬉しい。
  • nil・NULLの判定が不要になって嬉しい。

言語仕様

ここまでawkの概要が分かると、詳しい言語仕様を知りたくなる。よく使いそうな部分を抜粋してみた。

組み込み変数

たった7つの変数($数値、NF、NR、FS、RS、OFS、ORS)を覚えておくだけで、かなり便利に使える。

  • 今まで行列データと書いてきたが、awkでは、行をRecord(レコード)と呼んでいる。
  • 行の中で区切られた1列を、Field(フィールド)と呼んでいる。
  • RecordとFieldの意味が分かると、awkの組み込み変数を覚えやすくなるのだ。

$1

FS

$2

FS

$3

FS
(NF=フィールド数)
$NF

RS
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード1(処理中ならNR=1)
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード2(処理中ならNR=2)
フィールド1 フィールド2 フィールド3 フィールド末尾\n←レコード3(処理中ならNR=3)
  • NF=処理しているレコードのフィールド数(Number of Fields)
  • NR=処理しているレコードの先頭からの番号(Number of Record)
  • FS=入力時にフィールドを区切る文字(Field Separator)
  • RS=入力時にレコードを区切る文字(Record Separator)
  • OFS=出力時にフィールドを繋げる文字(Output Field Separator)
  • ORS=出力時にレコードを繋げる文字(Output Record Separator)
    • OFSを設定することで、スペース区切りから、カンマ区切りに変換してみる。
    • $1=$1は、OFSの変更を$0に反映させるため、何らかのフィールド操作が必要なのだ。
$ echo A B C | awk '{OFS=",";$1=$1;print}'
A,B,C
    • OFSを変更しても、フィールド操作が何もないと$0が更新されず、以前のままになる。
$ echo A B C | awk '{OFS=",";print}'
A B C

$で始まるフィールド変数は、配列のように振る舞う!

  • $数値は、フィールドの内容が代入された変数である。
    • $0には、行全体の内容が代入される。
    • $1には、1番目のフィールドの内容が代入される。
    • $2には、2番目のフィールドの内容が代入される。
    • $NFには、最後のフィールドの内容が代入される。
    • $NF以降は、未定義なので""が代入される。
  • $NFの例からも分かるように...
    • $変数は、変数の値が示すフィールドの内容が代入される。
 $ echo A B C D E F G | awk '{num=5; print "$num =", "$" num, "=", $num}'
 $num = $5 = E
    • $(式)は、計算結果が示すフィールドの内容が代入される。
 $ echo A B C D E F G | awk '{print $(NF-1)}'
 F
    • $(式)は、基本的に括弧で囲う必要がある。
    • 括弧なしでは、$NF=$7="G"と解釈され、その後"G" - 1が計算される。
 $ echo A B C D E F G | awk '{print $NF-1}'
 -1
    • "G"は数値に変換できないので0と評価され、0 - 1 = -1 となるのだ。
制御文
if (条件) 真の処理 else 偽の処理       条件によって、真偽どちらかの処理を実行する。(else以降は省略可能)
$ echo A B C D E F G | awk '{if(length > 8)print $1,$2 "..." $NF; else print}'
A B...G

$ echo A B C D | awk '{if(length > 8)print $1,$2 "..." $NF; else print}'
A B C D
  • ifやelseの後、複数処理を実行する時は{ }で囲う。
$ echo A B C D E F G | awk '{if(length > 8){s=$1 FS $2 "..." $NF; print s;} else print}'
A B...G

while (継続条件) ループ処理         継続条件が真の状態なら、ループ処理を続ける。
do ループ処理 while (継続条件)       継続条件が真の状態なら、ループ処理を続ける。(継続条件が偽でも、最低1回はループ処理を実行する)
for (初期設定; 継続条件; 増減設定) ループ処理 継続条件が真の状態なら、ループ処理を続ける。(for = 初期設定と増減設定が付属したwhile)
for (変数 in 配列) ループ処理        配列のキーを順に変数に代入しながら、ループ処理を繰り返す。
break                   for、while、doのループ処理を抜け出す。
continue                 for、while、doの次のループ処理へ移行する。
$ echo A B C D E F G | awk '{for(i=2;i<NF;i++)printf "%s ",$i; print ""}'</span>
B C D E F 
  • for、while、doの後、複数処理を実行する時は{ }で囲う。
$ echo A B C D E F G | awk '{for(i=2;i<NF;i++){s=s $i; print s;}}'</span>
B
BC
BCD
BCDE
BCDEF

getline                   awkは1行ずつ自動的に読み込んでくれるが、getlineは1行読み込みを自分で制御する時に使う。
next                    awkの処理を次の行に進める。
nextfile                   awkの処理を次のファイルに進める。
exit [ 式 ]                  awkの処理を中断して、式の結果をステータスコードとして返す。(ENDパターンは実行される)

print                    指定された内容を、出力する。(改行あり)
printf                    指定された内容を、指定されたフォーマットで、出力する。(C言語のprintfフォーマットに準ずる)
  • 3桁区切り
    • \047=シングルクォートの8進数表記
    • シングルクォート内に"%'d\n"と書くため、エスケープする必要があった。
$ echo 1234567| awk '{printf "%\047d\n", $0}'
1,234,567
  • 通常print
$ echo A B C D| awk '{printf "%s\n", $0}'
A B C D
  • 10桁幅の右寄せ
$ echo A B C D| awk '{printf "|%10s|\n", $0}'
|   A B C D|
  • 10桁幅の左寄せ
$ echo A B C D| awk '{printf "|%-10s|\n", $0}'
|A B C D   |

delete array[index]             配列の指定されたindexを削除する。([index]指定なしだと、配列全体を削除する)
$ echo | awk '{a[1]=100; print a["1"]}'
100

$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; for(i in a)print a[i]}'
nya-
one!
  • 未定義の配列内容は、""が返される。
$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; delete a["dog"]; print a["cat"], a["dog"]}'
nya- 

$ echo | awk '{a["dog"]="one!"; a["cat"]="nya-"; delete a; print a["cat"], a["dog"]}'
 
演算子
  • 優先順位が高い順の演算子リスト
優先順位演算子意味
高い( )グルーピング
$フィールドの参照
++ --インクリメント、デクリメント
^ **べき乗(どちらも同じべき乗)
+ - !プラス、マイナス、論理否定
∗ / %乗算、除算、剰余
+ -加算と減算
半角スペース文字列連接
< <= > >= != ==関係演算子
~ !~正規表現のマッチ、非マッチ
in配列への個別アクセス
&&論理的なAND
||論理的なOR
? : 3項演算子(条件 ? 真の処理 : 偽の処理)
低い= += -= *= /= %= ^=代入(例:n+=2は、n=n+2と同等)
関数
  • awkが用意する関数一覧
数値関数atan2 cos exp int log rand sin sqrt srand
文字関数gsub index length match split sprintf sub substr tolower toupper
その他の関数close fflush system
  • 上記の中で、よく使いそうな関数
関数機能引数を省略したときの意味
sub(正規表現, 変換語句, 処理対象の変数)正規表現にマッチした部分を、変換語句に、1回だけ置き換える。sub(/mac/, "Mac") == sub(/mac/, "Mac", $0)
gsub(正規表現, 変換語句, 処理対象の変数)正規表現にマッチした部分を、変換語句に、すべて置き換える。gsub(/mac/, "macintosh") == gsub(/mac/, "macintosh", $0)
index(文字列, 検索語)文字列に含まれる検索語の位置を返す。
length(文字列)文字列の長さを返す。length == length($0)
split(文字列, 配列変数, 区切り文字)文字列を区切り文字で分解して、配列変数に代入する。split("A B C", a) == split("A B C", a, FS)
sprintf("フォーマット", 値, 値...)printfの結果を文字列として返す。
tolower(文字列)小文字に変換する。tolower == tolower($0)
toupper(文字列)大文字に変換する。toupper == toupper($0)
  • 大文字・小文字を区別しないで検索したい時、awkはtolowerで全体を小文字に変換してから検索するしかない...。
    • あるいは、toupperで全体を大文字に変換してから検索するのだ。
$ echo Apple | awk '/apple/'

$ echo Apple | awk 'tolower ~ /apple/'
Apple
  • 一方、grepなら-iオプションでOK。
$ echo Apple | grep 'apple'

$ echo Apple | grep -i 'apple'
Apple

参考ページ

さらに詳しいawkの仕様については、以下のページがたいへん参考になりました。(素晴らしい情報に、感謝です!)


「awkは書かねぇ、たった一行」って何?

第37話「三方一両損」(落語の小噺の落ち)

「お〜かぁ〜(大岡)食わねぇ、たった一膳(越前)」。

第37話「三方一両損」
  • つまり「多くは食わねぇ、たった一膳」。
  • 転じて「awkは書かねぇ、たった一行」。
  • つまり「オーク(多く)は書かねぇ、たった一行」。

しまった...。タイトルを「多くは書かないawkの使い方」にしておけば良かった。

通りすがり通りすがり 2013/12/09 19:40 誤 touppe(文字列)
正 toupper(文字列)
...ね?
#いや、最後に"落ちがあった"ということか?

NanasiNanasi 2013/12/09 22:48 一行野郎はperlとかrubyでもできるし、perlとかrubyじゃないと出来ないことあるからperlとかrubyに詳しくなった方がコスパ高い気がする。いまさらawkに詳しくなるメリットがちょっと思いつかない

kariyamkariyam 2013/12/10 01:04 cat ファイル名 | awk 'スクリプト'
なら
awk 'スクリプト' ファイル名
でいけますよ!

あ 2013/12/10 01:51 perlやrubyと違って実行ファイル1個で完結してるというメリットがあるかと。
Windowsだとインストーラやレジストリ使うアプリは毛嫌いされる傾向があるけど、awkならその辺問題ない。

zariganitoshzariganitosh 2013/12/10 05:10 通りすがり さん、
ご指摘ありがとうございます!
修正しておきました。
さすがにawkと言えども、typoした省略までは無理ですよね(笑)

zariganitoshzariganitosh 2013/12/10 05:29 Nanasiさん、
> perlとかrubyじゃないと出来ないことある

おっしゃるとおり、awkでは不足する場面が多々あると思います。
しかし、awkが得意とする状況も、意外と多いのです。
例えば、ls -lの出力の5列目だけ欲しい時、ls -l | awk '{print $5}'で取り出せます。
もちろん、perlとかrubyでも取り出せますが、awkほどにはシンプルに書けないと思っています。
特にawkに詳しくなる必要は全然なくて、print $nでn列目が取り出せると覚えておくだけOKです。
それでけでも、自分は幸せをよく感じていました。

zariganitoshzariganitosh 2013/12/10 05:34 kariyamさん、
そうですよね、ファイル名指定のサンプルコードも書いておけば良かったと思っていたところでした。
まさしく、kariyamさんのコメントがそのコードになります。
ありがとうございます。

zariganitoshzariganitosh 2013/12/10 05:42 あ さん、
そう言えば自分は、いつも最初からawkが入っている環境しか使ったことがなかったです。(笑)

OkadaHiroshiOkadaHiroshi 2013/12/10 06:09 たしかに ruby は awk ほどには簡単に書けないかもしれにけど、ls -l の五行目なら

ls -l | ruby -ane 'puts $F[4]'

なのでそんなに面倒ではないのでは。

OkadaHiroshiOkadaHiroshi 2013/12/10 07:11 typo 五行目 → 五桁目

言いたかったことは、簡単なことなら ruby でも大差ないので、今更 awk を使わなくてもその学習コストをスクリプト言語にかけたほうが幸せになれるのではということです。

(プログラミング言語AWK http://www.amazon.co.jp/gp/product/4901280406/ には大変お世話になりましたが、もう手に入らないし)

kenbeesekenbeese 2013/12/10 10:32 プログラミング言語AWKは復刊してますよ! http://www.amazon.co.jp/dp/4904807006

zariganitoshzariganitosh 2013/12/10 10:38 OkadaHiroshiさん、
> ls -l | ruby -ane 'puts $F[4]'

おっと、Rubyでも簡単にできましたね。(笑)
私はRubyも好きです。実際よく使ってます。
http://d.hatena.ne.jp/zariganitosh/20131107/ruby_oneliner_omission
(そもそも、このブログの始まりは、RubyとRailsの試行錯誤の記録ですから)

ところで、置き換えにはよくsedを使います。
でも、改行を置き換えたい時は、Perlに頼ります。
列の取り出しにはawkをよく使いますが、
行を検索するだけなら、grep -iを使います。(大文字小文字を区別したくないことが多いので)
全部、Rubyで出来ることですが、やっぱり自分が気に入った書き方とか、覚えやすさみたいなものがあるんですよね。
どのコマンドやスクリプトも全部中途半端ですが、出来る範囲の使い方でどうにか間に合わせてます。

すべての言語を体系的に学習するとコストは高くなりますが、自分は必要な機能をその都度覚える程度なので、学習してる感覚はあまりないのです。
Webを検索して、いつも「なるほど、こんなに簡単に処理する方法があるのか!」と感動ばかりしてます。
その感動が、ブログ・日記になってます。
今回もawkの省略記法に感動したので、そこを書きました。
そのうち、C言語のワンライナーとか、アセンブラのワンライナーに感動して、何か書いてしまうかも?!(笑)

kinnekokinneko 2013/12/10 10:50 処理データに不要なヘッダがある場合の、NR>5とかも便利ですね。

zariganitoshzariganitosh 2013/12/10 11:22 kenbeeseさん、
おおっー、情報ありがとうございます!

> テキスト処理と演算に絶大な効果を発揮する「awk」は、開発当時の1970年代から、開発者たちによって「30年後に真価がわかるだろう」と予言されていたといいます。

商品の説明で、こんな内容紹介をされたら、思わず欲しくなっちゃいますよね。

zariganitoshzariganitosh 2013/12/10 11:30 kinnekoさん、
はい、NR>5とか、とっても便利です。
素直な気持ちで、直感的に読み書きできる所が嬉しいですよね。

NanashiNanashi 2013/12/10 12:17 > もちろん、perlとかrubyでも取り出せますが、awkほどにはシンプルに書けないと思っています。
仰る通りですね。

> 特にawkに詳しくなる必要は全然なくて、print $nでn列目が取り出せると覚えておくだけOKです。
なるほど、そういうことですか。awk前から興味あったんですけど、言語一個マスターする学習コストの高さに辟易としていて、そこまでやる価値あるのかなと思っていたんですが、便利なことろだけ部分的に覚えちゃうのならアリですね。

Yukto8492Yukto8492 2013/12/12 21:57 awk 大好き人間です:-)
でも配列が全て連想配列だったとは知りませんでした…

もちろんperl やruby でもワンライナーで使えるし、awk より便利な機能も多いでしょう。
でもBSD なら2.9BSD より前のを除いて、awk は殆どのUNIX(系OS)に標準で入ってるから、覚える価値はあると思うんですが。
# 正確にいうと、awk はUNIX Ver.7 からですね。

逆に商用UNIX だと、ruby どころかperl でさえ標準で入ってないシステムがあります。
# 現在は知りませんが、多分未だperl すら入ってないと思う。

だからUNIX システムのセットアップ(SG)をする人は、少なくともawk を覚えるべきでしょうね。
# 同じ理由で、vi(ed)も使えるべきかと。

ワンライナーの場合は、私もsed を使うことが多いです。

strftime()やsystime()は、nawk などのオリジナルのawk には無いですね。
sed & awk 本には、gawk での拡張と書いてありますが、他の派生awk ではどうかは分かりません。

殆どのawk の説明文ではパイプ使ってて、わざわざパイプ使わなくても…といつも思ってます。
まあed、sed と同じく、他のコマンドの結果を加工する使い方が多いんで、そうしているのでしょうね。

因みにawk は、某社マシンのマニュアル本で覚えました。
# このテのものでは珍しく、非常に分かりやすい説明のマニュアル本でした:-D

zariganitoshzariganitosh 2013/12/13 05:58 Yukto8492さん、
コメントありがとうございます。

> strftime()やsystime()は、gawk での拡張...

おっしゃるとおりです。自分の環境(OSX)でも使えませんでした。
gawkでの拡張機能は書かないようにしていたので、あとで削除しておきますね。

サンプルコードがcat | awkなのは、単にそのスタイルが見慣れているからですね。
自分の使い方として、awkの手前で何らかの処理をしていることがほとんどなので。
本当はawkですべて処理できるのかもしれません。
こんな記事を書いていても、awkをちゃんと使いこなせてません。(笑)

今回awkをちゃんと調べるきっかけは、連続する数列をハイフンで繋げる以下のワンライナーに感動したからでした。
(アルゴリズムを理解すれば、別にawkでなくても書けるのですが、awkでもこんなことができるのかと、$++iとは何なんだ?、最後の1は何なんだ?という感動と驚きがあったのです)

echo 2 3 4 6 9 10 | awk '{a=$1;for(i=0;b=$++i;a=b)if(a+1==b)$i=-$i;OFS=",";gsub(/,-([0-9]+,-)*/,"-")}1'

http://jarp.does.notwork.org/diary/201311c.html#20131127

zariganitoshzariganitosh 2013/12/13 06:24 追記
> awk は殆どのUNIX(系OS)に標準で入ってるから、覚える価値はあると思うんですが。

まったくその通りだと思います。
すべてを詳細に覚える必要は無くて、便利だと思う部分だけ決まり文句のように覚えるだけでも、価値あると思います。
(逆に、便利だと思わなければ、覚える必要は無いとも言えるのですけどね。最終的には人それぞれ、好みの問題なのかもしれませんが。)
awkに限らず、すべての言語に言えることですが、広く浅く知ることも大切だと思います。
それぞれの言語の特徴的な書き方を知るだけでも、物事を見る発想が広がりました。

okioki 2014/12/11 09:45 突然に失礼いたします。

いつも、参考にさせていただいている者で、お世話になっています。
awkで、どうしても仕上げたいものがるのですが、
3日間ほどはまって、、頭を抱えているところで、
失礼であるとわかりつつも、気分を害されたらそのまま無視していただいて結構ですので。。。

ヤフーの質問コーナーにも投稿しているのですが、
おそらく解答が得られないのではないかと思っております。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14139288704


もし、少しだけでもお力をお借りることができればと、
思い、追い詰められて書き込みしております。

本当に申し訳ございません。

zariganitoshzariganitosh 2014/12/14 14:54 アタマの体操と思い、やってみました。

cat <<EOS > sample.txt
2 6
0
123
222
21
1
332
EOS

cat sample.txt | awk '(NR==1){i=$1;e=$2} (NR>=2 && NR<=e+1){s=s+$1;if((NR-1)%i==0){print s;s=0}}' | awk '{if($1>s)s=$1} END{print s}'

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元