コマンドの繋ぎ方
一つひとつのコマンドは単機能*1であっても、コマンド同士を連携させることで、高度な情報に加工できる仕組みって、素晴らしいと思う。
$ cat .bash_history|cut -f1 -d' '|sort|uniq -c|sort -nr|head -n10 114 cat 64 echo 40 ls 28 rm 26 ps 20 growlnotify 20 _echo 19 sleep 17 ls_ 16 du
例えば、上記のコマンドは「最近よく使ったコマンドベスト10」を表示してくれる。(bash用)
結果を見ると、ろくにコマンドを使っていない(使えていない)のがバレバレだけど、.bash_historyというコマンド履歴のファイルから、一瞬にしてベスト10に集計してしまう作業が、たった一行で完結してしまう能力を知った時、俄然、コマンドへの興味が湧いてきた。
| パイプ
- コマンドの処理結果は、通常は画面に表示される。
$ echo ABC ABC
- リダイレクトでファイルを指定すれば、ファイルに書き込まれる。
$ echo ABC >file $ cat file ABC
- パイプで繋ぐことで、次のコマンドに引き渡せる。
$ echo ABC|tr A-Z a-z abc
- trは1文字単位で変換するコマンド。
- echoが渡したABCを受け取って、AからZを、aからzに変換している。(つまり、大文字を小文字に変換している)
- ちなみに、パイプで繋ぐことのできないコマンドもある。
$ echo ABC|echo $ echo 1 + 1|expr expr: syntax error
最近よく使ったコマンドベスト10になるまでの過程を追ってみた
- コマンド履歴ファイルの内容を表示する。
$ cat .bash_history alias cat test2.log cat test.log du test2.log du test2.log|cut -f 1 cd cd documents ...(中略)...
- 次に、スペース区切りで分割して、最初の一列目のみ取り出す。
$ cat .bash_history|cut -f1 -d' ' cat cat du du cd cd ...(中略)...
- さらに、コマンドをアルファベット順に並べ替える。
$ cat .bash_history|cut -f1 -d' '|sort ./ls .ls /bin/ls /bin/ps /bin/ps /bin/ps ...(中略)...
- さらに、同じコマンドをカウントしながら、重複を取り除く。
$ cat .bash_history|cut -f1 -d' '|sort|uniq -c 1 ./ls 1 .ls 1 /bin/ls 11 /bin/ps 1 @ 11 [ ...(中略)...
- カウント数が大きい順に並べ替えて、先頭から10行目までを表示する。(完了)
$ cat .bash_history|cut -f1 -d' '|sort|uniq -c|sort -nr|head -n10 115 cat 64 echo 40 ls 28 rm 26 ps 20 growlnotify 20 _echo 19 sleep 17 ls_ 16 du
改行
- 最もシンプルには、1行に1コマンドを書いて、行末で改行する方法。
- 改行がコマンドと次のコマンドの区切り記号になる。
$ cd ~/desktop $ ls -l total 209888 -rw-r--r-- 1 zari staff 319318 10 10 2010 Quicksilverショートカット1.PNG -rw-r--r-- 1 zari staff 320334 10 10 2010 Quicksilverショートカット2.PNG -rw-r--r--@ 1 zari staff 1929 6 1 17:48 textarea_autofit.html drwxr-xr-x 3 zari staff 102 1 12 19:08 今日.app/ drwxr-xr-x 3 zari staff 102 3 29 2010 前月.app/ drwxr-xr-x@ 3 zari staff 102 1 12 15:53 翌日.app/ drwxr-xr-x 3 zari staff 102 3 29 2010 翌月.app/ drwxr-xr-x 3 zari staff 102 1 12 17:15 年月日.app/ drwxr-xr-x 52 zari staff 1768 12 18 2010 整理整頓/ ...(中略)...
改行をエスケープ
- パイプで繋がったあまりにも長い1行は非常に読みにくい。
- 改行を\によってエスケープすることで、次の行も連続する1行と見なされる。
$ cat .bash_history\ > |cut -f1 -d' '\ > |sort\ > |uniq -c\ > |sort -nr\ > |head -n10 113 cat 66 echo 40 ls 28 rm 26 ps 20 growlnotify 20 _echo 19 sleep 17 ls_ 15 wc
;セミコロン
- ; は、改行と同等の意味を持つ。
- ; で区切れば、1行に幾つでもコマンドを並べられる。
$ sleep 10; growlnotify -m "完了!"
- 上記コマンドを実行すると、10秒後にgrowlで「完了!」と通知される。
- 但し、完了と通知されるまで、コマンドは実行中となる。
&アンパサンド
- & は、直前のコマンドをバックグラウンドで実行する。
- と同時に、コマンド同士の区切り記号としての役割もある。
$ sleep 10 & growlnotify -m "完了!"
- 上記コマンドを実行すると、すぐにgrowlで「完了!」と通知される。
- sleep 10 はバックグラウンドで実行され、次のgrowlnotify -m "完了!" がすぐに実行された結果である。
()括弧
- ところで、上記のコマンドは本意でなくて、本当は以下のように動作して欲しい。
- ターミナル上ではすぐに次のコマンド入力待ちになって欲しい。
- 10秒後にgrowlで「完了!」と通知される。
- 一連のコマンドをまとめてバックグラウンドで実行するには、()あるいは{}括弧で囲う。
$ (sleep 10; growlnotify -m "完了!") & # あるいは... { sleep 10; growlnotify -m '完了'; } &
- 上記コマンドを実行すると、ターミナル上ではすぐに次のコマンド入力待ちになる。
- 10秒後にgrowlで「完了!」と通知される。
- ()と{}の違いは...
$ (ps a) & [1] 28416 PID TT STAT TIME COMMAND 25278 s000 Ss 0:00.02 login -pf zari 25279 s000 S+ 0:00.44 -bash 28416 s000 R 0:00.00 ps a $ { ps a; } & [1] 28417 PID TT STAT TIME COMMAND 25278 s000 Ss 0:00.02 login -pf zari 25279 s000 S+ 0:00.44 -bash 28417 s000 S 0:00.00 -bash 28418 s000 R 0:00.00 ps a
- man bashには、()がサブシェル、{}が現状のシェル環境で実行と書いてあるが、実際に試してみると自分の環境ではmanとは逆の仕様のようだ。
&&
コマンド1 && コマンド2
- コマンド1が正常終了した場合のみ、コマンド2が実行される。
- もう少し正確に言えば、コマンド1が終了ステータス0の場合のみ、コマンド2が実行される。
例:
$ cat file && echo "内容を確認してください。"
上記コマンドは...
- fileが存在すれば、その内容を表示して、さらに「内容を確認してください。」と表示する。
- fileが存在しなければ、標準エラー出力のみ表示する。(echoは実行されない。)
||
コマンド1 || コマンド2
- コマンド1がエラー終了した場合のみ、コマンド2が実行される。
- もう少し正確に言えば、コマンド1が終了ステータス0以外の場合のみ、コマンド2が実行される。
例:
$ cat file || echo "ファイルが見つかりません。"
- 上記コマンドは、fileが存在しない時だけ、標準エラー出力を表示して、さらに「ファイルが見つかりません。」と表示する。
- ファイルが存在すれば、その中身を表示する。(echoは実行されない。)
実例
&&と()の違い
- sleepコマンドでエラーが発生することはまず考えられないので、以下のコマンドはどちらも同じ結果をもたらす。
$ (sleep 10; growlnotify -m "完了") & $ sleep 10 && growlnotify -m "完了" &
- 演算子の優先順位(1、2、3の順で優先される。番号内での順位は同じ)
- |
- && ||
- ; &
- &&の優先順位の方が、&より優先されるので、()括弧で囲ったような効果になるのだ。
- ところが、makeコマンドだとエラーが発生する可能性も十分ある。
$ (make; growlnotify -m "完了") & $ make && growlnotify -m "完了" &
- 上段は、エラーが発生しても、しなくても、「完了!」が通知される。
- 下段は、正常終了した時だけ、「完了!」が通知される。(エラーが発生すると通知されない)
&&とifの違い
- &&を活用すると、冗長なif文の代わりになりそうだが、微妙な違いがある。
$ [ -e file ] && echo '存在します。' 存在します。 $ [ -e _file ] && echo '存在します。' $ echo $? 1 $ if [ -e file ]; then echo '存在します。'; fi; 存在します。 $ if [ -e _file ]; then echo '存在します。'; fi; $ echo $? 0
- 存在しない時の終了ステータスが、違ってくるのだ。
- AppleScriptのdo shell scriptからコマンドを実行すると、この違いは大きい。
do shell script "[ -e ~/desktop/file ] && echo '存在します。'"
do shell script "[ -e ~/desktop/_file ] && echo '存在します。'"
- 上段は、ファイルが存在するので、「存在します。」という結果が返ってくる。
- 下段は、ファイルが存在しないと、終了ステータス0以外なので、do shell scriptがエラーになって、実行が中止されてしまう。
- do shell script内では、面倒でもif文を使う必要があるのだ。
do shell scriptをバックグラウンドで実行するには?
- 単純に&を付けてバックグラウンドで実行すれば良さそうだが、以下の書き方では「完了!」が通知されるまでの10秒間、AppleScriptが実行中になってしまう...。
do shell script "(sleep 10; /usr/local/bin/growlnotify -m '完了!') &"
- 正しくは、標準出力・標準エラー出力を破棄した上で、バックグラウンドで実行するのだ。
- 以下の書き方なら、AppleScriptの実行はすぐに終了し、10秒後に「完了!」通知が表示される。
do shell script "(sleep 10; /usr/local/bin/growlnotify -m '完了!') >& /dev/null &"
do shell script "(sleep 10; /usr/local/bin/growlnotify -m '完了!') >/dev/null 2>&1 &"
AppleScript・Automatorとの連携
- AppleScriptでは非常に面倒な処理や、存在しない機能も、コマンドを実行することで簡単に手に入るかもしれない。
例:
--日時をシンプルな書式で取得する
do shell script "date '+%Y-%m-%d %H:%M:%S'"
--スリープから復帰したことを検出する
do shell script "grep 'Wake:' /var/log/system.log"
--AirMacの接続が切れてしまうことを検出する
do shell script "grep 'Broadcast requests failed' /var/log/system.log"
--free_memory.logが1000行以上になったら、ログローテーションする
do shell script "f=\"${HOME}/Documents/free_memory.log\"; if [ `cat \"$f\"|wc -l` -ge 1000 ]; then mv \"$f.1\" \"$f.2\"; mv \"$f\" \"$f.1\"; cat \"$f.1\" | tail -n 100 >\"$f\"; fi;"
- また、AppleScriptやAutomatorは、GUIなOS環境と、CUIなコマンド環境を優しく接続してくれる働きがある。
例;
コマンドを繋ぐ組み合わせは無限大だ!想像力と地味な努力で、便利なツールを作り出せるかもしれない。
*1:決して単機能ではないのだけど