馬鹿の一つ覚え: Bashでシェルスクリプト

# Bourneシェル、実際にはBashでシェルスクリプトを書くための備忘録

#### Bashのバージョンを確認する
```shell
echo $BASH_VERSION    # 3.2.57(1)-release
```

#### カレントディレクトリのフルパスを取得する
```shell
echo $PWD
```

#### 部分文字列を取り出す
```shell
letters='abcdefghijklmnopqrstuvwzyz'
echo ${letters:0:4}    # 0から数えて0番目から4文字を取り出す; abcd
echo ${letters:7:16}    # 0から数えて7番目から16文字を取り出す; hijklmnopqrstuvw
echo ${letters:9}    # 0から数えて9番目から最後までを取り出す; jklmnopqrstuvwzyz
```

#### 文字列を比較する
```shell
proverb='A ship in the harbor is safe, but that is not what a ship is for.'
word=${proverb:2:4}
if [ $word != 'SHIP' ]; then    # 等価の場合は =
  echo $word
fi
```

#### 数値を比較する
```shell
if [ $? -eq 0 ]; then    # -eq, -ne, -gt, -ge, -lt, -le
  echo identical
fi
```

#### 文字列が空か1文字以上あるかをテストする
```shell
arg1=$1    # 第1コマンド引数からテストしたい文字列を取得
if [ -n "$arg1" ]; then    # ダブルクオートで囲うことが重要; 1文字以上の長さがあったら
  echo 'more than 0 characters (-n)'
else
  echo '0 character (-n)'
fi
if [ -z $arg1 ]; then    # ダブルクオートで囲まなくてもいい; 文字列長が0であったら
  echo '0 character (-z)'
else
  echo 'more than 0 characters (-z)'
fi
exit
```

#### ファイル名からパスや拡張子を取り除いた文字列を取得する
```shell
file_name=`basename /etc/ssh/ssh_host_rsa_key.pub`    # ssh_host_rsa_key.pub
file_name=`basename /etc/ssh/ssh_host_rsa_key.pub .pub`    # ssh_host_rsa_key
```

#### 実行されているスクリプト自身が保存されているパスを取得する
```shell
prefix=`dirname $(readlink $0 || echo $0)`
echo $prefix
```

#### コマンド引数の数を取得する。
```shell
echo $#    # コマンド自体は含まず、コマンドオプションやコマンド引数の総数が $# に入っている
```

#### 最終コマンド引数を取得する。
```shell
echo ${@:$#}
```

#### キーボードからの入力を促がす
```shell
read -p 'Type any string. ' str
echo $str
```

#### forループのために便利なコマンドseqを使う
```shell
seq 11
seq 52 7    # 1ずつ減らしながら52以下7以上の全ての整数を表示
seq -f "%03g" 3 372    # 数字の前に0を加えて桁を揃えて表示
```

#### 正規表現を使ってみる
```shell
mkdir test_{a,b,c,d,e,f,g,h}
for dir in test_*
do
  cd $dir/
  if [[ $PWD =~ ^.+_[c-f]$ ]]; then    # 名前の末尾が _c, _d, _e, _f のいずれかであるディレクトリを判定
    pwd    # .+ のところを [¥/¥w]+ と書いたら意図した通りに動作しなかったので要注意
  else
    date
  fi
  cd ../
done
rm -fr test_?/
exit
# 出力結果
# Fri Jul 21 15:43:54 JST 2017
# Fri Jul 21 15:43:54 JST 2017
# /tmp/test_c
# /tmp/test_d
# /tmp/test_e
# /tmp/test_f
# Fri Jul 21 15:43:54 JST 2017
# Fri Jul 21 15:43:54 JST 2017
```
=~ は使えるが !~ は使えない。 

#### タブや改行を出力する
```shell
echo -e "foo\tbar\nbaz\tqux"
```

#### 配列を使ってみる
```shell
var=(`cat variables.txt`)    # テキストファイルに1行ごとに書かれた空白類文字を含まない文字列を配列に
for variable in ${var[@]}    # 配列の全ての要素に対して処理
do
  echo $variable
done

declare -a names=("Alice" "Bob" "Carol" "David")    # この場合 " 省略可
declare -a years=(1985 1989 1995 1996)
echo ${#names[@]}    # 配列のサイズ 4
echo ${#years[@]}    # 配列のサイズ 4
for i in {0..3}    # 配列のindexは0-based; {0..3} 0から3を含んで3まで
do
  echo -e "$i\t${names[$i]}\t${years[$i]}"
done
```

#### 関数を定義してみる
```shell
function prepare_array()
{
  list=(`seq $1`)    # $1はこの関数の第1引数; 関数に戻り値は無い
}

if [ ! -z $2 ]; then    # 第2コマンド引数があれば処理を実行
  up_to=$2
  prepare_array $up_to    # その値を引数に関数呼び出し
  for element in ${list[@]}    # 戻り値は無いが、関数内で定義された変数が利用できる
  do
    printf " %s" $element
  done
  echo
fi
```

#### 直前のコマンドのプロセスIDを取得する
```shell
sleep 1024 &
previous_pid=$!
```

#### ファイルサイズが0か否かを確認する
```shell
if [ -s $1 ]; then    # 変数が定義されているか否かも -s で確認可能
  :
else
  rm $1
fi
```
その他のファイルテスト演算子: -e 存在するか; -d ディレクトリか; -f 通常のファイルか

#### ファイルが同一か否かを確認する
```shell
cmp foo bar
if [ $? -eq 0 ]; then
  echo identical
else
  echo different
fi
```

#### 否定、論理和、論理積を使う
```shell
if [ \( ! -e foo \) -a \( -e bar \) ]; then
  echo 1
elif [ \( ! -e foo \) -o \( -e bar \) ]; then
  echo 2
else
  echo 3
fi
```

#### 整数値をインクリメントする
```shell
i=95
while :
do
  echo $((i++))
  if [ $i -gt 105 ]; then
    exit $i
  fi
done

j=0
let j++
echo $j    # 1
let j+=8
echo $j    # 9
```

#### 簡単な計算をする
```shell
alice=57
generation=10
echo $(($alice / $generation))    # 5 商
echo $(($alice % $generation))    # 7 剰余
```

#### 乱数を取得する
```shell
echo $RANDOM    # 19168 (0から32767までの値)
echo $(($RANDOM % 10000 + 50000))    # 53478 (確率は一様でないながら50000から59999までの乱数を取得)
```