home / uni / shellscript シェルスクリプト

 と書いてみたけど、ほとんどbashの話題。 FreeBSDだと一般ユーザーではbashでないshが、スーパーユーザーではcsh(tcsh)が立ち上がったりするけど、使いにくいので真っ先にportsからbashを入れて置き換えてしまう・・・。 最新のは追いかけてないけど今でもそうなのかなぁ。
24 Oct 2014 エイリアス追加。

top シェル起動時のスクリプト

 以前メモ帳にまとめたんだけどな。 bashの場合、インタラクティブシェルかどうか、ログインシェルかどうかで読み込む起動スクリプトが違う。 また、リンクを張ってshとして起動されるとまた違う動作になる。

●定義

 ログインシェルとは、argv[0][0]=='-'であるか、--loginオプションと共に起動されたシェルのことである。 argv[0][0]=='-' というのは、起動するときに例えば -bash というコマンド名で起動した場合、である。 これはリンクを張って起動した場合も当てはまるが、多くの場合はloginコマンドがargv[0]に-bashを指定してexecした場合だろう (exec系の関数は実際に実行するプログラムのパスと、そのプログラムに渡す「最初の引数」=argv[0]=「通常はコマンド名」を別々に指定できる)。

 インタラクティブ(対話)シェルとは、入力とエラー出力が端末に接続されていて、-cオプションが指定されておらず、オプション以外の引数が指定されていない状態で起動されたシェルのことである。 つまり、キーボードから入力できて、出力は端末画面に表示されて、シェルスクリプトを実行するために起動されたシェルではない、ということ。

 上の条件を満たさなくても-iオプションを指定して起動すると強制的にインタラクティブ扱いになる。 インタラクティブ扱いだと環境変数PS1(プロンプト指定)がセットされ、変数$-iが含まれる。 echo $-としてみれば意味が分かるだろう。

●シェル起動時に実行されるスクリプト

ログインシェルの場合:

まず /etc/profile を実行。
続いて ~/.bash_profile・~/.bash_login・~/.profile のうち最初に見つかったものを実行。
--noprofileを付けるとこの挙動が抑制される。

ログインシェルが終了するとき:

~/.bash_logoutを実行。

ログインシェルではないインタラクティブシェルとして起動された場合:

~/.bashrcを実行。
-norcでこの挙動を抑制、--rcfileでファイル名を指定できる。

非インタラクティブシェルとして起動された場合:

BASH_ENV環境変数に指定されたファイルを実行。
あとで実験するが、インタラクティブでなければログインシェルでもBASH_ENVに指定されたファイルが実行される。

●shとして起動された場合に実行されるスクリプト

ログインシェルの場合:

/etc/profileを実行。
~/.profileを実行。
--noprofileを付けるとこの挙動が抑制される。

インタラクティブシェルとして起動された場合:

ENV環境変数で指定されたファイルを実行。 bashと違って、インタラクティブならばログインシェルの場合でもこの動作が行われる。

インタラクティブではない場合:

何も実行されない。

●ネットワーク越しに起動された場合

例えばsshdがbashをexecしたような場合。

.bashrcを実行。
-norcでこの挙動を抑制、--rcfileでファイル名を指定できるが、rshdやsshdから指定できなければならない。

あとで実験するが、ログインシェルではなく、ネットワークから接続された場合はインタラクティブ扱い、ということらしい。 ログインシェルならば非インタラクティブ扱いのまま。

●UIDとEUIDが一致しない場合

-pオプションをつけない限り、スタートアップファイルは実行されず、環境からシェル関数を引き継がない。 他にも若干違いがある。 -pを付けると通常と同じ動作になるが、EUIDはそのままらしい。 マニュアルの受け売りで深く考えたわけではないのでよく分からん。

●まとめ
ログイン非ログイン
インタラクティブ 1. /etc/profile
2. ~/.bash_profile・~/.bash_login・~/.profile のうち最初に見つかったもの
~/.bashrc
ネットワーク 1. /etc/profile
2. ~/.bash_profile・~/.bash_login・~/.profile のうち最初に見つかったもの
3. BASH_ENVで指定されたファイル
非インタラクティブ BASH_ENVで指定されたファイル

 bashでログインすると~/.bash_profileが読み込まれるので、こんなのを書いておく。

if [ -r ~/.profile ]; then . ~/.profile; fi
if [ -r ~/.bashrc ]; then . ~/.bashrc; fi

すると、bashでもshでもログインすると~/.profileが読み込まれる。 ~/.profileにはこんなのを書いておく。 FreeBSDではデフォルトでこうなっていた。

export ENV=~/.shrc

shの場合にはインタラクティブならログインシェルでも$ENVが読み込まれるので、これで~/.shrcが読み込まれる。 bashの場合にはログインシェルだとこの動作が行われないので、上の例のように~/.bash_profileから~/.bashrcを読み込むようにし、~/.bashrcで

if [ -r "$ENV" ]; then . "$ENV"; fi

とすることで、~/.shrcが読み込まれる。 以後、shをインタラクティブに起動するとENVで指定した~/.shrcが読み込まれ、bashをインタラクティブに起動すると~/.bashrc経由でENVで指定した~/.shrcが読み込まれる。

 システムワイドな設定(PRINTERとかかねぇ)は/etc/profileに書く。 常駐モノ(ssh-agentとかだな)や共通の環境(CVS_RSHとかだな)は~/.profileに書く。 bashじゃないと怒られるものは~/.bash_profileに書く。 shを起動したときに設定したい環境変数は~/.shrcに、bashを起動したときに設定したい環境変数は~/.bashrcで$ENVを読み込んだ後に書く。 shでログインした後、bashを起動したらPS1を再設定とかね。

・・・っていうことでいいのかな。

top 実験してみる

.bash_profileと.bashrcにechoを入れて実験してみる。 BASH_ENV環境変数についても実験するため、~/.bash_environというファイルを作り、echoを書いておいて、.bashrcでexport BASH_ENV='~/.bash_environ' しておく。

 bashのバージョンはCentOS5.5をインストールしてyum updateしたやつで、

$ bash --version
GNU bash, version 3.2.25(1)-release (i686-redhat-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

ちょっと古いな。 Cygwinのは4.1.10だった。

コンソールからログイン
Login: user
Password:
Last login: ...
[~/.bash_profile is read]
[reading ~/.bashrc]
[~/.bashrc is read]

普通にインタラクティブログインシェル。 ログインシェルだとインタラクティブでも.bashrcが実行されないので、.bash_profileの中に. ~/.bash_rcなどと書いてあり([reading ~/.bashrc]の部分がそう)、そのおかげで.bashrcが実行されている。

そのままbash実行
$ bash
[~/.bashrc is read]

普通にインタラクティブシェル。

bashを通してコマンド実行
$ bash -c 'ls -l /proc/$$/fd'
[~/.bash_environ is read]
合計 0
lrwx------ 1 user user 64  7月  7 22:59 0 -> /dev/pts/0
lrwx------ 1 user user 64  7月  7 22:59 1 -> /dev/pts/0
lrwx------ 1 user user 64  7月  7 22:59 2 -> /dev/pts/0
lr-x------ 1 user user 64  7月  7 22:59 3 -> /proc/3651/fd

非ログイン・非インタラクティブなので、BASH_ENV環境変数で指定したファイルが実行される。 余談だが、CentOS5.6を使ったらデフォルトでLESSOPEN='|/usr/bin/lesspipe.sh %s'となっていて、lesspipe.shが実行されるときに.bash_environが実行されてechoが出てしまい、これが原因でlessの動作が台無しに。 以後、echo '[~/.bash_environ is read]' >&2として実験。 これは標準エラー出力もファイルに出したいときによく使う2>&1の逆だね。

ログインシェルなのに非インタラクティブ
$ bash --login -c 'ls -l /proc/$$/fd'
[~/.bash_profile is read]
[reading ~/.bashrc]
[~/.bash_rc is read]
[~/.bash_environ is read]
合計 0
lrwx------ 1 user user 64  7月  7 23:16 0 -> /dev/pts/0
lrwx------ 1 user user 64  7月  7 23:16 1 -> /dev/pts/0
lrwx------ 1 user user 64  7月  7 23:16 2 -> /dev/pts/0
lr-x------ 1 user user 64  7月  7 23:16 3 -> /proc/3888/fd
ログインシェルに対する.bashrcと違って、ログインでも非インタラクティブならBASH_ENVで指定されたスクリプトが読み込まれるようだ。 あと、CentOS5.6では.bash_logoutに/usr/bin/clearが書いてあって、ログアウトすると画面がクリアされるのだが、この場合はクリアされなかった。 これはちょっとマズいんでない? Cygwinのbash-4.1.10でも.bash_logoutが実行されなかった。
sshでログイン
$ ssh localhost
Last login: ...
[~/.bash_profile is read]
[reading ~/.bashrc]
[~/.bashrc is read]
$ ls -l /proc/$$/fd
合計 0
lrwx------ 1 user user 64  7月  7 23:14 0 -> /dev/pts/1
lrwx------ 1 user user 64  7月  7 23:14 1 -> /dev/pts/1
lrwx------ 1 user user 64  7月  7 23:14 2 -> /dev/pts/1
lrwx------ 1 user user 64  7月  7 23:14 255 -> /dev/pts/1

当然インタラクティブログインシェル。

端末を割り当てずにログイン
$ ssh -T localhost
[~/.bash_profile is read]
[reading ~/.bashrc]
[~/.bash_rc is read]
stty: 標準入力: 無効な引数です
[~/.bash_environ is read]
ls -l /proc/$$/fd
合計 0
lrwx------ 1 user user 64  7月  7 22:46 0 -> socket:[13920]
lrwx------ 1 user user 64  7月  7 22:46 1 -> socket:[13920]
lrwx------ 1 user user 64  7月  7 22:46 2 -> socket:[13922]
ps x
  PID TTY      STAT   TIME COMMAND
 2826 ?        S      0:00 sshd: user@pts/0
 2827 pts/0    Ss     0:00 -bash
 3152 pts/0    S+     0:00 ssh -T localhost
 3155 ?        S      0:00 sshd: user@notty
 3156 ?        Ss     0:00 -bash
 3187 ?        R      0:00 ps x

 sshdがハイフン付きでbashを起動していてログインシェルとして扱われていることが分かる。 しかし、入出力は端末ではなく、-iも指定されていないのでインタラクティブではない。 したがってPS1が設定されず、プロンプトが出ない。 細かいことを言うとプロンプトはシステムのbashrcスクリプトが設定していたりするが、PS1が設定されているときのみPS1を再設定する形になっていて、PS1が元々設定されていないとこの例のようにプロンプトが出ない。 sttyが文句を言っているのは.bash_profileにstty -ixon -ixoffが書いてあるから。 非インタラクティブなので.bash_environも実行されている。

ネットワーク越しに起動
$ ssh localhost 'ls -l /proc/$$/fd'
[~/.bashrc is read]
合計 0
lrwx------ 1 user user 64  7月  7 23:03 0 -> socket:[20828]
lrwx------ 1 user user 64  7月  7 23:03 1 -> socket:[20828]
lrwx------ 1 user user 64  7月  7 23:03 2 -> socket:[20830]
lr-x------ 1 user user 64  7月  7 23:03 3 -> /proc/4611/fd

.bash_profileが実行されていないのでログインシェルではない。 また、コマンドが指定されているのでインタラクティブシェルでもない。 sshは後ろにコマンドを指定するとログイン扱いにせず、端末も割り当てないようだ。 まあ当たり前か。 この結果、ネットワーク越しのルールが適用されて、インタラクティブではないが.bashrcが実行され、マニュアルには書いてないがBASH_ENVは無視されて.bash_environは実行されていない。 つまり「インタラクティブ扱い」ということらしい。

 ということは、トンネル使って

$ ssh user@remote 'tar cfvz - outgoing' | tar xf -
なんてやるときに.bashrcが変なものを表示してると、ゴミが混ざるということか・・・。
強制的に端末を割り当てる
この場合、.bashrcが実行されないはずなので環境変数BASH_ENVを設定するにはsshd側に若干の設定が必要。 楽なのはsshd_configでPermitUserEnvironmentをyesに設定してsshdに-HUPを送り、~/ssh/.environmentにBASH_ENV=~/.bash_environと書いておく方法。 実験したら元に戻しておくのを忘れないように。
$ ssh -t localhost 'ls -l /proc/$$/fd'
[~/.bash_environ is read]
合計 0
lrwx------ 1 user user 64  7月  7 23:08 0 -> /dev/pts/1
lrwx------ 1 user user 64  7月  7 23:08 1 -> /dev/pts/1
lrwx------ 1 user user 64  7月  7 23:08 2 -> /dev/pts/1
lr-x------ 1 user user 64  7月  7 23:08 3 -> /proc/4781/fd
Connection to localhost closed.

コマンドが指定されているのでsshはbashを非ログインシェルとして起動し、bashは非インタラクティブと判断する。 入力が端末なのでネットワーク越しのルールも適用されない。 よって.bash_profileも.bashrcも実行されず、.bash_environだけが実行される。

top エイリアス

aliasはexportできない
$ alias hoge=fuga
$ alias
alias hoge=fuga
$ bash -c alias

 BASH_ALIASESという連想配列があるが、これもexportできないようだ。

aliasは行ごとに置換されるらしい
$ alias hoge="echo hoge"; hoge
-bash: hoge: command not found
$ hoge
hoge
$ unalias hoge; hoge
hoge
$ hoge
-bash: hoge: command not found

 最初のaliasでは定義しているのに見つからず、次のunaliasでは抹消したのにまだ実行できている。

aliasは非インタラクティブの場合は解釈されない
$ bash -c 'alias hoge="echo fuga"
> hoge'
bash: line 1: hoge: command not found

$ bash -i -c 'alias hoge="echo fuga"
> hoge'
fuga

 aliasの定義と実行の行は別にしないといけないので、コマンド文字列の途中で改行している。

 この挙動のため、たとえBASH_ENVを使ってaliasを定義したとしても、シェルスクリプトなどから呼ばれた場合はaliasは実行されない。 この挙動は変更できて、expand_aliasesシェルオプションを有効にするとaliasが解釈されるようになる。

$ bash -O expand_aliases -c 'alias hoge="echo fuga"
> hoge'
fuga

$ bash -c 'shopt -s expand_aliases; alias hoge="echo fuga"
> hoge'
fuga

 どちらも意味は同じ。 前者はbash起動時のコマンドラインオプションで指定、後者はbash起動後にshoptコマンドで設定。

 Subversionを最新のものに変えたときにこの挙動に気づいた。 うちのSubversionはコミットログ入力の際に秀丸が立ち上がるようになっている。 Subversionを最新のものに変えたら、秀丸からsvn ciすると、

svn: E205001: コミットに失敗しました (詳しい理由は以下のとおりです):
svn: E205001: 非対話的にログメッセージを取得するためのエディタを呼び出せません
と言われてコミットできなくなった。 この訳だと何が原因でエディタが呼び出せないのかいまひとつはっきりしない。 実は原文では、
svn: E205001: Cannot invoke editor to get log message when non-interactive
なので、「非対話モードの場合は、ログメッセージを取得するためにエディタを起動できません」が正しいっぽい。 秀丸のコマンド実行は入出力がリダイレクトされた状態になっているので、Subversionが対話モードではない、と判断したようだ。 CygwinのminttyからSlikSVNを起動するような場合も引っかかる(CygwinのSubversionなら当然大丈夫のはず)。

 svnのオプションとして--force-interacitveを付ければ動くというのは分かったが、シェルスクリプトやシェル関数にするのも面倒なので、bashのaliasにしてみた。 すると、minttyからはうまく動くのに、秀丸からbash経由で実行してもうまく動かない。 秀丸からbashを立ち上げると、bashは非対話モードで動くのでaliasが解釈されず、生のsvnがそのまま実行されていた。 仕方がないので、秀丸マクロの方でbashに-O expand_aliasesを渡して解決することにした。

top forkするのかしないのか?

 これってUnixシェルの普通の挙動なのかな?

$ bash -c 'ps w'
  PID TTY      STAT   TIME COMMAND
 4221 pts/0    Ss     0:00 -bash
 5241 pts/0    R+     0:00 ps w
$ bash -c 'ps w; ps w'
  PID TTY      STAT   TIME COMMAND
 4221 pts/0    Ss     0:00 -bash
 5242 pts/0    R+     0:00 bash -c ps w; ps w
 5243 pts/0    R+     0:00 ps w
  PID TTY      STAT   TIME COMMAND
 4221 pts/0    Ss     0:00 -bash
 5242 pts/0    R+     0:00 bash -c ps w; ps w
 5244 pts/0    R+     0:00 ps w

4221が親のbashで、先頭がハイフンなのでログインシェル。 psを一回だけ指定すると子bashがない。 forkせずにexecしたようだ。 それに対してpsを2回実行すると5242として子bashが現れる。

 これはssh経由で起動した場合も同じで、sshのマニュアルでは単に「コマンドラインの最後に書かれたコマンドを実行」となっているが、

$ ssh localhost 'ps wx'
[~/.bashrc is read]
  PID TTY      STAT   TIME COMMAND
 4220 ?        S      0:01 sshd: user@pts/0
 4221 pts/0    Ss     0:00 -bash
 5405 pts/0    S+     0:00 ssh localhost ps wx
 5408 ?        S      0:00 sshd: user@notty
 5409 ?        Rs     0:00 ps wx

.bashrcが実行されるのでシェルが介在しているにもかかわらず、forkせずにpsがexecされて子bashが出てこない。 psを2回実行すると、

$ ssh localhost 'ps wx; ps wx'
[~/.bashrc is read]
  PID TTY      STAT   TIME COMMAND
 4220 ?        S      0:01 sshd: user@pts/0
 4221 pts/0    Ss     0:00 -bash
 5434 pts/0    S+     0:00 ssh localhost ps wx; ps wx
 5437 ?        S      0:00 sshd: user@notty
 5438 ?        Ss     0:00 bash -c ps wx; ps wx
 5463 ?        R      0:00 ps wx
  PID TTY      STAT   TIME COMMAND
 4220 ?        S      0:01 sshd: user@pts/0
 4221 pts/0    Ss     0:00 -bash
 5434 pts/0    S+     0:00 ssh localhost ps wx; ps wx
 5437 ?        S      0:00 sshd: user@notty
 5438 ?        Ss     0:00 bash -c ps wx; ps wx
 5464 ?        R      0:00 ps wx

子bashが出る(5438)。

top 親環境か子環境か?

ケース1
$ foo=hoge
$ sh -c "/bin/echo $foo; foo=fuga; /bin/echo $foo"

 親環境で$fooが全部展開されてしまうので、出力はhoge / hoge

ケース2
$ foo=hoge
$ sh -c '/bin/echo $foo; foo=fuga; /bin/echo $foo'

 シングルクオートは変数を展開しない。 また、fooはexportしてないから、 / fuga

ケース3
$ foo=hoge
$ { /bin/echo $foo; foo=fuga; /bin/echo $foo }
 hoge / fugaになる。 その後親シェルでfooを調べると、fugaになる。
ケース4
$ foo=hoge
$ ( /bin/echo $foo; foo=fuga; /bin/echo $foo )

 これが不思議。 hoge / hogeでも / fugaでもなく、hoge / fugaになる。 その後親シェルでfooを調べると、当然hogeのまま。 さらに、

ケース5
$ foo=hoge
$ ( /bin/echo \$foo; foo=fuga; /bin/echo \$foo )

これは$foo / $fooになる。 sh -c "..."に渡せば当然 / fugaになるのに。

番外
$ echo `echo '\\'`
\
$ echo $(echo '\\')
\\

 バッククオートと$( )ではバックスラッシュの扱いが違うらしい。 '$foo'"$foo"は同じ結果になる。

top 起動スクリプト設定例

unset MAILCHECK
 メールチェックしない。デフォルトでは60秒に一回メールチェックするようになってるけど、ファイルがローカルホストに来なければ意味ないし。
export PS1='\u@\h \W\$ '
 プライマリプロンプト。user@host workdir$ というシンプルなもの。 一台のマシンから複数のホストへ接続するから、ホスト名は入れておかないと。
export PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}\007"'
 この変数に指定したモノが、プライマリプロンプトを出す直前に実行される。 改行なし、エスケープありのechoコマンドがひとつ指定されている。 ESC]0;というのはxtermのエスケープシーケンスで、アイコン名とウィンドウタイトル、つまり、端末ウィンドウのタイトルバーを変更する。 文字列は直後に続け、^G、つまりBELLで終わる。 ちなみに0を1にするとアイコン名のみ、2にするとウィンドウタイトルのみの変更。

 で、文字列の部分は${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}になっているから、「ユーザー名@ホスト名:カレントディレクトリ」になる。 %%.*は最長一致でサフィックスを削除だから、ホスト名はピリオドの直前まで。 /#$HOME/~は先頭からの一致部分を置換だから、ホームディレクトリはチルダに置換されるが、ホームディレクトリの外にいる場合はそのままフルパスになる。

 結局、コマンドプロンプトが出るたびにタイトルバーがプロンプトと同じように変わる。 タイトルバーを変えておくと端末ウィンドウを[Alt]+[Tab]なんかで切り替えるときに便利。

 某所の某ホストはホスト名がlocalhostのままになっていて、このままだと非常に紛らわしいので、

if [ "$PROMPT_COMMAND" != "" ]; then
    export PROMPT_COMMAND=${PROMPT_COMMAND/'${HOSTNAME%%.*}'/name-of-this-host}
fi

とわざわざ置き換えていたりする(笑)。

export CVS_RSH="`cygpath -u "c:\program files\putty\plink.exe"`"
 これはCygwinの設定。CVSで:ext:トンネルにplinkを使う。 Cygwinのパスを書くのが面倒なんで、cygpathを使っている。 Unix系なら単にsshと書いておけばいい。 別にCygwinでsshを使ってもいいんだけど、PuTTYの方がエージェントが使いやすいから・・・。 PuTTYから他のホストにログインするときにエージェントフォワードを有効にしておけば、ログインした先ではエージェントいらなくなるし・・・。
export HISTSIZE=5000; export HISTFILESIZE=5000
 コマンド履歴をいっぱい確保w
export LESS="-R"
 lessのオプション。 ANSIエスケープシーケンスを認識させる。 キカイによっては-iも指定していることがある。
stty -ixoff -ixon
 端末XON/XOFF制御を無効にする。 これをやっておかないとreadlineのヒストリ検索[Ctrl]+[R]で行き過ぎてしまった場合の逆戻り[Ctrl]+[S]が使えない。 逆に、これをやってしまうと[Ctrl]+[S]で画面が止まらないわけだが、そういう場合はそもそもlessのようなページャーを使うことが多いわけで、困ったことはない。

 sttyをベタに書くと入出力が端末ではなかったときに先ほどの例のように文句を言われる。 それがいやなら、

if [ -t 0 ]; then stty -ixoff -ixon; fi

とすればよい。[ -t 0 ] はファイルディスクリプタ0、つまり標準入力が端末につながっている場合に真。

shopt -s checkwinsize
 コマンド実行ごとにウィンドウサイズを調べ、環境変数COLUMNSとLINESを更新する。 これをやらないと端末ウィンドウの大きさを変えたときにreadlineによる編集がうまくいかなくなる。
alias hd='hexdump -C'
 16進バイト単位のダンプ。 これが一番見慣れている。 DOS時代に自分で作ったdumpというプログラムがあるのだが、Unixには元々dumpコマンドがあって名前が当たるので、ydumpと改称した。 こっちはJIS半角カタカナとかを表示しようとするからUni*ではそのまま使えないんだよな。 でも、ダンプ結果をテキストエディタで編集して、redumpというコマンドで編集結果をバイナリに戻せたりして、結構便利。 機会があったら紹介する・・・ほどのプログラムでもないか。
addpath (01 Nov 2012)
 プログラム開発、特にDLLを作っていると、カレントディレクトリや親子・兄弟関係にあるディレクトリをパスに加えたいことがある。 そんなときに。
addpath () {
    if [ -z "$1" ]; then echo "usage: addpath <dir>"; return 1; fi
    PATH=$PATH:`cd "$1" && pwd`
}

指定するディレクトリは相対パスでいい。指定されたディレクトリにcdしてpwdすることで絶対パスを得ている。 コマンドによる文字列の置き換えはサブシェルを起動する(bashのマニュアルには書いてないがPOSIXのマニュアルには書いてあった)ので、カレントディレクトリを覚えておく必要はない。 realpathというコマンドがあるシステムもあるし、pushd・popdを使ってもできそうだが、これだとPOSIXの範囲でできて、しかも簡単だ。 PATHが空だと頭に余計に:が付くが、空だとpwdが実行できないからまぁいいだろう。

remove-path (01 Nov 2012)
 パスを抜く。
remove-path () {
    if [ -z "$1" ]; then echo "usage: remove-path <dir>" >&2; return 1; fi
    abspath=$(cd "$1"; pwd | sed -e 's!/!\\/!g' -e 's/$/\\\/\\?/')
    PATH=$(
        echo $PATH |
        sed -e "s/^$abspath://" |
        sed -e "s/:$abspath\$//" |
        sed -e "s/:$abspath:/:/" |
        sed -e "s/^$abspath\$//"
    )
    echo $PATH
}
sedで処理するために/を\/に置き換え、さらに最後に/があってもなくてもいいように/?を付け加えて、頭・最後・途中・単独のものを順に削除している。 相対パスで指定すればいいのはaddpathと同じ。 addpathの場合は存在しないパスを指定することはまずないだろうが、remove-pathの場合は実際には存在しないパスを削除したいこともある(PATHにゴミが残っている場合)。 が、パスが存在してないとcdできないので削除できない。
list-path (01 Nov 2012)
 パスを行ごとにバラしてくれる。
list-path () {
    echo $PATH | sed -e 's/:/\n/g'
}
which
 多分man whichの例からパクってきたんだと思う。 エイリアスやシェル関数も探してくれる。
which ()
{
    (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot $@
}
ls関係
 CygwinをUTF-8で使っている場合はlocaleの関係でグループ名が化けるので、lsはLANG=SJISで実行して、iconvでUTF-8に変換するようなシェル関数が定義してある。 そのうちどこかで紹介するつもり。

 他に、ディレクトリだけ表示するために、

lsdir ()
{
    if [ $# -eq 0 ]; then
        ls -l -F --color "$@" | grep ^d
    else
        ls -ld -F --color "$@" | grep ^d
    fi
}
なんてのが定義してある。 ls -l すると、行の先頭にdが付くので、それをgrepで拾っている。 パラメータがないとls -lになるので、lsdirだけ打ち込むとカレントディレクトリにあるディレクトリが表示される。 パラメータを指定するとls -ldになるので、指定されたファイル名のうちディレクトリであるものだけが表示される。 例えばlsdir a*とやれば、aで始まるディレクトリが表示される。

 --colorは強制的にカラー表示にする。 デフォルトでは端末につながっているときだけカラー表示するが、出力をパイプでgrepに渡している関係でこうしないとカラーにならない。 ただ、こうしてしまうとリダイレクトやパイプラインでもカラーのままなので、less -R に渡してもカラーになる反面、sedに渡したりファイルに吐いて編集するような場合は都合が悪い。 まあ、そういうときは生のlsを使っておくれ。 どうしてもという人はif [ -t 1 ]で--colorを指定したりしなかったりすればよい。

alias start="cygstart" (01 Nov 2012)
 Cygwinのstartコマンド。 Windowsのstartコマンドはcmdの内部コマンドなので、Cygwinからは実行できない。 その代わりにcygstartコマンドがあるのだが、ちょっと打ちにくい。 だったらaliasにしてしまえ、と。
Cygwinのmount (01 Nov 2012)
 ホームディレクトリ以下をテキストマウントで使っている人なので、時々特定のディレクトリをバイナリでマウントしなおしたいことがある。 そんな時にこれ。
mount-binary () {
    if [ -z "$1" ]; then echo "usage: mount-binary <dir>"; return 1; fi;
    mount -o binary,noacl `cygpath -wa "$1"` `cygpath -ua "$1"`
}

 cygpathを使って指定されたパスをWin32絶対パスとCygwin絶対パスに変換しているので、引数に相対パスをひとつだけ指定すればいいところがミソ。 mount-binary bindirとすれば、カレントディレクトリにあるbindirというディレクトリがバイナリマウントされるし、mount-binary .とすれば、カレントディレクトリをバイナリマウントしてくれる。


Copyright (C) 2011-2012 You SUZUKI

$Id: shellscript.htm,v 1.11 2015/09/23 02:00:58 you Exp $