Android から移した写真ファイルの日付を修正2016年06月10日 14時45分37秒

アンドロイドから Windows へと写真を移動した。NTFS のドライブ。全てでは無いようだが、ファイルの更新時刻が変わってしまっているのを見付けた。時間と日付で並べて、探すときもあるので若干不便。

調べてみると、アンドロイドで撮った写真は日付と時間の名前で保存されている。awk を使ってファイル名から touch の日付に変換して、system を使って変換。

% ls
20131229_145738.jpg
20131229_164025.jpg
20131229_175337.jpg
20131230_133658.jpg
20131230_134607.jpg
20140107_161406.jpg
% ls *_* | nawk '{f=$0;
  gsub(/(_|...jpg)/,"",$0);
  cmd=sprintf("touch -t %s %s\n", $0, f);
  system(cmd)}'

awk で sub 関数で適合した文字列を取得2012年04月03日 10時56分30秒

awk でも、sed などの用にパターンに適合した文字列を取得できる。ちょっと厄介なのが、その説明が man に載っていないので、思い出せない時に、その構文を捜し出すのが大変な点だ。

sub 関数の中で、& を使うと、パターンに適合した取り出せる。


% nawk 'BEGIN{ str = "daabaaa"; sub(/a+/, "c&c", str); print str}'
dcaacbaaa

このことは、Gawk: Effective AWK Programming に記載されていて、9.1.3 String-Manipulation Functions に載っている。 組み込み関数でも、日本語訳が掲載されていた。

この例では`dcaacbaaa'が出力される。これは`&'は非文字定数として扱われ、"最左最長"の規則に従っている為である。 (セクション How Much Text Matches?を参照)。 スペシャルキャラクタ(`&')の効果はその前にバックスラッシュを付けることによって抑制することができる。例によって、文字列中に一つのバックスラッシュを入れるためにはバックスラッシュを二つ続けて書かなければならない。従って、置換文字列中に`&'という文字を含ませるには`\\&'と記述する。次に挙げる例は各行で最初に現れる`|'を`&'で置き換える。
ただ、この日本語訳のページの daabaaa の例の正規表現は間違っていて、正しい結果は出ない。GNU のサイトのものは正しく修正されている。

大量のファイルを同時に圧縮する2012年03月25日 11時43分36秒

時折、大量のファイルを圧縮することがある。一つずつのファイルの大きさは、数 MB から数 GB まで、多種多様。そんなファイルが数百にも上る。Solaris など、特にコア数が多いホスト上の時は、手っ取り早く並列処理したい。CPU リソースは余っている事が多いので、100 以上あるコアのうち、20、30 ぐらい使ったところで気がつく人はほとんどおらず、苦情も来ない。Solaris ほどコアの数があると、CPU を全てしっかり使い切るのは結構難しい。とは言ったものの nice 20 で処理しても、流石に一人で全ての CPU を 100% 使うのは気が引ける。

実は、大きさの異なる仕事を並列的に処理するスクリプトを書くのはとても難しい。シェルスクリプトを筆頭に、特定の数だけ並列化するライブラリがない。そうなると、自分で子プロセスを生成し、管理することになる。それに、各々の仕事量が異なっているので、仕事の割り振りも工夫が必要になる。一つだけ、大きい仕事ばかり抱えて、他のプロセスが直ぐに終わってしまったのでは、効果が下がる。そう考えていくと、ささっと書いて使い捨てられるほど、簡単にはいかない。

いくつかそのようなスクリプトの尻拭いをしなければいけないことがあったが、どれも醜いものだった。コード数が不必要に長い。余計な処理ばかり増えて、本題の作業効率が余り上がっていない。負荷が偏っていて、無駄が多い。どれも一生懸命、書いたのは見て取れるのだが、直す人の気苦労などまったくわかっちゃいない。

実は、その様に百行から数百行にわたる、たった一行のプログラムで並列処理の仕事配分をしてくれるプログラムがある。その名も make。make だけだとちょっと扱いづらいので awk も当てる。

残念ながら、make にコマンドラインから直接 make ルールを打ち込む人たち等は見たことがない。make をシェルスクリプトの様に、手順を追って書く人たちや、make に依存関係の処理を任せれば簡単なのに、スクリプトで難解な発明をし、四苦八苦している人たちはよく見かけるが。

さて、愚痴と能書きが多くなったがさて本題。make での古いやつだと並列処理が出来ないものもあるので、GNU make や BSD make など若干新しいものが必要になる。


% cat bzip20
#!/bin/sh

exec $* | \
  nawk 'BEGIN{ print "ALL:" }
    { print $1 ".bz2 : " $1;
    print "\tbzip2 " $1;
    print "ALL : " $1 ".bz2" }' | \
  gmake -j 20 -f -
% bzip20 "ls *.txt"
% bzip20 "find . -name '*.log'"

exec を通して、ファイルを探し、それらのファイルを圧縮するターゲットを作る。ALL を一番最初にしているので、全てが圧縮される。exec を通しているので、圧縮順を気にせず適当に渡したり、逆に大きさ順に並べて、極力処理が偏らないように出来る。

awk の行の位置2011年11月08日 15時00分46秒

awk で行を削除した後、split で再評価されるかどうか調べてみた。再評価されて、各行数が一つずつずれるかどうかに興味がある。

% nawk '{ $1 = ""; print 1, $0}{$2 = ""; print 2, $0}' 
A B C D
1  B C D
2   C D

B が消えているので、そのままの行番号のようだ。

last weekday by awk2011年10月26日 11時19分00秒

最後の曜日の日付を出力してみた。今回は、月の最後の平日。若干面倒なのが cal の出力。日曜日が一行目、土曜日が七行目となり、平日が丁度挟まれて真中になっている。そのため、土曜日の出力があるときは、金曜日を取得する必要がある。

% cal | NF >= 2{ if( NF >= 6 ) last = $6; else last = $NF}END{ print last }
31

シェルによってはコマンド行からでも複数の行に出来る。if else 句があると、一行では読みづらい。

% cal | nawk 'NF >= 2 {
  if( NF >= 6 )
    last = $6
  else
    last = $NF
}
END{ print last }'
31

月の最終 X 曜日の日付2011年10月12日 18時09分45秒

月の最終 X 曜日の日付を求める。例えば、2011 年 11 月の最後の金曜日は 28 日だ。このぐらいだったら、cal の手助けを得れば、大したことは無い。

% cal
    October 2011      
Su Mo Tu We Th Fr Sa  
                   1  
 2  3  4  5  6  7  8  
 9 10 11 12 13 14 15  
16 17 18 19 20 21 22  
23 24 25 26 27 28 29  
30 31

cal の出力は曜日によって固定されている。そこで、最終月曜日を得るには最後の列の二行目を取得すればいい。monday の値を順次更新していく。

% cal | awk '{monday=$2}END{print monday}'
31

ところがこれでは、最後の列に日付が無いと狂ってしまう。そこで、行数を点検する。


% cal | awk 'NF>=6{friday=$6}END{print friday}'
28

tail by awk 22011年05月28日 14時13分02秒

前回の tail から改良。前回の実装は単純な実装な為、とても遅い。そこで、今回は一行ずつずらすのではなく、書き込んだ位置を覚えておく。覚えておくと言っても awk が既に NR に読んだ行数を保存している。

% nawk '{line[NR % 10] = $0}END{for(i = 0; i < 10; i++)print line[(NR + i) % 10]}' /etc/services
isode-dua       17007/udp
biimenu         18000/tcp  #Beckman Instruments, Inc.
biimenu         18000/udp  #Beckman Instruments, Inc.
wnn4            22273/tcp  wnn6         #Wnn4 (Japanese input)
wnn4_Cn         22289/tcp  wnn6_Cn      #Wnn4 (Chinese input)
wnn4_Kr         22305/tcp  wnn6_Kr      #Wnn4 (Korean input)
wnn4_Tw         22321/tcp  wnn6_Tw      #Wnn4 (Taiwanse input)
wnn6_DS         26208/tcp  #Wnn6 (Dserver)
dbbrowse        47557/tcp  #Databeam Corporation
dbbrowse        47557/udp  #Databeam Corporation

% 演算子を使って、出力する行を決める。余計な行の移動が無くなったので、プログラムも若干簡潔になった。

% tail /etc/services
isode-dua       17007/udp
biimenu         18000/tcp  #Beckman Instruments, Inc.
biimenu         18000/udp  #Beckman Instruments, Inc.
wnn4            22273/tcp  wnn6         #Wnn4 (Japanese input)
wnn4_Cn         22289/tcp  wnn6_Cn      #Wnn4 (Chinese input)
wnn4_Kr         22305/tcp  wnn6_Kr      #Wnn4 (Korean input)
wnn4_Tw         22321/tcp  wnn6_Tw      #Wnn4 (Taiwanse input)
wnn6_DS         26208/tcp  #Wnn6 (Dserver)
dbbrowse        47557/tcp  #Databeam Corporation
dbbrowse        47557/udp  #Databeam Corporation

awk のプログラムは他の言語とは異なる。他の言語では、全体としてどう処理するかを考えてプログラムを書く。例えば、ファイルを開き、一行ずつ読み込みながら何をするのかを考えてから、一行ずつの処理を書く。awk では、一行ごとをどのように処理すれば良いのかを考えてから、プログラムを書く。また、そこらへんが awk 初心者にとっての、違和感にもなっている。

今回の要点。

  1. NR 変数
  2. % 演算子

tail by awk2011年05月26日 18時34分01秒

今回は awk で tail を実装する。tail はファイルの最後から行数を数えるので、色々と面倒になる。今までは、入力行をそのまま出力するか、ちょっと加工して最後に簡単な出力だけで終わっていた。tail が難しいのは、複数の行を保持しておかないといけないから。そこで、配列を用いる。

awk の配列はいわゆる連想配列で、任意の鍵をインデックスとして用いて値を保存することが出来る。もちろん、数字を用いても良い。

一つの制約として、変数を配列として用いると、それ以降は配列としてしか使えなくなる。スクリプトが大きくなると、意図せずに混ぜてしまいエラーになることがある。

単純に tail で十行表示する。


% tail /etc/services     
isode-dua       17007/udp
biimenu         18000/tcp  #Beckman Instruments, Inc.
biimenu         18000/udp  #Beckman Instruments, Inc.
wnn4            22273/tcp  wnn6         #Wnn4 (Japanese input)
wnn4_Cn         22289/tcp  wnn6_Cn      #Wnn4 (Chinese input)
wnn4_Kr         22305/tcp  wnn6_Kr      #Wnn4 (Korean input)
wnn4_Tw         22321/tcp  wnn6_Tw      #Wnn4 (Taiwanse input)
wnn6_DS         26208/tcp  #Wnn6 (Dserver)
dbbrowse        47557/tcp  #Databeam Corporation
dbbrowse        47557/udp  #Databeam Corporation

まず最初の実装は単純に配列の 0 から 9 に入力行を保持する。一行読む毎に、一行ずつ動かして、常に十行保持している。愚直で遅い実装だが、END 句では単純に print すれば良い。

% nawk '{for(i = 9; i >=0; i--)line[i+1] = line[i]; line[0]=$0}END{for(i = 9; i >=0; i--)print line[i]}' /etc/services
isode-dua       17007/udp
biimenu         18000/tcp  #Beckman Instruments, Inc.
biimenu         18000/udp  #Beckman Instruments, Inc.
wnn4            22273/tcp  wnn6         #Wnn4 (Japanese input)
wnn4_Cn         22289/tcp  wnn6_Cn      #Wnn4 (Chinese input)
wnn4_Kr         22305/tcp  wnn6_Kr      #Wnn4 (Korean input)
wnn4_Tw         22321/tcp  wnn6_Tw      #Wnn4 (Taiwanse input)
wnn6_DS         26208/tcp  #Wnn6 (Dserver)
dbbrowse        47557/tcp  #Databeam Corporation
dbbrowse        47557/udp  #Databeam Corporation

今回の要点。

  1. 連想配列

wc -c by awk2011年05月05日 11時15分47秒

以前にファイルの行数を数える wc -w を awk で実装した。今度は、ファイルの単語数を数える wc -c を awk で実装する。

繰り返すが awk の処理の基本は、入力行を一行ずつ処理すること。-w では一行読まれる毎に、一行の中の単語の数の NF 加算していった。今度は各行の文字数を足していく必要がある。awk には length( str ) という組み込み関数がある。渡した文字列の長さを返す。


% wc -c /etc/services
   83034 /etc/services
% awk '{ c+=length($0) }END{print c + NR}' /etc/services
83034

$0 は入力行全体を指す。そのため、length($0) は入力行の文字数になる。しかし、改行文字は、切り落とされるので最終的には、ファイルの行数分だけ文字数が減ってしまう。それを END 句で足す。

今回の要点。

  1. $0
  2. length()

wc -w by awk2011年05月02日 20時55分12秒

以前にファイルの行数を数える wc -l を awk で実装した。今度は、ファイルの単語数を数える wc -w を awk で実装する。

awk の処理の基本は、入力行を一行ずつ処理すること。一行読まれる毎に、一行の中のトークンの数が NF に保持される。これを全部足せば、ファイルの単語数になる。


% wc -w /etc/services
    9487 /etc/services
% awk '{w+=NF}END{print w}' /etc/services
9487

awk の変数は他の多数のスクリプト言語と同様に型宣言が無い。awk が扱う型は、数字型と文字列型のみの二種類になる。文字列か、数字かは式によって決定される。変数の初期値は空文字か 0 になっている。スコープは関数の引数以外は全て広域変数になる。その為、大型の awk スクリプトだと変数名が衝突してしまう事も起きやすい。

上では、変数 w に書く行の単語数を += で随時数字として足していく。そして、全ての行の処理が終わったら、値を出力する。

今回の要点。

  1. NF
  2. awk 変数