ShellScript
Bash

シェルスクリプト(Bash): \ で改行してる時の 1 行コメントアウト

More than 3 years have passed since last update.

シェルのコマンドで、オプションがたくさんあるときは、読みやすくするために改行することがあると思う。

somecommand --optionA=xxxx \
            --optionB=yyyy \
            --optionC=zzzz \
            --optionD=wwww

しかし、これを部分的にコメントアウトする時(たとえばオプションの組み合わせを試す際)

somecommand --optionA=xxxx \
            --optionB=yyyy \
            # --optionC=zzzz \
            --optionD=wwww

のようにコメントアウトして失敗した経験は、皆さんにはないだろうか?

echo での例示

たとえば、↓のようなシェルスクリプトを

sample.sh
#!/usr/bin/env bash

echo 'a' 'x' \
     'b' 'y' \
     'c' 'z' \
     'd' 'w'

コメントアウトして

sample.sh(コメントアウト後)
#!/usr/bin/env bash

echo 'a' 'x' \
     'b' 'y' \
     # 'c' 'z' \
     'd' 'w'

のようにして実行すると、

実行結果
$ ./sample.sh
a x b y
./sample.sh: line 6: d: command not found
$

と、このように。
コメントアウト以降が独立したステートメントとして扱われるため d を実行しようとする。
この例では d というコマンドがなかったから良いものの、
存在するようなケースでは致命傷を生む可能性がある。

回避策を考えてみた

よくある /* */ な範囲コメントアウトができれば、このような悲劇は避けられると思った。
1 行の範囲コメントを発明したい。

ヒアドキュメントを使う方法

シェルの範囲コメントアウトといえば、ヒアドキュメントとヌルコマンドを利用した方法が一般的だ。
クオートで囲めば、変数やコマンド置換の評価も止められる。

:<<'EOT'
somecommand
foo
bar
buzz
EOT

でもコレは複数行が前提に成ってしまうのでダメだ。
これをオプションの中でやりたい。

外側を式で囲む

行を $(:)\ で囲む戦法を思いついたのでやってみた。
コマンド置換でヌルコマンドに対して引数が渡るので、置換後は空になるというわけだ。
コマンド置換はダブルクォートで囲まないので、空文字列が挿入されることもない。

sample.sh(改)
#!/usr/bin/env bash

echo 'a' 'x' \
     'b' 'y' \
     $(: 'c' 'z' \ )\
     'd' 'w' 
実行結果
$ ./sample.sh
a x b y d w
$

これは一見よさそうだ。
このパターンだけならこれで正解。

さらなる問題

しかし ↑ ではコメントアウト対象が単なる文字列だったので済んだが、元のスクリプトが次のような例だとダメだ。

sample2.sh
#!/usr/bin/env bash

function inc () {
    echo 'called!' "$*" > /dev/tty
}

echo 'a' "$(inc 'x')" \
     'b' "$(inc 'y')" \
     'c' "$(inc 'z')" \
     'd' "$(inc 'w')" 

これを上記の方法で c の行をコメントアウトして実行してみよう。

sample2.sh(改)
#!/usr/bin/env bash

function inc () {
    echo 'called!' "$*" > /dev/tty
}

echo 'a' "$(inc 'x')" \
     'b' "$(inc 'y')" \
     $(: 'c' "$(inc 'z')" \ )\
     'd' "$(inc 'w')" 
実行結果
$ ./sample2.sh
called! x
called! y
called! z
called! w
a  b  d
$

ご覧のとおり、 c の行の $(inc 'z') は評価されてしまっている。
こいつもトラップだ。

短絡評価する

最終的に思いついたのがコレ
$(:||:)\ で囲む方法

sample2.sh(改2)
#!/usr/bin/env bash

function inc () {
    echo 'called!' "$*" > /dev/tty
}

echo 'a' "$(inc 'x')" \
     'b' "$(inc 'y')" \
     $(:||: 'c' "$(inc 'z')" \ )\
     'd' "$(inc 'w')" 
実行結果
$ ./sample2.sh
called! x
called! y
called! w
a  b  d
$

これで平和になった…。

まとめ

これが今思いついている中では、最も万能な 1 行コメントアウトの方法だと思う。(たぶん)
それにしても、あまりに技巧的なやり方過ぎてしっくりこない……。

そしておそらく、この解決法にもさらなる落とし穴があるに違いない。(落とし穴募集)

ところで

$(:||: )\

ちょっとかわいい顔文字みたいだと思いません?