read コマンドはバックスラッシュをそのまま変数にセットしないので注意が必要

とある非業務なシェルスクリプトの編集中に、ShellCheck が見慣れないエラーを吐いていることに気づいた。🤔

f:id:msh5_h:20190506230808p:plain

help コマンドで -r オプションの意味を調べてみる。
read は bash のビルトインコマンドなので、man ではなく help を使うのが正しい。

$ help read
read: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...]
    ...(省略)
    If the -r option is given, this signifies `raw' input, and
    backslash escaping is disabled. 
    ...(省略)

この説明だけだともう一つピンと来ないので、さらにググって調べてみる。

※-rオプションを指定しなかった場合、入力の末尾にあるバックスラッシュは行の継続と見なされる。バックスラッシュを入力するにはそれ自体をバックスラッシュでエスケープする必要がある。
readコマンドについてメモ | OpenGroove

どうやら read コマンドはバックスラッシュをメタキャラクタの一つとして扱っていて、特に末尾に置かれる場合には行継続として扱われる仕様とのこと。確かにバックスラッシュを標準入力として与えてみても、セットされる文字列にはバックスラッシュとして含まれない。

# バックスラッシュが先頭にあるケース(無視される)
$ read test
\foobar
$ echo $test
foobar

# バックスラッシュが途中にあるケース(無視される)
$ read test
foo\bar
$ echo $test
foobar

# バックスラッシュが末尾にあるケース(行継続される)
$ read test
foobar\
> baz 
$ echo $test
foobarbaz

# バックスラッシュが2文字連続するケース(エスケープされる)
$ read test
foo\\bar
$ echo $test
foo\bar

そこで r オプションを与えると、バックスラッシュもセットされる文字列に含まれるようになる。

$ read -r test
\foobar
$ echo $test
\foobar

$ read -r test
foo\bar
$ echo $test
foo\bar

$ read -r test
foobar\
$ echo $test
foobar\

今回 ShellCheck で引っかかったのは、シェルスクリプト中でパスワードの入力を求める箇所だった。ここに関してはバックスラッシュを入力として与えたときこれがセットされないのは不適切なので、r オプションをつけるように修正した(つまり ShellCheck は今回も神でした)👍