🦀

Rustでpwdの代替コマンドを作ったよ

2024/12/07に公開
2

こんにちは。元エンジニアの正訓(まさくに)です。ジョブチェンジして最近はマーケの勉強とかをしています。たぶんどの仕事もそうなんでしょうけど、エンジニアであった経験は何をやるにしても役立つものだなぁと実感しています。要件を決めるときとか、フローを最適化するときとか、マネジメントのこととか、組織設計のこととかです。

たぶんこれから先もエンジニアリング関係なく、色んなことをやっていくんだろうなぁと思うものの、おそらく基軸はエンジニアのような考え方で物事を進めていくのだろうと思いますし、情報はキャッチアップしていきたいと思います。なのでアドベントカレンダーにも依然として参加させてもらうことにしました。

pwd 読みにくい問題

そんなわけで僕はまだ割とiTerm2&zshを使っていて、結構シンプルにターミナルを使うのが好みだからか、あまりプロンプトを改造していないのですが、そのせいもあって pwd コマンドを結構叩くんですね。常にプロンプトに表示させとくとかしてないので、今どこー? って結構なる。みんなそうなりません? 言うてそこまでならないか、まぁいいか。

そんで pwd って、まぁだいたいパイプで通して何らかの処理に使うか、シェルスクリプト内で実行場所の確認、移動に使うか、目視確認して場所確認するかだとは思ってて、前者2つは機械的にプレーンな方がいいんですが、目視するとしたときに飾り気なさすぎることが前から気になっていまして、読まなきゃならないときに視認性が低い。


pwd 読みにくい問題

そこで Rust で代替のコマンドを作り始めようと考えました。いくつかのオプションを組み合わせることができるように考えます。とりあえずゴール地点は下記のように表示されるように実装します。(しました)

pwd の仕様について

でもちょっと待って、僕らは pwd の真価を引き出せているのだろうか、という疑問が湧きました。僕は pwd のことについてシェルのビルトインである、ことくらいしか知りません。

もしかして視認性を上げるオプションとかはもうあって、実は自分以外の人たちはそれを使っているから気にならないんじゃなかろうか。それに pwd の代替を作るとしたら本家の機能は踏襲しとかなければなりません。

そこで man と Wiki を確認してみました。

PWD(1)			    General Commands Manual			PWD(1)

NAME
     pwdreturn 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

(後略)

https://ja.wikipedia.org/wiki/Pwd

えー、フォーマットに関するものはなかったす。とはいえ、 -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 も含めてこういうところで仕様が考えられていたからなんですね。

成果物

https://github.com/masakuni-ito/rpwd

インストール

ごめんなさい、自分も詳しくないなか大変恐れ入りますが、みんな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 指定と同様です。ちなみに physicallogical を両方設定するとどうなるかというと物理の方を表示します。これも 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 に執着している人のように見えますが、そんなことはないです。

https://qiita.com/masakuni-ito/items/ecb461a4e8f2ead497cc

作ってみて思ったこと

一番時間をかけた――というより、いまでもあまり分かっていないのが本家 pwd における -L-P の意味というか意図です。シンボリックリンクの中に入って、その論理名が表示されるのはとても使いやすいからそれがデフォルトなのは理解できる、シンボリックリンクの中にいて、物理名を取得する必要性もありそうだから、それも理解できる。

でもそれだと -L を明示的に使う必要なくない? 物理ディレクトリのなかにいて、論理名を表示させることはできませんし。と思って、zshのコードまで見に行って、下記のようになっていました。

zshではここみたいなんですが、

https://github.com/zsh-users/zsh/blob/86d5f24a3d28541f242eb3807379301ea976de87/Src/builtin.c#L728-L738

  • 物理名を表示する
    • オプションに r が指定されているとき。rってなに。
    • オプションに P が指定されているとき。
  • (環境変数PWDから取ってきた)論理名を表示する
    • 関係するオプションが何も指定されていないとき。
    • chaselinks が指定されていながら L が指定されているとき。

chaselinks は文字通り、「リンクを追う」ので物理名を PWD にいれます。しかしその場合でも pwd は、 -L が指定されていれば論理名を表示する、ということらしいです。


chaselinks指定済みでも論理名が取得できる

なるほど、だとすれば一定理解できるのですが、zshの実装っぽかったので今回は実装しませんでした。

あと論理名の取得の仕方がRustでどうするのがベストか分からず環境変数PWDから持ってきています。

https://github.com/masakuni-ito/rpwd/blob/main/src/main.rs#L29-L35

環境変数から持ってくるのどーなんだろう、とは思ったんですが、本家でも cd とかで変更されるグローバル変数から設定していたので、ヨシとしました。

https://github.com/zsh-users/zsh/blob/86d5f24a3d28541f242eb3807379301ea976de87/Src/builtin.c#L1240-L1246

都合のよいところだけ都合よく拝借します。

他にもカラーは config で設定できたりすると素敵ですね、とか、そもそもテストとか書いてないんで顔向けできませんね、とかはあるんですが、ボチボチ改修していこう……。

まとめに代えて

3年くらい前から仕事でコードは一切書かなくなりました。 Rust をもっと実用レベルまで深めたいと思いつつ、何かを作っていかないと手になじまないですね。ずっとそんなことを思いながら、生成AIが出てきたので、今回もほとんどは生成AIと共作です。自分でちゃんと「Rustっぽさ」を理解できている気がしません。

やっぱり2025年は、弊社の第一選択言語を Rust にするぞ、ということを夢見て、ビジネスな人たちとエンジニアな人たちに総ボコられつつ(僕はそのための特別な訓練を受けているので大丈夫です)(弊社は暖かい会社です)、年末商戦の今年の目標達成に挑もうと思います。

pwd の勉強になりました。 rpwd 、割と便利じゃない? というところで、マジでまだ終わってないし、きれいに締められるかは努力次第なのですが、本年もお世話になりました。

NE株式会社の開発ブログ

Discussion

MeiMei

以下のツールを使うと簡単にhomebrewで入れられるようにできるらしいですよ!
https://github.com/axodotdev/cargo-dist

正訓正訓

マジすか!ありがとうございます!もうちょっと手直ししてやってみます。雑に作りすぎているので。