Shellの基本を学ぶ（５）制御構文


# while

test が true の間実行

```bash
while test; do
  ;; some code
done
```

# until

test が false の間実行

```bash
while test: do
  ;; some code
done
```

# for

それぞれのワードがVARに入る。ワードのクオートは不要

```bash
for VAR in WORDS; do
  ;; some code
done
```

例えば次のようなコマンドの実行。`ls`の実行結果を一つづつループで回している。

```bash
$ for i in $(ls); do echo $i; done
answer
bin
hello_world.sh
hw
hw.h
logfile
ls.txt
output.sh
tn.sh
variables
```

ちなみに、ダブルクオートで変数をくくると１つの変数として見なされる。

```bash
$ for i in hello world tsuyoshi; do echo $i; done
hello
world
tsuyoshi
$ for i in "hello world tsuyoshi"; do echo $i; done
hello world tsuyoshi
```

## Sample

サンプルは、引数を指定すると、該当の拡張子の前に第３引数の拡張子を挿入してくれるプログラム。
ちなみに、`echo mv ...`のパートはデバッグテクニックらしく、これで一旦実行して、実際に思った通りだったら、`echo`を外して実行する。特にforが該当の拡張子だけで回るのはカッコいい。

_forsample_

```bash
#!/bin/bash

if [[ $# -ne 2 ]]; then
  echo "Two parameters requires"
fi

for f in *"$1"; do
  base=$(basename "$f" "$1")
  echo mv "$f" "${base}$2.$1"
done
```

### 実行

実行時のtouch の使い方もカッコいい

```bash
$ touch file{1..10}.txt
$ touch image{1..5}.img
$ ls
file1.txt	file3.txt	file6.txt	file9.txt	image3.img
file10.txt	file4.txt	file7.txt	image1.img	image4.img
file2.txt	file5.txt	file8.txt	image2.img	image5.img
$ ./forsample txt bak
mv file1.txt file1.bak.txt
mv file10.txt file10.bak.txt
mv file2.txt file2.bak.txt
mv file3.txt file3.bak.txt
mv file4.txt file4.bak.txt
mv file5.txt file5.bak.txt
mv file6.txt file6.bak.txt
mv file7.txt file7.bak.txt
mv file8.txt file8.bak.txt
mv file9.txt file9.bak.txt
```

## C-Style Loop

```bash
for (( INIT; TEST; UPDATE )); do
  ;; some code
done
```

_counter_

```bash
#!/bin/bash

for (( i=0; i<10; ++i )); do
  echo "number ${i}"
done
```

## 実行

```bash
$ ./counter
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
```

ワンライナーでやろうと頑張ってみてうまくいかなかったが、do の後に`;`をつけていたのを削除するとうまくいった。

```bash
$ for (( i=0; i<10; ++i )); do echo "number ${i}"; done
number 0
number 1
number 2
number 3
number 4
number 5
number 6
number 7
number 8
number 9
```

_drawline_

サンプルが面白かったので、そのまま写経してみた。

```bash
#!/bin/bash

if [[ ! $1 ]]; then
  echo "need line length argument" >&2
  exit 1
fi

if [[ $1 =~ ^[0-9]+$ ]]; then
  length="$1"
else
  echo "Length has to be a number" >&2
  exit 2
fi

char="="
if [[ $2 ]]; then
  char="$2"
fi

line=
for (( i=0; i<length; ++i )); do
  line="${line}${char}"
done

printf "%s\n" "$line"
exit 0
```

### 実行

```bash
$ ./drawline 10
==========
$ ./drawline 10 --
--------------------
```

ここでポイントは、最後の `printf "%s\n" "$line"` でこれが　`printf "$line\n"` だと Substitution なので、最初の文字が`-`の場合、オプションと認識されてうまくいかない。フォーマットを使うことで回避できている。

# Break and Continue

* break: ループをやめる
* continue: 現在のイテレーションをスキップして、次から始める

他の言語と同じ。for, while, until で使える。次のサンプルは、ファイルの中をサーチして、該当の行が来たら表示するもの。

_mysearch_

```bash
#!/bin/bash

if [[ ! $1 ]]; then
  echo "First argument missing" >$2
  exit 1
fi 

flag=
while read -r; do
  if [[ $REPLY =~ $1 ]]; then
    echo "$1 found: $REPLY"
    flag="done"
    break
  fi 
done

if [[ ! $flag ]]; then
  echo "$1 not found."
fi 

exit 0
```

_data.txt_

```
This is the text.
You need to find a text.
It might be foo.
Also might be bar.
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,
Tsuyoshi
```

### 実行

```bash
$ ./mysearch crosstrek < ./data.txt
crosstrek found: But it is crosstrek.
$ ./mysearch wrx < ./data.txt
wrx not found.
```

例によって元のサンプルにセンスを感じたので写経。`$REPLY` の使い方が渋い

他の Pre-defined の変数はこちら。[9.1. Internal Variables](https://www.tldp.org/LDP/abs/html/internalvariables.html)。

デフォルトの変数を使うことで、シンプルに感じる。

```bash
#!/bin/bash

if [[ ! $1 ]]; then
  echo "First argument missing" >&2
  exit 1
fi 

found=""

while read -r; do 
  if [[ ! $found ]]; then
    if [[ $REPLY =~ $1 ]]; then
      found="yes"
    else 
      continue
    fi
  fi
  echo "$REPLY"
done

exit 0
```

### 実行

最後の行が表示されないが、それはもうすぐわかるらしい。

```
$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,
```

# case

```bash

case WORD in
  PATTERN1)
     some code;;
  PATTERN2)
     some code;;
    :
esac
```

他の言語と同じ

## Sample

_animals_

```bash
#!/bin/bash

case $1 in
  tiger)
    echo "Super cool!";;
  carp)
    echo "So so";;
  giant)
    echo "Boo!.";;
  *)
    echo "I don't think about it";;
esac
```

## 実行

```bash
$ ./animals tiger
Super cool!
$ ./animals giant
Boo!.
$ ./animals star
I don't think about it
```

## サンプル２

もうすこし複雑なパターンマッチも可能な様子

_filetype_

```bash
#!/bin/bash

case $1 in
  *.png|*.jpg|*.gif)
    echo "$1 is image";;
  *.cs|*.java|*.js|*.sh)
    echo "$1 is programming language source code";;
  *)
    echo "Don't ask me, I don't know";;
esac

exit 0
```

## 実行

```bash
$ ./filetype some.jpg
some.jpg is image
$ ./filetype Program.cs
Program.cs is programming language source code
$ ./filetype some.txt
Don't ask me, I don't know
```

# Command Groups

`{}` で括られたコマンド。複数のコマンドを一つのステートメントに入れる。I/O のリダイレクトをグループに適用できる。if や while ループに適用できる。リターンステータスは、最後のコマンド実行の結果になる。

`{cmd; cmd2; cmd3;}` というスタイル。セミコロンで新しい行を表現する。ブレースの周りにはスペースが必要。セミコロンで終了すること。

# || and &&

論理演算ではない用途でも使う。コマンドの実行結果によって、後続のコマンドの実行を決める。

## &&

先行したコマンド実行が成功した時のみ実行する。例えば次のはディレクトリ作成が成功した時のみ、そのディレクトリに移動する。

```
$ mkdir newdir && cd newdir
```
## ||

直前のコマンドが失敗した時のみ、次のコマンドを実行する。次のコマンドは、引数がない時のみ、エラーメッセージを表示する。

```
[[ $1 ]] || echo "missing argument">&2
```

例えば次のスクリプトは常に異常終了する。$1があったら、それはそれで正常終了だし、echoでエラーメッセージを表示しても、表示自体は成功するので、常に、`exit 1`になる。

```bash
[[ $1 ]] || echo "missing argument" >&2 && exit 1
```

こういう時には、コマンドグルーピングを使う

```bash
[[ $1 ]] || { echo "missing argument">&2; exit 1;}
```

## サンプル

先ほどのサンプルを改造する。検索した文字列のある行以降を表示する。前は、最後の行が表示されない問題があった。その原因は`while read -r; do` で最後のEOFを判断しているが、最後の行が`TsuyoshiEOF`となっている場合は、ここでtrueとなるため、最後の行が表示されない。`read -f` が false になった時のみ、`[[ $REPLY ]]` を評価する。もし、他に文字列が含まれていたらループを実行を続行する。

他には、コマンドグループを使って、文字列チェックを一業で記述している。

```bash
#!/bin/bash

[[ $1 ]] || { echo "First argument missing" >&2; exit 1; }

found=""

while read -r || [[ $REPLY ]]; do 
  if [[ ! $found ]]; then
    if [[ $REPLY =~ $1 ]]; then
      found="yes"
    else 
      continue
    fi
  fi
  echo "$REPLY"
done

exit 0
```

## 実行

ちゃんと最後の行が表示されている。

```bash
$ ./stripto crosstrek < data.txt
But it is crosstrek.
Keep on loving Subaru.
See you soon.

Regards,
Tsuyoshi
invincible:forloop ushio$ ./stripto
First argument missing
```