Corredor

ウェブ、プログラミングの勉強メモ。

Bash 上で使えるコマンド一覧を取得する!

ふと、Bash 上で使えるコマンドって全部でどれだけあるんだろう?と思い、一覧での取得方法を調べてみた。

ネットで見つけたやり方

↑ ドンズバな質問と回答が載っているページを見つけた。以下のコマンドで実行可能なコマンドを一覧で表示できるようだ。

$ for x in ${PATH//:/ }; do ls -1 $x; done | sort | uniq

環境変数 PATH は、コロン : 区切りで複数のディレクトリパスが羅列されている。コレを変数展開して、スペース区切りに直している。

for … in はデフォルトでスペースを区切りとして要素を分割するので、スペース区切りとなった PATH の内容を1要素ずつ取り出し、変数 x に割り当てている。

そしてこれを ls -1 (1列表示) の引数に渡し、PATH に書かれたディレクトリ配下のファイルを1ファイル1行ずつ表示している。イメージ的には以下のようなことを実現している。

$ ls -1 /usr/local/bin
$ ls -1 /usr/bin
$ ls -1 /usr/sbin

これらの結果が for コマンド一発の実行結果として done まで続き、その結果を sort して、uniq で重複を削り、完成、となっている。

いまさら改めて気付いたが、自分が「コマンド」と認識しているモノのほぼ全ては、環境変数 PATH で指定したディレクトリ内に実行可能ファイルがあるだけなのだ。which コマンドで所在が調べられるのだが、そりゃそうだ。

ということは、環境変数 PATH に羅列されたディレクトリ内のファイルを一挙に表示すれば、それが「コマンド一覧」とほぼ同義になるワケだ。

上述の記事でも触れられているが、Bash 組み込みコマンドは $ bash -c help、エイリアスは $ alias で確認することになる。また、~/.bash_profile などで定義した関数についてはこのコマンドでは取得できない。

自分の環境で上手く動かなかった

さて、さきほどのコマンドだが、自分の環境では上手く動かなず、一部エラーが出力されてしまった。コレは、環境変数 PATH に、スペースが入ったディレクトリパスが含まれていたせいである。

# PATH を出力したところの抜粋
$ echo $PATH
/usr/local/my original commands directory/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin

こんな風に my original commands directory/ というディレクトリパスが入っていた。

先程も書いたが、for コマンドはスペースを区切り文字と見なして要素を分割するのがデフォルトの挙動だ。つまり先程の例だと、

  • /usr/local/my
  • original
  • commands
  • directory/bin
  • /usr/local/bin ……

という風に分割されてしまったので、$ ls -1 original とか言われてもワケ分かんねえよ、というエラーが出ていたのだ。

コマンドを修正する

そこで、for コマンドに要素を分割させる時、$PATH でいうコロン : の位置で分割させるため、コマンドを少し修正してみる。

for コマンドが区切り文字として認識する文字を変更するには、$IFS という変数で違う文字を指定してやれば良い。

今回は、IFS に改行コード \n を指定して、「改行で1要素と区切る」ようにしてみる。

以下の記事でも触れられているとおり、バックアップ用の変数を用意しておくと、処理後に元の IFS に戻せるよ、とのこと。

まずはこんなコードになる。

# 元の IFS の内容を退避しておく
$ IFS_BK=$IFS

# 区切り文字を改行コードにする
$ IFS=$'\n'

# TODO : ココで処理…

# 区切り文字を元に戻す
$ IFS=$IFS_BK

次に、$PATH をコロン区切りから「改行区切り」にしてみる。コレは tr コマンドで置換すると実現できる。

$ echo $PATH | tr ':' '\n'

ということで、for コマンドに以下のように組み込んでやるのが良いだろう。

IFS_BK=$IFS
IFS=$'\n'

# $() でコマンドの実行結果を渡す
for x in $(echo $PATH | tr ':' '\n') ; do
  # スペース区切りのパスも解釈させるためダブルクォートで囲む
  ls -1 "$x"
done

IFS=IFS_BK

コレを1行にまとめて、以下のようにすれば、一発で動かせる。

$ IFS_BK=$IFS && IFS=$'\n' && for x in $(echo $PATH | tr ':' '\n') ; do ls -1 "$x" ; done | sort | uniq && IFS=$IFS_BK

コレで Windows GitBash でも MacOS でも Linux CentOS でもコマンド一覧を出力できた。いっぱいあるのね〜ん


2019-02-14 追記 : compgen なるコマンドがありました。

neos21.hatenablog.com

UNIXシェルスクリプト マスターピース132

UNIXシェルスクリプト マスターピース132