
以前、作業ディレクトリを切り替えるシェルスクリプトという記事を書いたのだが、いろいろと自分の使いやすいように変更を加えていった結果、実際はだいぶ違ったやり方をしているので改訂版的なものを紹介したい。
作業ディレクトリを移動するシェルコマンドとは cd (チェンジディレクトリ)のこと。
ls (リスト表示)と並んで最も頻繁に使用するコマンドだ。
今回のカスタマイズ要件は以下のとおり。
- $ mo 移動したいディレクトリのキーワード
というコマンドで予め登録しておいたディレクトリへ移動したい( mo は move の略) - 上記コマンドで移動完了後は自動的に続けて $ ls -F コマンドを実行
手を加えるのは以下2ファイル。
- 任意のフォルダに置いた disp.scm というスクリプト。
これは与えられたキーワードとリレーションしているディレクトリ(フルパス)を返す。
Gauche(Schemeの処理系)で動く。(いきなりニッチなものを取り出して申し訳ないので、後で Perl で書き直したものも紹介する) - disp.scm を実行した後、$ ls -F コマンドを実行するシェル関数を ~/.bash_profile に書き込む
さて、まずは Scheme で次のような内容のファイルを作成して disp.scm として保存しておく

#! /opt/local/bin/gosh
;; Scheme(Gauche)では main という名前の関数が
;; このスクリプトの呼び出し時に実行される
;; ちなみにコマンドラインオプション(キーワード)は
;; リスト args の cdr 部に格納されている
(define (main args)
(cond [(null? (cdr args)) (format #t "~a" ".")] ;; キーワードが空だったらカレントディレクトリを返す
[(equal? (cadr args) "all") (disp-all *alist*)] ;; キーワードが "all" だったら *alist* をそのまま表示
[else (disp (cdr args))])) ;; キーワードにひも付けされていディレクトリを返す
;; *alist* からキーワードと紐付いているディレクトリを探し出す関数
(define (disp args)
(cond [(null? args) (format #t "~a" ".")]
[(pair? args) (cond [(assoc (car args) *alist*)
(format #t "~a" (cdr (assoc (car args) *alist*)))]
[else (disp (cdr args))])]))
;; *alist* をそのまま表示する関数
(define (disp-all alst)
(cond [(null? alst) #f]
[else (and (print (car alst)) (disp-all (cdr alst)))]))
;; これが *alist* 本体
;; (key . dir) のペアを必要なだけ登録しておく
;; 後から追加したければこの *alist* に追加すればいい
;; ※以下は単なる例です。
(define *alist*
'(("dropbox" . "/Users/user_name/Dropbox")
("works" . "/Users/user_name/dir/works")
("blog" . "/Users/user_name/Documents/blog")
("dl" . "/Users/user_name/Downloads")
)
)
仮に上記の disp.scm を /Users/user_name/dir/disp.scm に保存したとしよう。
あなたの Mac に Gauche (コマンドは gosh )がインストールされていれば、
$ chmod 0755 /Users/user_name/dir/disp.scm $ /Users/user_name/dir/disp.scm works /Users/user_name/dir/works
と動いてくれるはずだ。
ここまで来れば、後は簡単。
~/.bash_profile を開いて、mo という名前の関数を書き込んでやればいい。

mo() {
cd $(/Users/user_name/dir/disp.scm $1)
ls -F
}
cd $(/Users/user_name/dir/disp.scm $1) の部分は少し説明が要るかもしれない。
シェルスクリプトでは関数が受け取った引数は自動的に $1 など「$」の後に数字が続く変数に格納される。
ちなみにこの mo 関数は $1 しか相手にしていないので最初の引数以外は無視される。
もちろん引数 $1 にはキーワードが入る。
もう一つ。シェルスクリプトでは $(Command) のように、「$」の後の () 内でコマンドを実行できる。
実行されたコマンドのリターン値(この場合 disp.scm が返したディレクトリ)がそのまま変数を展開した結果として親のコマンド(この場合 cd)に渡される。
~/.bash_profile に mo 関数を設置したら再読み込みして関数が動くか試してみよう。

$ source ~/.bash_profile $ mo works ... # ls が実行されるはず $ pwd /Users/user_name/dir/works
うまく動作しただろうか?
付録:disp.scm を Perl で書き直してみた
Scheme は個人的に大変好きな言語なのだが、なかなかにマイナーなので、Perl で書きお直したバージョンを載せておく。
改めて書いてみて気付いたが、この程度のスクリプトなら Perl の方が簡潔だ。

#! /usr/bin/perl
use warnings;
use strict;
# コマンドラインオプションからキーワードの読み込み
# @ARGV が空だったら . (カレントディレクトリ)を返して終了
my $keyword = shift @ARGV or (print "." and exit);
# キーワードとディレクトリのハッシュ
my %alist = (
"dropbox" => "/Users/user_name/Dropbox",
"works" => "/Users/user_name/dir/works",
"blog" => "/Users/user_name/Documents/blog",
"dl" => "/Users/user_name/Downloads",
);
# キーワードごとの処理
if (exists $alist{"$keyword"} ) {
# %alist にキーワードとマッチするペア発見
print $alist{"$keyword"};
}
elsif ($keyword eq "all") {
# キーワードが "all" だったら %alist の中身を表示
while (my ($key, $dir) = each %alist) {
print "$key => $dir\n";
}
}
else {
# キーワードにマッチするものが無い場合
# . (カレントディレクトリ)を返す
print ".";
}
これを disp.pl などとして保存すれば、前述の disp.scm と置き換えて使えるはず。
