パラメータ展開

前回は、変数とパラメータについて紹介した。シェルの変数はただ値を代入するだけではなく、その変数を利用するときに変数の値を書き換えたり、値が設定されていなかったりした場合にデフォルト値を設定するといったことができる。「パラメータ展開」と呼ばれることが多い機能だ。便利なので、全ての機能とまでは言わないまでも、よく使われるものは覚えておいたほうがよい。

パラメータ展開の主な機能は次の通りだ。

パラメータ 展開内容
$パラメータ パラメータの値に展開される
${パラメータ} パラメータの値に展開される
${パラメータ:-ワード} パラメータの値に展開される。パラメータが設定されていないかnullの場合にはワードが使われる
${パラメータ:=ワード} パラメータの値に展開される。パラメータが設定されていないかnullの場合には、ワードが設定された後、ワードが使われる
${パラメータ?} パラメータの値に展開される。パラメータが設定されていないかnullの場合にエラーになる
${パラメータ?メッセージ} パラメータの値に展開される。パラメータが設定されていないかnullの場合にエラーになり、指定したエラーメッセージが表示される
${パラメータ:+ワード} パラメータの値が設定されていないかnullの場合、nullが使われる。パラメータの値が設定されている場合にはワードが使われる
${#パラメータ} パラメータの値の長さ
${パラメータ%パターン} 後方最小一致パターンを削除した結果が使われる
${パラメータ%%パターン} 後方最大一致パターンを削除した結果が使われる
${パラメータ#パターン} 前方最小一致パターンを削除した結果が使われる
${パラメータ##パターン} 前方最大一致パターンを削除した結果が使われる

次によく使うものを紹介しようと思う。まず、変数に値が設定されていなかった場合に使える指定が「:-」と「:=」だ。:-は指定した値をデフォルト値として使い、:=は指定した値を代入した上でデフォルト値として使ってくれる。動作は次のようになる。

# echo $v

# echo ${v:-default word}
default word
# echo $v

# echo ${v:=default word}
default word
# echo $v
default word
#

こちらは使うかどうかは好みによるが、変数が指定されていなかった場合にエラーになる「?」の指定も覚えておいてもよいだろう。

# echo ${v?}
default word
# echo ${V?}
sh: V: parameter null or not set
#

また、こちらも使うかどうか微妙だが、「#」の指定で変数の値の長さを得ることができる。

# echo ${#v}
12
# v="あいうえお"
# echo ${#v}
5
#

シェルで便利なのは、変数を使用する際に、ちょっとした文字列のトリムが可能な点だ。「#」や「##」が前方の削除、「%」や「%%」が後方の削除となっている。次のサンプルを見れば、何となく使い方がわかるのではないだろうか。

# v=$(pwd)
# echo ${v}
/usr/local/share/zfs
# echo ${v%/*}
/usr/local/share
# echo ${v%/*/*}
/usr/local
# echo ${v%/*/*/*}
/usr
# echo ${v%%*/}
/usr/local/share/zfs
# echo ${v%%/*}

# echo ${v#/}
usr/local/share/zfs
# echo ${v#/*/}
local/share/zfs
# echo ${v##/*/}
zfs
#

具体的にどういった使い方ができるかだが、例えばパスからファイル名を取り出すbasenameコマンドや、パスからディレクトリを取り出すdirnameコマンドの動作は、シェルのパラメータ展開の機能を使っても実現することができる。次のようなイメージだ。

# echo ${v}
/usr/local/share/zfs
# basename ${v}
zfs
# echo ${v##/*/}
zfs
# dirname ${v}
/usr/local/share
# echo ${v%/*}
/usr/local/share
#

パターン指定などは慣れないと間違えることもあるので無理に使う必要はないが、覚えておくと結構便利な機能だ。

ただし、本格的に文字列の加工をしたいのなら、シェルのパラメータ展開の機能を使うよりも、Pythonやawk、sedといったほかのプログラミング言語を使ったほうがよいだろう。あくまでも簡単なトリムができるくらいの処理だと覚えておこう。

コマンド置換

シェルには変数の展開時に加工を行う機能以外にも、コマンドの実行結果を展開結果とする「コマンド置換」という機能が用意されている。

コマンド置換 内容
$(コマンド) コマンドをサブシェルで実行した結果が使われる。出力の最後の連続した改行は1つの改行へトリムされる
`コマンド` コマンドをサブシェルで実行した結果が使われる。出力の最後の連続した改行は1つの改行へトリムされる

コマンド置換についてはサンプルを見るのがわかりやすい。次のように、「$(date)」と書くと、その場所にdateコマンドの実行結果が展開される。

# date
2019年 8月 6日 火曜日 17時02分58秒 JST
# echo "【" $(date) "】"
【 2019年 8月 6日 火曜日 17時03分13秒 JST 】
# v=$(cal)
# echo $v
8月 2019 日 月 火 水 木 金 土 1 2 3 4 5  6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
# echo "$v"
      8月 2019
日 月 火 水 木 金 土
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

#

この機能は具体的な使用例を見ると使い方を理解しやすい。例えば、ifconfigコマンドで出力されるIPアドレスを変数に保持しておきたいケースを考えてみよう。この場合、次のようにコマンド置換を使えば実現できる。

# ifconfig en3
en3: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
    options=b<RXCSUM,TXCSUM,VLAN_HWTAGGING>
    ether 64:4b:f0:00:13:4c
    inet6 fe80::1c25:dc2f:20ad:b230%en3 prefixlen 64 secured scopeid 0x6
    inet 192.168.1.106 netmask 0xffffff00 broadcast 192.168.1.255
    nd6 options=201<PERFORMNUD,DAD>
    media: autoselect (1000baseT <full-duplex,flow-control>)
    status: active
# ifconfig en3 | grep 'inet ' | awk '{print $2}'
192.168.1.106
# ip=$(ifconfig en3 | grep 'inet ' | awk '{print $2}')
# echo $ip
192.168.1.106
#

算術展開

もう1つ、シェルには「算術展開」という特徴的な機能がある。これは整数の算術演算を行うというもので、「$(())」の中に計算式を書いておくことで、計算結果が得られる。

算術展開 内容
$((算術)) 算術式を評価した結果が使われる。指定できる数値は整数のみ。10進数、8進数(0から始まる)、16進数(0xから始まる)を表記することができる。シェル変数の読み取りや書き込みも可能
種類 内容
単項演算子 !、~、+、-
二項演算子 *、/、%、+、-、<<、>>、<、<=、>、>=、==、!=、&、^、|、&&、||
代入演算子 =、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=
条件演算子 ?、:

現在の主要プログラミング言語の視点からすると、この書き方はかなり奇妙に見える。しかし、これはこういうものだと飲み込むしかない。サンプルは次の通りだ。

# echo $((1 + 2 + 3 + 4 + 5))
15
# echo $((2 * 3 * 4))
24
# echo $((100 - 33))
67
#

算術演算で気をつける必要があるのは、算術展開の中で変数に値を書き込むことができるという点だ。次のサンプルを見てほしいのだが、このサンプルでは途中で変数iに1を加算しており、その結果は算術演算を超えて効果を持っている。さらに、代入を行う場合には「$」を指定せずに変数名だけを使っている。つまり、算術展開以外の場所で変数を定義するのと似たような仕組みになっている。

# i=1
# echo $(($i + 1))
2
# echo $((i = $i + 1))
2
# echo $i
2
# echo $((i += 1))
3
# echo $i
3
#

また、シェルの算術展開で計算できるのは整数であるという点にも注意が必要だ。実数は使用できないし、割り算の結果実数になってしまっても、小数点以下は扱われない(以下参照)。

# echo $((10 / 3))
3
# echo '10 / 3' | bc -l
3.33333333333333333333
#

実数も含めた計算も行いたい場合は、bcコマンドなどそれ専用のコマンドを使ったほうがよい。

シェルの提供しているパラメータ展開による文字列の加工や、算術展開による計算は、あくまでも簡単な処理をするためのものだと思っておこう。それぞれ複雑な処理が必要な場合には、専用のコマンドやプログラミング言語で処理を行うことをお薦めする。

参考資料