シェル

Ippei Kishida

Last-modified:2016/11/03 00:17:09.

『UNIX今日の技』でネタにした記事のうち、多くのシェルで共通している事項をまとめたものです。

1 シェルでのキーバインド

多くのシェルはデフォルトで Emacs キーバインドになっています (moon に program で入ったときの tcsh でもそうです)。 覚えておくと便利そうなのは、以下でしょうか。

私の場合、長いファイル名の一部を変更する時など [C-k][C-y][C-y] をよく使います。

キルバッファの中身は上書きしない限り残りますので、「このクソ長いコマンド打つ前にディレクトリ作っとかなあかんかった」ということをコマンド打ってる最中に思い出した時、一旦 [C-u] で削除して mkdir してからペーストするという使い方もできます (この目的では、zsh では [ESC-q] (コマンドラインスタック)という便利な機能があります)。

2 TAB補完

今時のシェルは大抵、補完機能を備えています。 例えば「cd /u[TAB]」と打てばファイル名を補完してくれます。 TAB補完しておいた方が速いしタイプミスも減るし、補完できる場面では補完機能を使わない理由はないと思います。

tcsh, zsh といったさらに先進的なシェルでは、「cd の場合は ディレクトリにしかマッチさせない」「tar zxvf の後は .tar.gz か .tgz しか補完しない」とか「補完候補を表示する」といった機能もあります。 何処かから設定を拾ってこれるのなら試してみるのも面白いでしょう。

3 ワイルドカード

「*」はよく使っていると思いますが、もう一度。 「*」はファイル(ディレクトリ)名のあらゆる文字列にマッチします。 単純に「*」とすれば全てのファイル/ディレクトリになりますし、「*.msi」とすれば拡張子が msi のファイルにマッチします。 一つ下のサブディレクトリの中身をおおまかに見たい場合は「ls * | less」などとすれば良いでしょう。

tcsh にはディレクトリを再帰的に掘るワイルドカードは用意されていませんが、「ls * */* */*/*」のようにすればそれに近い効果は出せます。

ワイルドカードはコマンド側(lsなど)で展開されるのではなく、シェルで展開されてコマンドに渡されるということも知っておくと、スクリプトを書くのに便利かもしれません。

mv や rm などではワイルドカードがどのように展開されるかという事に十分注意を払う必要があります。 怪しいな、と思ったときは ls や echo で実際にどのように展開されるか確認しましょう。

4 名前生成

例えば「mkdir calc{A,B,C}」とすると、calcA, calcB, calcC というファイルを作ります。 「シェルは「calc{A,B,C}」の部分を展開してから引数をコマンドに送る」ということを意識すれば様々な使い方ができます。 例えば、

なお、zsh では「{1..26}」で連番を生成してくれます。

5 ベル文字

ベル文字が意外と便利です。 ベル文字とは ASCII文字で定義された BEEP音を鳴らすための文字です。 シェルでは「echo ‘’」で出せます。 例えば、「make install; echo ‘’」としてやれば、make が終了すると「ピッ」と BEEP を出して知らせてくれるので、安心して本を読んでいることができます。 私は「alias bell “echo ‘’”」のようにエイリアスを作って利用しています。 カップめんを作る時は「sleep 180; bell」ですね。

ベル文字は端末に依存します。 Windows用の端末エミュレータでは、Putty は出せますが、TeraTermPro では出せないようです。 kterm, xterm, mlterm といった UNIX のX端末は問題なく出せるみたいです。

6 cd -

cd は、引数なしで実行するとホームディレクトリへ移動します。 また、「cd -」とすると「さっきいたディレクトリ(今のディレクトリに移動する前のディレクトリ)」に移動します。

zsh の人は、cd -[TAB] としてやれば、「pushd」で積んだディレクトリスタックが補完リストに現れます。

7 ゴミ箱を実装する

何気なく rm したら「あれ、そのファイルは……それは違う!」とか慌てたことがありませんか? 「rm hoge*」のつもりで「rm hoge *」としてしまい、涙を飲んだことがありませんか? そのような悲劇を繰り返さぬために、ということで今回はゴミ箱の実装を実演します。

でもやることは簡単。 mkdir ~/.trash してやって、 function rm() { mv $@ ~/.trash } を定義してやれば良いだけ。 でもこの rm() だと ~/.trash が存在しなければ削除できなくなったり色々問題があるので、私はより高機能化させた関数を ~/.zshrc に仕込んでます。

function rm() {
    if [ -d ~/.trash ]; then
        local DATE=`date "+%y%m%d-%H%M%S"`
        mkdir ~/.trash/$DATE
        for i in $@; do
            if [ -e $i ]; then
                mv $i ~/.trash/$DATE/
            else 
                echo "$i : not found"
            fi
        done
    else
        /bin/rm $@
    fi
}

rm */POTCAR とした場合、2番目以降の POTCAR で上書きしてしまいます

このままではゴミ箱の中身を空にすることができませんね。 手動でゴミ箱内のファイルを削除するには 「/bin/rm -rf ~/.trash/*」 を使います。

8 ゴミ箱の中身を定期的に削除する

「更新時刻がn日以上前のファイル」を削除するには find を使います。 /usr/bin/find /home/ippei/.trash ?! -mtime -7 -maxdepth 1 -exec /bin/rm -rf {} ?;

この呪文の意味は「man find」すれば書いてるんですが、一応解説しますと、

これを毎日自動的に実行させるには、~/.crontab に以下のように記述し、「crontab ~/.crontab」します。

0 0 * * * /usr/bin/find /home/ippei/.trash ?! -mtime -7 -maxdepth 1 -exec /bin/rm -rf {} ?;

これで毎日 0:00 に古くなったゴミから順番に消えていってくれます。

9 パスワードを打ち間違った?

という時どうしてますか? [C-c] で中断したり、バックスペースを多目に打ったししてませんか? こういう場合、[C-u] を打てば、これまで打ったパスワードはすべてキャンセルされます。

10 中ボタンでのペースト

X window system では普通、マウスでドラッグされた時点でバッファにコピーされ、中ボタンをクリックするとその場でペーストされます。 Windows からでも TeraTermPro などは中ボタンでコピーされた文字列がペーストされます。

スーパーユーザで作業しなければならない場合など「何度もパスワードを打つがの面倒だ」といった場合には、テキストエディタなどでパスワードを表示してその文字列をドラッグしておき(改行文字も含めておくのがコツ)中ボタンでパスワード入力、といったこともできます。

おっと、パスワードを画面に出すときは背後に誰もいないことを確認してからにしましょう。

11 複数のジョブを連続して実行する

「;」でコマンドを区切ることで、「一つのコマンドが終わったら次のコマンドを実行する」という指示を一回の入力ですることができます。 例えば「./configrure(10分) したあと make(30分) して make install(5分) せんならん」という時に「各段階ごとにターミナルをチェックして終わっていれば次のコマンド」って面倒でしょう。 「./configure ; make ; make install」で順番にやってくれます。

「;」ではなく 「&&」 で区切ると、「前のコマンドが成功していれば次のコマンドを実行」となります(正確には言えば、「終了ステータスが0ならば次を実行」)。 make コマンド関連はこっちでやった方が良いかもしれません。

私は以下の用途によく使います。

12 実行してしまったコマンドの後で何かをさせたいと思ったときは?

「そのコマンドで何か入力を要求されることがない」ことが明らかならば、画面出力を無視して、次に実行したいコマンドを打って置けば良いです。 シェルは標準入力を一旦キューに入れて、コマンドが要求する、又はシェルが要求する度に順番に処理していってるだけなのですから。

make のように「対話的に何か入力が要求されることがあり得る」状態ではやってはいけません。 本来、この用途には wait コマンドを使った方がスマートだと思います。 (『UNIX今日の技/ジョブとプロセス/バックグラウンドジョブを監視する(waitコマンド)』を参考のこと)

13 ストリームという概念(パイプとリダイレクト)

13.1 概説

シェルコマンドはテキスト端末を想定して作られており、多くのコマンドは結果を標準出力(テキスト端末)に吐き出します。 また、標準入力(キーボード入力)を受け付けるコマンドもあります。

これらの標準入出力のやりとりは「一連の文字列の流れ」と見ることができ、これをストリームと呼びます。 ストリームの出力先は変更することができます。

注意しなければならないのは、読んだファイルにそのままリダイレクトしようとするとファイルが壊れてしまうということです。 「cat list.txt > list.txt」した時に何が起こっているのでしょうか?

これは、パイプやリダイレクトが左から順番に「完全に」実行していくのではなく、「同時に」実行していく、というところにミソがあります。 「>」でリダイレクトした場合、ユーザがコマンドに ENTER を打ち込んだ瞬間に出力先のファイルを初期化、それからシェルが左にある cat を起こすけれど先に list.txt は初期化したのでファイルの中身はない、ということになります。

ちなみに「cat list.txt >> list.txt」すると、list.txt の中身を無限に繰り返し、ディスクを喰い尽くすまで終わりません。

13.2 岸田がよく使うパイプ

上記3つが殆どですね。 具体例は以下のような感じです。

ls -l | less
ls | wc (ファイル数を数える)
head */CONTCAR | less
head -n 5 CONTCAR | tail -n 1
grep 'T T T' POSCAR | grep Li | wc (可動設定した Li 原子を数える)
cat -n POSCAR | grep Li63  ('Li63'という文字列が出現する行番号)
grep Iteration */OUTCAR | grep 100 (収束してない計算を表示)
ps auxw | grep vasp | grep -v grep

13.3 不要な出力を捨てる

make などの出力が邪魔だと感じるならば、それを捨てることができます。 /dev/null (ヌルデバイス)へリダイレクトしてやれば OK です。 make > /dev/null ただし、標準エラー出力はそのまま端末に送られます。 >& を使えば、標準エラー出力も一緒にリダイレクトできます。

13.4 リダイレクト「<」の使い方

京都府にお住まいの匿名希望さんからのリクエストです。

“” “<”の使いかたがわかりません。知りたいー。例えば、 “” 「hoge inputfile」 “” 「hoge < inputfile」 “” はどう違うんでしょうか。

リダイレクト「>」の機能は、「右辺のファイルを出力モードで開いて、左辺の標準出力をそのファイルに流し込む」ことです。

「<」はその逆で、「右辺のファイルを読み込みモードで開いて、右辺のファイルの内容を左辺の標準入力として流し込む」ことになります。 ですから、「hoge < inputfile」は「cat inputfile | hoge」と等価になります。

一方「hoge inputfile」は、「コマンド hoge の引数として inputfile という文字列を与える」という意味です。 hoge が inputfile という文字列をどう処理するかは hoge に任されています。 cat 等では「inputfile というファイルを開いてそれを読み込む」とされていますが、例えば ls では「そのファイルが存在すればファイルの情報を表示する」となりますし、echo だったら “inputfile” という文字列をそのまま表示します。

14 環境変数

シェルはユーザと計算機の間の情報のやりとりを仲立ちするものです. そこでシェルはユーザが使用している環境やユーザに関する情報を 「環境変数」に保持し, 起動するアプリケーションに渡しています.

環境変数に設定されているため, 「ユーザ名が何か, ホスト名が何か」といった情報を ユーザが逐一明示的にコマンドに渡さなくてもシェルが自動的にやってくれます. 例えばアプリケーションのエラーメッセージが日本語で出ることがありますが, 多くの場合これは環境変数 LANG に「ja_JP.eucJP」(日本語)が設定されているからです. アプリケーションは環境変数 LANG をチェックし, そこに記述された情報に 基いて適切な言語を選択して出力している, ということになります.

15 シェル変数

シェルが管理する変数にはもう一つ, シェル変数があります. これはシェルから起動されたアプリケーションには渡されず, シェルが使用するだけの物です. シェルの動作をカスタマイズするオプションみたいなものです.

16 ヒアドキュメント

ヒアドキュメントは複数行をまとめて文字列にする手段です.

% cat - <<here
heredoc> hello, world!
heredoc> goodbye, world!
heredoc> here

(「heredoc> 」は zsh デフォルトのヒアドキュメント中のプロンプト) すると, 以下のように出力されます.

hello, world!
goodbye, world!

1行目と4行目の here は両者で一致していれば何でも良いです. 文字列中で「here」を使うのならば,

% cat - <<mario
heredoc> here we go!
heredoc> mario

UNIX を仕事に使ってると他人の書いたシェルスクリプトを解読する必要に迫られることがしばしばあります. ヒアドキュメントを積極的に使うことはあまりないかもしれませんが, 「こういうこともある」という程度に知っておくと便利かもしれません.

ヒアドキュメントは, シェルというよりプログラム一般に近いトピックですね. perl や ruby でも装備されていて, 私はスクリプトの USAGE を記述するのによく使ってます.

17 設定の有効化

~/.zshrc などの初期設定ファイルをいじってその設定を有効にしたい場合, source を使う手順は以前に紹介しました. source ~/.zshrc source ~/.zshenv しかしこれでは変更が反映されないことがあります. 変更内容が環境変数やエイリアスの削除だった場合でも環境変数などは 残ったままであり, ユーザが自分の手で unset や unalias する必要があります.

そこでシェルの設定を有効にする手段としては, exec を使う方法をオススメします. exec zsh exec コマンドは現在のシェルを破棄して, 新しく実行するコマンドです. (子プロセスとしてではなく, 現在のシェルのプロセスと置換する形で実行する) まっさらな状態から通常のシェルを起動する手順で初期設定ファイルを読み込むので, 設定されていない(削除された)環境変数などは登録されません.

exec コマンドは多くのシェルに装備されてますので, zsh だけでなく, tcsh や bash でも同様のことができる筈です.

18 外部コマンドと内部コマンド

18.1 概説

今迄「コマンド」と一口で言っていましたが, コマンドには大きく分けて 「外部コマンド」と「内部コマンド」の2種類があります.

外部コマンドとはシステム上に実際にファイルとして存在しているコマンドです.

内部コマンドとは実際にファイルを呼ぶのではなく, シェル自身が動作を返すコマンドです.

「どのコマンドがどっち」とかは全然知っておく必要はありません. (というか, 慣れてくると大体分かるようになります.) でもまあ, 言葉の意味を知っておいたら便利かもしれません.

さて, 「man builtin」で bsh, csh の代表的な内部コマンドを調べることができます. 中には echo のように外部コマンドとして用意されているけれども 多くのシェルで内部コマンドとして実装されているものもあります. その場合は通常は内部コマンドが呼ばれ, 外部コマンドを使いたい場合は 「/bin/echo」を用います.

「command echo」, 「」などでどちらが呼ばれるかは シェルの種類によって異なるようです.

              zsh  bash  tcsh
echo          ●    ●    ●
builtin echo  ●    ●   (なし)
\echo         ●    ●    ○
command echo  ○    ●    ○    
/bin/echo     ○    ○    ○    
(●:内部, ○:外部)

18.2 which コマンド(実際に実行されるコマンドを表示)

さて今「echo」と打ち込めば, それは何処の「echo」が実行されるのでしょうか? 打ちこんだコマンドが実際にどのように実行されるかを確認したい場面では「which」コマンドを使います.

% which echo
echo: shell built-in command #内部コマンド

% which /bin/echo
/bin/echo                    #外部コマンド

% alias echo='echo -n'

% which echo                 #エイリアス
echo: aliased to echo -n

% which -a echo              #全て表示
echo: aliased to echo -n
echo: shell built-in command
/bin/echo

which 自身は内部コマンドですが, 外部コマンドにも which(/usr/bin/which) があります. しかし外部コマンドの方はエイリアスの展開などはできないので 普通使うことはないと思います.

18.2.1 コマンドの存在をチェックする

初期化スクリプトやシェルスクリプトにおいてそのコマンドが存在するかを調べることができます. 例えば, 以下のどちらかの文を ~/.zshrc に入れておくと, vim が使用できる環境では「vi」で vim が起動するようになります.

if which vim >& /dev/null ; then  
  alias vi="vim"
  export EDITOR="vim"
fi

if [ -x `which vim` ]; then  
  alias vi="vim"
  export EDITOR="vim"
fi

これら二者はほぼ等価です. 前者は出力された文字列ではなく which コマンドの戻り値を使用しています. UNIX 的にはこちらの方がスマートだと思います.

後者は「which によって出力された文字列をファイルパスとして見たときに, 実行権限があるか」という判定をしています. ただ, which コマンドはそれ自体ファイルの実行権限をチェックしていますので, この if 文におけるチェック機構は本来ならば冗長であると言えるでしょう. しかしこちらの方が構文がスマートに見えるので, 私はこちらがお気に入りです.

18.2.2 絶対パスを変数に入れる

変数の内容にコマンドの絶対パスが要求される場合, which を用いることで計算機による環境の違いを吸収することができます.

例えば gcc が /usr/bin/gcc に入るか, /usr/local/bin/gcc に入るかは システムによって(あるいは管理者によって)異なりますが, export CC=which gcc としておくと, 実際にインストールされているパスを自動的に変数に格納します.

18.2.3 補足:whence(zsh 内部コマンド)

zsh ではコマンド名解釈の情報を包括的に調べるためのコマンド「whence」が 用意されていて, 以下のように等価になっています. (参考:man zshbuiltins)

19 生のコマンドを叩く(エイリアスや関数の展開を回避する)

よく使うコマンドをエイリアスや関数で再定義している人は多いと思います. 岸田は例えば, 「ls」を「ls -F –color=auto –show-control-char」(Linux)のようにエイリアスしています. こうしておけば ls は常に「カラー表示, 日本語ファイル名を表示」してくれます. しかし, 状況によってはエイリアスした方ではなく, 本来のプレインな「ls」を実行したい場合があります. ls のカラー表示が不具合を発生するなどの状況を想定してみて下さい. このような場合, コマンドを書き始める前に, 「command」というおまじないをすると本来の「ls」を実行してくれます. command ls

command コマンドのもう一つの用途は, 関数定義で再帰的に定義されるのを防ぐことです. 例えば私は, emacs を次のように再定義しています.

function emacs() { command emacs $* & }

この関数によって emacs は常にバックグラウンドで走ります. ここで command を使わなかったらどうなるでしょうか? 関数 emacs によって emacs が呼ばれるのですが, この時既に「function emacs()」によって関数 emacs が定義されているので, この関数 emacs が呼ばれます. そしてシステムリソースを喰い尽くすまで無限に関数 emacs を呼出し続けます. (岸田の FreeBSD 機はハングアップしました. 試さない方が良いでしょう) command はこのような問題を回避するためによく用いられます.

なお, tcsh ではコマンドの先頭に「」を付けて「」のように指定するテクニックもありますが, この方法はシェルによって挙動が異なるため, あまり正しいお作法ではないような気がします.