rcmdnk's blog
Last update

CRON-O-Meter

cronの設定をしておいたつもりが設定されてなかったのですが、 Vimの設定のせいだったのでそれについて。

Sponsored Links

MacのデフォルトEDITOR

MacのデフォルトのEDITORは/usr/bin/viに設定されています(少なくとも今のMacでは) 1

ただし、これはviになってますが実態はvimです。

なのでcrontab -eなどとしてエディタを呼ぶプロセスを立ち上げると vimを呼んで編集作業に入ります。

.bashrcなんかに

export EDITOR=vi

等と書けばどこの環境でもデフォルトEDITORが上書きされてこれが使われる様になります。

crontab -eでの編集

crontab -eでcronジョブを編集する際、Vim等で開くのは実際の設定ファイルではなく、 それのコピーを作って編集しcrontabがオリジナルファイルをアップデートする、 と言う形になっています。

オリジナルの設定ファイルはMacでは

/usr/lib/cron/tabs/USER

という様にユーザ名で保存されています 2 。 このファイル(tabsディレクトリ自体も)は管理者のみが見れる様になっていて、 無理やり見てみると

 # DO NOT EDIT THIS FILE - edit the master and reinstall.
 # (/tmp/crontab.pP2i90rWvI installed on Thu Jun XX XX:XX:XX 2014)
 # (Cron version -- $FreeBSD: src/usr.sbin/cron/crontab/crontab.c,v 1.24 2006/09/03 17:52:19 ru Exp $)
 ...

の様な感じで、編集しないでね、と言う文言が設定ファイルの最初に付け加えられています。

crontab -eとすると、まず、/private/tmp/crontab.XXXXXXXXXX(XXXXXXXXXXはランダムな文字列) というファイルを開いてそれを開きます。 3

:write等で保存すると、これらのテンポラリーなファイルがアップデートされるだけで、 オリジナルはそのまま。 最後に編集を終えて:quitした後にcrontabが内容をオリジナルの方へ持っていく 形になっています。

Vimのバックアップ機能

問題はVimのバックアップ機能との兼ね合いにあります。

Vimでは編集を始めるとき、通常バックアップを作っていて、その作り方によってこの様な 作業に影響が出ることがあります。

Vimにはbackupcopy(bkc)という設定項目があって、この値が

  • yes: コピーを作ってオリジナルのものを上書きする
    • Pros: リンクだとか所有者だとかのファイルのプロパティがそのまま保たれる。
    • Cons: コピーを作る分ちょっと時間がかかる。ファイルがリンクの時はバックアップ名がリンクの名前になる。
  • no: ファイルの名前を書いて新しいファイルに書く
    • Pros: 速い。
    • Cons: ファイルのプロパティが違うものになる場合がある。ファイルがリンクの場合、新たなファイルはリンクでなくなる。
  • auto: 適宜どちらかを選ぶ

となります。 yesnoでは

これだとよくわからないので実際にやってみます。

バックアップを作る設定でbackupcopyyesにしてみます。

.vimrc
1
2
3
set backup "keep a backup file
set backupdir=. "directory for a backup file
set backupcopy=yes "make copy for backup, and write to original one
$ touch test.txt
$ ls -i
10393293 test.txt
$ vim test.txt # and do at least ":write"
$ ls -i
10393293 test.txt 10393552 test.txt~
$ vim test.txt # and do at least ":write"
$ ls -i
10393293 test.txt 10393584 test.txt~

とバックアップファイルのinodeナンバーが変わっていきます。 (他のターミナルからVimを立ち上げて:wとするだけでも変わるのでそれで簡単に確認できます。)

今度はnoにしてみます。

.vimrc
1
2
3
set backup "keep a backup file
set backupdir=. "directory for a backup file
set backupcopy=no "rename file, and write to new one
$ rm -f test.txt*
$ touch test.txt
$ ls -i
10394581 test.txt
$ vim test.txt # and do at least ":write"
$ ls -i
10394782 test.txt 10394581 test.txt~
$ vim test.txt # and do at least ":write"
$ ls -i
10394919 test.txt 10394782 test.txt~

と、今度は元々のファイルのinodeナンバーを持ったバックアップファイルが出来て、 元々のファイルのところには新しいinodeナンバーを持った物が出来ている事が分かります。

この場合はautoで行うと、noの方と同じ結果になりました。

さらにリンクの場合、yesだと

$ rm -f test.txt*
$ touch test.txt
$ ln -s test.txt zzz.txt
$ ls -i
10401021 test.txt 10401335 zzz.txt
$ vim test.txt # and do at least ":write"
$ ls -i
10401021 test.txt 10401335 zzz.txt 10401721 zzz.txt~
$ ls -l
... test.txt
... zzz.txt -> test.txt
... zzz.txt~

となり、新しいバックアップファイルが追加されただけの形。 一方、noだと

$ rm -f *.txt*
$ touch test.txt
$ ln -s test.txt zzz.txt
$ ls -i
10402011 test.txt 10402334 zzz.txt
$ vim test.txt # and do at least ":write"
$ ls -i
10402011 test.txt 10402631 zzz.txt 10402234 zzz.txt~
$ ls -l
... test.txt
... zzz.txt
... zzz.txt~ -> test.txt

となり、リンクだったzzz.txtは実態ファイルになり、 リンクはバックアップの方に移ってしまいます 4

こちらはファイル構造自体を崩してしまうので、 シンボリックリンクがある環境で編集するときにset backupcopy=noにしておくのは ちょっとむずかしいと思います。

この、リンクに対する物をautoでやってみると yesと同じ様になり、確かに適宜良い方を選んでる感じです。

デフォルトではbackupcopyautoになってるので、 普段使いでは問題なく、速く出来るところは速く行える様に適宜選択されている様です。

crontab -eの編集でVimを使う時の問題点

この様なVimの仕様があるので、 crontabの様なコマンドの中で使われるときには問題が起こる様です。

Vimで:help crontabにも書いてありますが、 問題が起こるのは上のbackupcopyの値がnoの時です。

autoにしておいても、crontabの中でVimが開くファイル自体は一度コピーされた 通常ファイルなので、上でやったようにnoと同じ動きをするので問題になります。

原因としては上にある様に、本来crontabがコピーしてきたものが Vimによって名前を変えられ、同じ名前のものとして新しいファイルを作るので、 所有者などのファイルのプロパティが変わってしまい正しく処理出来なくなります。

バックアップを作る設定でbackupcopynoautoにしていると crontab -eで編集し終わって終了するときに

crontab: temp file must be edited in place

と言われ、変更が反映されずに終わってしまいます。 (さらに/private/tmp/crontab.XXXXXXXXXXが残る。。。)

対処法

対処法としては、

  • backupcopyを常にyesにする
  • バックアップを作らない

の二つが考えられます。

どちらの場合も、すべての場面で適用してしまうとせっかく賢い機能が ごくたまにしか使わないcrontabだけのために失われてしまうので 失われてしまうので、BufReadなどを使って、

autocmd BufRead /private/tmp/crontab.* set backupcopy=yes

とすれば5crontabの時だけ強制的にバックアップをコピーで作るように出来ます 6

バックアップ自体をしない場合には

autocmd BufRead /private/tmp/crontab.* set nobackup nowritebackup

の様に、backupwritebackupの方もnoに設定しておきます 7

また、もう一つbackupskipというオプションがあって、に関するオプションがあって、 これを

set backupskip=/private/tmp/crontab.*

とすると、これに該当するファイルを開くときにはバックアップをしなくなります。 このオプションがある意味を考えるとBufReadでバックアップをオフにするよりは これを使うべきかも。

ただ、backupskipには予め/tmp/*等幾つかの値が含まれている (Windows環境とLinuxで違うものだったり環境を考慮したものにもなってる)ので、 これを残すために

let &backupskip="/private/tmp/*," . &backupskip

の様letを使って/private/tmp/*を加えて上げる方が良いと思います。

追記: 2017/05/30

letをわざわざ使わなくても

set backupskip+=/private/tmp/*

+=することでフォルダを付け足すことが出来るのでこちらを使った方が良いです。 (コメントありがとうございます。)

追記ここまで

この他にもバックアップのディレクトリ指定のbackupdirを空に(set backupdir=) するとこの場合もバックアップは無効になります。

GNU版、BSD版の違い

Macでは余りcrontabを使わないので、 以前の環境ではvimの設定をする前に書いたのか問題なく書けたて気がしますが、 途中からアップデートしたものとか実は有効でなかったのかもしれません。 (dotfiles等のGitレポジトリののアップデートくらいでしかしてない上、 普段少し変更するとコメントを残すためになるべく手でcommit & pushしてたので 確認せずに気づかなかっただけかも。。。)

ただ、Linux環境ではよく使っていて、きちんと動いてることも確認していますし、 ちょっとやってみたら同じ.vimrcでも問題なかったので少し違いを見てみました。

まず、cronコマンド自体がGNU版、BSD版があるのでそれぞれで微妙に違います。

  • 設定ファイルの場所
    • BSD: /usr/lib/cron/tabs/USER
    • GNU: /var/spool/cron/crontab/USER
  • GNU版の方には-i(-r時にy/Yと確認するようになる)、-s(SELinux様セキュリティ設定?) のオプションがある。

みたいな感じ。この辺りは普段それほど気にするところでは無いと思いますが、 注釈等にも書いてあるように、Linux環境ではテンポラリーなファイルが /tmp/crontab.XXXXXXXXXXに作られます。 Macと違ってこちらの/tmpは通常ディレクトリなので、backupskipでの指定も /tmp/*となってればスキップするようになります。

Vimでのデフォルトに/tmp/*が含まれているので、これで問題はない、ということも言えます。

ただ、気になってこれを敢えて外して(set backupdir=と書いて)試してみたところ それでも問題なくcrontab -eで更新出来ました。

色々いじってみて気づいた違いとして、backupを取らない様な状態で、

  • BSD: :wqと保存して終了した場合でも、元のファイルとの違いがなければcrontab: no changes made to crontab
  • GNU: 元のファイルとの違いが無くても:wqと保存して終了するとcrontab: installing new crontab

と、少し違いがあります。BSDの方は一度ファイルを保存しても その後ファイル比較を行って変更がなければcronの本来のファイルを変更しないみたいですが、 GNUの方は有無を言わず上書きしています。

さらに、GNU(Linux)の方でset backupset backupcopy=noを設定して、 set backupskip=として必ずバックアップを作るようにしても 問題なくアップデート出来ました。 (/tmpを見るときちんとバックアップが残っています。)

なので問題が起こるのは、BSDの方では、ファイルの中身やプロパティをきちんと比較しようとして いるために問題が起こる一方、 GNU版では変更は特にチェックせずにテンポラリファイルが一度でも保存されたら 単に中身だけをコピーする、みたいな事をしているから問題が起こらない、という感じです。

Sponsored Links
  1. Linuxなんかだと大概edコマンド。

  2. Linux(GNU版?)だと/var/spool/cron/crontab/USER)

  3. Linux(GNU版cron?)だと/tmp/crontab.XXXXXXXXXX。 Macでも/tmp/private/tmpへのシンボリックリンクになっているので /tmp/crontab.XXXXXXXXXXにも見えます。

  4. この場合もバックアップファイルの名前はリンクの名前になるので、 上のyesの場合の悪い点としてファイルの名前の事をあげてる意味がよくわかりません。。。

  5. ここで/private/tmpでなくて、/tmp/crontab.*でも大丈夫です。 ちょっと良く理解してませんが、BufReadBufEnterで使う際には どちらでも有効になりました。

    ただ、下に書いてある様にbackupskipの方の設定では privateの方しか効きませんでした。

  6. crontab時に他のファイルを参照することもあるかもしれないので、 setlocalを使ってる例なんかもありましたが、 backupcopyはバッファ毎には出来ない設定のようで、setlocalを使っても グローバルな設定になるみたいです。

    バッファ毎に出来るものと出来ないものがある事自体初めて知りました:

    Vim: :h option-summary

  7. backupが設定されてる時はwritebackupの値にかかわらず、 ファイルを保存するときに常に バックアップが作られ、保存後も残ります。 古いバックアップファイルがある場合は消してから新たなバックアップファイルを作ります。

    backupnobackupに設定され、writebackupが設定されてる時には、 ファイルを保存する時にはバックアップが作られますが、 保存が成功するとそのバックアップファイルは消されます。 (なのでよほどサイズの大きいファイルを保存しようとして時間がかかったりしない限り ~ファイルは見えません。) また、この場合にも、古いバックアップファイルがある場合には先にそれを消し、 後には残らない様になります。

    nobackupかつnowritebackupが設定されているときはこのバックアップは行われません。

    一方で、swapfileオプションというのがあって、 こちらは編集中常にファイルの変更を監視しながらswapファイル(バイナリファイル)を作って、 クラッシュ時等にそれを元に復活出来る事があります。 swapfileを有効にしていればwritebackupだけ有効にしておいても あまり意味が無い気もします。。。

Sponsored Links

« Vimをコマンドラインから使ってまとめて複数コードファイルのインデントを調整する 古いMacの消去 »