atコマンドを使ってタスクを実行してみる

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
アット

この記事を三行にまとめると

エロいメールが誤送信されちまったぁぁぁ
単発処理ならatを使う方が良いでしょう
まさにあっと驚くタメゴローでした
サーバーで定期的に実行したい処理があるときに、cronを使うことがありますよね。例えば毎日0時にデータベースのバックアップを取るとか。

では定期的じゃなくて、単発で一回だけ処理を実行させたい時はどうするか。

そういう時には「at」というコマンドを使うという手があります。まさしく指定した時間に一度だけ処理を実行してくれるコマンドです。

cronで1月20日の0時に実行するみたいなことも設定できますが、年までは指定できないので、ほっとくと来年の1月20日にも同じ処理が走ってしまう。一年前にポッと作った処理なんて覚えてないかもしれないから、「去年彼女に送ったエロいメールが取引先に一斉に誤送信されちまったぁぁぁ」みたいな事態が発生するかもしれない。

わざわざcronを使って彼女にエロいメールを送るのもどうかしてますが、いずれにしても単発処理ならatを使う方が良いでしょう。

macのターミナルなどからSSHでサーバーに接続できているという前提でお話ししますが、その点はご了承くだせえ。



基本的な使い方

一番基本的な使い方としてはこんな↓感じです。

$ at 18:00

atコマンドを使用する時は時刻の入力が必要です。上記の場合は「今日の18時に処理を実行させます」という設定。

atコマンドを実行すると、「at>」という文字が画面に出てきます。ここに実行したい処理内容を書きます。例えばlsコマンドを実行させたいのならこう。

$ at 18:00
at> ls

エンターを押すともう一度「at>」が画面に出てきます。もし他に実行したいものがなければ、CtrlキーとDキーを同時押しで処理を終了できます。

$ at 18:00
at> ls
at> Ctrl+D

これで18時にlsコマンドを一度だけ実行するというジョブができました。



作成されたジョブの一覧を見るには、atコマンドに「l」オプションをつけるか、「atq」というコマンドを実行します。まだ実行されていないジョブの一覧が表示されます。

$ at -l
$ atq

//実行結果
1 2017-01-20 18:00 a root
2 2017-01-20 19:00 a root

「1」とか「2」はジョブ番号です。ユニークなIDみたいなもんかな。ジョブを削除したい場合などに使用します。一番後ろにあるrootはこのジョブを実行するユーザーです。ユーザーの前にある「a」は……なんだろ?

この一覧表示はジョブ番号や実行日時は確認できますが、実際にどんな処理を実行するかまでは分かりません。lsコマンドを実行するのかもっと他の処理をするのかは上記のどこにも書いてないですね。

もしそれを確認したければ、「/var/spool/at/」というフォルダの下にジョブごとの実行ファイルが作成されているので(サーバーの設定によっては他の場所にあるかもしれませんが)、そのファイルの中を見れば分かります。このファイルはジョブが実行されると自動的に削除されます。



ジョブを削除するには、dオプションとジョブ番号をつけてatコマンドを実行します。

$ at -d 1

これで番号1のジョブを削除できます。あるいは先ほどの「/var/spool/at/」の中の実行ファイルを直接削除してもジョブは消えます。コマンドだと一件ずつしか削除できないっぽいので、一括で削除する場合は実行ファイルをrmコマンドなどでまとめて削除するしかないみたいです。まあうっかり消しちゃダメなファイルまで消してしまうおそれがあるので、あまりやらない方が良いかもですけど。



日にちの指定

時刻だけ指定すると、今日のその時刻に実行されるという設定になります。今日以降の別の日に実行させたければ、時間の後ろに年月日の入力も必要。

$ at 18:00 01/31/17

これだと2017年の1月31日の18時に実行されます。

年月日の入力形式はいくつかあるみたいですが、「mm/dd/yy」「mm.dd.yy」など、基本的に「月、日、年の下2桁」の順で書くっぽいです。



ファイルを読み込む

atコマンドの入力時に実行したい内容を直接書いても良いですが、ファイルを読み込んでジョブを作成することもできます。

$ at 18:00 -f /var/www/sample.sh

「f」オプションの後にファイルのパスをつけると、そのファイルの中身を使ってジョブが作成されます。いろんな人の情報を参考にする限りでは、拡張子がshのシェルスクリプトのファイルを読み込むようにと書いてあるのですが、僕が試した限りでは別にtxtファイルとかでも大丈夫でした。もしかしたらできるけど推奨されないやり方なのかもしれませんが。

$ at 18:00 -f /var/www/sample.txt

//sample.txtの中身
ls

こんな風にsample.txtの中身を書いとけば、先ほどと同様に18時にlsコマンドが実行されるジョブの出来上がりです。fオプションを使った場合は「at>」が出てきません。



CakePHPのシェルを実行する

CakePHPでシェルを作成してatコマンドで実行する場合もやり方は一緒です。

$ at 18:00
at> /usr/bin/php /var/www/cakephp/Cake/Console/cake.php Sample execute -app /var/www/cakephp/app
at> Ctrl+D

///var/www/cakephp/app/Console/Command/SampleShell.php
App::uses('Shell', 'Console');
class SampleShell extends Shell {
  function execute() {
    〜 処理を書く 〜
  }
}

これで18時にSampleShell.phpのexecute()の処理を行うことができます。

ちなみにここでは「/var/www/cakephp」の下にCakePHPの本体が入っているという前提ね。あとシェルの書き方はCakePHP2系の書き方です。2系と3系のシェルの書き方の違いについてはこちらをどーぞ。

CakePHP3を触ってみました 〜シェル&マスク〜



fオプションでファイルを読み込んでCakePHPのシェルを実行させるならこうです。

$ at 18:00 -f /var/www/sample.txt

//sample.txtの中身
/usr/bin/php /var/www/cakephp/Cake/Console/cake.php Sample execute -app /var/www/cakephp/app



PHPでatコマンドを実行

ここまではターミナル上などでatコマンドを実行する場合の話でしたが、例えばphpの中でatコマンドを使用したい場合もあるかもしれない。

fオプションでファイルから処理内容を読み込むなら、普通にexec()などでatコマンドを実行できます。

exec('at 18:00 -f /var/www/sample.txt');

こんなんでOK。

でも最初に書いた「at>」に直接書く方の場合は、exec()などでは無理っぽい。

fオプションを使わないのであれば、exec()ではなくproc_open()という関数を使って書くと良いみたいです。proc_open()ってのは……えーとあれだ……うん、あれ。

正直僕もよく分かってないので、説明が欲しい人はマニュアルを読んでくれぃ。

proc_open

まあ、何はともあれ実際にやってみましょう。百聞は一見にしか何とやらって言うし。

$desc = array(
  0 => array('pipe', 'r'),
  1 => array('pipe', 'w'),
  2 => array('pipe', 'w'),
);

$proc = proc_open("at 18:00", $desc, $pipe);

if($proc) {
  //コマンドの登録
  fputs($pipe[0], '/usr/bin/php /var/www/cakephp/Cake/Console/cake.php Sample execute -app /var/www/cakephp/app');

  //パイプを閉じる
  fclose($pipe[0]);
  fclose($pipe[1]);
  fclose($pipe[2]);

  proc_close($proc);
}

細かい説明は抜き……というか、ぶっちゃけよく分かっていません。何せ以下の記事を参考(一般的には丸パクリと呼ぶ)にさせていただいただけなので。あざっす。

PHPを利用して crontab や at にジョブを登録する

とりあえずproc_open()を使うことで標準入力が可能な状態になるみたいなので、それで「at>」の時と同じようにコマンドを入力することができると、何かそんな感じだと思います、たぶん。標準入力っていうのは……何だろね。コマンドライン上での入力みたいな感じ? PHP上からコマンドラインに何かを入力して実行するみたいな言い方で良いのかな。どうだろう。

「おみゃーは何が言いたいんじゃ」って人は、標準入力とかstdinとかでググってみてくれぃ。



実行権限がない人

例えばatコマンドをapacheなどのユーザーで実行する場合。もしかしたらこんなエラーが出るかもしれない。

This account is currently not available.

簡単に言えば実行権限がない的なことのようです。

SSHなどでサーバーにアクセスすると、etcというフォルダに下に「passwd」というファイルがあると思います。名前の通りサーバーに登録されているユーザーのパスワードファイルなのですが、この中を見ると、apacheユーザーのところがこんな風に書いてあるかもしれない。

apache:x:48:48:Apache:/var/www:/sbin/nologin

この「/sbin/nologin」ってのがあると、上記のようなエラーが出るみたいです。

こういう場合はusermodコマンドを使って、ユーザーの情報を変更してあげれば解決します。

$ usermod -s /bin/bash apache

passwdファイルを直接編集しても書き換えられるようだけど、うっかり変に書き間違えちゃったりすると「あれれ〜? サーバーに入れなくなっちゃったぞー」みたいな不祥事を招く可能性もあるので、usermodでやる方が安心安全です。






そんなわけでatコマンドの使い方でしたー。

正直な話、こんなコマンドがあることをつい最近まで知りませんでした。でも定期的にではなく一回だけサーバーで処理を実行する方法が必要になりまして、最初はcronを上手く使って何とかしようなんて考えてたんですが、一応調べてみたらatというコマンドの存在に気づきまして、まさにあっと驚くタメゴローでした。

上記のproc_open()を使ったやり方を紹介してくださっている記事の中では、ジョブ番号を取得するところまで書いてくれているので、それをデータベースに登録しておけば、あとでPHPからジョブを削除することも可能だと思います。あとでatで。

ではでは、今日はこの辺で。
 もしかしたら何か関連しているかも? 
 みんなからのコメント 
2017年10月28日 16:42:48
T
勉強になりますたm(__)m
文も面白いのでニヤニヤしながら読ませて頂いてます!!
有難う御座います!
2017年10月28日 21:28:29
まっち~(管理人)
お役に立てて何よりです。
文面もお褒めいただきまして二倍嬉しいです。
こちらこそありがとうございますm(_ _)m
2018年08月14日 21:35:58
匿名
助かりました。権限で引っかかってました
2018年08月14日 22:37:48
まっち~(管理人)
いえいえ、無事に解決できたみたいで良かったです。