Bash

変数展開

記述 説明
${parameter} parameter値に置換される。
ex1) a=10; echo $a     # => 10
${parameter:-word} parameterがunsetまたはnullの時、wordに置換される。
ex1) echo ${a:-"def"}  # => def
${parameter:+word} parameterがunsetまたはnullの時、置換されず、そうでなければparameterの値に置換される。
ex1) a=10; echo ${a:+20}  # => 20
ex2) echo ${a:+20}        # =>
${parameter:=word} parameterがunsetまたはnullの時、wordの値がparameterに代入される。
ex1) b=${a:="c"}; test "$a" = "$b" && \
     echo "a = b" # => a = b
${parameter:?word} parameterがnullまたはunsetの時、wordの値が 標準エラー出力とshellに出力される。インタラク ティブでないshellは終了する。そうでければ、parameterの値に置換される。
ex1) echo ${a:?"error"} ; echo "not exec" # => -bash: a: error 
ex2) a=10; echo ${a:?"error"}; echo "ok"
     # => 10 ok
${parameter:offset}
${parameter:offset:length}
parameter文字列をoffsetからlength数までの 部分文字列を返す。
ex1) a="abcde"; echo ${a:0:2}    # => ab
ex2) a="abcde"; echo ${a: -1:1}  # => e
${!prefix*}
${!prefix@}
名前がprefixで始まる変数名を展開する。区切り 文字はIFSの値が使われる。
ex1) a1="a1";a2="a2"; echo ${!a@}
     # => a1 a2
${!name[@]}
${!name[*]}
nameが配列の場合、keyのリストに展開される。 nameが配列でない場合、nameがsetされていれば 0、でなければnullに展開される。
ex1) a=(9 10); echo ${!a[@]}  # => 0 1
ex2) a=1; echo ${!a[@]}       # => 0
ex3) echo ${!a[@]}            # =>
${#parameter} parameterの文字列長。
ex1) a="hoge"; echo ${#a}    # => 4
${parameter#word}
${parameter##word}
パス名の前方一致によるパターン展開。#は、 shortest maching、##は、longest maching。
ex1) a="/c/b.txt.swp"; echo ${a#*.}
     # => txt.swp
ex2) a="/c/b.txt.swp"; echo ${a##*.}
     # => .swp
${parameter%word}
${parameter%%word}
パス名の後方一致によるパターン展開。%は、 shortest maching、%%は、longest maching。
ex1) a="/c/b.txt.swp"; echo ${a%.*}
     # => /c/b.txt
ex1) a="/c/b.txt.swp"; echo ${a%%.*}
     # => /c/b
${parameter/pattern/string} パラメータ置換。
ex1) a="abbb"; echo ${a/b*/d}    # => ad
ex2) a=b.txt; echo ${a/#*./a.}   # => a.txt
ex3) a=b.txt; echo ${a/%.*/.log} # => b.log

フロー制御

ループ構造

until

条件が真になるまで繰り返し実行される。

n=5
t=1
until [ $n -eq 0 ]; do
  let t=$n*$t
  let n=$n-1
done
echo "5! = $t"

### output ###
5! = 120

while

条件が真の間繰り返し実行される。

$ cat server.txt
web1
web2
web3
db1

以下では、server.txtから1行ずつ読み込み、文字列に”web”が含まれている場合に出力する。

while read line && [[ "$line" =~ "web" ]]; do
  echo $line
done < sample.txt

### output ###
web1
web2
web3

for

馴染みのあるfor文。他の高級言語の構文と同様に書ける。

# for ((expr1; expr2; expr3)); do commands; done

for ((i=0; i < 10; i++)); do
  echo $i
done
((i++)) # increment
echo $i

### output ###
0
1
2
3
4
5
6
7
8
9
11

拡張for文、foreach文のようにも書ける。

for word in $(cat server.txt); do
  echo $word
done

### output ###
web1
web2
web3
db1

条件構造

if

基本的な条件分岐。elsifでもなく、else ifでもなく、elifに注意。

if [ $# -eq 0 ]; then
  echo "Usage: if.sh <arg>"
  exit
elif [ $# -eq 1 ]; then
  echo "args[0] = $1"
else
  echo "args = $@"
fi


### output ###
$ bash if.sh
Usage: script.sh <arg>
$ bash if.sh 1
args[0] = 1
$ bash if.sh 1 2 3
args = 1 2 3

case

他の高級言語でいうとswitch文に当たる。比較部分は、(.+)(で囲っても良い。以下は、起動時に渡された引数をParseするシンプルな例。

#!/usr/bin/env bash
#
# case word in [ [(] pattern [| pattern]…) command-list ;;]… esac
#

while [ $# -gt 0 ]; do
  case "$1" in
    --help)
      echo "Usage: case.sh [OPTIONS]"
      exit
      ;;
    (-v)
      # () でも良い
      echo "Version 1.0"
      ;;
    --verbose)
      VERBOSE=1
      echo "VERBOSE -> on"
      ;;
    -o|--output)
      shift
      OUTPUT="$1"
      echo "outpuf -> $OUTPUT"
      ;;
    -*)
      echo "invalid option" 2>&1
      exit 1
      ;;
  esac
  shift
done

### output ###
$ bash case.sh -v --verbose --output /tmp
Version 1.0
VERBOSE -> on
outpuf -> /tmp

最新のbashではコマンドの区切り文字に、;;;&;;&が用意されておりそれぞれ解釈が異なるので注意。;;は、command-listの実行を終え次の句の評価へ移る。;&は、fall throughの動きになる。;;&は、引き続き後続するwordのマッチを試みる。実際の動きは以下のようになる。

#!/usr/bin/env bash
#
# case word in [ [(] pattern [| pattern]…) command-list ;;]… esac
#
while [ $# -gt 0 ]; do
  case "$1" in
    --help)
      echo "Usage: case.sh [OPTIONS]"
      exit
      ;;
    (-v)
      echo "Version 1.0"
      ;&
    --verbose)
      VERBOSE=1
      echo "VERBOSE -> on"
      shift
      ;;&
    -o|--output)
      shift
      OUTPUT="$1"
      echo "outpuf -> $OUTPUT"
      ;;
    --ver*)
      echo "ver"
      ;;
    -*)
      echo "invalid option" 2>&1
      exit 1
      ;;
  esac
  shift
done

### output ###
$ bash case.sh -v
Version 1.0
VERBOSE -> on
$ bash case.sh --verbose
VERBOSE -> on
ver

select

シンプルな選択リストを表示して標準入力から値を受け取ることができる。

#!/usr/bin/env bash
#
# select name [in words …]; do commands; done
#

ANSWER=

select ans in yes no; do
  ANSWER=$ans
  break;
done

echo "Your Answer is '$ANSWER'"

### output ###
 bash select.sh 
1) yes
2) no
#? 1
Your Answer is 'yes'

in wordsが省略されている場合は、$@が指定されたかのように振る舞う。

echo "Which do you like ?"
select ans; do
  ANSWER=$ans
  break;
done

echo "Your Answer is '$ANSWER'"

### output ###
Which do you like ?
1) apple
2) orange
#? 1
Your Answer is 'apple'

((..))、$((..))

算術式。letと等しい。statusは、式の評価の結果が0の場合は1を、そうでない場合は0を返す。

i=0
while [ $i -lt 5 ]; do
  echo $i
  ((i++))
done

echo "# status test"
((0)) ; echo "((0)) returns $?"
((1)) ; echo "((1)) returns $?"

### output ###
0
1
2
3
4
# status test
((0)) returns 1
((1)) returns 0

[[..]]

拡張正規表現やPattern Matchingによる条件評価が可能。

#!/usr/bin/env bash

STRING="This is a pen. This is a dog. This is a cat."

# Matching with regexp
[[ $STRING =~ [[:space:]]*a[[:space:]](pen) ]] && {
  # マッチした文字列はBASH_REMATCH変数で参照可能
  echo ${BASH_REMATCH[0]}
  echo ${BASH_REMATCH[1]}
}

# Pattern Matching as if the extglob shell option were enabled
STRING="pendogcat"
echo '[[ $STRING = ?(pen*) ]] ; echo $?'
[[ $STRING = ?(pen*) ]] ; echo $?

### output ###
a pen
pen
[[ $STRING = ?(pen*) ]] ; echo $?
0

[..], test

条件の評価を行なう。

if [ -f ~/.bashrc ] ; then
. ~/.bashrc
fi

グルーピングコマンド

コマンドをグループ化して実行できる。

( list )

サブシェルで実行される。以下の例では、サブシェルでカレントディレクトリを一時的に変更し、また元のカレントディレクトリで後続の処理を実行している。サブシェルで定義した変数は、元のコンテキストでは無効となっている。

echo $PWD
(
  cd ..
  echo $PWD
  VARS="subshell"
)
echo $VARS
echo $PWD

### output ###
$ pwd
/Users/guest/workspace/bash
$ bash group.sh 
/Users/guest/workspace/bash
/Users/guest/workspace

/Users/guest/workspace/bash

{ list; }

現在のシェルのコンテキストで評価される。リストのコマンド実行前後でカレントディレクトリは変更されている。

echo $PWD
{
  cd ..
  echo $PWD
  VARS="same context"
}
echo $VARS
echo $PWD

### output ###
$ bash group2.sh 
/Users/guest/workspace/bash
/Users/guest/workspace
same context
/Users/guest/workspace

また、以下のようにリダイレクトと組み合わせるとログファイルへの出力などを一箇所にまとめることができる。

{
  echo "==> `date`"
  if [[ "hoge" =~ "foo" ]]; then
    echo "INFO - match"
  else
    echo "ERROR - not match"
  fi
} >> out.log

### output ###
$ bash group3.sh
$ cat out.log 
==> 2016年 7月 8日 金曜日 20時56分54秒 JST
ERROR - not match

Coprocesses

サブシェルで非同期に実行する仕組み。サブシェルとはcoproc実行時に生成されるファイルディスクリプタを経由してやりとりすることができる。

NAMEは、commandのPIDやファイルディスクリプタを参照するための変数に使われる識別名。指定しない場合は、デフォルトでCOPROCという名前が使われる。サブシェルのPIDは、NAME_PIDで参照できる。

#
# coproc [NAME] command [redirections]
#


do_loop() {
  i=0
  while test $i -lt 10; do
    echo "loop $i"
    read line <&0
    echo "reply from parent -> $line"
    ((i++))
    sleep 0.5
  done
}

coproc do_loop
echo "waiting until coprocess is done."
while read line; do
  echo $line
  echo "ok" >&${COPROC[1]}
done <&${COPROC[0]}

wait $COPROC_PID
echo "done"

### output ###
$ bash coproc.sh 
waiting until coprocess is done.
loop 0
reply from parent -> ok
loop 1
reply from parent -> ok
loop 2
reply from parent -> ok
loop 3
reply from parent -> ok
loop 4
reply from parent -> ok
loop 5
reply from parent -> ok
loop 6
reply from parent -> ok
loop 7
reply from parent -> ok
loop 8
reply from parent -> ok
loop 9
reply from parent -> ok
done

関数

name () compound-command [ redirections ]
    or
function name [()] compound-command [ redirections ]

コマンドをグループ化して名前で一連のコマンド群を呼び出すことができる。以下ような特徴がある。

  • unset -f nameで関数定義を削除することができる。
  • 関数のbodyを囲む{ }では、前後にブランクや改行での区切りが必要。
  • 関数への引数は$NUMBERで参照できる(引数は1から)
  • DEBUGRETURNトラップは関数に継承されないため、もし設定を引き継ぎたい場合は、set -o functrace、関数内でDEBUGRETURNのトラップを指定する、declare -tで宣言するのいずれかの方法を用いる必要がある。
  • FUNCNEST変数でネスト数を指定することができる。ネスト回数を超えて関数を呼び出しした場合、関数の実行は中断される。
  • return 数値で関数の終了ステータスを指定できる。指定がない場合は、関数の最終コマンドの実行結果が関数の終了ステータスとなる。
  • local varnamevarnameは関数ローカルの変数となる。
#!/usr/bin/env bash

function hello() {
  # 関数ローカル変数
  local la="$1"
  # ネスト数をカウント
  ((NEST_COUNT++))
  echo "FUNCNAME: $FUNCNAME"
  echo "FUNCNEST: $FUNCNEST"
  echo "  NEST_COUNT: $NEST_COUNT"
  echo "\
#!/usr/bin/env bash
function hello() {
# 関数ローカル変数
local la="$1"
# ネスト数をカウント
((NEST_COUNT++))
echo "FUNCNAME: $FUNCNAME"
echo "FUNCNEST: $FUNCNEST"
echo "  NEST_COUNT: $NEST_COUNT"
echo "\$0: $0"
echo "Hello, \$la: Hello, $la"
# 再帰
hello
# 終了ステータスは1
return 1
}
FUNCNEST=2
NEST_COUNT=0
# This function definition overwrides existed one.
# hello() {
#   echo "hello function was overwride"
# }
hello "haikikyou"
# 修了ステータス
echo "\$?: $?"
### output ###
$ bash func.sh 
FUNCNAME: hello
FUNCNEST: 2
NEST_COUNT: 1
$0: func.sh
Hello, $la: Hello, haikikyou
FUNCNAME: hello
FUNCNEST: 2
NEST_COUNT: 2
$0: func.sh
Hello, $la: Hello, 
func.sh: 行 12: hello: maximum function nesting level exceeded (2)
$?: 1
: $0" echo "Hello, $la: Hello, $la" # 再帰 hello # 終了ステータスは1 return 1 } FUNCNEST=2 NEST_COUNT=0 # This function definition overwrides existed one. # hello() { # echo "hello function was overwride" # } hello "haikikyou" # 修了ステータス echo "$?: $?" ### output ### $ bash func.sh FUNCNAME: hello FUNCNEST: 2 NEST_COUNT: 1 $0: func.sh Hello, $la: Hello, haikikyou FUNCNAME: hello FUNCNEST: 2 NEST_COUNT: 2 $0: func.sh Hello, $la: Hello, func.sh: 行 12: hello: maximum function nesting level exceeded (2) $?: 1

trap DEBUGと-o functraceを使って以下のようなデバッグもできる(以下のサンプルは簡単かつ稚拙な例、bashdbの方が高度で実用的である)。

#!/usr/bin/env bash
#
_lines=()
_i=0
_file=$1
_steps=0

# ステップごとに呼ばれる
debugger() {
  local lineno
  let lineno=$1+1
  (( $_steps > 0 )) && echo "line $lineno: ${_lines[$1]}"
  (( _steps++ ))
}


# デバッグ対象ソースを配列に保存
while read; do
  _lines[$_i]=$REPLY
  ((_i++))
done < $_file

set -o functrace
trap 'debugger $(( $LINENO-1 ))' DEBUG
source $_file # 実行
sample() {
  local i=0
  while test $i -lt 5; do
    echo "i = $i"
    let i=i+1
  done
}
sample

これを実行すると以下のようになる。

 bash  trap.sh sample.sh 
line 8: sample
line 1: sample() {
line 2:   local i=0
line 3:   while test $i -lt 5; do
line 4:     echo "i = $i"
i = 0
line 5:     let i=i+1
line 3:   while test $i -lt 5; do
line 4:     echo "i = $i"
i = 1
line 5:     let i=i+1
line 3:   while test $i -lt 5; do
line 4:     echo "i = $i"
i = 2
line 5:     let i=i+1
line 3:   while test $i -lt 5; do
line 4:     echo "i = $i"
i = 3
line 5:     let i=i+1
line 3:   while test $i -lt 5; do
line 4:     echo "i = $i"
i = 4
line 5:     let i=i+1
line 3:   while test $i -lt 5; do

条件式

[[test[で使われる。

 式 説明
-a file fileが存在すればTrue
-b file fileが存在しブロックスペシャルファイルの場合はTrue
-c file fileが存在しキャラクタスペシャルファイルの場合はTrue
-d file fileが存在しディレクトリの場合はTrue
-e file fileが存在すればTrue
-f file fileが存在し通常ファイルの場合はTrue
-g file fileが存在しSet Group IDがセットされている場合はTrue
-h file fileが存在しシンボリックリンクの場合はTrue
-k file fileが存在しスティッキービットがOnの場合はTrue
-p file fileが存在し名前付きPIPEの場合はTrue
-r file fileが存在し読み取り可能の場合はTrue
-s file fileが存在しファイルサイズが0以上の場合はTrue
-t fd ファイルディクリプタ(fd)がopenされており、ターミナルを指している場合はTrue
-u file fileが存在しSet User Idがセットされている場合はTrue
-w file fileが存在し書き込み可能な場合はTrue
-x file fileが存在し実行可能な場合はTrue
-G file fileが存在し有効なグループIDに属する場合はTrue
-L file fileが存在しシンボリックリンクの場合はTrue
-N file fileが存在し最後に読み取りされてから修正された場合はTrue
-O file fileが存在し有効なユーザーIDに属する場合はTrue
-S file fileが存在しソケットファイルの場合はTrue
file1 -ef file2 file1とfile2が同じデバイスファイルでinode番号が同じ場合はTrue
file1 -nt file2 file2がfile2より最終更新日が新しい、または、file1が存在しfile2が存在しない場合はTrue
file1 -ot file2  file2がfile2より古い、または、file2が存在しfile1が存在しない場合はTrue
-o optname optnameが有効な場合はTrue
-v varname varnameの変数がセットされている場合はTrue
-R varname varnameの変数がセットされかつ名前参照の場合はTrue
-z string stringの長さが0の場合はTrue
-n string stringの長さが0でない場合はTrue
string1 == string2
string1 = string2
string1とstring2が等しい場合はTrue
string1 != string2  string1とstring2が等しくない場合はTrue
string1 < string2 string1が辞書並びでstring2より前に来る場合はTrue
string1 > string2 string2が辞書並びでstring1より前に来る場合はTrue
arg1 OP arg2 以下数値比較で用いる。

  • arg1 -eq arg2
    • arg1とarg2が等しい
  • arg1 -ne arg2
    • arg1とarg2は等しくない
  • arg1 -lt arg2
    • arg1はarg2より小さい
  • arg1 -le arg2
    • arg1はarg2以下
  • arg1 -gt arg2
    • arg1はarg2より大きい
  • arg1 -ge arg2
    • arg1はarg2以上

参考リンク

  • man bash
  • https://www.gnu.org/software/bash/manual/bashref.html
  • http://qiita.com/kiida/items/3beb1bf718cdc2f0798a
  • http://wiki.bash-hackers.org/start
入門bash 第3版

入門bash 第3版

posted with amazlet at 16.07.29
Cameron Newham Bill Rosenblatt
オライリージャパン
売り上げランキング: 243,409