UNIXの基礎（後編）

# この記事について

UNIX系のパソコンについて基本的なことをまとめています。この記事を読んでちゃんと理解すればUNIXについての理解がかなり深まると思います。前編は以下のリンクからどうぞ。

UNIXの基礎（前編）
https://qiita.com/minnsou/items/6b6b74fcd79bd00ace1a

# 環境設定

### 環境の設定方法

UNIXで使われるシェル（sh, csh, bashなど）はカスタマイズができるようになっている。コマンドのカスタマイズの仕方は大きく分けて2つある。
1. 設定ファイルによる方法
設定ファイルとはコマンドの設定を記述するファイルで、一般に設定ファイルを使うコマンドは、起動されると最初に設定ファイルの内容を読み込み、その記述によって動作を変える。設定ファイルは例えばshなら`.profile`だし、bashなら`.bash_profile`や`.bashrc`を使う。これはシェルによって異なる。
2. 環境変数による方法
環境変数は変数に値を設定して使う。コマンドは起動されると必要に応じて環境変数の値を調べ、設定されている値によって動作を変える。環境変数は設定ファイルの中で設定するのが普通。

### 対話的シェルと非対話的シェル

シェルの起動には**対話的**と**非対話的**の2種類がある。対話的というのは「人間が要求を出すとそれをコンピュータが処理する」というような作業を繰り返し行うような形態のこと。非対話的というのは処理の内容をすべてあらかじめファイルか何かに記述しておき、コンピュータがその内容を読み出して処理するような形態のこと。
cshの設定ファイルである`.cshrc`のように非対話的シェルでも読み込まれるような設定ファイルでは、メッセージを表示したり端末を設定する場合には通常エラーになるので注意。cshならシェル変数`$prompt`を使って場合分けをする必要がある。

### シェル変数と環境変数

シェルの設定ファイルではシェル変数および環境変数、エイリアスなどの設定を行う。
**シェル変数**はシェルが固有にもつ変数。この変数の中には特別な意味を持った変数があり、そういった変数を設定することでシェルの動作を変更できる。
**環境変数**はシェルではなく、UNIXで使われるすべてのプロセスがそれぞれ保有する変数。あるプロセスから別のプロセスが作られた時に、新しく作られたプロセスは元のプロセスで設定されていた環境変数をそのまま受け継ぐようになっている。

シェル変数と環境変数は、「環境変数は子プロセスに渡されるが、シェル変数は渡されない（環境変数はカーネルが管理しているので、子プロセスが生成されたとき、つまりシェルからコマンドが起動されたときにカーネルがシェルの環境変数のコピーを子プロセスに渡せるが、シェル変数はシェルが管理するので**カーネルは感知しない**）」という点で異なる。シェル変数と環境変数が混乱する時は**「シェル変数はシェル自身の動作を設定するために使い、環境変数はシェルが起動するコマンドの動作を設定するために使う」**と覚えると良い。

シェル変数はsh、bash、zshだと`variable=value`で設定する。`=`の両端にスペースは入れない。シェル変数の例としては、

- プロンプト（`PS1`は`$`、`PS2`は`>`、`PS4`は`#`が基本の設定。なお、Macの`PS1`のデフォルトは` \h:\W \u \$ `で、`\h`はホスト名、`\W`がカレントディレクトリ名、`\u`はユーザ名を表す。）
- historyに保存するコマンドの数（`HISTSIZE`）
- ヒストリ置換で用いられる文字（`histchars`、これはデフォルトで`!`と`^`だけど変数には何も入っていないので、`histchars=%`みたいに設定すればこの文字は変えられる）

などがある。

環境変数の例としてはPATH, TERM, HOME, USER, SHELLなど(bashは小文字ではだめ)で、一般に大文字である。どれも`echo $PATH`とかで見れる。自分が定義したシェル変数aを環境変数にしたい時は、`export a`とする。ただしこのようにした環境変数も、そのシェルからログアウトすると消えてしまうので、永続的に残したい時には`.bashrc`等に書いておく必要がある。

`printenv`コマンドや`export`コマンドで全ての環境変数が見れるし、`man environ`でそのシステムで使われている代表的な環境変数についての説明が見れる。シェル変数を全部見るのは`declare`コマンド（ただし定義されている関数も見える）。

### エイリアス

コマンドに別名をつける機能。この機能により、次のようなことができる。
1. タイピングの手間を減らす（`alias md=mkdir`とか）
2. 新しいコマンドを作る（独自のコマンドを作るとか）
3. タイプミスを先取りする（`alias mroe=more`とか）
4. コマンドの意味を変える（`alias ls='ls -F'`とか）
5. シェルスクリプトではできないことを行う（現在使っているシェルのシェル変数や環境変数を設定するのはシェルスクリプトでは無意味、なぜならそのシェルスクリプトを実行する時には別のシェルが起動されるから）

### 端末の設定

端末の設定も、シェルの設定ファイルで設定される項目の一つ。端末の設定の最も重要なコマンドは`stty`コマンドである。`stty`コマンドでは、端末の割り当てキーや出力のモードなどの設定を行うことができる。例えば`stty erase ^H`というコマンドで一文字削除のキーが`Ctrl-H`になる。以下は`stty`で変更できるキーのうち、よく使うものを書き出した。

| 機能      | デフォルトのキー   | 意味                             |
|:---------|:-----------------|:--------------------------------|
| erase    | ^? (Del)         | 1文字削除                        |
| kill     | ^U               | 1行削除                          |
| werase   | ^W               | 1単語削除                        |
| susp     | ^Z               | サスペンド(ジョブ制御)             |
| intr     | ^C               | 実行の停止                       |
| eof      | ^D               | ファイルの終了(シェルの場合ログアウト)|

# （UNIXでの）正規表現

正規表現とは、ある規則に基づいて文字列の集合を表す方法の1つである。今後この記事で使われる正規表現は、UNIXで使われる正規表現であって、オートマトン理論や言語理論で使われる正規表現とは少し異なることに注意。

例えば`^a.*tion$`という文字列では、「行の最初(`^`)にaがあり、任意の1文字(`.`)の0回以上の繰り返し(`*`)があり、tionが続いて、行の終わり(`$`)がある」という意味になる。`^`や`*`のように、特別な意味を持つ文字のことを**メタキャラクタ**という。

正規表現は1種類ではなく、ツールによって実現している機能が制限されていたり、表現方法が微妙に異なる。ここでは、大きく2つに分けて紹介する。

1. `grep`や`ed`、`vi`、`sed`、`less`系の正規表現
2. `egrep`や`awk`系の正規表現

なお、**シェルのファイル名置換**で使われる`*`(任意の文字列)や`?`(任意の1文字)のようなメタキャラクタとは記号の意味が少し異なることに注意。`ps | grep *tion`のようにコマンド入力してしまうと、`*`がシェルによって**先にファイル名置換されてしまう**。`ps | grep '*.tion'`のように`''`で囲む必要がある。


### 1. `grep`や`ed`、`vi`、`less`系の正規表現

- `\m` メタキャラクタ`m`の意味をなくす
- `^` 行の先頭
- `$` 行の終わり
- `.` 任意の一文字
- `[c1c2...cn]` 並べた文字のうちの任意の1文字(例えば`[abc]`なら`a`か`b`か`c`）
- `[^c1c2...cn]` 並べた文字以外の任意の1文字(例えば`[^abs]`なら`a`と`b`と`s`以外の任意の1文字)
- `r*` 正規表現`r`の0回以上の繰り返し
- `\(r\)` タグ付き正規表現`r`（これは正規表現をグループ化するもので、この後ろに`*`をつけると0回以上の繰り返しが表せる）
- `\N` `N`番目のタグ付き正規表現がマッチした文字列を表す

例えば、

- `a*c`は、「`c`」「`ac`」「`aac`」などにマッチする。
- `\([ab]c\)\1`は、「`acac`」または「`bcbc`」にマッチする。「`\([ab]c\)`」で「`ac`」をマッチした時は「`\1`」は「`ac`」を表すので、「`acbc`」などにはマッチしない。

### 2. `egrep`や`awk`系の正規表現

基本的には上記の正規表現と同じだが、言語理論で定義されている正規表現の形式が使えるようになった。

- `r+` 正規表現`r`の1回以上の繰り返し
- `r?` 正規表現`r`が1つある、またはない
- `r1 | r2` 正規表現`r1`または`r2`（orの意味）
- `(r)` 正規表現`r`のグループ化(これによりタグ付き正規表現は`egrep`等では使えなくなった)

例えば、

- `ab+c`は、「`abc`」「`abbc`」「`abbbc`」などにマッチする
- `(a|b)c`は、「`ac`」または「`bc`」にマッチする


# 検索と置換

### 検索

基本的に**ファイル内の単語の検索**にはviとかemacsの検索や`grep`を使い、**ファイル名の検索**は`find`コマンドを使う。

viではコマンドモードで`/keyword`で順方向検索、`?keyword`で逆方向検索をする。emacsは`Ctrl-S keyword`で順方向検索、`Ctrl-R keyword`で逆方向検索をする。

`grep`や`egrep`、`fgrep`は、あるデータを受け取り、その中から指定された文字列と一致する行を抜き出して出力するコマンド。`grep keyword files`というのが基本の形で、ファイル名`files`が指定されていない時は、標準入力からデータを入力する。`grep`はいわゆる「grepファミリー」の中で最古参のもの。`egrep`は基本は`grep`だが、扱える正規表現が拡張されている。`fgrep`は複数の文字列を同時に検索できるが、正規表現は使えない。

`find`コマンドはファイルそのものの検索を行う。findコマンドの標準的な使い方は以下。

```
find ディレクトリ 検索条件
```

例えば`find . -name '*.py'`とすればカレントワーキングディレクトリ以下で名前の最後が`.py`となっているファイルを全部探すという意味になる。他にも`find . -name '*.py' -atime +15`とすれば16日以上アクセスされていないファイルを検索してくれる。`-atime`は最終アクセス時刻(access time)を表す。

もう少し実用的な例としては`find . -name '*~' -exec rm {} \; -print`がある。これは`~`で終わるファイル名を削除(`rm {}`)して、表示をする(`-print`)というもの。`-exec`オプションは、エスケープされたセミコロン(`\;`)までのUNIXコマンドを実行するもので、`{}`には条件に適合したパス名が入るようになっている。

### 置換

`vi`での置換は、`vi`のもう１つの顔である`ex`コマンドの置換機能と同じである。`ed`や`sed`も似たような置換コマンドを使う。基本的には次のような書式をしていて、`pattern`にマッチする文字列を`string`に置換する。

```
range s/pattern/string/g
```

`range`には置換範囲を指定する。例えば`%`で全体、`start,end`で`start`から`end`までの行を表す。`pattern`や`string`には正規表現が使える。最後の`/g`は1行中にある全ての置換該当箇所を変更することを意味する。ここらへんの詳細は、`sed`コマンドを紹介するところで詳しく扱う。なお、emacsの置換は`M-%`(`M-x query-replace`)で可能。

# フィルタ

**フィルタ**とは、標準入力から入力したデータを加工して、その結果を標準入力へ出力するようなプログラムのこと。大きく分けて2つあり、1つ目はフィルタ自身にプログラムを与えてフィルタ機能を指定できるもの（`sed`、`awk`、`perl`など）、2つ目ははじめから機能が決まっているもの（`head`、`tail`など）である。

### フィルタの例

`head`コマンドと`tail`コマンドを用いて新しい機能を持つフィルタを例として作る。`belly n m`でn行目からm行目までを取り出すフィルタとする。bellyの中身は以下。

```.sh
#!/bin/bash
from=$1
to=$2
arg=$(($to - $from + 1))
head -$to | tail -$arg
```

これで`cat -n foo.txt | ./belly 12 14`というコマンドで`foo.txt`の12行目から14行目を取り出すことができる。

### `tee`コマンド
引数として指定したファイルと標準出力の両方に、標準入力からの入力を出力するフィルタ。
例えば　`make | tee bar.txt` とすれば`make`コマンドの結果をbar.txtに出力しつつ、画面にも出力できる。

### `sort`コマンド
標準入力からの入力を行単位でソートして標準出力に出すフィルタ。
例えば　`ls -l | sort -nr +4` だと４番目のフィールド（ファイルの大きさ）を数値として比較し降順に並べる。
`-n`は数値として比較する、`-r`は指定された方法で逆順で表示、`-f`は大文字と小文字を区別しないという意味。

### `uniq`コマンド
標準入力からの入力の隣接した行が同じ内容であれば重複とみなし、その重複行を除いて標準出力に出すフィルタ。
例えばOLDディレクトリにファイルaaとbb、ccがありNEWディレクトリにファイルaa1とbb、ccがあるとする。
`(ls OLD; ls NEW) | sort`とすると

```
aa
aa1
bb
bb
cc
cc
```
と表示されるが、`(ls OLD; ls NEW) | sort | uniq`とすると

```
aa
aa1
bb
cc
```
と表示される。かぶっているbbとccが消える。

### `tr`コマンド
標準入力からの入力を文字単位で置換して標準出力に出すフィルタ。
`tr old-string new-string`という形で使い、old-stringに現れる文字をnew-stringに対応する文字で置き換える。`tr ab XY`ならaをXに、bをYに置き換える。`tr a-z A-Z`なら全てのアルファベットが大文字で置換されて表示される。
例えば`ls | tr a-z A-Z`とすれば、ファイル名が全て大文字で表示される。
なお、`-d`オプションを指定すると、指定した文字を削除するフィルタになるので、`cat file | tr -d '\015'`とすると制御文字のCRを消したものができる（Windowsは行末がCR LFでUNIXはLFだけなので上記のコマンドで改行が直る）。

### `expand`コマンド

標準入力からの入力のタブを相当する数の空白に変換して標準出力に出すフィルタ。
`unexpand`は逆に連続した空白をタブに置き換えるフィルタ。

### `fold`コマンド
標準入力からの入力を、各行の長さが一定文字数となるように強制改行してから標準出力に出すフィルタ。
例えば`expand file | fold -65`というコマンドで行幅を65文字にする。`fold`コマンドはタブを解釈しないのでこの例のようにexpandで前処理する。

### `pr`コマンド
標準入力からの入力をラインプリンタ向けの出力形式に変換して標準出力に出力するフィルタ。具体的には、各ページ番号のヘッダとして、ファイルの名前や時刻、ページ番号を追加することや文章をマルチカラム化するといった処理が可能になる。

### `sed`コマンド
`sed`(stream editor)はフィルタとして用いることを目的として設計されたエディタ。
`sed command`というのが基本的な形。`command`は「どのアドレスにマッチする行に、どのような関数を適用するか」を表したもの。`[ address1 [, address2 ] ] function`という形式である。アドレスを1つ指定するとその行のみ、アドレスを2つ指定するとその2つのアドレスの間の全ての行に対して、アドレスを指定しないと全ての行に対して関数functionを適用する。

アドレスの指定にはいくつかの方法がある。数字で行数を表したり、正規表現も使える。例えば`10p`なら、10行目にp関数を適用する。`/^a/,/^b/p`なら、aで始まる行からbで始まる行までにp関数を適用する。

各関数については以下。

- p関数は処理中の**行の内容を出力する**ために使う関数。sedは標準ではコマンドで処理した結果を出力するが、`-n`オプションを指定した時は自動的な行の出力を行わないようになるので、p関数で明示した行だけが表示されるようになる。例えば`cat -n /usr/share/dict/words |sed -n '100, 105p'`ってやると100行目から105行目までの各行が表示される。
- d関数は入力から**不要な行を削除する**ために使う関数。例えば`sed '/^BEGIN/,/^END/d'`というコマンドだと行頭でBEGINから始まる行から、行頭でENDで始まる行までを削除する。
q関数はアドレスにマッチした時点でsedの実行を終了する関数で`sed -e '100, 105p' -e '105q'`のように使う。`-e`は複数の関数を処理したい時に使うオプション。この例では105行目の時点でsedの実行が終了する。
- s関数は**正規表現を利用した文字列置換**を行う関数。`s/regular-expression/replacement/flags`という形で表現する。処理中の行に関して検索パターン`regular-expression`に指定した正規表現にマッチする最初の文字列を、置換パターン`replacement`に指定した文字列で置換する。`flags`にはs関数の動作を変更するためのフラグを指定する。例えばgなら行内で正規表現にマッチする文字列を全て置換する(デフォルトでは最初にマッチする文字列のみしか置換しない)。pならマッチした行を出力する。
- y関数は`tr`コマンドと同じく、**入力単位で置換する**関数。例えば`y/aiueo/AIUEO/`とすればaiueoの文字が全て大文字になる。

その他にも分岐や中間保存の結果などできるが、詳細は省略する。

### `awk`(オーク)コマンド

awkはパターンに従って入力行を処理するための言語。入力の各行がスペースやタブのような区切り文字で分割されている表のような形式のファイルの処理を得意としている。cに少し近い。awkを実行するには`awk program`という引数として`program`を指定する方法と、`awk -f program-file`としてプログラムをファイル`program-file`に書き込んでおいてそれを`-f`オプションで指定する方法がある。
`program`の中身は

```
pattern1 { action1 }
pattern2 { action2 }
...
```

であり、**入力に対するパターン`pattern`**とパターンにマッチした際に**実行するアクション`action`**を対にして記述する。`pattern`が省略された時は、全ての入力行に大してアクション`action`が行われる。`{ action }`が省略された時は、`pattern`にマッチした行を出力する。

正規表現によるパターンは`/`囲んで指定する。`awk '/^From:/'`とすればFrom:で始まる行を全て出力する。なお、コマンドラインでawkプログラムを指定する場合には、**awkに与えるプログラムが1つの引数になるように'で囲む**のを忘れないようにする。
`awk`は入力行を区切り文字によって自動的にフィールドに分割する。例えば`ls -l`なら`$1`にモード(`-rw-r--r--`とかのやつ)、`$2`にハードリンクの数、`$3`は所有者名、`$4`にグループ名、`$5`にはファイルの大きさなどのように入る(`ls -l`の表示順になっている)。

例えば`ls -l | awk '$5 > 1000{ print "size of file", $9, "is",  $5;}'`とすると

```
size of file filename1 is 34694
size of file filename2 is 1011
size of file filename3 is 56203
```

のようにファイルのサイズが1000より大きいものだけ表示される。なお、`action`には`print`以外にも変数の代入や演算、`if`や`for`などの制御文も入れることができるが詳しくは省略する。

# 圧縮とアーカイブ

### 圧縮

コンピュータ科学の分野では、何らかの形式で表現された情報をその内容を失うことなくその分量（データ量）を減らすための仕組みが古くから考えられてきた。このような仕組みのことをデータ圧縮、あるいは単に**圧縮**という。圧縮されたファイルを元に戻すことを**伸長**という。

`compress`コマンドは圧縮率はそこまで高くないけど、処理速度も遅くなく、ほとんどのUNIXシステムに標準で付属している。圧縮すると.Zという拡張子が付く。伸長には`uncompress`コマンドを使う。
`gzip`コマンドだと、.gzという拡張子が付く。これはGNUソフトウェアの一種。`gzip`コマンドで圧縮されたファイルを伸張する時には`gunzip`コマンドを使う。なお`zcat`というコマンドを使えば`compress`コマンドで圧縮されたファイルも`gzip`コマンドで圧縮されたファイルも伸長できる場合がある。`bzip2`という圧縮コマンドもある。伸長には`bunzip2`コマンドを使う。

### アーカイブ

複数のファイルをまとめて1つのファイルにすることを**アーカイブする**という。アーカイブするためのコマンドには`tar`, `shar`, `cpio`, `zip`などがある。

`tar`は`tape archive`の略で、元々は磁気テープにアーカイブファイルを作るためのコマンドだった。しかし、使用できるメディアが磁気テープに限られているわけではない。このコマンドは`tar key filename...`という形で使用する。keyには`c`、`t`、`x`、`r`、`u`という5つのコマンド文字のうち1つのみと、必要な場合はオプションが含まれる。

- `c`はtarファイルを作る
- `t`はtarファイルの中身を調べる
- `x`はtarファイルからファイルを取り出す
- `r`と`u`はtarファイルの末尾にファイルを追加する

よく使うオプションは以下。

- `v`オプションはverbose（おしゃべりな）の意味で、これを指定するとアーカイブの処理対象となるファイル名を順次出力する
- `f`オプションは引数を1つ取り、作成するtarファイルの名前を指定する


ただしtarにおけるオプションには「`-`」という文字を付けずに、コマンド文字や他のオプションと繋げて指定する。例えば`tar cvf srcfiles.tar ./src`で、./srcを元にscrfiles.tarというtarファイルを作る。

GNU tarもあり、元々の`tar`コマンドを拡張している。これは`gtar`というコマンド名の時もあるし、`tar`というコマンド名になっている時もある。
アーカイブのためのコマンドとしては`shar`、`cpio`、`zip`など他にもたくさんあるが、ここでは省略する。

# 時刻

### 現在の時刻を知る

時刻を見るには`date`コマンドを使う。`-u`オプションでグリニッジ標準時になる。

### カレンダーを見る

カレンダーを見るには`cal`コマンド。例えば`cal 11 2019`で2019年11月のカレンダーが見れる。なお、引数が1つの場合には、年であると解釈されるので注意。

### アラーム時計を使う

`leave`コマンドはアラーム時計として使うことができる。`leave 1920`とすれば19時20分にアラームがセットされ、ビープ音とともにメッセージが出てくる。`leave +30`という指定の仕方（30分後にメッセージを出す）もあり、現在からの相対時間でアラームを設定することも可能。いらない時は`kill`コマンドを使う。

### インターバルを取る

`sleep`コマンドでは、文字通り何もしないで休むコマンドである。引数に与えられた秒数だけ何もしないで休み、終了するコマンド。例えば`sleep 300; echo "Tea is just ready." &`というコマンドを実行すると、5分後に`echo`コマンドが実行されてメッセージが表示される。

### 指定した時刻にコマンドを実行する

自分の決めた時刻にコマンドを実行する方法のひとつとして、`at`コマンドがある。これは指定した時間に指定されたシェルスクリプトを実行するもの。`at time [filename]`という書式でtimeに起動する時間、filenameに実行するシェルスクリプト名を入れる。filenameを指定しないときは標準入力からシェルスクリプトを読み込んでそれを実行する。例えば`at 6am 6 script`というコマンドなら6月6日の午前6時にscriptが実行される。ただしこのコマンドはデフォルトでshを使う。またこのスクリプトの標準出力や標準エラー出力は**メールで来る**ので、それが嫌ならリダイレクション（`> result.out`とか）をする。

`at`コマンドで実行を指定したシェルスクリプトの状況は`atq`コマンドで知ることができる。`at`コマンドで指定したシェルスクリプトの実行を取り消すには`atrm`コマンドを使う。

### 定期的に仕事を行う

UNIXには定期的にコマンドを起動するような仕組みが用意されている。この仕組みを使うと毎日不要なファイルを自動的に削除する、というようなことが可能になる。この仕組みは**cron**というデーモンによって実現されている。また、定期的に実行されるコマンドの設定を行うファイルを**crontab**ファイルという。cronデーモンはcrontabファイルの内容を参照して、コマンドの定期的な実行を行う。

crontabファイルの設定には`crontab`コマンドを使う。crontabファイルを新たに作成する場合や、その内容を変更する場合には`crontab -e`とする。そうするとエディタが起動され、そのユーザのcrontabファイルが読み込まれる。

crontabファイルには、1行に1つの設定をかく。一般ユーザが使うcrontabファイルでは、各行は空白で区切られた以下のような6つの要素で構成される。

```
min hour day mon day-of-week command
```

最初の4つは、分(min)、時(hour)、日(day)、月(mon)の指定である。day-of-weekは曜日の指定で、0が日曜日、1が月曜日のように指定する。

例えば

```
0 0 1 1 * find $HOME -name '*core' -atime +7 exec rm {} \;
```

という行があると、1月1日の0時0分に`find $HOME -name '*core' -atime +7 exec rm {} \;`というコマンドが実行される。コマンドを指定する最初の5つのフィールドで数字の代わりに`*`を指定すると、そのフィールドの数字がいくつの時でもコマンドが実行されるようになる。crontabファイルの内容の表示には`-l`オプションをつけて実行する。crontabファイルを削除する時には`crontab -r`で削除する。

### コマンドの実行時間を測る

コマンドの実行時間を測るには`time`コマンドを使う。`time command`の形式。

# bashプログラミング

### シェルスクリプト 

一括で実行するようにコマンドをあらかじめファイルに格納したものを**シェルスクリプト**という。やや古めかしい言い方だが、実行を開始する前に処理方法を決めておくという意味で**バッチ**と呼ぶこともある。また何らかの目的のために処理が組まれているという点から**シェルプログラム**と呼ばれることもある。

シェルスクリプトを実行するときに毎回`bash script_name`と打つのは面倒なので、ファイルの先頭に`#!/bin/bash`と入れておくとコマンドとして実行できるようになる。つまり`./file_name`と打つだけでこれが実行できるようになる（ただし`chmod +x file_name`のようにファイルのモードを実行可能にしておく必要がある）。このようにコマンドとして実行できるシェルスクリプトを**シェルコマンド**と呼ぶ。

### シェルコマンド実行時の挙動

まずbashはコマンド名が`file_name`ということを知ると、コマンドサーチパスからこのファイルを探す（ただし`./file_name`の時は既にパスを指定しているのでパスから探すことはしない）。そしてそのファイルが実行可能モードの時、ファイルの1行目をみる。

1. 行頭が`#!`の時は、そのファイルの実行をカーネルに要求する。そして`#!`の後の絶対パス名で指定されるファイルをプログラムとして起動する。例えば`#!/bin/csh`とか`#!/usr/bin/python`とか。テキストファイルをその入力とする。絶対パスの後に書かれているものは引数とみなす。
2. それ以外の場合は`bash`を起動して、残りのテキストファイルをその入力とする

なお`#`の扱いは対話的にシェルを使っている時と、非対話的異なるので注意。シェルスクリプトで`#`を使っている時は**コメントとして認識されてしまう**ので注意。例えば`rm important#`とコマンドラインから実行させるのとシェルスクリプトで実行させるのでは、消えるファイルが異なってしまう。シェルスクリプトで`important#`を消したい時には、`important\#`のようにエスケープする。

bashとかを起動させた時、起動させるbashのカレントワーキングディレクトリと起動させたカレントワーキングディレクトリは**一致している**。相対パスも使ってシェルコマンドを実行させることもできる。

### 変数の詳細

bashにおいて、変数では**配列**も使用することができる。例えば`names=(hatter duchess alice)`という配列を宣言する。これで`echo ${names[0]}`のように値を参照することができる。

シェルスクリプトを書く時には、ある変数が定義されていない時にはデフォルト値を使用したり、配列の数に応じた処理をしたい時がある。そのためにbashには、変数が定義されているかどうかを調べたり、配列の数を調べる方法が用意されている。基本的なものを以下にあげる。

| 指定               | 意味                                             |
|:------------------|:-------------------------------------------------|
| ${variable}       | 変数varibleの値（例えば変数の値`$a`の直後に文字`z`をつけたい時に`$az`だと変数`az`と解釈されてしまうので`${a}z`とする） |
| ${#variable}      | 配列variableの要素数                               |
| $#                | シェルコマンドを起動した時の引数の数                   |
| $0                | シェルコマンドのファイル名                           |
| $num              | シェルコマンドを起動した時のnum番目の引数              |
| $*                | シェルコマンドを起動した時の全ての引数(配列ではなく文字列) |
| ${variable:-word} | variableで指定される変数が定義されていればその値、定義されていなければwordの値             |
| ${variable:=word} | variableで指定される変数が定義されていればその値、定義されていなければwordの値として、さらにその値をvariableにセットする                            |
| ${variable:?word} | variableで指定される変数が定義されていればその値、定義されていなければwordの値を表示して終了  |
| ${variable:+word} | variableで指定される変数が定義されていればその値、定義されていなければ空文字列             |

なお、標準入力から変数に入力するには`read`コマンドを使う。

シェルスクリプトの実際の例は`script.sh`というファイルに書いた。


```script.sh
#!/bin/bash
echo "this result is produced by $0"
echo '$#' is $#
echo '$*' is $*
echo '$$' is $$ '(process id)'
echo ${word:-abcg}
echo '$1' is ${word:=$1}
```

これを`./script.sh hello bash`とコマンドラインで実行すると以下のようになる。
<img width="319" alt="スクリーンショット 2020-04-04 15.26.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/37d42ea8-0c45-1dd7-85b2-8c3d840d65ce.png">
`$$`はそのシェルのプロセスIDを表す。これを使う典型例としてシェルスクリプトで使う**一時ファイルを作る**という例がある。`tmp=tmp.$$`のように代入しておけば、プロセスIDはそのプロセスが終了するまでは絶対に被らないという性質を用いて安全に一時ファイルが作れる。

### I/Oリダイレクション

プロセスの入力や出力は**デスクリプタ**と呼ばれる0から始まる番号を使って指定する。0は標準入力、1は標準出力、2は標準エラー出力を表す。`num>file`という形でデスクリプタ`num`への出力を`file`に出力することが可能。リダイレクションの`>`の後に`&`とデスクリプタ`num`をつけて`>&num`とすると、`num`のファイルにその出力がリダイレクトされる。分かりづらいので例を挙げる。

以下の`output.sh`は標準出力にstdoutという文字列を、標準エラー出力にstderrという文字列を出すスクリプトである。

```output.sh
#!/bin/bash
echo "stdout" >&1
echo "stderr" >&2
```

これはhttps://qiita.com/laikuaut/items/e1cc312ffc7ec2c872fc からいただきました。

1. `./output.sh > a`で、aにstdoutの文字列が入る。これは`./output.sh 1> a`と同じで1が省略されているだけ。
2. `./output.sh 2> a`で、aにstderrの文字列が入る。
3. `./output.sh 1> a 2> b`で、stdoutの文字列がaに、stderrの文字列がbに入る。
4. `./output.sh >& a`で、aに両方の文字列が入る。`>`と`&`の間にスペースを入れるとエラーとなる。
5. `./output.sh > a 2>&1`では、aに両方の文字列が入る。
6. `./output.sh 2>&1 > a`では、**aにstdoutの文字列しか入らない**。これは最初の`2>&1`の時点でstderrという文字列を**標準出力に出力してしまうから**である。そのあとで`> a`をするので標準出力だけがファイルに書き込まれる。

### 制御構造について

プログラミングでは、条件判断をした上で、次にどの部分を実行するか決めたり、ある条件を満たすまで処理を繰り返すということが必要な場合がある。このような実行を制御するための構文として、ここでは

1. if
2. for
3. while
4. case
5. 関数
6. 制御構造を支えるその他のコマンド

を取り上げる。

#### 1. if

ifの文法は以下。

```
if commandlist1
then commandlist2
else commandlist3
fi
```

**コマンドリスト（commandlist）** とは、コマンドを`;`や改行などで区切って並べたもの。`if`コマンドでは`commandlist1`の実行結果(つまり最後のコマンドの終了ステータス)が正常終了(0)の時は条件が成立したとして`commandlist2`を実行する。そうでなければ`commandlist3`を実行する。`fi`はifを逆にしたもの。elifも存在する。

#### 2. for

forの文法は以下。

```
for variable in list
do commandlist
done
```

`for`文では、リスト`list`の先頭のワードを変数`variable`の値としてセットして、`do`から`done`までのコマンドリスト`commandlist`を実行する。次にリストの2番目のワードを`variable`にセットして...を繰り返す。`continue`コマンドや`break`コマンドもある。

リストは複数の表現方法がある。`for i in a1.py a2.py`のように列挙しても良いし、`for i in *.py`のようにファイル名置換を用いても良い。bashでは`IFS`という区切り文字を表す環境変数に`:`を設定して`for i in $PATH`とすることで、パスのひとつひとつを変数`i`に代入することができる。

具体例としては以下の`if_for.sh`。

```if_for.sh
#!/bin/bash
for i in u.c t.c
do
    echo $i
    cc -c $i
    if [ $? != 0 ] # ここはif test $? != 0でも同じ
    then break
    fi
done
```

`cc`はコンパイルするコマンド。コンパイルに失敗する(u.cとかt.cというファイル名は適当に決めた名前なのでコンパイルできない)ので`$?`には0以外の値が入る。`[`コマンドは`[ exp ]`という形式で、`exp`の式を計算して結果が真なら正常終了し(終了ステータスとして0を返す)、そうでないなら異常終了する(終了ステータスとして1を返す)コマンド。`[`コマンドと同じものに`test`コマンドがある。今回はu.cというファイルは（多分）存在しないのでちゃんとforループが止まれば大丈夫。

式`exp`には様々な表現があるので一部を示す。

- `-a filename` ファイルfilenameが存在する
- `-d filename` ファイルfilenameが存在してディレクトリである
- `str1 = str2` 文字列str1と文字列str2が等しい
- `str1 != str2` 文字列str1と文字列str2が等しくない
- `num1 -eq num2` 数として比較した時に、整数num1 = 整数num2(equal)である
- `num1 -gt num2` 数として比較した時に、整数num1 > 整数num2(greater than)である

#### 3. while

whileの文法は以下。

```
while commandlist1
do commandlist2
done
```

これはcommandlist1が正常終了する(終了ステータスとして0を返す)までcommandlist2を繰り返すというもの。具体例としては以下の`while.sh`。

```while.sh
while true
do
sleep 10
w
done
```

これは10秒ごとに`w`コマンドを実行するというもの。`w`コマンドは誰がログインしているか、そして何をしているかを表示するコマンド。`while`の後のコマンドは、それが正常終了すれば（つまり`$?`が0のとき）ループに入る。`true`コマンドは正常終了するだけのコマンドなので永遠にループする。`false`コマンドもあり、これは異常終了するだけのコマンド。

#### 4. case

caseの文法は以下。

```
case string in
pattern1 ) commandlist1 ;;
pattern2 ) commandlist2 ;;
...
esac
```

まず文字列`string`が`pattern1`とマッチするかを調べて、マッチしたら`commandlist1`を実行する。マッチしない時は`pattern2`に移る。これを繰り返すというのがcase文である。最後の`esac`は`case`を逆から記述したもの。具体例としては以下の`case.sh`。

```case.sh
#!/bin/bash
for word in *
do
    case $word in
        *.out)
            echo $word is out file;;
        *.sh)
            echo $word is sh file;;
        *)
            echo $word is other file;;
    esac
done
```

例えばあるディレクトリに`aa.out` `bb.sh` `cc.txt`というファイルがある時、これを実行すると

```
aa.out is out file
bb.sh is sh file
cc.txt is txt file
```

と表示される。

#### 5. 関数

関数は`name () { commandlist ; }`という形式で定義できる。この定義を実行した後では、`name`をコマンドとして実行すると、対応する`commandlist`が実行される。具体例は以下で、func.shに書いた。

```func.sh
go () {
echo "file name = $1"
if test ! -r $1     # !は否定の意味
then echo $1 is not found
else echo $1 is found
fi
}
go foo.txt
go bar.txt
```

`foo.txt`というファイルが存在し、`bar.txt`というファイルが存在しないディレクトリで`./func_script.sh`で実行すると

```
file name =  foo.txt
foo.txt is found
file name =  bar.txt
bar.txt is not found
```
となる。

#### 6. 制御構造を支えるその他のコマンド

・`exec`コマンドは、現在`exec`コマンドを実行しているシェル自体の実行を終了して、指定されたファイルを実行する。例として`exec /bin/sh`というコマンドなら、これに成功すればshに移るし、失敗すればそのままのシェルで続行される。

・`eval`コマンドは、コマンドライン`commandline`を引数として与えてそれを実行させる。例えば`eval echo hello`ならhelloだけが表示される。これが使われるのは
1. あるコマンドが出力した文字列自体をコマンドとして実行するとき
2. 入れ子になった変数を評価するとき
である。1の例としては`eval $SHELL --version`とかで、これでバージョンがわかる。2の例としては`eval '$'$temp`で、これで変数`temp`が指す変数の中身を参照できる。

・`exit`コマンドは、そのシェルの実行を終了させるもの。`exit expr`とすると式`expr`の値を終了ステータスとして終了する。正常終了なら`exit 1`で、異常終了なら`exit 0`とするというのが普通である。式`expr`は省略可能。

・`source`コマンドは、cshやbash、zshなら存在する。shの場合は`.`を使う。`bash shell_script`の場合は**新たにbashのプロセスを起動してshell_scriptを実行する**が、`source`や`.`で実行すると**そのシェルで実行する**。以下のsource_dot.shを`source`と`bash`でそれぞれ実行すれば、終了後のカレントディレクトリが異なることがわかる。ただしzshは`.`で`source`の代わりとすることはできなくなった。

```source_dot.sh
echo 1 \$PWD is $PWD
echo 2 ls
ls
echo 3 change dir
cd ..
echo 4 \$PWD is $PWD
echo 5 ls
ls
```

・`goto`コマンドは、`goto label`という行があると、`label:`と書かれた行に移動するコマンド。


### シグナル処理

bashとかshでは`trap`コマンドにより、シェルスクリプト中に受け取ったシグナルに対する処理を指定できる。`trap command num`ならシグナル番号`num`で指定されたシグナルが発生した場合にコマンド`command`を実行する。`trap '' num`ならシグナル番号`num`で指定されたシグナルを受け付けない(''を実行する)ようにする。`trap num`ならシグナル番号`num`で指定されたシグナルの処理をもとに戻す。具体的な例としてCtrl-Cで止めることのできないスクリプトであるloop.shを紹介する。

```loop.sh
trap "echo 'you tried to kill me!'" TERM
trap "echo 'you hit CTRL-C!'" INT
while true; do
      sleep 5
      ps
done
```

これを`bash loop.sh`とかで起動すると、5秒毎に`ps`コマンドを実行し続ける。このプロセスは`Ctrl-C`では止めることができない。`Ctrl-C`ではINTというシグナルが送られるが、`echo`コマンドを実行した後に、またwhile文に戻ってしまうためである。`Ctrl-Z`で一度プロセスを停止して、psコマンドでプロセスID(PID)を調べて`kill`コマンドを打ってもこのプロセスは生きている（`fg`コマンドでフォアグラウンドに戻ってもyou tried to kill me!が表示されてまた`ps`コマンドが実行され続ける）。これは`kill`コマンドではTERMというシグナルが送られるからである。このプロセスを終了したい時は、`kill -KILL`コマンドを使えば良い。

### ログアウトの時に送られるシグナル

UNIXの設定によっては、ログアウトするとその端末から実行している全てのプロセスにHUP（HangUP）シグナルを送ることがある。HUPシグナルに対する処理を特にしていないプロセスはHUPシグナルを受け取ると終了する。ログアウト後も実行させたいプロセスは`nohup`の後にコマンドをつけて起動する。例えば`nohup python aa.py &`としておけば、ログアウトしたあとも`python aa.py`は動き続ける。

### 複雑な置換

変数`n`に変数名が格納されているとき、その値を取り出すために`${$n}`と書いても置換が行われない。これは`$n`の変数置換後には、もう変数置換を行わないからである。その変数の値を取り出すには`eval`などを利用してもう一度評価させる必要がある。具体的には以下。
<img width="450" alt="スクリーンショット 2020-04-04 17.20.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/79dedc9d-e578-0785-61e6-16840f5e0b5b.png">

シェルは置換（エイリアス置換、ヒストリ置換、変数置換、コマンド置換、ファイル名置換など）が全て終わった後でコマンドラインを引数に分解する。そのため、変数の値が**空文字列**であったり**空白を含む**場合であると、n番目の引数と取得するといった時に**添字がずれる**可能性があるので注意。

### 組み込みコマンド

シェル自身が実行するコマンドを**組み込みコマンド**という。組み込みコマンドを内部コマンドと呼び、それ以外のコマンドを外部コマンドということもある。あるシェルのプロセスの状態を参照したり、状態を変更することは基本的に他のプロセスからはできない。よってシェルのプロセスのカレントワーキングディレクトリを変更する`cd`とかは組み込みコマンドである。`kill`コマンドのように組み込みコマンドと外部コマンドの両方が存在する場合もあるが、外部コマンドの`kill`はシェルが記憶しているジョブ番号を利用できないという制約がある。

### bashを支えるコマンド

・`basename`コマンドと`dirname`コマンド
`basename`コマンドはパス名からディレクトリと拡張子を取り去ったものを出力する。拡張子を指定しなければ、パス名からディレクトリを取り除いたものを出す。
`dirname`コマンドはパス名からファイル名を取り去ったものを出力する。実行例を見る方がわかりやすい。
<img width="379" alt="スクリーンショット 2020-04-04 23.33.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/765683da-4b14-df39-131f-5dd5ce27a539.png">


・`expr`コマンド
引数の式を計算し、計算結果を標準出力に出力するコマンド。`expr exp`という形式。式`exp`には様々な形式があり、その一部を示す。

| 指定               | 意味                                             |
|:------------------|:-------------------------------------------------|
| `exp1 & exp2`     | 式`exp1`と式`exp2`のどちらかが空や0なら0を出力し、そうでないと`exp2`を出力する            |
| `exp1 = exp2`     | 式`exp1`と`exp2`が整数で一致すれば1を出力し、そうでなければ0を出力する（どちらかが整数でなければ文字列の比較を行う）                                |
| `exp1 + exp2`     | 加算した結果を出力する（他にも-や*、/、%などが使える）    |
| `length str`      | 文字列`str`の長さを出す                             |
| `substr string exp1 exp2`| 文字列`string`の`exp1`番目から`exp2`番目の部分文字列を出力する                          |


ただしbashには`$(( exp ))`という構文が組み込まれているので、このコマンドを使わなくても良い。shではよく使うコマンドである。
<img width="312" alt="スクリーンショット 2020-04-04 17.38.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/32dbedb3-0436-ef07-ab39-b0d5dc14d6d1.png">

# シェルについて

UNIXでは様々なシェルが使われる。最も代表的なシェルはshやcshだが、現在ではこれらのシェルが使われることはあまりない。なぜなら、これらをベースに色々と機能を強化した便利なシェルがあるからである。代表的なシェルを簡単に紹介する。

- tcshは、cshを元に機能を拡張したシェル。特にコマンドライン編集などの対話的作業のための機能の拡張が充実している。
- kshは、shの機能を強化したシェル。David Korn氏によって開発されたので頭文字のkを取っている。
- bashは、GNUプロジェクトで開発されているシェル。shの構文をベースにしている。
- zshは「決定版のシェル」を目指して作られたシェル。およそ現存するほとんど全ての機能を取り込んでいる野心的なシェル。shの構文をベースにしている。

### 便利な機能

比較的新しいシェルには、shやcshにない様々な機能がある。代表的な機能について、以下のようなものがある。

- 数値の範囲を指定するファイル名置換
- スペルの自動修正機能
- コマンドの定期的な実行機能
- 高度なリダイレクション
- コマンドの定期的な実行
- 時間を指定したコマンドの実行
- ログインとログアウトの監視機能

シェルはいっぱいあるので、自分にあうシェルを探そう。

# 終わりに

これでまとめは一旦終わりです。以上の内容は[UNIX super text](https://www.amazon.co.jp/新The-UNIX-Super-Text-改訂増補版/dp/4774116823)の上巻だけなので、機会があれば下巻もやるかも。なんかミス等あればご指摘いただけると幸いです。