Bashの括弧

最近Bashで凝ったものを作ろうとして、Bashについて結構しらべた。ネット上の情報は散らばっていたので、不完全ながら仕入れたネタをまとめようかと思う。特にカッコについて。


Bashのカッコには以下の種類がある。

  • { }
  • ${ }
  • ( )
  • $( )
  • <( )
  • >( )
  • (( ))
  • $(( ))
  • [ ]
  • [[ ]]

{ } : 複数のコマンドをまとめる

波括弧{ }で複数のコマンドをまとめて1つのコマンドとして扱うことができる。{ }に対して標準入出力をパイプでつないだりリダイレクトして利用する。

例1:

fjk@x240:~$ echo "world" | { echo "hello";  cat - ; } 
hello
world

例2:

fjk@x240:~$ { for i in {1..3}; do echo ${i}; done; } > 123.txt
fjk@x240:~$ cat 123.txt 
1
2
3

なお、{1..3}1 2 3に展開される。{1..5..2}とすると、1 3 5になる。

${ } : 変数を展開する

${VAL}VALという変数を展開する。

$VALでも同じことが出来るが、文字列を結合したいときにどこまでが変数なのか分からなくなる。統一感を出すために変数の展開には全て${}を使うのがよいと思う。

fjk@x240:~$ VAL="value"
fjk@x240:~$ echo "${VAL}"
value
fjk@x240:~$ echo "$VAL"
value
fjk@x240:~$ echo "${VAL}A"
valueA
fjk@x240:~$ echo "$VALA"

fjk@x240:~$

また、" "で括らないと変数が代入したときのままで展開されないので、特に理由がない限りは" "で括ることを忘れないようにする。

fjk@x240:~$ VAR="a b  c"
fjk@x240:~$ echo ${VAR}
a b c
fjk@x240:~$ echo "${VAR}"
a b  c

( ) : サブシェルで実行する

( )で括られたものは別のシェルを開いて実行したように振る舞う。サブシェルと呼ばれます。*1

{ }と異なるのは、( )内で変数を操作しても外には影響を与えない点である。

fjk@x240:~$ VAL=1; (echo "${VAL}"; VAL=2; echo "${VAL}"); echo "${VAL}"
1
2
1

( )内でcd, umask等を適用しても、( )内に閉じて影響するのが便利だ。

fjk@x240:~$ umask
0002
fjk@x240:~$ (umask 0777; : > file)
fjk@x240:~$ ls -l file 
---------- 1 fjk fjk 0  3月  6 15:56 file
fjk@x240:~$ umask
0002

( )に対してパイプ・リダイレクトをつないだりすることも可能だ。特に標準入出力に何も繋がれていないときは、( )の標準入出力は呼び出し元の標準入出力と一致している。

fjk@x240:~$ (echo "hello") > /dev/null
fjk@x240:~$ (echo "hello")
hello

また、( )は配列の宣言にも使えます。

fjk@x240:~/works$ a=("A" "B" "C")
fjk@x240:~/works$ echo "${a[0]}"
A
fjk@x240:~/works$ echo "${a[1]}"
B
fjk@x240:~/works$ echo "${a[2]}"
C

$( ) : サブシェルの標準出力を値として得る

$( )で、$( )内の標準出力を値として得ることができる。` `でも同様のことは可能だが、$( )はコマンドを入れ子にすることができます。

fjk@x240:~$ HELLO=$(echo "$(echo hello) $(echo world)")
fjk@x240:~$ echo "${HELLO}"
hello world

<( ), >( ) : サブシェルの標準入力・標準出力をファイルとして扱う

<( )>( )はサブシェルをファイルとして扱うことが出来るそうです。使いどころが分からないので調べきれていません。

fjk@x240:~$ while read line; do echo "${line}"; done < <(echo hello; echo world)
hello
world

2016-03-12 追記

コメントにてご指摘をいただきましたが、<( )diffのようにファイルのみを入力にできるコマンドを一時ファイルを明に作ることをせずに使いたいときに便利そうです。依然として、>( )の使い道は不明です。

(( ))$(( )) : 数値計算をする

Bash数値計算をするなら、exprコマンドよりも、(( ))$(( ))を使うのが簡単です。

fjk@x240:~$ INT=1; ((INT++)); echo $INT
2
fjk@x240:~$ INT=3; ((INT=INT**2)); echo $INT
9

(( ))内では変数に$を付ける必要がありません。

(( ))を使うと、C言語風のforループができます。

fjk@x240:~$ for ((i=0; i < 3; i++)); do echo "${i}"; done
0
1
2

$(( ))では値を得ることができます。

fjk@x240:~$ echo $((2**10))
1024

[ ] : 条件の真偽を判定

[ ]testコマンドのシンタックスシュガーのようなものです。[ ]内の条件が満たされていたらexit codeが0、満たされていなければexit codeが1となる。

fjk@x240:~$ [ "hello" = "hello" ]; echo $?
0
fjk@x240:~$ [ "hello" = "hello!" ]; echo $?
1

判定できる条件は色々あるが、ここでは割愛する。以下は参考です。

if 文と test コマンド | UNIX & Linux コマンド・シェルスクリプト リファレンス

[[ ]] : 条件の真偽を判定

[[ ]][ ]と似ていますが、[ ]より強力です。

[ ]内では変数を" "で括らないと予期せぬエラーが起きる。

fjk@x240:~$ EMPTY=""; [ ${EMPTY} = "" ]; echo $?
bash: [: =: unary operator expected
2
fjk@x240:~$ EMPTY=""; [ "${EMPTY}" = "" ]; echo $?
0

しかし、[[ ]]内では変数を" "で括る必要がない。

fjk@x240:~$ EMPTY=""; [[ ${EMPTY} = "" ]]; echo $?
0

また、=~正規表現にマッチしているかどうかの判定ができる。これはかなり便利です。正規表現" "で括らないことに注意する。

fjk@x240:~$ HELLO="hello"; [[ ${HELLO} =~ ^he ]]; echo $?
0

他にも、and, orに-a, -oではなく&&, ||の記号が使えます。

*1:調べきれてはいないが、新しいプロセスをフォークすると書いている記事もあったけれども、どうも必ずしもforkするわけではなさそう。