スクリプト集

back

新山がふだん使っているスクリプト (shell, perl) をまとめたもの。

あなたの環境でそのまま動くとは限りません。


basenameのかわり

bash を使っているときは、 `basename $i` のかわりにシェルの変数置換: ${i/*\//} が使える。

拡張子だけを削りたいなら ${i/.*/} なども可能 (ただしこれは abc.def.htmlabc のみにしてしまうので注意)。


lessdir - ファイルに対しては less、ディレクトリに対しては ls を起動

(実際には新山は「l」という一文字のコマンドにしている、 less のかわりにいつもこれを使っている)

#!/bin/sh

# 環境変数 $PAGER に値が設定されてない場合は、
# デフォルトとして less を使う。
PAGER=${PAGER-less}

# このスクリプトに与えられた引数を見る。
if [ $# -eq 0 ]; then
  # 引数がない場合、hoge | lessdir のように起動されたのだと考える。
  # $PAGER を起動そのまま起動しておしまい。
  # あとはこいつが、このスクリプトの標準入力を継承するだろう。
  exec $PAGER
elif [ -d "$1" ]; then
  # 最初の引数がディレクトリを表していれば、
  # ls モードと解釈する。引数ぜんぶ (最初のも含め) ls に渡しておしまい。
  exec ls -F $*
else
  # それ以外なら、ページャとして扱われたのだと考える。
  # 引数ぜんぶ (最初のも含め) $PAGER に渡しておしまい。
  exec $PAGER $*
fi

lessdirauto - lessdir の改良版。端末の大きさに応じて cat と less を使い分ける

(表示するファイルすべてを一時的に /tmp 以下に保存するので、 あまり長いファイルの表示には向いていない)

#!/bin/sh

# 一時ファイルの名前を変数 t に入れる
# この名前は重複してはまずいので、とりあえず$$を使って
# プロセスIDを含めておく。
t=/tmp/L$$

# 途中でこのプロセスが異常終了したときに、
# 一時ファイルを自動的に消すようにtrapを仕掛ける。
cleanup() {
  rm $t; exit 1;
}
trap cleanup INT HUP TERM;

# 実際に表示する内容を、$t で表される一時ファイルに入れる。
# この部分は lessdir と同じだが、exec は使わない。
if [ $# -eq 0 ]; then
  # 引数がない場合、hoge | lessdirauto のように起動されたのだと考える。
  # cat にこのスクリプトの標準入力を継承させ、入ってきたものを全部
  # $t に吐かせる。
  cat > $t
elif [ -d "$1" ]; then
  # 最初の引数がディレクトリを表していれば、
  # ls モードと解釈する。ls の結果をまず全部 $t に吐かせる。
  ls -oF $* > $t
else
  # それ以外なら、ページャとして扱われたのだと考える。
  # 表示するファイルを全部 $t に吐かせる。
  cat $* > $t
fi

# (端末の行数 - 2) を変数 height に入れる。
# stty size は「25 80」のように「行数 桁数」の順で出力するので、
# それをまずいったん配列に入れて、最初の要素 ($1) だけを取り出すことで
# これが実現できる。あとは expr に渡して 2 だけ引いてやる。

# (stty を普通に呼ぶと、標準入力がパイプからのリダイレクトであるときに
# うまく動作しない。そこで、/dev/tty をリダイレクトすることで無理矢理端末を
# 見させるようにする。)
set -- `stty size </dev/tty`
height=`expr $1 - 2`

# 一時ファイル $t の行数を変数 lines に入れる。
# wc は「12 435 7880 filename」のように「行数 単語数 文字数 ファイル名」の
# 順で出力するので、これも最初の要素だけを取りだせばよい。
set -- `wc $t`
lines=$1

# 表示する行数 (lines) が端末の行数 (height) より多ければ less -X を、
# そうでなければ cat を使う。
if [ $lines -gt $height ]; then
  less -X $t;
else
  cat $t;
fi

# 最後に一時ファイルを消して、おしまい。
rm $t;

showhelp - コマンドのヘルプを表示する

(よくコマンドによっては、オプションの長いヘルプを標準エラー出力に 吐くので、less で見ようとしても消えてしまう。このコマンドは showhelp cdparanoia などとやると、 単に cdparanoia --help を実行してその標準出力および 標準エラー出力を lessdirauto (上記参照) に流すだけのもの)

#!/bin/sh

# 与えられたコマンド文字列に --help という引数をつけて実行する。
# このとき標準エラー出力を標準出力にリダイレクトし、エラーメッセージも
# 一緒にパイプを流れるようにする。
$* --help 2>&1 | lessdirauto

querycode - ファイルの漢字コードを判定する

(新山は自分の書いた web ページの漢字コードが 全部 jis (iso-2022-jp) かどうかをチェックするのにこれを使っている)

#!/bin/sh

# すべての引数についてチェック。
for fname in $*; do
  # 安全のため、そのファイルが「普通の」ファイルであることを確認する。
  if [ -f $fname ]; then
    # ここで nkf を使って、そのファイルを各漢字コードに変換してみる。
    # 変換結果と元のファイルを cmp で比較し、同じならば
    # nkf に指定した漢字コードを使っていることになる。
    # ファイルに日本語が含まれていない場合は、
    # デフォルトで jis と判定される。
    # これが嫌な場合は nkf の順番を変えること。
    if nkf -j $fname | cmp -s $fname; then
      # ファイル名を一緒に出力してもいいかも。
      echo jis
    elif nkf -s $fname | cmp -s $fname; then
      echo sjis
    elif nkf -e $fname | cmp -s $fname; then
      echo euc
    else
      # どの漢字コードでもなかった場合。
      echo unknown
    fi
  fi
done

rsync のラッパ

#!/bin/sh
exec rsync -Cauvbz --delete --backup-dir ~/.old \
        --exclude 'LOCAL' --exclude 'local' \
        --include '*.obj' --include '*.Z' \
        $*

ホームディレクトリ上の特定のディレクトリをリモートホストへ転送

get host1:dir とすると、host1 上の ~/dir の変更点が ローカルな ~/dir に反映される。

put host1:dir とすると、ローカルな ~/dir の変更点が host1 上の ~/dir に反映される。

2つのマシンで作業するときに楽。

#!/bin/sh
cd
for i in $*; do
 h=`expr "$i" : '\(.*\):'`
 d=`expr "$i" : '.*:\(.*\)'`
 if [ ! "$d" ]; then d=.; fi
 echo $d
 if [ "$h" -a -d "$d" ]; then
  case $0 in
  *get) rs "$h:$d/" "$d/";;
  *put) rs "$d/" "$h:$d";;
  esac
 fi
done

ファイルを16進ダンプ

#!/usr/bin/perl
open(IN, "od -tx1 -Ax @ARGV |");
while(<IN>) { chop; @f=split(' ');
print $_,"  "; shift(@f); $s=''; foreach $i (@f) { $c=pack("H2",$i);
$c='.' if (ord($c)<32); $s.=$c; } print "   " x (16-@f); print "$s \n"; }

ファイルのパーミッションを 644 に

ときどき、 tar のソースツリーなどを展開すると パーミッションが 444 になっていてむかつくため。

#!/bin/sh
find $* -type f | xargs chmod 644 2>/dev/null
find $* -type d | xargs chmod 755 2>/dev/null

簡易サーバとクライアント

ucspi-stcp 使用。 プロトコルのチェック等に。

(まずこのスクリプト waitio をインストールしておく必要がある)

#!/usr/bin/perl
open(TTYIN,"<&6"); binmode(TTYIN); select(TTYOUT); $|=1;
open(TTYOUT,">&7"); binmode(STDIN); select(STDOUT); $|=1;
$rin='';
vec($rin,fileno(STDIN),1)=1; vec($rin,fileno(TTYIN),1)=1;
do {
 $n=select($rout=$rin,undef,undef,undef);
 if (vec($rout,fileno(STDIN),1)) { sysread(STDIN,$_,1024); s/\r//g; print TTYOUT $_; }
 if (vec($rout,fileno(TTYIN),1)) { exit if (!sysread(TTYIN,$_,1024)); s/\n/\r\n/g; print STDOUT $_; }
} while(1);

(サーバ側)

#!/bin/sh
p=${1-1111}
echo "waiting on $p..."
exec env - PATH=$PATH tcpserver -vRHl0 -c1 0 $p waitio 6</dev/tty 7>/dev/tty

(クライアント側)

#!/bin/sh
if [ $# -lt 2 ]; then echo "usage: client host port"; exit 1; fi
exec tcpclient -RHl0 -- $1 $2 waitio

TeX のコンパイルと xdvi 表示を自動でやる

stwm 専用。AucTeX に比べて何がよいかというと、ウインドウマネージャと 結託しているので、何度 xdvi を実行しても余計なウインドウが開かないこと。 後述の ftime 使用。

#!/bin/sh
# atex
wmcmd=stwmcmd
bibtex=${BIBTEX:-jbibtex}
latex=${LATEX:-platex}
xdvi=${XDVI:-xdvi}
mode=dvi

#  print usage
usage() {
  echo 'usage: atex [-fpdcj] filename
  -f: force compile
  -c: compile only
  -p: preview only
  -d: cleanup
  -D: cleanup including ps
  -j: use jlatex209
  -P[printer]: print'
  exit 1
}

#  check if it is new
isnew() {
  test `ftime -m $1` -gt `ftime -m $2`;
  return;
}

#  clean up
cleanup() {
  if isnew $1.tex $1.aux; then
    echo "cleanup $1.aux";
    rm $1.aux $1.bbl $1.blg $1.toc
  fi
  rm $1.log
}

#  preview
preview() {
  if [ "$xdvi" -a "$wmcmd" ]; then
    # run xdvi
    icon=`$wmcmd -t $xdvi q iconified`
    if [ "$icon" ]; then
      # xdvi already running
      if [ $icon = 0 ]; then
      # xdvi not iconified
      $wmcmd -t $xdvi stack 1
      $wmcmd -t $xdvi warp
      else
      # xdvi iconified
        $wmcmd -t $xdvi iconify 0
      fi
    else
      # xdvi not running
      $xdvi $fname.dvi >/dev/null 2>&1 &
    fi
  fi
}

#  compile
compile() {
  $latex $fname
  if [ `grep '^LaTeX Warning: Citation .*undefined' $fname.log | wc -l` -ne 0 ]; then
    $bibtex $fname
    $latex $fname
  fi
  if [ `grep '^LaTeX Warning: Citation .*undefined' $fname.log | wc -l` -ne 0 ]; then
    $bibtex $fname
    $latex $fname
  fi
}

#  print
print() {
  dvips $fname
  if [ $printer = 0 ]; then
    mpr $fname.ps
  else
    lpr -P$printer $fname.ps
  fi
}

#  get option
set -- `getopt P:fpdDcj $*`
if [ $? != 0 ]; then usage; fi
while [ "$1" != -- ]; do
  case $1 in
  -f)	mode=force;;
  -p)	mode=preview;;
  -d)   mode=clean;;
  -D)   mode=cleanps;;
  -c)	mode=compile;;
  -j)	latex=jlatex209;;
  -P)   mode=print; printer=$2; shift;;
  esac
  shift
done
shift
if [ ! "$1" ]; then usage; fi
cd `dirname $1`
fname=`basename $1 .tex`

#  main
case $mode in
compile)
  cleanup $fname
  compile;;
preview)
  preview;;
clean)
  rm -f $fname.dvi $fname.log $fname.aux $fname.bbl \
	$fname.blg $fname.toc;;
cleanps)
  rm -f $fname.dvi $fname.log $fname.aux $fname.bbl \
	$fname.blg $fname.toc $fname.ps;;
force)
  if compile; then
    preview
  fi;;
dvi)
  if isnew $fname.tex $fname.dvi; then
    cleanup $fname
    if compile; then
      preview
    fi
  else
    preview
  fi;;
print)
  if isnew $fname.tex $fname.dvi; then
    clean $fname
    if compile; then
      print
    fi
  else
    print
  fi;;
esac

dtar - フロッピーに tar 書きこみ

フロッピーに生のtarを書きこむスクリプト。 むかしは Mac で SUNTAR を使っていたため。

#!/bin/sh
case `hostname` in
godot)	dev=/dev/fd0;;	# linux
snow)	dev=/dev/fd0a;;
*)	echo "$0: not supported on this machine."; exit;;
esac;

cmd=$1;
if [ ! "$cmd" ]; then cmd=dir; else shift; fi;
case $cmd in
add)		exec /usr/local/bin/tar -L 1440 -r -v -f $dev $*;;
new)		exec /usr/local/bin/tar -L 1440 -c -v -f $dev $*;;
ext)		exec /usr/local/bin/tar -L 1440 -x -v -f $dev $*;;
dir)		exec /usr/local/bin/tar -L 1440 -t -v -f $dev;;
*)		echo "usage: $0 {add,new,ext,dir} file ...";
esac;

formv - ファイル名一括変換

sed を使っています。

#!/bin/sh
if [ $# -lt 2 ]; then echo 'usage: formv s/pat1/pat2/ file ..'; exit 1; fi
s=$1
shift
for i in $*; do
  j=`echo "$i"|sed "$s"`
  echo $i "->" $j;
  mv $i $j
done

ftime - ファイルの更新時刻を数値で表示

シェルスクリプトで make もどきをしたい場合に使う。

#!/usr/local/bin/perl

if ($ARGV[0] eq '-a') {
    @_ = stat($ARGV[1]);
    $a = $_[8];
} elsif ($ARGV[0] eq '-m') {
    @_ = stat($ARGV[1]);
    $a = $_[9];
} elsif ($ARGV[0] eq '-c') {
    @_ = stat($ARGV[1]);
    $a = $_[10];
} elsif ($ARGV[0]) {
    @_ = stat($ARGV[0]);
    $a = $_[9];
} else {
    $a = time();
}
if (! $a) { $a = 0; }
print "$a\n";

jlynx - 日本語入力可能な lynx を別ウインドウで起動

日本語の検索エンジンや掲示板に入力するときはコレ。

#!/bin/sh
export SHELL; SHELL=tcsh
kterm -xrm 'KTerm*VT100*translations: #override Ctrl<Key>j: begin-conversion(JAPANESE_CONVERSION)\n' \
	-T jlynx -n jlynx -geom 80x50-0-0 -e /usr/local/bin/lynx &

lpo - 汎用ファイル印刷

拡張子を見て ps, ps.gz, pdf, テキストを区別し印刷する。

#!/bin/sh
#  lpo:
#	-P printer	: speficy printer
#	-m		: speficy twocolumns
#	-1		: onecolumn
#	-p		: onecolumn, portlait-duplex
#	-2		: twocolumn, landscape-duplex

set -- `getopt P:m1d2 $*`
if [ $? != 0 ]; then
    echo "usage: lpo [-Pprinter] [-m1d2] [file ...]"
fi

p=pst
m=0
a=-np

while [ $1 != -- ]; do
  case $1 in
  -P)	shift; p=$1;;
  -m)	m=1;;
  -1)   m=0; a=-p; p=ps;;
  -p)   m=0; a=-p; p=psdp;;
  -2)   m=1; p=psdl;;
  esac
  shift;
done
shift;

spool() {
  if [ $m = 1 ]; then
    psmulti -nodecor $* | lpr -P$p $*;
  else
    lpr -P$p $*;
  fi
}

if [ $# = 0 ]; then
  spool; exit;
fi

for i in $*; do
  case $i in
  *.ps)
    spool $i;;
  *.ps.gz)
    gzip -dc $i | spool;;
  *.pdf)
    t=/tmp/prt.$$
    acroread -toPostScript -size a4 -shrink -pairs $i $t
    spool $t
    rm $t;;
  *.dvi)
    /usr/local/bin/dvips -f $i | spool;;
  *)
    if [ "X$a" = "X-np" ]; then m=0; fi
    a2ps $a $i | spool;;
  esac
done

mj - ^M を ^J に変換

むかし、Mac でテキストを書いていたときに使っていたんよ。

#!/bin/sh
if [ ! "$1" ]; then echo "usage: mj filename ..."; exit; fi
while [ "$1" ]; do
  if [ \( -h "$1" \) -o \( ! -f "$1" \) ]; then
    echo "mj: $1 is not regular file.";
  else
    echo "mj: convert: $1"
    f=/tmp/mj.$$;
    mv $1 $f;
    tr '\^M' '\
' < $f > $1;
    rm $f;
  fi;
  shift;
done

sig - シグネチャを表示

メールを書くのに .signature ファイルは使っていません。 かわりにこれを使ってファイルをバッファに取りこみます。 こうすると切り換えもできて便利。

#!/bin/sh
case $1 in
x)	echo '--
 東京工業大学 情報理工学研究科 計算工学専攻
 新山 祐介  euske@cl.cs.titech.ac.jp';;
e)	echo 'SHINYAMA Yusuke
--
 Dept. of Computer Science, Tokyo Inst. of Technology,
 2-12-1, Ookayma, Meguro-ku, Tokyo, 152-8552, JAPAN
 E-MAIL: euske@cl.cs.titech.ac.jp
 FAX: +81 3-5734-2915';;
*)	echo '--
 新山 祐介  euske@cl.cs.titech.ac.jp';;
esac;

(で、 .emacs には次のように書いておく)

;;  signature
;;
(defun sig () (interactive) (call-process "sig" nil t t))
(defun xsig () (interactive) (call-process "sig" nil t t "x"))
(defun esig () (interactive) (call-process "sig" nil t t "e"))

xrsh - xon の kterm版

デフォルトで xterm でなくて kterm を起動する。あと、プロセス ID を 表示するのとエラー出力を ~/.xsession-errors に追加するのが便利。

#!/bin/sh
if [ ! "$*" ]; then
  echo "Usage: xrsh <hostname> [command ...]"; exit 1;
fi
if [ ! "$DISPLAY" ]; then
  echo "xrsh: Can't open display."; exit 1;
fi

target=$1; shift;
program="$*";
if [ ! "$program" ]; then
  program="kterm -T kterm@$target -n $target";
fi;
errorfile="$HOME/.xsession-errors";

rsh $target "sh -c 'SHELL=$SHELL DISPLAY=$DISPLAY $program >>$errorfile 2>&1 & echo \"xrsh: $target: \$!\"'";

Last modified: Mon Apr 29 15:33:47 2002
Yusuke Shinyama