普段 emacs を使っている人に質問なのですが、
root 作業するときどうしてますか?
私は、GNU Screen
の中で emacs をずーっと立ち上げっぱなしにしていて、
ほとんどの作業を emacs の中で行なっています。
もちろんコマンドラインから何かを実行するときも、
emacs の shell モード (正確に言うと j-shell.el なんですが ^^;) の中で
行なっています。
いきおい、root になるときも shell モードで「su」を実行することになります。
で、root 権限でファイルを読み書きしようとしたとき、
どうするのがいいか、というのが問題です。
そんなの root で emacs を実行しておけばええやん、
という声が聞こえてきそうですが、
root 権限で常に emacs が動いている、というのは
想像するのもおぞましいですし、かといって
編集するたびに root で emacs を立ち上げるのは、
(起動に時間がかかるので) もっと嫌です。
そもそも root 権限で emacs (に限らずエディタならなんでも) を
立ち上げるには、
Screen の別ウィンドウで行なわなければならず、
ウィンドウの切替で作業がかなり煩雑になってしまいます。
ちなみに、ずーっと以前は、ange-ftp を使っていました。
つまり、emacs から root@localhost へ ftp して
(root の) ファイルを読んできて編集し、
保存するときも ftp で書込む。
この方法は root でログインする、
という気持ち悪さがもともとあったので、
ftpd を走らせなくなったのを機会に止めました。
で、それ以来使っているのが、
今回紹介する emacsclient を使う方法です。
もしもっといい方法があるぞっ、というかたがいらっしゃいましたら、
是非教えてください (_O_)。
- o -
emacs 上で「M-x server-start」と入力すると、 emacsserver (gnuserv) を
走らせておくことができます。
この状態で、コマンドラインから
% emacsclient ファイル名
などと emacsclient (gnuclient) を実行すると、
引数に指定したファイルを、emacs で編集することができます。
したがって、root 権限でファイルを編集するときは、
まずファイルを emacs で読み書きできるようテンポラリファイルへコピーし、
それを emacsclient で開き、
テンポラリファイルが変更されたら、
それを元のファイルへ root 権限で書き出せばよいことになります。
私は suemacs と名付けた以下のような perl スクリプトを書いて使っています。
例えば
# suemacs /root/.cshrc
などと実行すると、
/root/.cshrc の内容が /tmp/suemacs5544/.cshrc_0 へコピーされ、
emacsclient /tmp/suemacs5544/.cshrc_0
が実行されます。
suemacs スクリプト
#!/usr/bin/perl
$user = $ENV{'LOGNAME'};
$tmp = "/tmp/suemacs$$";
$Debug = 0;
$Once = 0;
use POSIX ":sys_wait_h";
use Getopt::Std;
getopts('do') || &help;
$Debug = 1 if $opt_d;
$Once = 1 if $opt_o;
sub help {
print <<EOF;
Usage: suemacs <opt> <file>...
opt: -o ; write once on closing
-d ; for debug
EOF
exit 1;
}
if ($> != 0 || ! $user) {
exec "emacsclient", @ARGV;
}
($login, $pass, $uid, $gid) = getpwnam($user) or die;
print "login: $login, uid: $uid, gid: $gid\n" if $Debug;
umask 077;
mkdir $tmp || die;
chown $uid, $gid, $tmp;
for ($i=0; $i < @ARGV; $i++) {
my $tmpfile = $ARGV[$i];
$tmpfile =~ s@.*/@@;
$tmpfile = "$tmp/${tmpfile}_$i";
push @argv, $tmpfile;
my $mtime = &cp($ARGV[$i], 0, $tmpfile);
push @mtime, $mtime;
chown $uid, $gid, $tmpfile;
}
if (!$Debug) {
if (!fork) {
close(STDOUT);
close(STDERR);
open(">&STDOUT", "/dev/null") || die;
open(">&STDERR", "/dev/null") || die;
} else {
exit 0;
}
}
if (!fork) {
($(, $)) = ($gid, $gid);
($<, $>) = ($uid, $uid);
exec "emacsclient", @argv;
exit 0;
}
my ($ret);
do {
sleep 1;
$ret = waitpid(-1,WNOHANG);
print "ret: $ret, status: $?\n" if $ret > 0 && $Debug;
for ($i=0; $i < @argv; $i++) {
$mtime[$i] = &cp($argv[$i], $mtime[$i], $ARGV[$i])
if $ret > 0 || ! $Once;
unlink $argv[$i] if $ret > 0;
}
} until ($ret > 0);
rmdir $tmp;
exit 0;
sub cp {
my ($src, $stime, $dst) = @_;
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks) = stat($src);
if ($stime == $mtime) {
print "$src is not modified.\n" if $Debug;
return $mtime;
}
print "cp $src $dst\n" if $Debug;
open(SRC, $src) || die;
open(DST, ">$dst") || die;
while (<SRC>) {
print DST;
}
close(DST);
close(SRC);
utime $atime, $mtime, $dst;
return $mtime;
}