はじめに
ワンライナーマニアによるワンライナーマニアへのTips共有です。
今となっては当たり前のようにやっていますが、
ワンライナー入門したときはかなり頭を悩ませた課題だったので、投稿してみました。
※ワンライナーマニアとは?
私が(勝手に)命名しました。
あらゆる場面でLinuxの複雑なスクリプトを1行で完結させようとする人たちを指します。
例えば繰り返し処理等で、素直に以下のようにシェルファイルを作成すればいいところを
$ cat test.sh
#!/bin/bash
for line in `cat ./hoge.txt`
do
echo $line
done
$ ./test.sh
aaa
bbb
ccc
以下のようにコマンドライン1行で完結させる人を指します
$ for line in `cat ./hoge.txt`; do echo $line; done
aaa
bbb
ccc
ワンライナーにおけるデバッグ
ワンライナーは かっこいい というメリットしかない一方で、
意図しないバグが発生すると原因特定に余計な時間を使うと考えています。
デバッグ時のメリット/デメリット
シェルファイルを作った場合
- シェル内にechoでデバッグメッセージ等を差し込みやすい
- 一部処理をコメントアウトさせることで、被疑箇所に絞った実行等ができる
-
bash -x test.sh
と書くことで1コマンド毎に実行状況をトレースできる ←重要
と、メリットしか浮かばないのですが
ワンライナーで書くと
- デバッグメッセージを仕込むと、1行がどんどん長くなり、余計分かりにくくなる
- ターミナル上での操作の場合、1行が長すぎて、途中に処理を差し込むといった作業が面倒になる
とデメリットだらけです。。。
ワンラインナーマニアはどうやってデバッグすればいいの?
ワンライナーは かっこいい というメリットしかないのはわかりきっているのですが、
デバッグ(バグ解析)ですらシェルファイルを作ったら負け、、、というジレンマに陥るのがサガです。
そんなあなたは bash
コマンドを活用しましょう!
デバッグ方法その1
一般的に bash
コマンドの使用例として紹介されている多くの記事は、
bash -x test.sh
と記述することでシェルファイルをデバッグ実行できることを紹介しています。
実は bash
には -c
オプションで、シェルファイルではなく文字列でコマンドを渡すことができます。
今回の場合は、
$ bash -x -c 'for line in `cat ./hoge.txt`; do echo $line; done'
と実行してみましょう!
デバッグ方法その2
awk
コマンドを活用する方はお気づきかもしれないですが、
$ bash -x -c 'コマンド'
bash
にコマンドを渡す時点で単一引用符(シングルクオーテーション)を使ってしまうという事態が発生します。
コマンド内でも単一引用符を使いたい場合は。。。
$ bash -x -c $'for line in `cat ./hoge.txt`; do echo $line; done | awk \'{print $1}\''
-
-c $'コマンド'
の構文に変更する - コマンド内の単一引用符をエスケープする
事で対処できます。
(エスケープが少々面倒ですね)
こちらは man bashのクオートのセクションで紹介されています。
デバッグ方法その3
コマンド内で引用符を使いたい、、、
でもエスケープしたくない、、、
という方は、全コマンドをデバッグモードにする最終手段を取りましょう。
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入れたてではこの程度ですが、諸々設定した環境で実行したら、プロンプトのデバッグ結果で3画面分ほど埋まりました(笑)
奥の手、として考えておくといいと思います。
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
例えば上記の場合、3階層にネストしていることが表現されています。
ワンライナーにおけるデバッグの弱点
上記でデバッグ方法を紹介しましたが、やはり欠点があります。
シェルファイルに比べて、途中に試しに処理を追加してみる、外してみるといったことが比較的やりずらいかなと思います。
実用性としては、 for
や while
を1行で書き、 awk
で込み入った解析をしない時、でしょうか。
この記事を書いて思ったこと
大量のファイルに対して同じルールで内容を集計する、といった処理をする場合に、Bashの for
や while
が効果を発揮します。
これがバッチ処理のスクリプトであれば、シェルファイルを作るのですが、、、
上司から『ちょっと集計してー』とお願いされたりした時、なんとなく1行のコマンドでやりくりしてみたくなったりもします。
ちょっとした繰り返し処理としてワンライナーを使えると、いろいろな場面で時短ができるのが、個人的なメリットです。
一方で、他人が書いたワンライナーは、複数行に分けたときよりも難解になることが多いと思います。
『処理のどこが詰まっているか分からなくなった』
『シェルファイルに転記して実行しないとデバッグできない』
という状況に陥った場合、もうそのコマンドは人様に見せられる状態じゃないかもしれないですね。
デバッグしやすいか否か、が、人様に見せれるワンライナーか否か、の基準になるかもしれません。
さいごに
初めて自分がワンライナーを意識し始めた時っていつかな?
私の場合、Dockerに触れ始めたころかなと思います。
Dockerfileにはディレクトリ作成といった簡単な初期設定を全て && で繋げて1行で書く風習があると思います。
Dockerfileは行毎にレイヤーが分かれるから、なるべく基礎構築処理はまとめる!みたいな理由があるらしく
『レイヤーまとめてどういうメリットがあるんだ・・・?』と疑問を感じながら見様見真似でワンライナーし始めたのがきっかけでした。
ワンライナーといっても、上記みたいにまとめて複数の処理を実行することを目的にしている場合もあれば、
前回の処理結果をどんどん後ろに回して解析をする、みたいなケースもあります。
自分語りになってしまいましたが、すべてのワンライナーマニアに栄光あれ!