割りと便利だけど微妙に忘れがちなbashのコマンド・チートシート

自分用にメモしておく

# コマンド実行

## `CMD1; CMD2`, `CMD1 && CMD2`
* `;`は`CMD1`の結果に関わらず`CMD2`も実行される
* `&&`は`CMD1`の結果が正常な場合のみ`CMD2`が実行される

## `CMD1 || CMD2` - 失敗時に後続コマンドを実行する
* `CMD || printf "%b" "MSG"`でエラーメッセージを表示する
    * エラーメッセージ表示後`exit 1`したい場合 = `CMD || { printf "%b" "FAILED.\n" ; exit 1 }`
        * `CMD || printf "%b" "FAILED.\n" ; exit 1`と波括弧無しで書くと期待通り動作しない（CMDが成功時も`exit 1`してしまう）

## `CMD &` - バックグラウンド実行
* `CMD &`で`[1] 4592`のようにジョブ番号とプロセスIDが表示される
    * killしたければ`kill %ジョブ番号` か `kill プロセスID` する
    * フォワグラウンド実行に戻したければ
* `$!`で、直前にバックグラウンド実行したジョブのプロセスIDを取得可能
* さらに `wait ${PID}`で待つと、バックグラウンドジョブが終了するまで待つことが出来る

```bash
    # バックグラウンドで実行したhubotの終了を待つ例
    ./bin/hubot -a ${HUBOT_ADAPTER} --name ${HUBOT_IRC_NICK} 2>&1 &
    HUBOT_PID=$!  # PIDを取得

    wait ${HUBOT_PID}  # ここで終了まで待つ
    RET=$?  # wait直後の $? で、バックグラウンドジョブの終了コードを取得できる
    echo "Hubot is end. PID: ${HUBOT_PID}"
```

## `$(CMD)` - サブシェルでコマンド実行、実行結果は`$()`に置換される
* 結果の改行はスペースに置換される
    * `$IFS`変数によって決まる（これのデフォルトがスペース）

## `nohup CMD &` - 実行中のバックグラウンドジョブの継続
* シェルを終了してもコマンドを継続したい場合につける
    * 付けずにシェルを終了すると（シェルの子プロセスである）バックグラウンドジョブには SIGHUP が送られ終了する 

## `$?` - コマンドの戻り値
* 直前のコマンドの戻り値判定は2重括弧を使って`if (( $? ))`と書ける

## `type`, `which`, `locate`, `apropos`コマンドやマニュアルを探す

## `file`, `stat` - ファイルの情報を表示

# ドキュメンテーション

## `: DOCUMENT` - 組み込みドキュメント
* `: <<'EOD'  ... EOD`

# ハイフンのtips

## `CMD - ...` ハイフンで標準入（出）力を受け取る/出力する
* [【linux】コマンドの引数を標準入力から渡す at softelメモ](https://www.softel.co.jp/blogs/tech/archives/2151/comment-page-1)
    * 「なんだよ、ファイルしか受け付けないのかよ」と思われる場合でも、ハイフンを指定すると、標準入出力への入出力が可能
    * dockerの公式インストールチュートリアルにある`wget -qO- https://get.docker.com/ | sh`とかもこのパターン
        * `-O <ファイル名>`のところを`-`とすることで標準出力に出して、それをそのままパイプで後続コマンドに繋げている

## `CMD -- ...` ハイフン2つ = それ以降をコマンドラインオプションとは解釈しなくなる
* 例えばハイフン付き文字列そのものをgrepしたいときは `grep -- "-hoge" *` のようにする
* これはコマンドオプションの構文解析に使われている`getopt()`の機能で、`getopt()`はコマンドオプションに--を見つけるとオプションの解析を終了する
* 最近良く見る`curl http://hoge.com/huga.sh | sh`形式のcurl→shインストールで、スクリプトオプションを指定したい時にも使える
    * `curl -sSf https://static.rust-lang.org/rustup.sh | sh -s -- -y` - rustup.sh に `-y` オプションを渡して実行
        * `-s` で標準入力からの受け取りを指定
        * `--` で それ以降をただの入力文字列と解釈させる
        * `-y` をオプションとして入力させる

# 変数

## シェルで「変数がセットされていない」とはどういうケースか？
* `""`空文字
* 明示的に`unset VAR`された変数

## `$*`と`$@`の空白を含んだ引数の取り扱いの違い
* `./hoge.sh *.txt`と実行した場合
    * `for FN in "$*"`は `"aaa.txt bbb ccc.txt ddd.txt"`を受け取るのに対し、
    * `for FN in "$@"`は `"aaa.txt" "bbb ccc.txt" "ddd.txt"`を受け取る

## `${:-}` - 変数未指定時にデフォルト値を使う
* `FILEDIR=${1:-"/tmp"}` - 第1引数が "未指定" なら "/tmp"をデフォルト値として利用する
* デフォルト値を取得・利用はするが、アサインはしない。`${VAR:-"hoge"}`は `VAR` に"hoge" をセットするわけではない

## `${:+}` - 変数指定時にデフォルト値を使う
* `FILEDIR=${VAR:+"/tmp"}` - `VAR`が設定されていたら "/tmp"を(代わりに)利用する

## `${VAR:=}` - 変数未指定時にデフォルト値を設定
* `cd ${HOME:=/tmp}` - `${HOME}` が "未指定" なら、代わりにデフォルト値をセット
    * "未指定" とはどういうケースか？ = `""`空文字 もしくは `unset`されている ケース
* デフォルト値を取得・利用し、変数へのアサインも行う。`${VAR:="hoge"}`は `VAR` に"hoge" をセットする

## `${VAR=}` - 変数未指定時にデフォルト値を設定, `:`無し
* とにかく `${VAR:=}` と混同しやすいが、こちらの "未指定" は `unset`されている ケースのみが対象で、空文字は対象外

## `${VAR:?"MSG"}` - 変数がセットされていない場合、メッセージを出力してexitする

```bash
USAGE="usage: myscript scratchdir sourcefile conversion"
FILEDIR=${1:?"Error. You must supply a scratch directory."}
FILESRC=${2:?"Error. You must supply a source file."}
CVTTYPE=${3:?"Error. ${USAGE}"}

$ ./myscript /tmp /dev/null
./myscript: line 5: 3: Error. usage: myscript scracthdir sourcefile conversion
```

```bash
# エラー時にコマンドを動かしたければこう書ける
CVTTYPE=${3:?"Error. $USAGE. $(rm $SCRATCHFILE)"}
# ./myscript: line 5: 3: Error. usage: myscript scracthdir sourcefile conversion


# でもこっちの方がコードの可読性は高いね。商用コードならこうすべきだね
# ただしエラーメッセージにファイル名・行番号は出力されないけどね
# ${3:?} はデバッグ時には便利だけどね

if [ -z "$3" ]
then
   echo "Error. $USAGE"
   rm $SCRATCHFILE
fi
```

## 変換

```
name:number:number - 部分文字列取得
#name - 文字列の長さ
name#pattern - 文字列前方のpatternを削除(最短マッチ)
name##pattern - 文字列前方のpatternを削除(最長マッチ)
name%pattern - 文字列後方のpatternを削除(最短マッチ)
name%%pattern - 文字列後方のpatternを削除(最長マッチ)
name/pattern/string - 置換(一箇所)
name//pattern/string - 置換(全体)
```

## `VAR=(VAL1 VAL2 VALn)` - 配列

### `VAR[0]` - 配列の最初の要素

### `VAR[@]` - 配列の全要素

### `VAR[#]` - 配列の要素数

# 配列
* `(VAR1 VAR2 VAR3)` と `()`(カッコ) で括って区切りを入れれば配列に変換される
* コマンドの実行結果を配列化したい場合も同様

```bash
HOGE=$(ls -ld $1)

declare -a HUGA
HUGA=(${HOGE})  # 変数をカッコで括る
```

* `read -a` で入力結果を配列化

# setコマンド

## `set -e`
* スクリプト内のコマンドが失敗したらそこでスクリプトを終了する
* `set +e` すれば↑を解除し、失敗しても終了しなくなる

## `set -o 何かOPTION`
* `set -o noclobber` - ファイルへの上書きリダイレクトを禁止する
    * `CMD >| file`と`>|`を使うと強制的に上書きリダイレクト可能


# 条件分岐 `if`, 

## `if [ 条件 ]`

### `if (( 算術比較式 ))`
* 例えば`if [ $VAR -eq 0 ]`は`if (( $VAR == 0 ))`と比較式記号で書ける
* 引数の個数チェックはこう書ける

```bash
if [ $# -lt 3 ]
then
   :
fi

if (( $# < 3 ))
then
   :
fi
```

## `[ 条件 ]` と `[[ 条件 ]]`
* `[` はtestコマンド、`[[`はbash組み込みコマンド
    * `which [`すると組み込みコマンドである事が分かる
* 挙動の違いは、条件式で未定義変数を使った場合
    * `[ $UNSET_VAR = "HOGE"]` するとエラーを吐くが、`[[ $UNSET_VAR = "HOGE" ]]`はエラーを吐かない

## `[[ 条件 ]] && { CMD1; CMDn; } || { CMDe1; CMDen; }` 三項演算子

## ファイルのテスト

```
-nt is newer than
-ot is older than
-ef have the same device and inode numbers

-b File is block special device (for files like /dev/hda1)
-c File is character special (for files like /dev/tty)
-d File is a directory
-e File exists
-f File is a regular file
-g File has its set-group-ID bit set
-h File is a symbolic link (same as -L)
-G File is owned by the effective group ID -k File has its sticky bit set
-L File is a symbolic link (same as -h) -O File is owned by the effective user ID -p File is a named pipe
-r File is readable
-s File has a size greater than zero
-S File is a socket
-u File has its set-user-ID bit set
-w File is writable
-x File is executable
```

## `-a` and, `-o` or
* 複合条件はこんな感じで`\(`と`\)`を使って書く - `if [ -r "$FN" -a \( -f "$FN" -o  -p "$FN" \) ]`

## 文字列比較

### `if [ "$VAR" ]`, `if [ -z "$VAR" ]` - 空判定
* 前者は "has text", 後者は`is zero length`をチェックする

### 一致比較, `-eq`と`=`(の注意点)
* `-eq`は空白や前ゼロを無視して比較する

```bash
VAR1=" 05 "
VAR2="5"

# これはtrue
if [ "$VAR1" -eq "$VAR2" ]

# これはfalse
if [ "$VAR1" = "$VAR2" ]
```

### `if [[ $VAR == パターンマッチ ]]`, `if [[ $VAR =~ 正規表現条件 ]]` 
* `if [[ "${MYFILENAME}" == *.jpg ]` - パターンマッチの例
* `shopt -s OPTION`する事でパターンマッチの挙動を変更できる
    * `shopt -s extglob` - 拡張パターンマッチを使う
    
    ```
    @( ... ) Only one occurrence
    *( ... ) Zero or more occurrences
    +( ... ) One or more occurrences
    ?( ... ) Zero or one occurrences
    !( ... ) Not these occurrences, but anything else
    ```

    * `shopt -s nocasematch` - 大文字小文字を区別しない
* `if [[ "$CDTRACK" =~ "([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$" ]]` - 正規表現例
    * マッチした部分文字列は、`BASH_REMATCH`という配列に格納される

# 多岐分岐 `case`, 

```bash
case $FN in
   *.gif) gif2png $FN  # パターンマッチは大文字小文字区別しない（したければ shopt -s nocasematch)
       ;;  # breakする
   *.png) pngOK $FN
       ;;
   *.jpg) jpg2gif $FN
       ;;
   *.tif | *.TIFF) tif2jpg $FN  # | はor条件
       ;;
   *) printf "File not supported: %s" $FN  # どの条件にもマッチしない場合
       ;;
esac
```

# ループ

## `for 条件文`

### `for f in /path/to/*` 
* パス＋ワイルドカード でマッチするファイル全件のループ

### `for (( i=0 ; i < 10 ; i++ ))` - カウンタループ
* 元は`for i in 1 2 3 4 5 6 7 8 9 10`こう
* `for (( i=0, j=0 ; i+j < 10 ; i++, j++ ))` 複数変数を使うことも可能
* 小数を使いたければ`seq`を使う（整数でも使えるが）
    * `for fp in $(seq 1.0 .01 1.1)`

## `while 条件`
* 例 `while [ -z "$LOCKFILE" ]`
* `(( ))`で算術評価式が書ける `while (( COUNT < MAX ))`
* 入力を受け付けるループ `while read READVAR`, 入力可能な間は内部では 0 を返している
    * EOFが来たら -1 を返す

* ファイル入力ループはこう書く
    * 後者の`cat |`を使うパターンはパイプを使ってるのでサブシェルとしてループが動く＝ループ内で変数を操作してもループ外に影響が出ない（影響を及ぼすことが出来ない）

```bash
# ファイル入力ループ
while read lineoftext
do
  process that line
done < file.input

cat file.input | \
while read lineoftext
do
  process that line
done
```
```bash
svn status mysrc | \
while read TAG FN
do
    if [[ $TAG == \? ]]
    then
        echo $FN
        rm -rf "$FN"
    fi
done
```

* __条件 が "trueの間＝戻り値が0の間"だけループが回る__
    * `1` はtrue扱い, `while (( 1 ))` は無限ループ (``(( 1 ))`` は 0 を返している)

# `select` - 選択メニュー

## `select v in ${ARR} do ... done`
* `ARR`の内容を元に選択メニューを表示する
* 選択したindexが`${REPLY}`に、内容が${v}に設定される

```bash
DBLIST=$(sh ./listdb | tail +2)
select DB in $DBLIST
do
   echo Initializing database: $DB
   mysql -uuser -p $DB <myinit.sql
done
```

* `PS3`をいじると、選択プロンプトをカスタマイズ出来る

# 出力, リダイレクト

## `echo`
* `-n` - 改行を出力しない
* `-e` - 特殊文字を変換して出力

```bash
$ echo -e 'hi\c'
hi$
```

## `printf`

```bash
$ printf '%s = %d\n' Lines  $LINES
Lines = 24

$ printf '%-10.10s = %4.2f\n' 'GigaHerz' 1.92735
GigaHerz   = 1.93
```

### 書式
* `%b`,`%s` - 前者は`¥n`を改行として __扱う__ 。後者は扱わない（`"¥n"`という文字列扱い）

## `CMD >& cmd.out`とか`CMD &> cmd.out` - STDOUTとSTDERRを同じ出力先に出す
* `CMD > cmd.out 2>&1` この書き方よく見るけど、冗長だよね

## `{ CMD1; CMD2; CMDn; } > hoge.out`, `(CMD1; CMD2; CMDn) > hoge.out` - 複数センテンス
* `{}`, `()`内の全コマンドの結果をリダイレクトする
    * `{}`抜きで`CMD1; CMD2; CMDn > hoge.out`と書くと、`CMDn`の処理結果しかリダイレクトされない
    * `{}`はコマンドのグルーピングとして機能している
    * `()`は サブシェルが立ち上がる
* `{}`, `()`の違い
    * `{}`はコマンドの前後に空白が必要
    * `{}`は最終コマンドの後にも`;`が必要
    * `()`はサブシェルで実行される
        * つまり別プロセスで実行される
        * `{ cd /tmp; ls } > hoge.out`したら実行後のpwdは`/tmp`になるが、`(cd /tmp; ls) > hoge.out`実行後のpwdは変わらない 


# ヒアドキュメント

## `<<EOF  ... EOF`
* ↓このスクリプトは期待通り動かない

```sh
# ヒアドキュメント内ではデフォルトではエスケープが効いてない
# ので "$X" のような文字列は変数として認識される

grep $1 <<EOF
# name amt
pete $100
joe  $200
sam  $ 25
bill $  9
EOF
```

* ヒアドキュメント内をエスケープしたければ、`<<\EOF`と "EOF"の前に"\"を付ける
    * もしくは`'EOF'`とシングルクォートで囲む
    * `E\OF`でも動く

## `<<-EOF  ...`
* `<<-EOF`することで、ヒアドキュメント内にインデントを書くことが出来る

# プロセス置換
[bashのプロセス置換機能を活用して、シェル作業やスクリプト書きを効率化する - 双六工場日誌](http://sechiro.hatenablog.com/entry/2013/08/15/bash%E3%81%AE%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9%E7%BD%AE%E6%8F%9B%E6%A9%9F%E8%83%BD%E3%82%92%E6%B4%BB%E7%94%A8%E3%81%97%E3%81%A6%E3%80%81%E3%82%B7%E3%82%A7%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%84%E3%82%B9)

* `<(コマンドリスト)` - コマンドの結果をファイルとして扱う
* `>(コマンドリスト)` - 出力先をコマンドに渡す

# ファイルディスクリプタ
[[Bash]標準出力・標準エラー出力の全て（1>&2とか）まとめ | Coffee Breakにプログラミング備忘録](http://to-developer.com/blog/?p=1001)

* `1` - 標準出力
* `2` - 標準エラー出力
* `3`以降 - 未割当

`exec` と併用して色々出来る

* 出力先を変更する

```bash
# Optional, save the "old" STDERR
exec 3>&2

# Redirect any output to STDERR to an error log file instead
exec 2> /path/to/error_log

# script with "globally" redirected STDERR goes here
# Turn off redirect by reverting STDERR and closing FH3
exec 2>&3-
```

* Net Redirection (curlやwgetっぽい通信をbashで行う)

```bash
# Finding My IP Address
exec 3<> /dev/tcp/www.ippages.com/80  # fd 3 の入出力をtcpに割当
echo -e "GET /simple/?se=1 HTTP/1.0\n" >&3  # GETリクエストを割り当てたfdにリダイレクト
cat <&3
```

* プロセス置換との併用


# 入力

## `read -p "MSG " VAR` - メッセージ付き入力プロンプト
* ユーザに`MSG`を表示して入力を促し、入力内容を`VAR`変数に格納する
* ↓こんな関数を用意して

```sh
function choice {
  CHOICE=''
  local prompt="$*"
  local answer
  read -p "$prompt" answer
  case "$answer" in
     [yY1] ) CHOICE='y';;
     [nN0] ) CHOICE='n';;
     *     ) CHOICE="$answer";;
  esac
} # end of function choice
```

* こう呼ぶと便利かもしれない

```sh
choice "Do you want to look at the error log file? [Y/n]: "
```

## `read -s -p "MSG " PASSWD` - パスワード用入力プロンプト
* `-s`は文字のエコーイングをoffにするので、ユーザー入力の後に改行が表示できない
    * 回避策として`printf "%b" "\n"`とかすると良い
* 入力したパスワード（を格納した環境変数）は、 メモリにplain textで乗っかる
    * `/proc/core`から覗けちゃう

# 計算

## `expr 式`, `$(( 式 ))`, `let 式` - 計算
* [ShellScript - シェルで変数のインクリメントに expr を使うと100倍遅い件 - Qiita](http://qiita.com/d_nishiyama85/items/a117d59a663cfcdea5e4)
* `let`は式の中に空白を書けない＝可読性が低い。`$(( ))`は書ける

```bash
let Y=(X+2)*10
Y=$(( ( X + 2 ) * 10 ))
```

## `(( 算術評価式 ))`
* 例えば変数の加算は...
    * これはエラー → `$(( ${i}++ ))`
    * これもエラー → `(( ${i}++ ))`
    * __これが正 → `(( i++ ))`__ （算術評価式で変数を使うのに、その内容を展開しちゃうと `0++`とか`1++`になっちゃう）
        * `((`の前に`$`を付けない
        * 変数名に`$`を付けない

* 複数の変数をまとめて加減算するときの注意 = `let`を使え

```bash
$(( x+=5 , y+=8 ))  # これはエラー（ `$(( ))` は、別のコマンドの引数にするか、代入しないといけない）
let x+=5 y+=8  # こう書く。式の区切りはスペース

echo $(( x+=5 , y+=8 ))  # カンマ演算子は第2の式の値を返すので、こう書くと y の値がechoされる
```

## `awk "BEGIN {print \"The answer is: \" $* }";`
* `awk BEGIN`ブロックの`$*`に計算式を渡すと計算結果を出力する

```bash
function calc
{
   awk "BEGIN {print \"The answer is: \" $* }";
}

$ calc 2 + 3 + 4
The answer is: 9

$ calc (2+2-3)*4
-bash: syntax error near unexpected token `2+2-3'

$ calc '(2+2-3)*4'
The answer is: 4

$ calc \(2+2-3\)\*4
The answer is: 4

$ calc '(2+2-3)*4.5'
The answer is: 4.5
```

# `PATH` と `hash`コマンド
[bash(csh)のhashとか言う、気づかないけど便利な機能 - それマグで！](http://takuya-1st.hatenablog.jp/entry/2015/01/24/042733)


気がついて点は適宜修正していく