それゆけ!ターミナル部 第5回ワンライナーのコツを身につけよう!
skill

それゆけ!ターミナル部 第5回
ワンライナーのコツを身につけよう!

2016.12.15

みなさんこんにちは!ターミナル部部長のタナカトモフミです。

本連載では意外と見落としがちな基本的な事柄からちょっとマニアックなテクニックまで、ターミナルを使いこなすための方法を若手エンジニアのタミ夫くんとシェルスキー先生と一緒に学んでいきます。

今回はワンライナーの登場です。ワンライナーとは様々なコマンドを組み合わせて1行で複雑な処理を行うというもので、マスターすると作業効率が驚くほどアップします。また、うまくワンライナーを書けるとカッコイイ!という副次的な効果もあります。是非カッコいいワンライナーを書けるようになりましょう。

タナカトモフミ

いつもはカチャパーン!とキーボードを連打しているタミ夫くんですが、今日はシェルスキー先生のディスプレイが気になって仕方ないようです。どうしたのでしょうか?

チラッ

チラチラッ

!?

ジーーーー

なんじゃなんじゃ。そんなガン見されていたら仕事に集中できんじゃろ。

いや、なんかちょいちょいかっこよく黒い画面にブワーって書いて、ドヤ顔してるんで、それなによ?って見てるんス。

なんのことを言っとるのかさっぱりわからんな。
いい感じにワンライナーを書けるようになりたいってことかのう?

かっこよくブワーってできたらなんでもいいっス。

ワンライナーを書きたいなら、パイプライン処理や主要なコマンドをまずは知るところからじゃな。

まずはリダイレクトをおさらいしよう!

コマンドラインを使いこなすには、基本を抑えることが近道です。まずは、標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)について復習しましょう。

ターミナルで実行したコマンド(プログラム)の出力は、標準出力(stdout)と標準エラー出力(stderr)の2種類に分かれています。デフォルトでは、どちらも同じく画面に出力されるため違いがわからないようになっていますが、プログラムはどちらに何を出すかを制御することができます。一般的に、標準出力には、プログラムで出すべき本質的な結果を出力し、標準エラー出力には、その他のエラーメッセージを出力します。

これらの出力先を画面(ターミナル)ではなく、ファイルにしたり、画面に表示しないようにしたりすることができます。

# 例: dir1 ディレクトリを lsした結果が以下の場合
$ ls dir1/
a.txt   b.txt   c.txt

# 標準出力の出力先を dir1-files.txt というファイルにする
$ ls dir1 1>dir1-files.txt
# するとlsの結果がターミナルに表示されない
$ cat dir1-files.txt
a.txt
b.txt
c.txt

上記のように、コマンドの末尾に「1>ファイル名」をつけることでファイルに出力できるようになります。この「1」 は、標準出力の事を指し、標準エラー出力は「2」となります。同様に、「2>ファイル名」をつけることで標準エラー出力をファイルにすることもできます。また、「1」はデフォルト値として省略することができるため、「>ファイル名」と書くこともできます。明示的に「1」を指定している例はあまり見かけないでしょう。

基本的には以下の4パターンを覚えておくと良いでしょう。

# パターン1: 標準出力をファイルにする(1は省略)
$ ls dir1 >dir1-files.txt

# パターン2: 標準出力と標準エラー出力をそれぞれ指定する
# 標準出力を dir1-files.txtに、標準エラー出力を dir1-err.txt にする
$ ls dir1 >dir1-files.txt 2>dir1-err.txt

# パターン3: 標準出力と標準エラー出力の出力先を同じにする
# 例: 標準出力をファイルにし、標準エラー出力を標準出力と同じにする。2>&1 がポイント
$ ls dir1 >dir1-files.txt 2>&1

# パターン4: 出力を捨てる /dev/null がポイント
$ ls dir1 >/dev/null 2>&1

パターン4で使用した「/dev/null」 は Mac OS X やLinux、FreeBSDといったUNIX系のOSで使える特殊なファイルです。このファイルに向かって書き出したデータは、そのまま捨てられます。このように出力先を切り替えることをリダイレクトと言います。

パイプライン処理を覚えよう!

次に、標準入力(stdin)についておさらいしましょう。標準入力はその名の通り、プログラム(プロセス)への入力(INPUT)になるものです。標準入力は、キーボードからの入力や、ファイルを使用できるほか、別のプログラムの標準出力(stdout)を繋ぐことができます。これにより、複数のプログラムを組み合わせたパイプライン処理を実現しています。

まずは簡単な例から始めましょう!lsコマンドの標準出力をheadコマンドの標準入力と繋いでみましょう。headコマンドは先頭から指定された部分までを出力するコマンドです。

# 例: dir1 ディレクトリを lsした結果が以下の場合
$ ls dir1/
a.txt   b.txt   c.txt


# head コマンドで上から1つのみ表示する
$ ls dir1/ | head -1
a.txt

このように、「|」記号を使用することで、lsコマンドの標準出力とheadコマンドの標準入力を繋いだパイプライン処理をしています。さらに、複数のコマンドを続けて繋ぐこともできます。

$ ls dir1/ | head -2 | tail -1
b.txt

テキストファイルやログファイルの特定の部分を表示したり、絞り込んだりといった用途から、コマンドの出力から欲しい部分を抜き出すなど可能性は無限大です。

パイプライン処理に使えるコマンドはたくさんあります。まずは以下の基本コマンドを練習で一通り使ってみるといいでしょう。

コマンド説明
head ファイルの先頭から指定された行数を表示する
tail ファイルの末尾から指定された行数を表示する
grep 指定されたパターンにマッチする行を表示する
sort 行をソートする
uniq 連続する同じ内容の行をフィルタする
tr 指定された文字の置換、削除
cut 指定された文字で分割し、特定のフィールドを表示する
wc 単語数、行数、文字数、バイト数をカウントする

コマンドを組み立てて実行しよう!

パイプライン処理で絞り込みや置換をした結果、それらを使ってコマンドを実行したいという場面がよくあります。例えば、ps コマンドの出力を絞り込んで、該当のプロセスだけをkillしたい、findで見つけたファイルにgrepをかけて特定の文字列があるファイルだけをコピーしたいなどです。このようなケースでは、sedやawkといったコマンドを使うと便利なワンライナーを書くことができる場合が多々あります。

# find コマンドで dir1 ディレクトリ以下の .txt ファイルを表示
$ find ./dir1 -type f -name "*.txt"
./dir1/a.txt
./dir1/b.txt
./dir1/c.txt

# さらに見つかったファイルにgrepをかけたい。grepコマンドの引数として渡したい。
# sed を使って先頭に grep コマンドを挿入すれば、実行したいコマンドができる。
$ find ./dir1 -type f -name "*.txt" | sed 's/^/grep -H ".1" /'
grep -H ".1" ./dir1/a.txt
grep -H ".1" ./dir1/b.txt
grep -H ".1" ./dir1/c.txt

# 最後に実行する
$ find ./dir1 -type f -name "*.txt" | sed 's/^/grep -H ".1" /' | sh
./dir1/a.txt:a1
./dir1/b.txt:b1
./dir1/c.txt:c1

このように、目的のコマンドを実行するための文字列に加工して、最後にshコマンドでまとめて実行するというテクニックは簡単で覚えやすいのでオススメです。

sedやawkは、このようなケースで活躍するコマンドですが、奥が深く書籍になるほどですから、ここでは紹介しきれません。しかし、覚えておけばとても役に立つコマンドです。ぜひ覚えておきましょう。

xargsを覚えよう!

xargsコマンドは、まさにパイプライン処理のための便利コマンドです。xargsは、標準入力からスペース、タブ、改行文字といったもので区切られた文字を受け取り、それらを引数として別のコマンドを実行するユーティリティです。xargsを使用すれば、先ほど紹介したfindの後にgrepするパターンも以下のように簡潔に書くことができます。

$ find ./dir1 -type f -name "*.txt" | xargs grep -H ".1"
./dir1/a.txt:a1
./dir1/b.txt:b1
./dir1/c.txt:c1

では、簡単な例を見ながらxargsがどのようなコマンドなのか理解していきましょう。

$ ls dir1/*.txt | xargs head -2
==> dir1/a.txt <==
a1
a2

==> dir1/b.txt <==
b1
b2

==> dir1/c.txt <==
c1
c2

上記は、「テキストファイルのリストを標準入力で受け取り、それらを引数として head -2 コマンド(先頭の2行を表示するコマンド)を実行する」という例になります。これは、以下のコマンドを実行したのと同じになります。

# head コマンドに3つのファイルを指定したものと同じ
$ head -2 dir1/a.txt dir1/b.txt dir1/c.txt
==> dir1/a.txt <==
a1
a2

==> dir1/b.txt <==
b1
b2

==> dir1/c.txt <==
c1
c2

head コマンドが3回実行されるわけではない点に注意しましょう。引数1つにつき1つずつコマンドを実行したい場合や、2個ずつにしたい場合などは、「-n」オプションを使って制御することができます。

# 1つずつ実行したい場合. 出力結果がそれぞれのコマンドの実行結果になっているので出力が少し異なる.
$ ls dir1/*.txt | xargs -n 1 head -2
a1
a2
b1
b2
c1
c2

# 2つずつ実行にした場合。対象が3個なので、2個をまとめて実行した後に、1つだけ実行されているので出力が異なる.
$ ls dir1/*.txt | xargs -n 2 head -2
==> dir1/a.txt <==
a1
a2

==> dir1/b.txt <==
b1
b2
c1
c2

コマンドによっては、複数の引数を指定した場合も個別に実行した場合も同じなので注意しましょう。xargsコマンドを使うことでパイプライン処理での加工、絞り込みした集合に対してコマンドを実行、ということができるようになりました。

繰り返しを1行で書こう!

ワンライナーでは、パイプライン処理以外にもforやwhileを1行で書いたり、連番を作る方法も知っておくと便利です。

# seq コマンドとforループを組み合わせる例
$ for i in `seq 1 3`; do echo "Hello, $i"; done
Hello, 1
Hello, 2
Hello, 3

# bashであれば同じことを以下のように記述できる
$ for i in {1..3}; do echo "Hello, $i"; done

# while でループ。無限ループなので、Ctrl-cで止めましょう。
$ while true; do date; sleep 1; done
2016年 11月19日 土曜日 13時47分34秒 JST
2016年 11月19日 土曜日 13時47分35秒 JST
2016年 11月19日 土曜日 13時47分36秒 JST

# ワイルドカードを使うこともできます
$ for i in dir1/*.txt; do echo $i; done
dir1/a.txt
dir1/b.txt
dir1/c.txt

これを起点にパイプライン処理に繋ぐことも可能です。

# find/grepの例を、findではなく forループ を使う
$ for i in dir1/*.txt; do echo $i; done | xargs grep -H ".1"
dir1/a.txt:a1
dir1/b.txt:b1
dir1/c.txt:c1

# 無限ループさせながら、grep で絞る。該当の行が出力されたタイミングで画面に表示される
# Ctrl-c で止めましょう
$ while true; do date; sleep 1; done | grep 10
2016年 11月19日 土曜日 13時50分10秒 JST

ループはちょっとした作業のワンライナーを書く際の起点として使いやすいので、覚えておくと良いでしょう。

まとめ

あれ?なんか違うなー。

どうしたんじゃ。

いやワンライナーでどうにかこれをやりたくて。

まふむふむ。なるほどな。それはパイプライン処理しなくても、そのコマンドのオプション指定だけで大丈夫じゃ。

えーそういうパターンもあるんスかー。早く言ってくださいよー。

お、おう。コマンドのオプションを全部覚えるのは難しいから、manコマンドを使って調べるのがオススメじゃ。

ワンライナーを覚えたてのタミ夫くん、どうやらなんでもかんでもワンライナーで書きたくて仕方がないようです。シェルスキー先生の言う通り、コマンドで予め用意されているオプションで解決できる場合もあるので、まずはmanコマンドで調べてみることも忘れないようにしましょう。

次回はbashのカスタマイズについて紹介する予定です。お楽しみに!

原稿: 株式会社ビズリーチ ターミナル部部長 タナカトモフミ
株式会社ビズリーチ所属。ScalaコードをVimで書く日々をおくる。たまにうっかりIntellij IDEAに浮気してしまう。社内のVim部とEmacs部を和解させ、ターミナル部に統合することに成功したが、社内はEclipse, Intellij IDEA, Sublime Text, Atomが主流のため、戦いは続く。
https://twitter.com/tanacasino

この記事はどうでしたか?

おすすめの記事

キャリアを考える

BACK TO TOP ∧

FOLLOW