(bash)=
# bash

bash は、複数の[コマンド](command)を組み合わせて処理を自動化するために使われる[シェル](shell)です。基本的にはコマンドを順番に並べるだけで処理を書けるため、非常に手軽に利用できます。また、他のプログラミング言語と同様に、条件分岐や繰り返し処理を書くことも可能です。一方で、文法が独特なため、複雑な処理を書くのにはあまり向いていません。

bash を実行した瞬間、卒業要件も消えた。伝説を残さないように、ここからは慎重に読み進めてください。引き返すなら、今です。


## 特殊変数

bash を含むシェルには、**環境変数**（**environment variable**）や**シェル変数**（**shell variable**）と呼ばれる特殊な変数があらかじめ定義されています。これらの変数は、慣習的に大文字で表記されます。bash で新しい変数を作成する際に、これら環境変数と同じ名前を使ってしまうと、実行環境が破壊され、プログラムが正しく動作しなくなる可能性があります。そのため、変数名の衝突には十分注意する必要があります。

代表的な環境変数には、以下のようなものがあります。

| 変数      | 説明                                                                 |
| ------- | ------------------------------------------------------------------ |
| `SHELL` | 現在使用しているシェルの実行ファイルへのパス。                                             |
| `PATH`  | コマンド検索パス。プログラム実行時に、この変数で指定されたディレクトリの中からコマンドが検索され、最初に見つかったものが実行されます。 |
| `HOME`  | ホームディレクトリのパス。                                                       |
| `USER`  | 現在のユーザー名。                                                           |
| `PWD`   | 現在の作業ディレクトリのパス。                                                     |
| `PS1`   | プロンプトの表示形式。                                                         |
| `LANG`  | 言語などのロケール設定。                                                        |


実際に、これらの変数の中身を `echo` コマンドで表示してみましょう。変数を表示させるためには、変数を `${}` で囲みます。

```bash
echo ${SHELL}
```

```script-output
/bin/bash
```

```bash
echo ${HOME}
```

```script-output
/home/rispy
```

```bash
echo ${PATH}
```

```script-output
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
```

```bash
cd ~/Desktop
echo ${PWD}
```

```script-output
/home/rispy/Desktop
```

ここで紹介した以外にも、多くの環境変数が定義されています。現在設定されている環境変数を一覧表示するには、`printenv` コマンドを使用します。

```bash
printenv
```





## 変数

### 数値

bash の変数が取り得る値は基本的に文字列です。例えば、変数に `1` を代入しても、`hello world` を代入しても、どちらも文字列として認識されます。変数に値を代入するには `=` を使用します。`=` の左右には空白を入れないでください。他のプログラミング言語では `=` の左右に空白を入れても問題ありませんが、bash ではエラーになります。

まず、変数に数値を代入する例を示します。次の例では、変数 `x` に 1 を代入し、変数 `y` および変数 `z` に、`x` に 1 を足したつもりの値を代入しています。

```bash
x=1
y=1+x
z=1+${x}
```

変数に値を代入すると、プログラム（ターミナル）が終了するまでその値が保持されます。次は `echo` コマンドを使って変数の中身を出力する例です。

```bash
echo ${x}
```

```script-output
1
```

```bash
echo ${y}
```

```script-output
1+x
```

```bash
echo ${z}
```

```script-output
1+1
```

このように、変数 `y` と `z` では異なる結果が出力されます。変数 `y` には `1+x` という文字列がそのまま代入されています。つまり、2 行目で使用している x は変数 `x` ではなく、単なる文字 x として扱われています。一方、変数 `z` では `${x}` によって変数 `x` の中身が展開され、`1+1` という文字列になって代入されています。このように、bash で変数であることを明示するには `${x}` のように記述します。なお、変数 `z` が 2 ではなく `1+1` になっているのは、`=` の右側が計算されず、あくまで文字列として扱われているためです。


### 数値計算

bash では、変数の値は基本的に文字列として扱われるため、そのままでは数値計算ができません。数値計算を行うには、`expr` コマンドを使用します。`expr` の後ろに書かれた式は、数値や数式として評価されます。足し算と引き算は、そのまま `+` や `-` を使って記述できますが、掛け算、割り算、剰余算を行う場合は、`\*`、`\/`、`\%` のように、演算子の前にバックスラッシュ（`\`）を付ける必要があります。これは、`*` や `/`、`%` などがコマンドライン上で特別な意味を持つため、そのまま使用することができないためです。

なお、バックスラッシュ（`\`）は、日本語環境では円マーク（`¥`）に見える場合がありますが、内部的には同じ文字として扱われます。そのため、表示が異なっていても動作上の違いはありません。


```bash
x=5
expr 2 \* ${x} + 1
```

```script-output
11
```

```bash
expr ${x} \/ 2
```

```script-output
2
```

```bash
expr ${x} \% 2
```

```script-output
1
```

次に、`expr` の計算結果を他の変数に代入する例を示します。コマンドの実行結果を変数に代入するには、実行文全体をバッククォート `` ` `` で囲みます。これにより、コマンドの出力結果が文字列として変数に代入されます。

```bash
x=3
z=`expr 2 \* ${x} + 1`

echo ${z}
```

```script-output
7
```

bash を使った計算はやや手間がかかります。数値計算を行う場合は、Python や R を起動して計算した方が効率的です。


### 文字列

変数に文字列を代入するには、数値の場合と同様に `=` を使用します。ただし、文字列に空白が含まれている場合は、ダブルクォート（`"`）で囲む必要があります。

```bash
a=hello

b=hello world
```

```script-output
Command 'world' not found, but can be installed with:
sudo snap install world
```

このように、空白を含まない文字列であれば、そのまま変数 `a` に代入されますが、空白を含む文字列の場合は、空白以降の部分が別のコマンドとして解釈されてしまい、代入に失敗します。

もう 1 つ例を示します。この場合は、空白の後にある `ls` がコマンドとして実行され、ディレクトリ内のファイルやディレクトリの一覧が表示されます。このとき、エラーは発生しませんが、変数 `c` には何も代入されません。

```bash
c=hello ls
```

```script-output
Desktop  Documents  Downloads
```

```bash
echo ${c}
```

```script-output
```

このように、想定外の動作が発生しているにもかかわらず、エラーが出ない場合があります。そのため、文字列を扱うときは特に注意が必要で、必ずダブルクォートで囲む習慣を付けることをおすすめします。

```bash
d="hello world"
```

変数の中身を扱うときにも注意が必要です。単純に画面へ出力するだけであれば、次のいずれの書き方でも問題ありません。

```bash
echo ${d}
```

```script-output
hello world
```

```bash
echo "${d}"
```

```script-output
hello world
```

しかし、出力ではなく、`rm ${d}` のように他のコマンドの引数として変数を使う場合には注意が必要です。このとき、ダブルクォートで囲まないと、空白で区切られた複数の引数として解釈されてしまいます。たとえば、次のコマンドでは、ダブルクォートで囲まれている場合は `"hello world"` という 1 つのファイルあるいはディレクトリを削除しようとしますが、ダブルクォートで囲まれていない場合は `"hello"` と `"world"` という 2 つのファイルあるいはディレクトリを削除しようとします。

```bash
rm ${d}
```

```script-output
rm: cannot remove 'hello': No such file or directory
rm: cannot remove 'world': No such file or directory
```

幸いにも、上記の例では削除対象のファイルあるいはディレクトリが存在しないためエラーになっていますが、もし `"hello"` と `"world"` という名前のファイルあるいはディレクトリが存在していた場合は、意図せず両方とも削除されてしまいます。これは非常に危険な動作です。文字列を扱うときは、代入時だけでなく、その変数を利用する際にも常にダブルクォートで囲む習慣を付けると安全です。

bash では変数の値は常に文字列として扱われるため、中身が数値か文字列かを深く考えず、基本的にはダブルクォートで囲んでおきましょう。ヘルメットと同じで、見た目はさておき、安全第一です。



### 文字列置換

bash には簡単な文字列操作機能がいくつかようされています。部分文字列の切り出しや検索、置換などが可能です。部分文字列の切り出しには `:` を使用します。

```bash
s="0123456789"
echo "${s:2:5}"
```


```script-output
23456
```

```bash
echo "${s:7}"
```

```script-output
789
```

パターンマッチによる削除には `#` や `%` を使用します。`#` は先頭から、`%` は末尾からパターンにマッチした部分を削除します。さらに、`##` や `%%` を使うと最長マッチになります。

|              | 文字列先頭からマッチ      | 文字列末尾からマッチ      |
| ------------ | ---------------------- | --------------- |
| 最短マッチ削除 | `${s#pattern}`         | `${s%pattern}`  |
| 最長マッチ削除 | `${s##pattern}`        | `${s%%pattern}` |


たとえば、次の例では、`${s#a*t}` によって、文字列 `s` の中で、`a` から始まり、任意の文字列が 0 個以上続き（`*`）、`t` で終わる部分文字列を先頭から探します。 この場合は、`agt` とマッチします。そのため、最初の `agt` が削除され、`ccctgg` が出力されます。

```bash
s="agtccctgg"

echo "${s#a*t}"
```


```script-output
ccctgg
```

一方、`${s##a*t}` では、最長マッチを行うので、先頭から最後に現れる `a` から `t` までの部分文字列が削除されます。そのため、`agtccct` が削除され、`gg` が出力されます。

```bash
echo "${s##a*t}"
```

```script-output
gg
```

`%` および `%%` も基本的に同様に動作しますが、こちらは文字列の末尾からマッチングを行います。この場合は、`g*g` をマッチするので、`g` から始まり、任意の文字列が 0 個以上続き、`g` で終わる部分文字列が削除されます。


```bash
s="agtccctgg"

echo "${s%g*g}"
```

```script-output
agtccct
```

```bash
echo "${s%%g*g}"
```

```script-output
a
```

これらのマッチ削除機能を利用すると、拡張子の変更などに便利です。

```bash
img="input.jpeg"

echo "${img%.jpeg}.jpg"
```

```script-output
input.jpg
```

```bash
libname="leaf_1.fq"
fq1="${libname%_1.fq}_1.fq"
fq2="${libname%_1.fq}_2.fq"

echo "${fq1}"
```

```script-output
leaf_1.fq
```

```bash
echo "${fq2}"
```

```script-output
leaf_2.fq
```

文字列置換には `/` を使います。最初の一致だけを置換する場合は `/`、すべて置換する場合は `//` を使用します。

```bash
s="caagccaagct"

echo "${s/aag/AAG}"
```

```script-output
cAAGccaagct
```

```bash
echo "${s//aag/AAG}"
```

```script-output
cAAGccAAGct
```

文字列検索や置換に利用するパターンは、基本的に**ワイルドカード**（`*` や `?`）を使用します。正規表現は使えません。高度な文字列操作が必要な場合は、[Python](python) や [Perl](perl) を使う方が効率的です。


## 配列

配列を利用すると、複数の値を 1 つの変数にまとめて保存できます。配列を作成するには、変数名の前に `()` を付けて、要素を空白で区切って並べます。

```bash
samples=("leaf" "root" "shoot")
```

要素を取り出すにはインデックスを指定します。bash では 0 から数えます。

```bash
echo "${samples[0]}"
```

```script-output
leaf
```

```bash
echo "${samples[1]}"
```


```script-output
root
```

```bash
echo "${samples[2]}"
```

```script-output
shoot
```

また、次のように値が定義されていないインデックスを指定すると、何も表示されません。

```bash
echo "${samples[3]}"
```

```script-output

```



配列全体を取得するには `[@]` を使います。

```bash
echo ${samples}
```

```script-output
leaf
```

```bash
echo ${samples[@]}
```

```script-output
leaf root shoot
```

要素数は `#` を使います。

```bash
echo ${#samples[@]}
```

```script-output
3
```


要素を追加するには `+=` を使います。

```bash
samples+=(flower seed)

echo ${samples[@]}
```

```script-output
leaf root shoot flower seed
```

`ls` の結果を配列に保存することもできます。

```bash
cd ~/Desktop/takarabako/fastq
ls
```

```script-output
flower_1.fastq.gz  leaf_1.fastq.gz  root_1.fastq.gz  seed_1.fastq.gz  shoot_1.fastq.gz
flower_2.fastq.gz  leaf_2.fastq.gz  root_2.fastq.gz  seed_2.fastq.gz  shoot_2.fastq.gz
```


```bash
fq=(`ls *.fastq.gz`)

echo ${#fq[@]}
```

```script-output
10
```


```bash
echo ${fq[@]}
```


```script-output
flower_1.fastq.gz flower_2.fastq.gz leaf_1.fastq.gz leaf_2.fastq.gz root_1.fastq.gz root_2.fastq.gz seed_1.fastq.gz seed_2.fastq.gz shoot_1.fastq.gz shoot_2.fastq.gz
```





## 分岐処理

ある条件に応じて処理を切り替えることを分岐処理といいます。コインを投げて表ならコカコーラ、裏ならペプシコーラ、というのが典型例です。ちなみに、社会に出ると、条件は常に曖昧で、結果はだいたい決まっています。分岐しているように見えて、どちらを選んでも修正が入ります。


### if （文字列）

bash の分岐処理は、`if` 文または `case` 文を使います。`if` 文は、「もし条件が成り立つなら処理を行う」という、最も基本的な分岐処理を行う制御文です。例えば、変数 `coin` に `"head"` という文字列が代入されたときに `"Coke"` を出力し、`"tail"` という文字列が代入されたときに `"Pepsi"` を出力する場合は、次のように書けます。


```bash
coin="head"

if [ "${coin}" = "head" ]
then
	echo "Coke"
fi

if [ "${coin}" = "tail" ]
then
	echo "Pepsi"
fi
```

```script-output
Coke
```

この例では、`coin` の中身が `"head"` であるため、最初の `if` ブロックが実行されて `"Coke"` が出力されます。一方、次の `if` ブロックでは条件が成り立たないため、処理は実行されません。

head/tail のように選択肢が 2 つしかない場合は、`if` を 2 回書く代わりに、`else` を使うこともできます。次の例では、`coin` が `"head"` のときに `"Coke"` を、それ以外の場合はすべて `"Pepsi"` を出力します。


```bash
coin="head"

if [ "${coin}" = "head" ]
then
	echo "Coke"
else
	echo "Pepsi"
fi
```

```script-output
Coke
```

条件が 3 つ以上ある場合は、`if` - `elif` - `else` を使って分岐します。


```bash
mood="happy"

if [ "${mood}" = "happy" ]
then
	echo "Coke (PET) x 2"
elif [ "${mood}" = "excited" ]
then
	echo "Coke (PET) x 5"
elif [ "${mood}" = "normal" ]
then
	echo "Coke (can)"
elif [ "${mood}" = "sad" ]
then
	echo "Coke (bottle)"
else
	echo "Coke"
fi
```

````script-output
Coke (PET) x 2
````

「等しくない」ことを判定したい場合は、`=` の代わりに `!=` を使います。

```bash
mood="sad"

if [ "${mood}" != "sad" ]
then
	echo "Coke (PET)"
else
	echo "Coke (bottle)"
fi
```

```script-output
Coke (bottle)
```

ここまでコカコーラを推しても、研究費の協賛は来ません。マーケティング的には満点、財務的には却下でしょうか。



### if（数値）


`if` 文では数値の比較を行うこともできます。その際には、数値専用の比較演算子を使用します。次の例では、変数 `score` の値に応じて評価結果を出力します。

* `score` が 100 のとき：`"excellent"`
* `score` が 80 以上のとき：`"good"`
* `score` が 60 以上のとき：`"pass"`
* `score` が 60 未満のとき：`"failure"`

```bash
score=86

if [ ${score} -eq 100 ]
then
	echo "excellent"
elif [ ${score} -ge 80 ]
then
	echo "good"
elif [ ${score} -ge 60 ]
then
	echo "pass"
else
	echo "failure"
fi
```

```script-output
good
```

なお、このプログラムでは、`score` が 100 より大きい値の場合でも `"good"` と表示されてしまいます。余力があれば、`score` が 100 を超えた場合に `"cheating"` を出力するように修正してみてください。

数値の比較には、次のような演算子がよく使われます。

| 演算子   | 使用例                 | 意味                  |
| ----- | ------------------- | ------------------- |
| `-eq` | `[ ${x} -eq ${y} ]` | `x` と `y` が等しければ真   |
| `-ne` | `[ ${x} -ne ${y} ]` | `x` と `y` が等しくなければ真 |
| `-lt` | `[ ${x} -lt ${y} ]` | `x` が `y` より小さければ真  |
| `-le` | `[ ${x} -le ${y} ]` | `x` が `y` 以下なら真     |
| `-gt` | `[ ${x} -gt ${y} ]` | `x` が `y` より大きければ真  |
| `-ge` | `[ ${x} -ge ${y} ]` | `x` が `y` 以上なら真     |


`if` 文では、複数の条件を一つの `if` 文で判定することができます。必要に応じて、`-a` および `-o` の使い方を調べながら活用してみてください。


### case

条件が複数あるとき、`if` 文で複数の `elif` を並べるとコード全体が見にくくなることがあります。そのような場合には `case` 文を使うと、コードが読みやすくなります。なお、`case` 文は文字列の比較にしか使えません。数値の比較には使えないため注意してください。


```bash
mood="happy"

case "${mood}" in
	happy)
		echo "Coke (PET) x 2"
		;;
	excited)
		echo "Coke (PET) x 5"
		;;
	normal)
		echo "Coke (can)"
		;;
	sad)
		echo "Coke (bottle)"
		;;
	*)
		echo "Coke"
		;;
esac
```

```script-output
Coke (PET) x 2
```

ここでは文字列が完全一致するかどうかで条件判定を行っています。条件を正規表現で書くこともできますが、正規表現の使い方は難しいため、ここでは説明を省略します。


## 繰り返し処理

繰り返し処理には `for` 文と `while` 文があります。`for` 文は、あらかじめ繰り返し対象や回数が決まっている場合に使います。一方、`while` 文は、最初に回数を決めず、ある条件を判定し、その条件が真である間処理を繰り返します。どちらも繰り返し処理用の文であり、`for` 文で書ける処理は `while` 文でも書けますし、その逆も可能です。

### for

`for` 文では繰り返し対象を指定します。例えば、配列に含まれているすべての要素に対して同じ処理を繰り返すときに使用します。まず、配列の各要素を出力する例を示します。

```bash
samples=(leaf root shoot)

for sample in ${samples[@]}
do
    echo "${sample}"
done
````

```script-output
leaf
root
shoot
```

この例では `echo` コマンドのみを使用していますが、実際には `do` から `done` の間にさまざまなコマンドを書いて利用します。例えば、leaf.fastq、root.fastq、shoot.fastq の拡張子をそれぞれ leaf.fq、root.fq、shoot.fq に書き換えたり、あるディレクトリ内に存在するすべての圧縮ファイルを解凍したりする場合などに使用できます。

次に、fastq ディレクトリにある名前が `fastq.gz` で終わるファイルをすべて `fq.gz` に書き換える例を示します。

```bash
cd ~/Desktop/takarabako/fastq

fastq_files=`ls *.fastq.gz`

for fq in ${fastq_files[@]}
do
    echo ${fq}
    mv ${fq} ${fq%.fastq.gz}.fq.gz
done
```

`for` 文のためだけに使う場合は、`fastq_files` のような一時的な変数を作成しなくても構いません。その場合は、次のように書くことで、`ls` の実行結果に直接 `for` 文を適用できます。

```bash
for fq in `ls *.fq.gz`
do
    echo "${fq}"
done
```

```script-output
flower_1.fq.gz
flower_2.fq.gz
leaf_1.fq.gz
leaf_2.fq.gz
root_1.fq.gz
root_2.fq.gz
seed_1.fq.gz
seed_2.fq.gz
shoot_1.fq.gz
shoot_2.fq.gz
```

### while

`while` 文は、「条件を判断し、その条件が真である間処理を行う」ことを繰り返す文です。条件の書き方は `if` 文と同様です。次の例では、変数 `i` が 5 未満である間、`i` の値を出力します。

```bash
i=0

while [ ${i} -lt 5 ]
do
	echo ${i}
	i=`expr $i + 1`
done
```

```script-output
0
1
2
3
4
```

`while` 文は、例えば乱数を利用した処理で失敗する可能性があるプログラムを、成功するまで繰り返し試行したい場合などに使えます。また、不安定なネットワーク環境において、正しくダウンロードできるまで再試行したい場合にも便利です。

次の例では、確率 0.8 で裏が出るコインを投げ続け、表が出るまでに投げた回数を計算しています。このコードでは乱数を使用しているため、実行するたびに異なる値が出力されます。何回か実行して結果を比べてみてください。

```bash
n=0

while [ `expr $RANDOM \% 10` -ge 2 ]
do
  n=`expr ${n} + 1`
done

echo ${n}
```

```script-output
7
```

`while` 文を使ってファイルの中身を読み込むこともできます。ファイルの内容を `while` 文に渡すには、次のようにリダイレクトを使用します。次の例は、`acorns.txt` ファイルの各行を読み込み、その内容を出力する例です。

```bash
cd ~/Desktop/takarabako

while read line
do
	echo "${line}"
done < acorns.txt
```

```script-output
tree	weight	height	diameter
kunugi	5.55	2.27	1.89
kunugi	4.62	1.98	1.84
 ...
 ...
matebashii	3.13	2.76	1.40
matebashii	2.20	2.54	1.21
```

リダイレクトの代わりにパイプを使用することもできます。パイプを使う場合は、`cat` コマンドや `grep` コマンドの実行結果を `while` 文に渡します。次の例は、`acorns.txt` ファイルのうち `"kunugi"` を含む行だけを読み込み、出力する例です。

```bash
grep "kunugi" acorns.txt | while read line
do
	echo "${line}"
done
```

```script-output
kunugi	5.55	2.27	1.89
kunugi	4.62	1.98	1.84
kunugi	5.05	2.08	1.90
kunugi	5.44	2.18	1.91
kunugi	5.60	2.20	1.93
kunugi	4.83	2.19	1.85
kunugi	5.55	2.16	2.00
kunugi	5.13	2.20	1.87
kunugi	6.03	2.26	2.01
kunugi	7.41	2.35	2.12
```

