Shell Script Tips 完全版

## 面白いTips

### ファイルを読み込む

`.`

```bash:
# sourceコマンドは短縮可能
. ~/.bashrc
# 同じ意味
source ~/.bashrc
```

### ファイルの拡張子を変換する

`in *.json`, `${i%.*}.foo`, `( *.json )`

```bash:
# ファイルが複数の場合
$ for i in *.json;do echo ${i%.*}.yml;done
> foo.yml, bar.yml

# 一つのファイルを変換(配列を利用するので、複数の場合も扱いやすくなる)
$ file=( *.json )
$ echo ${file[1]%.json}.yml
```

### 配列に値を追加する

`( ${foo[@]} bar )`

```bash:
$ foo=( a b c )
$ foo=( ${foo[@]} d )

# 配列を削除
$ unset "foo[2]"
```

### git logをHEADを参照し順に見ていく

`%+9s %s`, `$(($o+1))`

```bash:
$ o=0; git log --oneline | while read l; do printf "%+9s %s\n" "HEAD~${o}" "$l"; o=$(($o+1)); done | less

# 表記法には他にも色いろある
printf "%.5e\n"
```

### 連番の作成

`..`, `%04d`

```bash:
echo ${01..30}

# for
print "%04d" %i
```

###出力を表示しない

`/dev/null 2>&1`

```
$ echo foo > /dev/null 2>&1
# バックグラウンド
$ echo foo > /dev/null 2>&1 &
```

### 右上に時刻を表示する

`$(($(tput cols)-n))`

```bash:
$ while sleep 1;do tput sc;tput cup 0 $(($(tput cols)-29));date;tput rc;done &
```

### 出力の強制保存

`>!`

```bash:
$ touch foo.json
$ echo "Shell Script!" >! foo.json
```

### コマンド履歴を使った置換

`:gs`

```bash:
$ echo -e https://qiita.com/api/v2/users/syui
# zshの書き方、bashでも同じことが出来るがzshよりも面倒
$ echo !-1:h:h:h:h:t:gs/qiita/github/
> github.com
```

### フォーク爆弾

このコマンドは絶対に実行してはいけない。

`:`

```bash:
$ :(){ :|:& };:
```

### 日付で前後を計算に入れる

`-2 day`

```bash:
# Linux
$ date +"%Y-%m-%d" --date "-2 day"
```
### 少数演算を条件にする

```bash:
#!/usr/bin/env bash
if [ $(echo "$a <= 0.5" | bc) -eq 1 ]; then;echo $a;fi
```

### 変数が未定義の場合にエラーを出す

`${foo:?""}`

```bash:
$ foo=""
$ echo ${foo:?""}
```

### バックグラウンド処理を待つ

`wait $!`

```bash:
#/bin/bash
sleep 100 &
wait $!
```

### 位置パラメータを使用

```bash:
$ foo=(123 abc ABC)
$ echo ${foo[2]:1}
> bc
```

### 計算する

`let`を使えます。`expr`などもありますが。

```bash:
$ let a=1+2
$ echo $a
> 3
```

### 変数の表記が使えない場合の配列の全行を参照する

`eval`

```bash:
$ eval echo '${'$EVAL_VAL'[@]}'
```

## 基本的によく使うTips

### 変数への代入

Shell Scriptでは、変数へ代入するための幾つもの方法があります。例えば、Pythonでは少なく、Perlでは多いですが、Shell Scriptのそれは、これらの言語よりも遥かに複雑です。その理由の一つがターミナルに直結して処理を行うことにあると思われます。

そして、シェルにも様々な種類があり、微妙に変数の代入のやり方についても違いがあります。

```bssh
# sh
$ foo=123
$ export foo

# bash
$ foo=123
or
$ export $foo
```

変数にも環境変数やシェル変数があります。環境変数は引き継がれるが、シェル変数は引き継がれないと覚えておけばよいでしょう。もちろん、環境変数であっても引き継がれない場合もありますが、使っているうちにわかってくると思います。ここで細かいことを言っても仕方ありませんし、細かいことを詳細に書いていく記事でもありませんので、次に進みます。

以下、主に、`bash`でのTipsになりますが、私は基本的に`zsh`を使いますので、間違いがあるかもしれません。その場合はご指摘ください。

### 変数の峻別

通常は、普通に先頭に`$`を置くことで、変数を意味しますが、前後に置かれる文字列によっては違う意味にもなってきます。したがって、その場合は、`{}`で囲うことで、より正確な表記が可能です。これはどうなんだろうとわからない時は、出来る限り`{}`を使いましょう。可読性にも役立ちます。

```bash
$ echo $USER$SHELL
or
$ echo ${USER}${SHELL}
```

### シングルとダブルクォート 

`"`と`'`では、変数を使えるか、厳格な文字列で解釈されるかで違ってきます。

```bash
$ echo "$SHELL"
> /bin/bash

$ echo '$SHELL'
> $SHELL
```

### 文字数を調べる

先頭に`#`を持ってきます。この特殊文字は先頭に置くことで、文字数を調べるときによく使われます。先頭`#`は文字数と覚えておけばよいです。

```bash
$ echo ${#foo}
```

### 配列を扱う

配列で扱うには、`()`で囲った上で、そこに正規表現や数字を使って値を取り出せます。シェルの配列は`()`と覚えておけばよいです。

```bash
$ foo=(a b c)
$ echo ${foo[1]}
$ echo ${#foo}
```

### 変数の抽出

変数のうちで特殊な表現を使い、条件に一致するものを取り出せます。URLやファイルの拡張子、ファイル名などを変数から取り出したい時に使います。

#### URL

```bash
$ url=http://qiita.com/syui

# ユーザーを取り出したい場合
$ echo ${url##*/}
> syui

# ホームアドレスを取り出したい場合 
$ echo ${url%/*}
> http://qiita.com

# プロトコルを取り出したい場合
$ echo ${url%%/*}
> http:
```

#### FILE

```bash
$ file=/home/syui/foo.json

# ディレクトリを取り出したい場合
$ echo ${file%/*}
> /home/syui

# ファイル名を取り出したい場合
$ echo ${file##*/}
> test.json

# 拡張子を取り出したい場合
$ echo ${file##*.}
> json
```

#### ZSH

ちなみに、`zsh`にはこんなややこしい正規表現を使わなくても、以下のようにして変数からの抽出を行えます。取り出せる順で言うと、先頭から`:h`、`:t`の順となります。`head`と`tail`コマンドを想起して覚えると良いでしょう。ただし、これらは主に、コマンドラインで有効な表現です。気をつけて使いましょう。`!!`みたいなもので、履歴を利用します。

```bash
$ url=https://github.com/syui/foo.json

$ echo $url:r
> https://github.com/syui/foo

$ echo $url:h
> https://github.com/syui

$ echo $url:h:h:t
> github.com

$ echo $url:h:t
> syui

$ echo $url:t
> foo.json

$ echo $url:e
> json
```

### 履歴の利用

シェルには、コマンド履歴を扱うために役立つ特殊な変数が用意されています。

```bash
$ ls -a /home/syui/foo.json

# 例えば、直前のコマンドを実行したい場合
$ !!

# 直前のコマンドから値を取り出したい場合(!でも良いけど、できれば!!の方が良い)
$ echo !!0
> ls
$ echo !!1
> ls -a
$ echo !!2
> ls -a /home/syui/foo.json
$ echo !!$
> /home/syui/foo.json
```


ちょっと分かりにくいですが、こんな感じで、数字や正規表現を使えます。


### ifのオプション一覧と数値の比較

よく使うものは、変数の有無である`-z`とファイルやディレクトリをチェックする`-f, -d`あたりです。`-f, -d`の代わりに、ファイルが存在する場合の`-e`を使ってもいいかもしれません。

プション | 使用例 | オプションの意味
---|---|---
`-d` | `test -d file` | file が**ディレクトリならば真**となる。
`-f` | `test -f file` | file が**普通のファイルならば真**となる。
`-s` | `test -s file` | file が **0 より大きいサイズならば真**となる。
`-e` | `test -e file` | file が**存在するならば真**となる。
`-r` | `test -r file` | file が**読み取り可能ならば真**となる。
`-w` | `test -w file` | file が**書き込み可能ならば真**となる。
`-x` | `test -x file` | file が**実行可能ならば真**となる。


`test`オプション

プション | 使用例 | オプションの意味
---|---|---
`-z` | `test -z string` | string の**文字列長が 0 ならば真**となる。
`-n` | `test -n string` | string の**文字列長が 0 より大ならば真**となる。

####その他のifオプション


Primary| Meaning
---|---
[ `-a` `FILE` ]| True if `FILE` exists.
[ `-b` `FILE` ]| True if `FILE` exists and is a block-special file.
[ `-c` `FILE` ]| True if `FILE` exists and is a character-special file.
[ `-d` `FILE` ]| True if `FILE` exists and is a directory.
[ `-e` `FILE` ]| True if `FILE` exists.
[ `-f` `FILE` ]| True if `FILE` exists and is a regular file.
[ `-g` `FILE` ]| True if `FILE` exists and its SGID bit is set.
[ `-h` `FILE` ]| True if `FILE` exists and is a symbolic link.
[ `-k` `FILE` ]| True if `FILE` exists and its sticky bit is set.
[ `-p` `FILE` ]| True if `FILE` exists and is a named pipe (FIFO).
[ `-r` `FILE` ]| True if `FILE` exists and is readable.
[ `-s` `FILE` ]| True if `FILE` exists and has a size greater than zero.
[ `-t` `FD` ]| True if file descriptor `FD` is open and refers to a terminal.
[ `-u` `FILE` ]| True if `FILE` exists and its SUID (set user ID) bit is set.
[ `-w` `FILE` ]| True if `FILE` exists and is writable.
[ `-x` `FILE` ]| True if `FILE` exists and is executable.
[ `-O` `FILE` ]| True if `FILE` exists and is owned by the effective user ID.
[ `-G` `FILE` ]| True if `FILE` exists and is owned by the effective group ID.
[ `-L` `FILE` ]| True if `FILE` exists and is a symbolic link.
[ `-N` `FILE` ]| True if `FILE` exists and has been modified since it was last read.
[ `-S` `FILE` ]| True if `FILE` exists and is a socket.
[ `FILE1` `-nt` `FILE2` ]| True if `FILE1` has been changed more recently than `FILE2`, or if `FILE1` exists and `FILE2` does not.
[ `FILE1` `-ot` `FILE2` ]| True if `FILE1` is older than `FILE2`, or is `FILE2` exists and `FILE1` does not.
[ `FILE1` `-ef` `FILE2` ]| True if `FILE1` and `FILE2` refer to the same device and inode numbers.
[ `-o` OPTIONNAME ]| True if shell option "OPTIONNAME" is enabled.
`[ -z` STRING ]| True of the length if "STRING" is zero.
`[ -n` STRING ] or [ STRING ]| True if the length of "STRING" is non-zero.
[ STRING1 == STRING2 ] | True if the strings are equal. "=" may be used instead of "==" for strict POSIX compliance.
[ STRING1 != STRING2 ] | True if the strings are not equal.
[ STRING1 &lt; STRING2 ] | True if "STRING1" sorts before "STRING2" lexicographically in the current locale.
[ STRING1 &gt; STRING2 ] | True if "STRING1" sorts after "STRING2" lexicographically in the current locale.
[ ARG1 OP ARG2 ]| "OP" is one of `-eq`, `-ne`, `-lt`, `-le`, `-gt` or `-ge`.  These arithmetic binary operators return true if "ARG1" is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to "ARG2", respectively. "ARG1" and "ARG2" are integers.

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html


シェルでは、数値比較は、正直覚えにくいです。演算。なので、`((n<n))`のほうが覚えやすいです。

プション | 使用例 | オプションの意味 | 数式
---|---|---|---
`-eq` | `test num1 -eq num2` | num1 と num2 が**等しければ真**となる。 | num1=num2
`-ne` | `test num1 -ne num2` | num1 と num2 が**等しくなければ真**となる。 | num1≠num2
`-lt` | `test num1 -lt num2` | num1 が num2 **より小ならば真**となる。 | num1&lt;num2
`-le` | `test num1 -le num2` | num1 が num2 **以下ならば真**となる。 | num1≦num2
`-gt` | `test num1 -gt num2` | num1 が num2 **より大ならば真**となる。 | num1&gt;num2
`-ge` | `test num1 -ge num2` | num1 が num2 **以上ならば真**となる。 | num1≧num2

追記 : 数値比較には、`((1<3))`などとして行うことができます。そちらのほうが覚えやすいです。

### if文字列比較

```bash:
# 同じ場合
if [ "foo" = "$bar" ];then
# 異なる場合
if [ "foo" != "$bar" ];then
```

`or`である`-o`や`and`を意味する`-a`も使えます。

```bash:
# || = -o, && = -a
if [ "foo" = "$bar" ] && [ "foo" != "$bar" ];then
```

http://shellscript.sunone.me/if_and_test.html

### case文の使用

私は`case`文を好みます。文字列や数値の比較が自然にできます。

```bash:
case $foo in
 [fF]oo|FOO) echo ok
 ;;
 [bB]ar|BAR) echo no
 ;;
 [fF]*) echo no
 ;;
esac

[a-z]*) #先頭が小文字なら
[!0-9]) #数字ではない文字列
???) #3文字の文字列
```


## あまり使わない応用的なTips

### 変数の変換

変数はもう一度入れ直すことや、変換することで、中に入っているものを変えられます。シェルには現在の値を上手く変換する表現がいくつも用意されています。それはとても一般的で、汎用性があるものです。

```bash
# fooに数値が入っていなければ、文字列であるbarに変換する
$ ${foo:-bar}
> bar

$ foo=1
$ ${foo:-bar}
> 1
```

その他、以下の様な代入や書き方があります。

```bash
$ ${foo:+bar}

# 拡張子の変換
$ ls
$ file=( *json )
$ echo ${file%.*}.yml
```

### 任意の文字を指定して値を取り出す

これについては、正規表現でよく使われる`?`を使うとできます。

```bash
$ foo=123abc
$ echo ${foo#??}
> 3abc
```

## その他

```bash:
# 先頭が数字以外で始まるファイル
$ ls [^0-9]*

# 直前のlsから始まるコマンド
$ !ls:p

# for文の書き方
for (( i=1;i<=3;i++ ))
do
 echo $i
done

# コマンド結果を変数に入れる
foo=`ls`
foo=$(ls)

# testコマンドを利用する, オプションなどはif文と共通します
$ test "foo" = "foo"
$ test "foo" != "foo"

# 大文字を小文字に変換
$ tr '[A-Z] '[a-z]'

# 行数を指定する
$ awk "NR==1"

# ターミナル出力を操る
$ exec 1< foo
$ exec 1<&-
```