Rustでpwdの代替コマンドを作ったよ
こんにちは。元エンジニアの正訓(まさくに)です。ジョブチェンジして最近はマーケの勉強とかをしています。たぶんどの仕事もそうなんでしょうけど、エンジニアであった経験は何をやるにしても役立つものだなぁと実感しています。要件を決めるときとか、フローを最適化するときとか、マネジメントのこととか、組織設計のこととかです。
たぶんこれから先もエンジニアリング関係なく、色んなことをやっていくんだろうなぁと思うものの、おそらく基軸はエンジニアのような考え方で物事を進めていくのだろうと思いますし、情報はキャッチアップしていきたいと思います。なのでアドベントカレンダーにも依然として参加させてもらうことにしました。
pwd
読みにくい問題
そんなわけで僕はまだ割とiTerm2&zshを使っていて、結構シンプルにターミナルを使うのが好みだからか、あまりプロンプトを改造していないのですが、そのせいもあって pwd
コマンドを結構叩くんですね。常にプロンプトに表示させとくとかしてないので、今どこー? って結構なる。みんなそうなりません? 言うてそこまでならないか、まぁいいか。
そんで pwd
って、まぁだいたいパイプで通して何らかの処理に使うか、シェルスクリプト内で実行場所の確認、移動に使うか、目視確認して場所確認するかだとは思ってて、前者2つは機械的にプレーンな方がいいんですが、目視するとしたときに飾り気なさすぎることが前から気になっていまして、読まなきゃならないときに視認性が低い。
pwd
読みにくい問題
そこで Rust で代替のコマンドを作り始めようと考えました。いくつかのオプションを組み合わせることができるように考えます。とりあえずゴール地点は下記のように表示されるように実装します。(しました)
pwd
の仕様について
でもちょっと待って、僕らは pwd
の真価を引き出せているのだろうか、という疑問が湧きました。僕は pwd
のことについてシェルのビルトインである、ことくらいしか知りません。
もしかして視認性を上げるオプションとかはもうあって、実は自分以外の人たちはそれを使っているから気にならないんじゃなかろうか。それに pwd
の代替を作るとしたら本家の機能は踏襲しとかなければなりません。
そこで man
と Wiki を確認してみました。
PWD(1) General Commands Manual PWD(1)
NAME
pwd – return working directory name
SYNOPSIS
pwd [-L | -P]
DESCRIPTION
The pwd utility writes the absolute pathname of the current working
directory to the standard output.
Some shells may provide a builtin pwd command which is similar or
identical to this utility. Consult the builtin(1) manual page.
The options are as follows:
-L Display the logical current working directory.
-P Display the physical current working directory (all symbolic
links resolved).
If no options are specified, the -L option is assumed.
ENVIRONMENT
Environment variables used by pwd:
PWD Logical current working directory.
EXIT STATUS
The pwd utility exits 0 on success, and >0 if an error occurs.
EXAMPLES
Show current working directory with symbolic links resolved:
$ /bin/pwd -P
/usr/home/fernape
Show the logical current directory. Then use file(1) to inspect the
/home directory:
$ /bin/pwd
/home/fernape
$ file /home
/home: symbolic link to usr/home
(後略)
えー、フォーマットに関するものはなかったす。とはいえ、 -L
、 -P
といったオプションは知りませんでした。どんな挙動かというと、実際には下記のような挙動になっているようです。
ito.masakuni@n-p0015 % ls -l
lrwxr-xr-x@ - ito.masakuni 7 12 02:35 logical_dir -> /Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/physical_dir
drwxr-xr-x@ - ito.masakuni 7 12 02:35 physical_dir
ito.masakuni@n-p0015 % cd logical_dir #シンボリックリンクに移動
ito.masakuni@n-p0015 % pwd # オプションなしだと論理ディレクトリ
/Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/logical_dir
ito.masakuni@n-p0015 % pwd -P # -Pをつけると物理ディテクトリ
/Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/physical_dir
ito.masakuni@n-p0015 % pwd -L # -Lをつけると論理ディレクトリ
/Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/logical_dir
なるほど、確かにシンボリックリンクに移動したとき、pwdは基本的にそのシンボリックリンクの名前を表示しているし、 ../
とかしたときも物理ディレクトリの上へ行くこともないのは、 cd
も含めてこういうところで仕様が考えられていたからなんですね。
成果物
インストール
ごめんなさい、自分も詳しくないなか大変恐れ入りますが、みんなRust入れてビルドしてください。磨き込んでそのうち brew
とかで入れられると素敵ですよね。
git clone https://github.com/masakuni-ito/rpwd.git
cd rpwd
cargo install --path .
オプション
下記のディレクトリのなかで実行します。
ito.masakuni@n-p0015 % ls -l
lrwxr-xr-x@ - ito.masakuni 7 12 02:35 logical_dir -> /Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/physical_dir
drwxr-xr-x@ - ito.masakuni 7 12 02:35 physical_dir
ito.masakuni@n-p0015 % cd logical_dir #シンボリックリンクに移動
ito.masakuni@n-p0015 % pwd
/Users/ito.masakuni/Desktop/work/tmp/abc1/def2/ghi3/jkl4/mno5/logical_dir
-h, --help:ヘルプを表示する
オプションなし:現在の作業ディレクトリを表示する
pwd
コマンドと同様です。
-L, --logical:現在の作業ディレクトリの論理名を表示する
pwd
コマンドの -L
指定と同様です。
-P, --physical:現在の作業ディレクトリの物理名を表示する
pwd
コマンドの -P
指定と同様です。ちなみに physical
と logical
を両方設定するとどうなるかというと物理の方を表示します。これも pwd
と同じにしました。
-d, ---divide:余白のあるセパレータで作業ディレクトリを表示する
pwd
には存在しないオプションです。セパレータの両端に半角空白を入れることによって視認性を高めます。
-c, --color:色を付けて作業ディレクトリを表示する
pwd
には存在しないオプションです。ディレクトリごとに色をつけて視認性を高めます。今のところ色は固定なのでターミナルによっては表示が壊れたり見にくかったりするかもしれません。
-s, --stairs:階段状に作業ディレクトリを表示する
pwd
には存在しないオプションです。ルートから今いるディレクトリまでを階段状に表示して視認性を高めます。
実用
オプションは今のところ --help
以外は全盛りができます。
自分は下記のようにaliasを張って、 pwd
自体は生かしつつ、 p
で表示できるようにしています。
if type "$HOME/.cargo/bin/rpwd" >/dev/null 2>&1; then
alias p='rpwd --color --divide --stairs'
else
alias p='echo && c=`pwd` && ds=() && d=" " && while [ "$c" != "/" ]; do ds=(`basename $c` ${ds[@]}) && c=`dirname $c`; done && if [ ${#ds[@]} -eq 0 ]; then echo ${d}"/"; else for dir in ${ds[@]}; do echo "${d}"/${dir} && d="${d} "; done; fi && echo'
fi
後半のelseに入ってるのは、シェルスクリプトだけで stairs
を実現する試みです。詳しくはずっと前に下記に書きました。なんかすっごく pwd
に執着している人のように見えますが、そんなことはないです。
作ってみて思ったこと
一番時間をかけた――というより、いまでもあまり分かっていないのが本家 pwd
における -L
と -P
の意味というか意図です。シンボリックリンクの中に入って、その論理名が表示されるのはとても使いやすいからそれがデフォルトなのは理解できる、シンボリックリンクの中にいて、物理名を取得する必要性もありそうだから、それも理解できる。
でもそれだと -L
を明示的に使う必要なくない? 物理ディレクトリのなかにいて、論理名を表示させることはできませんし。と思って、zshのコードまで見に行って、下記のようになっていました。
zshではここみたいなんですが、
- 物理名を表示する
- オプションに
r
が指定されているとき。rってなに。 - オプションに
P
が指定されているとき。
- オプションに
- (環境変数PWDから取ってきた)論理名を表示する
- 関係するオプションが何も指定されていないとき。
- chaselinks が指定されていながら
L
が指定されているとき。
chaselinks は文字通り、「リンクを追う」ので物理名を PWD
にいれます。しかしその場合でも pwd
は、 -L
が指定されていれば論理名を表示する、ということらしいです。
chaselinks指定済みでも論理名が取得できる
なるほど、だとすれば一定理解できるのですが、zshの実装っぽかったので今回は実装しませんでした。
あと論理名の取得の仕方がRustでどうするのがベストか分からず環境変数PWDから持ってきています。
環境変数から持ってくるのどーなんだろう、とは思ったんですが、本家でも cd
とかで変更されるグローバル変数から設定していたので、ヨシとしました。
都合のよいところだけ都合よく拝借します。
他にもカラーは config で設定できたりすると素敵ですね、とか、そもそもテストとか書いてないんで顔向けできませんね、とかはあるんですが、ボチボチ改修していこう……。
まとめに代えて
3年くらい前から仕事でコードは一切書かなくなりました。 Rust をもっと実用レベルまで深めたいと思いつつ、何かを作っていかないと手になじまないですね。ずっとそんなことを思いながら、生成AIが出てきたので、今回もほとんどは生成AIと共作です。自分でちゃんと「Rustっぽさ」を理解できている気がしません。
やっぱり2025年は、弊社の第一選択言語を Rust にするぞ、ということを夢見て、ビジネスな人たちとエンジニアな人たちに総ボコられつつ(僕はそのための特別な訓練を受けているので大丈夫です)(弊社は暖かい会社です)、年末商戦の今年の目標達成に挑もうと思います。
pwd
の勉強になりました。 rpwd
、割と便利じゃない? というところで、マジでまだ終わってないし、きれいに締められるかは努力次第なのですが、本年もお世話になりました。
NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion
以下のツールを使うと簡単にhomebrewで入れられるようにできるらしいですよ!
マジすか!ありがとうございます!もうちょっと手直ししてやってみます。雑に作りすぎているので。