[Linux]ワンライナーマニアによるデバッグTips

# はじめに

ワンライナーマニアによるワンライナーマニアへのTips共有です。

今となっては当たり前のようにやっていますが、
ワンライナー入門したときはかなり頭を悩ませた課題だったので、投稿してみました。

※ワンライナーマニアとは？
私が（勝手に）命名しました。
あらゆる場面でLinuxの複雑なスクリプトを１行で完結させようとする人たちを指します。

例えば繰り返し処理等で、素直に以下のようにシェルファイルを作成すればいいところを

``` 一般人.
$ cat test.sh
#!/bin/bash

for line in `cat ./hoge.txt`
do
  echo $line
done

$ ./test.sh
aaa
bbb
ccc
```

以下のようにコマンドライン１行で完結させる人を指します

``` ワンライナーの人.
$ for line in `cat ./hoge.txt`; do echo $line; done
aaa
bbb
ccc
```


# ワンライナーにおけるデバッグ

ワンライナーは **かっこいい** というメリットしかない一方で、
意図しないバグが発生すると原因特定に余計な時間を使うと考えています。

# デバッグ時のメリット/デメリット

シェルファイルを作った場合

* シェル内にechoでデバッグメッセージ等を差し込みやすい
* 一部処理をコメントアウトさせることで、被疑箇所に絞った実行等ができる
* `bash -x test.sh` と書くことで１コマンド毎に実行状況をトレースできる **←重要**

と、メリットしか浮かばないのですが

ワンライナーで書くと

* デバッグメッセージを仕込むと、１行がどんどん長くなり、余計分かりにくくなる
* ターミナル上での操作の場合、１行が長すぎて、途中に処理を差し込むといった作業が面倒になる

とデメリットだらけです。。。

# ワンラインナーマニアはどうやってデバッグすればいいの？

ワンライナーは **かっこいい** というメリットしかないのはわかりきっているのですが、
デバッグ（バグ解析）ですらシェルファイルを作ったら負け、、、というジレンマに陥るのがサガです。

そんなあなたは `bash` コマンドを活用しましょう！

## デバッグ方法その１

一般的に `bash` コマンドの使用例として紹介されている多くの記事は、
`bash -x test.sh` と記述することでシェルファイルをデバッグ実行できることを紹介しています。

実は `bash` には `-c` オプションで、シェルファイルではなく文字列でコマンドを渡すことができます。

今回の場合は、

```
$ bash -x -c 'for line in `cat ./hoge.txt`; do echo $line; done'
```

と実行してみましょう！

## デバッグ方法その２

`awk` コマンドを活用する方はお気づきかもしれないですが、

```
$ bash -x -c 'コマンド'
```

`bash` にコマンドを渡す時点で単一引用符（シングルクオーテーション）を使ってしまうという事態が発生します。
コマンド内でも単一引用符を使いたい場合は。。。

```
$ bash -x -c $'for line in `cat ./hoge.txt`; do echo $line; done | awk \'{print $1}\''
```

* `-c $'コマンド'` の構文に変更する
* コマンド内の単一引用符をエスケープする

事で対処できます。
（エスケープが少々面倒ですね）

こちらは [man bash](https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html)のクオートのセクションで紹介されています。

## デバッグ方法その３

コマンド内で引用符を使いたい、、、
でもエスケープしたくない、、、
という方は、全コマンドをデバッグモードにする最終手段を取りましょう。

```
set -x
```

上記コマンドを実行することで、同一コンソール上で実行されるすべてのコマンドがデバッグモードで動作するようになります。

解除する場合は

```
set +x
```

とコマンドを実行します。

『初めからこれを紹介してくれよ。。。』
と思われるかもしれないですが、場合によっては一番厄介な代物です。

全てのコマンドをデバッグモードに切り替えると、エンターを叩くたびに以下のような大量のデバッグ情報が出力されるようになります。

```
$
++ __git_ps1
++ local exit=0
++ local pcmode=no
++ local detached=no
++ local 'ps1pc_start=\u@\h:\w '
++ local 'ps1pc_end=\$ '
++ local 'printf_format= (%s)'
++ case "$#" in
++ printf_format=' (%s)'
++ local ps1_expanded=yes
++ '[' -z '' ']'
++ '[' -z '4.4.23(1)-release' ']'
++ shopt -q promptvars
++ local repo_info rev_parse_exit_code
+++ git rev-parse --git-dir --is-inside-git-dir --is-bare-repository --is-inside-work-tree --short HEAD
++ repo_info=
++ rev_parse_exit_code=128
++ '[' -z '' ']'
++ return 0
```

これはプロンプト（Linuxではログインユーザや現在時刻が常にコマンドの左に出たりしますよね？あれです）
を表示するたびに内部で実行されたコマンドのデバッグ結果が表示されています。

GitBash入れたてではこの程度ですが、諸々設定した環境で実行したら、プロンプトのデバッグ結果で３画面分ほど埋まりました（笑）

奥の手、として考えておくといいと思います。

# bash -xの実行結果の見方

デバッグの実行結果を見てみましょう。

```
$ bash -x -c 'for line in `cat ./hoge.txt`; do echo $line; done'
++ cat ./hoge.txt
+ for line in `cat ./hoge.txt`
+ echo aaa
aaa
+ for line in `cat ./hoge.txt`
+ echo bbb
bbb
+ for line in `cat ./hoge.txt`
+ echo ccc
ccc
```

## ++の個所

バッククオーテーションの処理が該当します。
優先的に実行され、コマンド結果を展開してから後続処理が動くからですね。

`$()` も同じ動きになります

```
$ bash -x -c 'echo $(echo "hoge")'
++ echo hoge
+ echo hoge
hoge
```

## +の個所

++の場合を除き、上から実行された順番にコマンドが表示されます。
これを見るメリットは、変数が展開された状態で実行コマンドが表示されます。

これを見て大半の人は
『文字列整形を間違えていた！』
『引数の文字列にバックスラッシュが含まれている！CRLFになっている！』
とバグを見つけることができます

## 接頭辞無しの個所
標準出力/エラー出力です
あまり使う場面はないと思いますが、、、バグと思わしき箇所との突き合せ等で活用できると思います

## 補足

接頭辞の+の個数は、実行されたコマンドに依存します。

```
$ bash -x -c 'echo $(echo $(echo "hoge"))'
+++ echo hoge
++ echo hoge
+ echo hoge
hoge
```

例えば上記の場合、３階層にネストしていることが表現されています。

# ワンライナーにおけるデバッグの弱点

上記でデバッグ方法を紹介しましたが、やはり欠点があります。

シェルファイルに比べて、途中に試しに処理を追加してみる、外してみるといったことが比較的やりずらいかなと思います。

実用性としては、 `for` や `while` を１行で書き、 `awk` で込み入った解析をしない時、でしょうか。

# この記事を書いて思ったこと

大量のファイルに対して同じルールで内容を集計する、といった処理をする場合に、Bashの `for` や `while` が効果を発揮します。

これがバッチ処理のスクリプトであれば、シェルファイルを作るのですが、、、
上司から『ちょっと集計してー』とお願いされたりした時、なんとなく１行のコマンドでやりくりしてみたくなったりもします。

ちょっとした繰り返し処理としてワンライナーを使えると、いろいろな場面で時短ができるのが、個人的なメリットです。
一方で、他人が書いたワンライナーは、複数行に分けたときよりも難解になることが多いと思います。

『処理のどこが詰まっているか分からなくなった』
『シェルファイルに転記して実行しないとデバッグできない』
という状況に陥った場合、もうそのコマンドは人様に見せられる状態じゃないかもしれないですね。

デバッグしやすいか否か、が、人様に見せれるワンライナーか否か、の基準になるかもしれません。

# さいごに

初めて自分がワンライナーを意識し始めた時っていつかな？
私の場合、Dockerに触れ始めたころかなと思います。

Dockerfileにはディレクトリ作成といった簡単な初期設定を全て && で繋げて１行で書く風習があると思います。
Dockerfileは行毎にレイヤーが分かれるから、なるべく基礎構築処理はまとめる！みたいな理由があるらしく
『レイヤーまとめてどういうメリットがあるんだ・・・？』と疑問を感じながら見様見真似でワンライナーし始めたのがきっかけでした。

ワンライナーといっても、上記みたいにまとめて複数の処理を実行することを目的にしている場合もあれば、
前回の処理結果をどんどん後ろに回して解析をする、みたいなケースもあります。

自分語りになってしまいましたが、すべてのワンライナーマニアに栄光あれ！

スペシャルサンクス：
　[@hanohrs](https://qiita.com/hanohrs)
　[@ootahrc](https://qiita.com/ootahrc)