ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

読んだ: レガシーコード改善ガイド

Web 上でそこそこ評価が高いようですね。

自分はリファクタリング経験が結構あった (テストを書くことで保護して変更する) ので本書から得られるものはそこまで多くはなかったのですが、テストを書くとかリファクタリングの経験がそんなにない人は本書から得られるものは多いと思います。 これまでテストを書いたことがほとんどないとか、テストしやすい設計について考えたことがないというようなソフトウェアエンジニアはぜひ読んでみてください! 普段からテストを書いたりしてるけど、テストがないコードを変更するという経験をしたことが少ない人にも参考になるかと思います。

内容紹介

本書では、「レガシーコードとは、単にテストのないコードです」 と述べられています。

第 1 部は、既存のソフトウェアを変更する際の理由を 4 つ (要件追加、バグ修正、リファクタリング、最適化) に分類したり、レガシーコードを変更する際にテストで保護することがいかに有効であるか、単体テストを行いやすくするために何を考慮すべきか、といったこと、そして、リファクタリング単体テストに有効なツール (IDE とかテストハーネスとか) の紹介がされます。

第 2 部が本書の主たる部分で、レガシーコードを変更する際の 「具体的な問題設定」 と、それに対する 「具体的な解決策」 がそれぞれの章に書かれています。 安全に変更するためにはテストで保護する必要がある、という主張のもと、いかにして依存関係を排除してユニットテストを書くか、ということが述べられていたり、テストでの保護が難しい場合に、変更箇所以外に影響が及ばないように変更するにはどうすればよいか、というようなことが書かれています。

例えば、6 章 「時間がないのに変更しなければなりません」 では、既存のシステムに追加された要件を満たすように短時間でシステムを変更する必要があるが、(既存のメソッドに変更を加えた場合に) 追加された要件以外の部分に影響がないことを確認することが困難である、という状況に対する解決案が述べられています。 他の章も、章の表題自体がソフトウェア開発者が直面しがちな問題を表していて、どの章でどういう問題について述べられているかすぐにわかるでしょう。

対象となっている言語は、主に C、C++JavaC# です。

第 3 部は 25 章 「依存関係を排除する手法」 のみからなり、第 2 部の説明の中で使われた依存関係を排除するためのリファクタリング手法がカタログ形式で掲載されています。

感想

全体として、

  • テストしづらいコードをどういう風に変更すればテストしやすくなるのか、
  • 依存関係を排除するためにどうすればいいのか、
  • 理解しづらいコードを理解するためにどういう手段を取ると良いか、

ということが、著者の経験を基に述べられています。 内容的にはさほど高度なものはなく、ちゃんとテストを書きながら保守開発を行うということをしていれば得られる知見が文書としてまとめられている、という感じです。 そういう意味では、経験のあるソフトウェアエンジニアが読んだところで得られるものはほとんどないでしょう。

一方で、そのような知見が 1 冊の本としてまとめられているというのは価値あることで、保守開発の経験が少ない人は読むといろいろ得られるものがあるでしょう。 実際の開発時に本書で述べられている手法を使うかどうかはともかくとして、本書で述べられているような手法があることはソフトウェア開発者であれば知っておくべきだと思いますし、本書に書かれている内容を知識として持っていないソフトウェアエンジニアには、是非本書を読んでほしいと思います。

「Typetalk Hack Kyoto」 に参加しました

「Typetalk Hack Kyoto」 という nulab 主催のイベントに参加しました。

イベントの様子は上の記事にまとめられています。

Typetalk とは

nulab が開発しているチャットツールで、2 月に正式リリースされたもの。 基本はビジネス向けではあるけれど、「いいね!」 を付けられるなど、楽しい要素も盛り込んでいるらしい。

Typetalk Hack Kyoto

内容は @tksmd さんによる Typetalk の話とハッカソン (実際に Typetalk の API を使って何か作る)。

tksmd さんは 『開発現場に伝えたい 10 のこと』 の著者の 1 人。 tksmd さんが書かれた部分は nulab のブログで公開されている。

『早く行きたいなら、一人で行け。遠くに行きたいなら、みんなで行け。』 というアフリカの諺が紹介されていて、なるほどー、という感じ。

Typetalk の API について

Web からもスマートフォンアプリからも、基本的に同じ API を使用するようになっている。 また、製品で使用している API は、基本的にサードパーティに公開しているとのこと。 その理由などは次のエントリで書かれている。

Web 側とスマートフォンアプリ側での違いとしては、認証方式が違うということがある。 あと、一部 API (管理系 API) は web 側でしか使用していなくて、サードパーティへの公開もされていないらしいけれど、将来的には公開する予定とのこと。

開発者が Typetalk API を使う場合は、認証には OAuth 2 が使用できる。 次の 2 つの方式がサポートされている。

  • Client Credentials: 単一ユーザーで使う場合 (バッチ処理とか)
  • Authorization Code: 複数ユーザーに提供する場合

API についての雑感

Typetalk の API を使うのは、(HTTP 通信さえできれば) 結構簡単にできる感じだった。 テスト完了時にテスト結果を通知するボットを作るとか、わりと手軽にできると思う。

OAuth 2 を使ったことは今まであんまりないのだけれど、OAuth 2 は使いやすくて良いなー、と思った。 サービス提供側としても OAuth 2 のことをちゃんと調べておきたい。 nulab ではサーバーサイドの OAuth 2 実装として次の Scala プロジェクトが公開されているので、参考にできそう。

それと、統一された API を提供し、web からもアプリからもそれを使用する、という設計方針について。 サーバーサイドのメンテナンスコストが小さくなるし、こういうツール系の web サービスについては Ajax で (クライアントサイドで) 動的に画面を生成するというのは一つの選択肢として良さそう。 とはいえクライアントサイドが複雑になるので、そこら辺は難しいところだなーと感じた。 *1

書いたもの

自分は Groovy で Typetalk に通知メッセージを送るようなボットを作ろうと思ったけど、Groovy で HTTP 通信するのに慣れてなかったから API を叩く簡単な処理ぐらいしか公開できるものはできなかった。 Gist で公開しているので、Typetalk の API を使う処理を Groovy で書く際の参考にどうぞ。

Groovy での HTTP 通信

せっかくなので少し。

Groovy の HTTP 通信用のライブラリとして、HTTP Builder というものがある。

Apache HttpClient のラッパーなのだけれど、Groovy で使いやすいような API になっていたり、組み込みで XMLJSON の変換処理が行われたりするのが便利。

非同期での HTTP 通信などもやりやすそうな感じ。

*1:個人的にはクライアントサイドの JS のメンテナンスはサーバーサイドのコードのメンテナンスよりも難しいと思ってる (サーバーサイドがどんな感じかによっても違うけど、一般的にはそうかなーと思ってる)。

読んだ: 集合知プログラミング

ユーザーへの推薦やカテゴリ分類、いわゆるデータマイニングに興味があったので読みました。

集合知プログラミング

集合知プログラミング

本書では集合知について次のように書かれています。

人々は集合知という言葉を長い間使い続けてきた。 それは新たなコミュニケーション技術の到来とともに、ますます人気と重要性を増して来ている。 集合知という表現は、集団の意識や超常現象を想起させるが、技術者がこの表現を使う場合は、今までにない知性を生み出すために、集団の振る舞い、嗜好、アイデアを結びつけることを指す。

『集合知プログラミング』 1.1 節 「集合知とは何か?」

本書は、何らかの集団 (例えば web ページの集合) を分類したり、集団の中から特定のユーザーに推薦すべきものを決定したり、集団から検索したり、といったことについて基本的な手法を述べるものです。 特に web サービス上に存在する集団に対して適用されることが念頭に置かれた説明が多く、web サービス開発に携わっている人であれば応用できる箇所を具体的に連想しながら読み進めていくことができると思います。

内容的には初心者向けという感じですので、本書を読むにあたって統計や機械学習についての基礎的な知識を持っている必要はありません。

内容紹介

1 章は序章で、「集合知とは何か」 というような話や、機械学習についてや学習アルゴリズムについて書かれています。

2 章から 11 章までは、具体的な問題を設定し、それを解決するために種々のアルゴリズムが紹介されます。 実際のコードも豊富に用意されていて、全体的に理解しやすい内容になっています。 ちなみにコードは Python で書かれています。

12 章では、本書で紹介されているアルゴリズムがまとめられています。

アルゴリズムまとめ

12 章では、本書で出てきたアルゴリズム一覧がまとめられています。 ここでも、簡単にアルゴリズムをまとめておきます。

ベイジアン分類器

ベイジアン分類器は、特徴のリスト (例えば文書中に頻出する単語のリスト、など、対象となる項目に含まれるか否か簡単に判別できるもの) を抽出できる項目の集合に対して作用させることができるものです。 本書では、スパムフィルタリングや、カテゴリ分類などの文書分類システムを作成するという文脈で紹介されていました。

ベイジアン分類器の長所は、トレーニングの速度と巨大なデータセットに対する問い合わせの速度が速いことです。 また、学習結果の解釈も容易という点も利点です。 トレーニングの際には過去のトレーニングデータを必要としません。 欠点としては、複数の特徴の組み合わせを元に分類する、ということができないことです (特徴はそれぞれ独立している)。

決定木による分類器

yes/no で答えられる質問がノードになっていて、末端に項目の集合がある、というような二分木を決定木といいます。 末端の項目の集合がなんらかのグループであるように決定木を作れば、その決定木を使って項目の分類ができます。 「アキネイター」 なんかは末端の項目数が 1 であるような決定木という感じがしますね (内部的にどうなってるのかわかんないですが)。

決定木を作る時は、各ノードの質問を何にするかを決定する方法が重要になります。 質問による集団の分割で情報量が最も多くなるように、各ノードの質問を決めていきます。 (分割前のエントロピーから分割後の 2 つの集団の各エントロピーの合計を引くことで、情報ゲインを計算する。)

決定木の利点として大きなものは、学習済みの決定木を使うことが容易なことです。 決定木を図にしてしまえば、人が見るだけで新しい項目がどこに分類されるのかすぐにわかります。 また、入力データとして数値を使用することもできます。

新たに項目を追加して決定木を作り直したい場合は、過去のトレーニングの結果に追加するというようなことはできず、すべての項目を対象にして決定木を一から作り直す必要があります。

ニューラルネットワーク

ニューラルネットワークは、分類器にも応用できますし、数値を予測する問題に適用することもできます。

ニューラルネットワークは、複数の入力 (例えば web ページを検索するためのキーワードのリスト) があった場合に、重みづけされた複数の項目 (例えば web ページの検索結果の URL 一覧) を出力するというものです。 入力と出力の間には 1 つ以上の隠れニューロン層があることが 「ニューラルネットワーク」 という名前の由来だそうです。 (言葉で書かれていてもわかりづらいと思いますが、図で表すとわかりやすいので気になる人は適当に調べてください。)

入力と出力の間に隠れ層が存在する、というのが重要で、入力と出力が直接的ではなく間接的に結び付いていることで、「入力の組み合わせ」 というものに対応できるようになっています。

利点としては、入力として 1 か 0 の値 (存在するかどうか) だけでなく、任意の数値をとることができるという点や、トレーニングを継続的に行うことができるという点があります (既にある決定木に新しいトレーニングデータを追加するさいに一からやり直す、というような決定木のようなことはする必要がない)。 欠点としては、入力から出力に至る流れが複雑で、(ベイジアン分類器や決定木と比べて) 解釈が困難である、という点があります。 ネットワークのサイズやトレーニング率を決める確固たるルールがないことも欠点としてあるようです。

サポートベクトルマシン (SVM)

『多分、本書でカバーする手法の中でもっとも洗練されたものだろう』 と書かれています。

SVM は、多次元空間上に分布する対象項目を分類するための超平面を探す、というものですが、最終的に導き出される超平面はマージン最大のものになっているという点が特徴です。 超平面を探した後は、新しい項目について超平面のどちらに属しているかを調べることで分類を行います。

計算にはドット積が使われるのですが、その際にカーネルトリックという技術が使われます。 詳細は割愛。

利点としては、非常にパワフルな分類器で、正しいパラメータを与えることで、本書で紹介されている他の手法と同等程度かそれ以上にうまく動作するという点があります。 また、分類の際には超平面のどちら側に点があるかを調べるだけなので、新たな観測値の分類が高速であるということがあります。 欠点としては、最良のカーネル変換関数とそのパラメータが、データセットごとに異なっているので、毎回それらを探す必要があるということがあります。 また、ニューラルネットワークと同様に、ブラックボックス的な技術ということもあります。 データが高次元で扱われるため、実際にどうやって分類されているかを解釈することは難しいです。

決定木のような他の手法は、小さなデータセットに対しても興味深い情報を生み出しますが、SVM は一般的に巨大なデータセットに対して非常に有効な手法です。

K 近傍法

特定の商品の価格がどの程度になるのかを予想するための手法として、本書では K 近傍法が紹介されていました。

価格を知りたい商品と、既に価格を知っている商品とで似ている度合いを調べ、最も似ている K 個の商品の価格の平均 (あるいは重みづけ平均) を取ることで対象の商品の価格を予想する、という感じの手法です。

似ている度合いの計算などには込み入った関数が使われるものの、解釈が容易です。 データが変わっても再トレーニングする必要はありません。 ここら辺は K 近傍法の利点です。

欠点としては、予測を立てるためにすべてのトレーニングデータが必要で、数百万もあるような巨大なデータセットを使う場合は、スペースが必要というだけでなく実行時間の問題となります。 また、特徴ごとの重み (縮尺係数) を決定するのに長い時間がかかることがあることも欠点のひとつです。

クラスタリング

クラスタリングの教師無し学習の技術として、本書では階層的クラスタリングと K 平均法クラスタリングが紹介されています。

階層的クラスタリングは、それぞれの項目が 1 つのクラスタに属している状態から開始し、クラスタの中心位置 (そのクラスタに含まれる項目すべての平均) が最も近い 2 つのクラスタをマージして新たなクラスタを作る、ということを繰り返してクラスタリングしていきます。 結果としては項目のツリーができあがります。

K 平均法クラスタリングは、予めグループ数を決めておき (グループ数を K とする)、項目の集団が属するの空間中に適当に K 個の点を配置して、それらの点を (項目との距離を使って移動位置を決めて) 動かすということを繰り返していきます。 最終的に、これらの点がクラスタの中心となります。

多次元尺度構成法

これも教師無し学習の技術です。 予測を行うのではなく、さまざまな項目がどのように関係しているかを理解するための助けをします。

多次元空間上で表現されるデータセットの項目間の距離をできるだけ保ったまま、より低次元の空間でデータセットを表現する (例えば画面上で表現するために 2 次元にする)、というものです。

非負値行列因子分解 (NMF: non-negative matrix factorization)

非負値行列というのは、非負値のみからなる行列です。 そのような行列を因子分解するというのが非負値行列因子分解です。 (そのまんまですね。)

で、これが何かというと、例えば各行が項目 (例えばブログ記事) を表していて、各列が何らかの実データ (例えば文書中に頻出する単語の個数) を表していると行列が存在する場合、それを因子分解することで特徴の行列と重みの行列が得られる、という感じで使うことができます。

NMF 自体は、もともと画像処理の分野で有名になったものみたいですね。

最適化

旅費と空港での待ち時間を最適な感じにするように旅行の行程を決定する、とか、学生の部屋割りを最適にする、とか、そういう問題の最適化の手法として、模擬アニーリングと遺伝アルゴリズムが紹介されています。 何をもって最適とみなすかは、こちらが与えたコスト関数により決定されます。 (コスト関数の出力が最小になるものが最適。)

このような最適化の問題で困難なのは、パラメータを少しずつ変化させていって最小になるところを探す、というような方法だと、局所最小にいたりはするものの大域最小にいたらないことがあることです。

模擬アニーリングは、ランダムに推測した解からスタートして、ランダムな方向、小さな距離にパラメータを移した類似解のコストを計算して解を改善していく、というものです。 コストが高いうちは、移動先のコストの方が高くても一定の確率でそれを新しい解とします。 コストが小さくなっていくにしたがって、移動前よりも高いコストの解を採用する確率は小さくなっていきます。 これは、物理学における合金の冷却に触発されたアルゴリズムらしいです。

もうひとつの遺伝アルゴリズムの方は、個体群と呼ばれる複数の無作為解からスタートし、個体群中のいくつかの良い解を選択し、それらを元に突然変異させた個体や、組み合わせ交叉させた個体を生み出し、それらを次の世代の個体群とする、ということを繰り返していくものです。 世代を重ねることで改善されていきます。

感想

集合知」 という言葉は数年前の流行言葉という印象があって、使われる場面によっては非常に夢見がちな何かであったりすることもあると思うのですが、当然ながら本書はエンジニアリングのための地に足のついた書籍です。 Web サービスを開発していると、ユーザーに対して推薦を行いたいということや文書などをカテゴリ分類したいということがよくあると思うのですが、そういったことを行うための手法について初心者向けに書かれていますし、問題設定も 「web サービスを使用する」 ことを前提にしたものが多いですので、web サービスに関わる人にとってはわかりやすいしとっつきやすいと思います。

「ユーザーへの推薦をしたい」 とか 「カテゴリ分類したい」 とかそういうことを思ってるけど具体的にどこから手を付けたらいいかわからない、というような人がまずは読んでみる本として適していると思いました。 数式などのアルゴリズム部分の詳細まではそんなに立ち入っていない (全然ないわけではないですが) ので、そういう詳しい部分については別の書籍や論文などを追っていけばよいでしょう。

非常にためになったし読んで良かったです。

集合知プログラミング

集合知プログラミング

JAX-RS アプリケーションを Groovy で書いて Grizzly で動かすサンプルコード

JAX-RS 実装として Jersey 2.6 を使って JAX-RS アプリケーションを書いて、Java 製 HTTP サーバーの Grizzly で動かす、ということをしてみたのでサンプルコードを残しておく。 Groovy で書いた。

依存ライブラリの管理も Grape を使ってコード中で行っているので、groovy コマンドが使える環境であれば次のコードを適当なファイル (test.groovy とか) にコピペして groovy test.groovy とコマンド入力すれば実行することができる。 初回起動時は依存ライブラリを取ってくるので時間がかかるはず。

手元で web アプリケーションを動かしたいけどちゃんとしたプロジェクトを作るほどではない、みたいなときに Groovy で書いて Grizzly 上で動かしたら便利だなー、と思った。

実際に書いたコード

上のドキュメント (「4.5.1.2. Grizzly HTTP Server」) にあるコードをベースにしている。

@Grab(group='org.glassfish.jersey.containers', module='jersey-container-grizzly2-http', version='2.6')

import org.glassfish.grizzly.http.server.HttpServer
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory
import org.glassfish.jersey.server.ResourceConfig

import java.net.URI
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit

import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.core.UriBuilder

@Path("/")
class HelloWorldResource {
    @GET
    @Produces("text/plain; charset=utf-8")
    String getResource() {
        return "Hello world!"
    }
}

final URI baseUri = UriBuilder.fromUri("http://localhost/").port(10082).build()
final ResourceConfig config = new ResourceConfig(HelloWorldResource.class)
final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(baseUri, config)

// 以下、終了処理
final mainThreadWaiter = new CountDownLatch(1);
addShutdownHook {
    println "To be shutdown..."
    mainThreadWaiter.countDown()
    Future f = server.shutdown() // `final` 修飾子が有効でなくても `server` 変数のメモリ可視性って保証される?
    f.get(15, TimeUnit.SECONDS)
}

mainThreadWaiter.await()
println "Main thread stopped"

cron 設定ファイル (crontab ファイル) の置き場所と書式について

上の記事を読んで興味を惹かれたので、『WEB+DB PRESS Vol.79』 の 「cron 周りのベストプラクティス」 を読んだ。 Web 上でも公開されている。 (2014-06-23 追記。)

そこでは cron 設定 (crontab) ファイルの記述方法として crontab コマンドを使用する方法が書かれていた *1。 で、記事を見てるうちに、以前 crontab コマンドやら /etc/cron.d やらの違いがよくわからずに調べて社内日記に書き残しておいたことを思いだしたのでブログで公開しておこうと思う。

ちなみに man の内容などは Debian 7 で見てるので、別ディストリビューションだと違ったりするのかもしれない。

WEB+DB PRESS Vol.79

WEB+DB PRESS Vol.79

  • 作者: 成瀬ゆい,そらは(福森匠大),西磨翁,小川航佑,佐藤新悟,塚越啓介,藤原亮,堀哲也,田村孝文,桑野章弘,松浦隼人,中村俊之,田中哲,福永亘,杉山仁則,伊藤直也,登尾徳誠,近藤宇智朗,若原祥正,松木雅幸,奥野幹也,後藤秀宣,羽二生厚美,笹田耕一,平河正博,東舘智浩,渡邊恵太,中島聡,A-Listers,はまちや2,川添貴生,山田育矢,伊藤友隆,村田賢太,まつもとゆきひろ,佐野岳人,山口恭兵,千葉俊輝,平松亮介,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2014/02/22
  • メディア: 大型本
  • この商品を含むブログ (4件) を見る

man について

本記事中で 『cron(8) の man』 などのように書いてある場合、man 8 cron という感じで man コマンドを使うことでその文書を読める。

crontab ファイルの置き場所

cron 設定をどこに書くことができるのか

cron(8) の man を見ると次のように書かれている。

cron は、スプール領域 (/var/spool/cron/crontabs) に置かれた crontab ファイルを探す (これらのファイルには /etc/passwd 内のアカウントを元にした名前がつけられている)。 見つかった crontab ファイルはメモリにロードされる。 このディレクトリの crontab ファイルは直接アクセスすべきではないことに注意せよ。 ファイルのアクセスや更新には crontab を使用すべきである。

cron は /etc/crontab も読み込む。このファイルのフォーマットは少々異なっている ( crontab(5) を参照)。 さらに cron は /etc/cron.d 内のファイルも読み込み、それらのファイルを /etc/crontab ファイルと同様に処理する (/etc/cron.d 内のファイルは /etc/crontab の特別なフォーマットに従う。つまりユーザ名のフィールドを含む)。 しかし、/etc/cron.d 内のファイルは /etc/crontab とは独立している。 つまり、例えば環境変数の設定を /etc/crontab から継承したりはしない。 この機能の用途は、/etc/cron.{daily,weekly,monthly} ディレクトリよりも細かな予定の調節を必要とするパッケージが、 /etc/cron.d に crontab ファイルを追加できるようにすることである。

ということで、cron 設定を書く場所 (crontab ファイルの置き場所) としては次の 3 つがある。

  • 各ユーザーの crontab ファイルの置き場所
    • /var/spool/cron/crontabs ディレクトリ内 (crontab コマンドで読み書きする)
  • root 権限が必要 (?) な crontab ファイルの置き場所
    • /etc/crontab ファイル
    • /etc/cron.d ディレクトリ内

crontab ファイルの書式は crontab(5) の man で見ることができる。 /var/spool/cron/crontabs 下に置く場合の書式と /etc/crontab や /etc/cron.d 下に置かれる crontab ファイルの書式は違うので注意が必要 (詳細は後述)。

crontab コマンドでユーザーごとの設定を書く

/var/spool/cron/crontabs 下のユーザーごとの crontab ファイルを読み書きする際は、crontab コマンドを使用する。 詳細は crontab(1) の man を参照のこと。

ちなみに、crontab コマンドに -r オプションを渡すと crontab が削除されてしまうため、crontab ファイルはユーザーのホームディレクトリなりなんなりでバージョン管理しておいて、crontab コマンドにそのファイルパスを渡すことで cron 設定を行う、というのがベストプラクティスとなっている。

これについては 『WEB+DB PRESS Vol.79』 の 「cron 周りのベストプラクティス」 にも書かれていた。

crontab の書式

crontab(5) の man には次のように書かれている。 実行時刻指定のフィールドの詳細については man を参照のこと。

各行には 5 つの時刻・日付フィールドがあり、 さらにコマンドと改行文字 ('\n') が続く。 (略) フィールドはスペース区切りでもタブ区切りでもかまわない。

(略)

「第 6」フィールド (行の残りの部分) には実行されるコマンドを指定する。 その行のコマンド部 (改行文字または % 文字まで) が /bin/sh (またはその crontab ファイルの SHELL 環境変数で指定されたシェル) によって実行される。 コマンド中にパーセント記号 (%) が バックスラッシュ (\) によってエスケープされずに置かれていると、 改行文字に置き換えられ、最初に現れた % 以降の全てのデータは 標準入力としてコマンドに送られる。 シェルの行末の "\" のような、 コマンドの単一の行を複数行に分割して記述する方法は、crontab にはない。

man にあるサンプルは次のような感じ。

# コマンドの実行に、デフォルトの /bin/sh ではなく /bin/bash を使用する。
SHELL=/bin/bash
# (この crontab の所有者に関らず) あらゆる出力を `paul' にメールする。
MAILTO=paul
#
# 毎日、日付変更の 5 分後に実行する
5 0 * * *       $HOME/bin/daily.job >> $HOME/tmp/out 2>&1
# 毎月初日の 2:15pm に実行する -- 出力は paul にメールされる
15 14 1 * *     $HOME/bin/monthly
# 平日の午後 10 時に実行してジョーを心配させる
0 22 * * 1-5    mail -s "午後10時だ" joe%ジョー、%%お前の子どもはどこだい?%
23 0-23/2 * * * echo "毎日 0,2,4..時 23 分に実行する"
5 4 * * sun     echo "日曜 4 時 5 分に実行する"

ジョーを心配させててひどい。

/etc 下の crontab について

/etc/crontab

/etc/crontab はシステム管理者が触るためのもので、普通はいじらない感じにしておくらしい。 デフォルト (?) では /etc/cron.daily/ や /etc/cron.weekly/ ディレクトリ内のスクリプトを定期的 (cron.daily なら 1 日ごと、cron.weekly なら 1 週間ごと) に実行するようになってるぽい。

/etc/cron.d ディレクトリ下の crontab

この中に置かれたファイルも cron による実行対象である。 cron が自動的にファイルの編集時刻をチェックしてくれるので、/etc/cron.d ディレクトリの中のファイルを変更しても crond の再起動などは必要ない。

crontab の書式

crontab(5) の man には次の記述がある。 /var/spool/cron/crontab 下の crontab の書式と違うのは、実行ユーザーの設定を行うためのフィールドがある点である。

各行には 5 つの時刻・日付フィールドがあり、 さらにコマンドと改行文字 ('\n') が続く。 システムの crontab ファイル (/etc/crontab) は同様のフォーマットを使用するが、 時刻・日付フィールドとコマンドの間で、 コマンドを実行するユーザ名を指定する。

man には次のような例が書かれていた。

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user    command
42 6 * * *        root    run-parts --report /etc/cron.daily
47 6 * * 7        root    run-parts --report /etc/cron.weekly
52 6 1 * *        root    run-parts --report /etc/cron.monthly

crontab の書式に関するおまけ

標準出力とエラー出力をファイルに出力したい場合

コマンドの出力結果は、通常はメールで送信されるが、以下のようにリダイレクトしてファイルに書きだすこともできる。 エラー出力も標準出力と同じファイルに書きだしたければ 2>&1 って付けておけばよい。

* * * * * /usr/bin/perl -e'print STDOUT "test\n"; print STDERR "test error\n";' >> $HOME/cron_test.log 2>&1

実行日の決定のはまりどころ

crontab(5) の man より。

注意: コマンド実行の日は 2 つのフィールドで指定できる — 月内日および曜日である。 もし両方のフィールドが制限指定 (* 以外) であると、いずれかのフィールドが現在時刻と合った時にコマンドが実行される。 例えば、

30 4 1,15 * 5

とすると、毎月 1 日と 15 日および毎週金曜日の 午前 4:30 にコマンドが実行される。

月内日と曜日の両方を指定していると、それぞれ独立に実行日の指定に使われるので注意!! *2

crontab をどこに置くのがいいのか

Web サービスなどのための crontab ファイルを /etc/cron.d 下に置くか、ユーザーごとの crontab として設定するか、どっちがいいのだろー、と思ったりした。 会社の方針とか、cron 以外の部分の運用によってもどちらがいいのか変わってくると思うけれど。

会社の人と話して教えてもらった /etc/cron.d 下での運用の利点としては、複数ファイルに分けて管理することがしやすい、ということがある。 確かにプロジェクトあたりに大量の cron 設定があるならファイルを分けておきたい。

それと設定ファイルを探しやすいということもある。 「ちゃんと調べてないですが」 という注意付きで以下のようなコメントを貰った。

12:59 (********) /var/spool/cron ← このへんのディレクトリは実装依存です
13:00 (********) /etc/crontab とか /etc/cron.d は大抵の実装で共通だからうれしい

私は前任者が書いた crontab ファイルを探すという経験をしたことはないのだけれど、ブックマークコメントにも 「/var/spool/cron 下だと見逃すことがある」 というようなことが書かれていたので、経験者的には /etc/cron.d の方が嬉しそう、という雰囲気を感じる。

もちろん root 権限が必要であるというような欠点もあるので、まあどっちにするかは組織での運用方針次第だろうなーという気がする。

(ちなみに私は /etc/cron.d 下に置くことが多い。)

*1:もしかしたら /etc/cron.d とかについても触れられていたかもしれないけど、ざっと見た感じなかったと思う。

*2:これも 「cron 周りのベストプラクティス」 に書かれてた。