開眼シェルスクリプト2013年6月号

Pocket
LINEで送る

出典: 技術評論社SoftwareDesign

18. 開眼シェルスクリプト 第18回サーバを股にかける

 皆様、いい季節いかがお過ごしでしょうか。
この原稿を書いているのが3月頭ですので、筆者は現在、
杉からの花粉ハラスメントを受けている最中です。

 そんな時差を利用した小話はいいとして、
今回のテーマは「サーバを股にかける」です。
時をかける少女じゃなくて、
サーバをかけるオッサンになろうということで、
いろいろネタを準備しました。

 字しか書けない端末のよいところは、一つの端末に字を書くだけで
あっちこっちのコンピュータを気軽に使う事ができることです。
vncやリモートデスクトップではそうはいかず、
あっちこっちの画面を覗いているうちに疲れてきます。
データを移すのにも一苦労です。
やりたいことに対して情報量が多いことは、
決してよいことではありません。

 そういえば、ガンカーズのUNIX哲学には、
主要な9か条の他に、二軍の10か条がありますが、
その中に、

「90パーセントの解決を模索せよ。」

というのがあります。
プログラムを書いたり、仕事をしたりすると、
本筋でない雑事が気になるものですが、
それにはある程度目をつぶれということを言っています。

 これは、時短の発想であるとも解釈できます。
私の仕事の場合は、端末とウェブブラウザがあれば、
仕事の90%は片付いてしまいます。
そのうちの端末を使う数十%の仕事は字だけ見てさっさと終わってしまうので、
メニューをマウスでクリクリしている人よりは、
例え残りの10%で困ったとしても、
トータルでも時間を得しているはずです。

 ちなみに残りの10%のうち、9%がお絵描きで、あと1%が得体の知れない何かで、
これはさすがに端末ではやりません。合った道具を使います。

 これはもう、余談も余談ですが、世の中には10%が気になりすぎて、
脳みそに刷り込まれて、その10%のことを「最重要事項」だ、
と思い込んでバランスの悪い主張をする人々がいます。
また、それに反論ばかりしているうちに脳みそに刷り込まれ、
やはりそれが「最重要事項」になってしまう犠牲者も出現します。

 90%ルールは、その負のループから我々を救ってくださるのです。
あなかしこあなかしこ。

18.1. 環境等

18.1.1. 使用する環境

 今回は多種多様です。リスト1で、簡単に説明します。
なるべく皆様を混乱させないように注意しながら話を進めます。

  • リスト1: 登場するマシン・サーバ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//1. 筆者のMacBook Air(uedamac)
uedamac:~ ueda$ uname -a
Darwin uedamac.local 12.2.1 Darwin Kernel Version 12.2.1: (略)
uedamac:~ ueda$ bash --version
GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin12)
Copyright (C) 2007 Free Software Foundation, Inc.

//2. VPS上のFreeBSD(bsd)
bsd /home/ueda$ uname -a
FreeBSD bsd.hoge.hoge 9.0-RELEASE FreeBSD 9.0-RELEASE #0: (略)

//3. VPS上のUSP友の会サーバ(tomonokai)
[ueda@tomonokai ~]$ cat /etc/redhat-release
CentOS release 6.3 (Final)

//4. ビジネス版Tukubaiが使えるサーバ(usp)
[ueda@usp ~]$ cat /etc/redhat-release
CentOS release 5.9 (Final)

友の会のサーバは、 www.usptomo.com というホスト名でDNSに登録されています。
bsd は秘密のサーバですが、
bsd.hoge.hoge で登録されているとしておきます。

18.1.2. 鍵認証の設定についてちょっと

 シェルスクリプトという範囲の話ではありませんが、
今回は scp コマンドや ssh コマンドを多用しますので、
ssh接続の鍵認証の方法について触れておきます。
(脚注:パスワードを入れなくてもログインできるアレのことです。念のため。)
いや、手順については「ssh 鍵認証」
などと検索すれば方法が書いてあるのでここでは説明しません。
が、鍵認証はクライアントとサーバ、公開鍵と秘密鍵が登場して、
どっちで何をするのか慣れるまで非常に混乱するので、
そんな人のために、次の一文を書いておきます。

「ssh接続される方(サーバ)は危険に晒されるので、
接続してくる奴をリスト化して管理しなければならない。」

このリストに登録されるのは、「接続してくる奴」の公開鍵です。

 ですから、クライアント側では秘密鍵と公開鍵を準備し、
公開鍵をサーバに登録してもらうという手続きを行うことになります。

 これを頭に入れて、設定をお願い致します。

18.2. 通信あれこれ

18.2.1. bashの /dev/tcp/

 さて本題に入っていきましょう。まずは bash の機能を使ってみます。
どのバージョンからかは調べていませんが、少なくとも3以降のbashには、
リスト2の方法で、
特定のホストの特定のポートに file の内容を送信する機能があります。
リダイレクトの左側は、 echo でも grep でもなんでもかまいません。

  • リスト2: bashで通信するときの書式
$ cat file > /dev/tcp/<ホスト名>/<ポート番号>

 早速使ってみましょう。と言ってもこの機能単独だと、
いたずら程度くらいしか思いつきませんので、
リスト3のようにUSP友の会のサーバを餌食にしてみました。
皆さんもなにかメッセージを残してもらって構いませんが、
あまり連発しないでください。

  • リスト3: apacheにいたずらする
1
2
3
4
5
#macからUSP友の会のサーバにちょっかいを出す
uedamac:~ ueda$ echo aho > /dev/tcp/www.usptomo.com/80
#USP友の会のサーバのログに記録が残る
[root@tomonokai ~]# tail -n 1 /var/log/httpd/access_log
123.234.aa.bb - - [03/Mar/2013:00:58:21 +0900] "aho" 301 231 "-" "-"
 リスト4のように調べると分かるように、
/dev/tcp/ はシステム側にあるわけではなく、

bashが擬似的にファイルに見せかけているようです。

  • リスト4: /dev/tcp は存在しない
1
2
uedamac:~ ueda$ ls /dev/tcp
ls: /dev/tcp: No such file or directory

/dev/udp/ も準備されていますので、
UDPを使うサービスにもちょっかいが出せます。

18.2.2. netcatを使う

 bash の /dev/tcp/ を使うと、基本、
データをポートに投げつけることしかできません。
投げつけたデータの受け手として、
Netcat を紹介します。

 大抵の環境には、 nc というコマンドで Netcat が使えます。
bashからテキストを投げて、 nc で受けてみましょう。
もちろん文字は暗号化されずにそのまま送られるので、
秘密のものは送らないようにしましょう。
この実験をするには、受信側で使うポートが開いている必要があります。

  • リスト5: 10000番ポートで通信する
1
2
3
4
5
6
7
8
9
//先に nc で受信側のポートを開いておく
//ncが立ち上がったままになる
[ueda@tomonokai ~]$ nc -l 10000 > hoge

//データを投げる
uedamac:~ ueda$ echo ひえええええ > /dev/tcp/www.usptomo.com/10000
//ncが終わって、hogeの中に文字列が
[ueda@tomonokai ~]$ cat hoge
ひえええええ

 リスト6のようにシェルスクリプトにして実行すると、
ちょっとしたサービスのように振る舞います。

  • リスト6: whileループで何回も受信
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[ueda@tomonokai ~]$ cat file.sh
#!/bin/bash

mkdir -p ./tmp/

n=1
while nc -l 10000 > ./tmp/$n.txt ; do
        n=$1) n + 1 
done

//立ち上げる
[ueda@tomonokai ~]$ ./file.sh
//送る
uedamac:~ ueda$ echo ひえええええ > /dev/tcp/www.usptomo.com/10000
uedamac:~ ueda$ echo どひぇー > /dev/tcp/www.usptomo.com/10000
uedamac:~ ueda$ echo NOOO! > /dev/tcp/www.usptomo.com/10000
//Ctrl+cしてファイルができていることを確認
[ueda@tomonokai ~]$ ./file.sh
^C
[ueda@tomonokai ~]$ head ./tmp/{1,2,3}.txt
==> ./tmp/1.txt <==
ひえええええ

==> ./tmp/2.txt <==
どひぇー

==> ./tmp/3.txt <==
NOOO!

 Netcat は Wikipedia に
「ネットワークを扱う万能ツールとして知られる。」
とあるように、単にポートをリッスンするだけでなく、
データの送信側になったり、
邪悪な組織のポートスキャナになったりします。

18.3. ファイルを転送する

 さて、いつも大きなデータを扱っている人は、
サーバ間で何十GBものファイルをコピーしなければいけないことがあります。
このようなときはリスト7のように、普通は scp を使うことでしょう。
リスト中の -P 11111 は、USP友の会のサーバが
でフォルトの 22 番でなく 11111 番でssh接続を受け付けているため、
必要となります(脚注: 実際には別のポートを使っています)。

  • リスト7: 普通に scp でファイルをコピー
1
2
3
4
5
6
bsd /home/ueda$ time scp -P 11111 TESTDATA www.usptomo.com:~/
TESTDATA                           100% 4047MB   4.0MB/s   16:48

real    16m49.064s
user    3m2.550s
sys     13m38.727s

 実は、 scp には圧縮してデータを送る -C
というオプションがあります。リスト8のように使います。
ただ、圧縮はCPUを酷使するので効果のある場合は限られます。
1回しか試していないのでかかった時間は参考程度にしかなりませんが、
user時間で圧縮にかなり時間を使っていることが分かります。

  • リスト8: 圧縮送信したらかえって遅くなった
1
2
3
4
5
6
bsd /home/ueda$ time scp -C -P 11111 TESTDATA www.usptomo.com:~/
TESTDATA                           100% 4047MB   2.6MB/s   26:16

real    26m16.678s
user    20m33.275s
sys     6m55.593s

 実は、暗号化しなくてよいならリスト9のように転送する方が速いことがあります。
user時間はほとんどゼロです。

  • リスト9: ポートをダイレクトに使ってファイル転送
1
2
3
4
5
6
7
8
//受信側で待ち受け
[ueda@tomonokai ~]$ nc -l 10000 > TESTDATA
//送信
bsd /home/ueda$ time cat TESTDATA > /dev/tcp/www.usptomo.com/10000

real    12m3.584s
user    0m0.000s
sys     10m22.737s

 CPUが速くて通信速度が遅いときは、
scp-C オプションが有効になりますが、
上の nc の方法で gzipbzip2 などを挟んで送った方が、
速いこともあります。速いこともある、というより、
本来圧縮は scp の仕事ではないはずですし、
圧縮の方式も自由に選べるべきなので、
面倒ですがこっちの方がUNIX的です。
ただまあ、そういうチューニングは本当に困ったときだけにしておきましょう。

 一つの巨大なファイルを複数のサーバにコピーしたい場合は、
リスト10のようなことを試みてもよいでしょう。
頭がこんがらがるかもしれませんが、
ちゃんと書けばちゃんと動きます。

  • リスト10: 一度の転送で二つのサーバにファイルをコピー
1
2
3
4
5
6
7
//友の会サーバで10000番ポートからファイルへリダイレクト
[ueda@tomonokai ~]$ nc -l 10000 > TESTDATA
//bsdサーバで9999番ポートからの出力をteeでファイルにためながら
//友の会サーバにリダイレクト
bsd /home/ueda$ nc -l 9999 | tee TESTDATA > /dev/tcp/www.usptomo.com/10000
//手元のMacからbsdサーバにデータを投げる
uedamac:~ ueda$ cat TESTDATA > /dev/tcp/bsd.hoge.hoge/9999

 この方法のようにサーバを数珠つなぎにすると、
何台ものサーバに同時にコピーができます。
ただし、サーバが同じハブにぶらさがっていると、
ハブにトラフィックが集中します。

 あともう一個だけ紹介します。
sshコマンドを使ってもファイルを転送できます。
この例で、sshコマンドが標準入力を受け付けることが分かります。

  • リスト11: ssh コマンドの標準入力を使う
1
2
3
4
5
bsd /home/ueda$ time cat TESTDATA | ssh -p 11111 www.usptomo.com 'cat > TESTDATA'

real    16m22.054s
user    2m46.163s
sys     12m44.448s

18.4. リモートマシンで計算する

 さて、もっと便利に使ってみましょう。
このままではコピーだけで今回が終わってしまいます。
(それはそれで面白いかもしれませんが・・・)

 例えば、今使っているマシンが遅い場合や使いたいコマンド等が
インストールされていない状況を考えます。
私の場合は、USP研究所のビジネス用 Tukubai
コマンドを使いたい場合や、
あるマシンのTeXの環境を使いたいという場合がこれに相当します。

 一例として、手元にあるファイルをリモートのサーバで
ソートして戻してもらうことを考えましょう。

 まずリスト12に、普通のシェルスクリプトを示します。
これは、あるリモートのサーバに scp でファイルを送り込み、
ソートした後にファイルを戻すという処理です。
Macの sort
コマンドで1千万行のソートなんかやっちゃったらいつ終わるのか読めないので、
これくらいのことは行う価値はあります。

  • リスト12: 「べたな」リモートサーバの使い方
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//このデータ(1千万行)を左端の数字でソートしたい
uedamac:~ ueda$ head -n 2 TESTDATA10M
2377 高知県 -9,987,759 2001年1月5日
2910 鹿児島県 5,689,492 1992年5月6日
uedamac:~ ueda$ cat sort.sh
#!/bin/bash -xv

scp -P 11111 ./TESTDATA10M usp.usp-lab.com:~/
//msortは、マルチスレッドの高速ソートコマンド
ssh -p 11111 usp.usp-lab.com "msort -p 8 key=1 ~/TESTDATA10M > ~/ueda.tmp"
scp -P 11111 usp.usp-lab.com:~/ueda.tmp ./TESTDATA10M.sort

//手元のMacで実行
uedamac:~ ueda$ time ./sort.sh

real    4m1.717s
user    0m13.969s
sys     0m10.090s
//結果が得られた
uedamac:~ ueda$ head -n 2 TESTDATA10M.sort
0000 岩手県 5,630,892 2006年5月26日
0000 新潟県 1,367,399 1998年8月22日

 こういった通信ばっかりのシェルスクリプトを書いた人は
そんなにいないと思いますが、
シェルスクリプトなど所詮、人の操作のメモ書きですので、
いつも scp, ssh を使っていれば理解できるでしょう。

 ところでこのシェルスクリプトでは、
中間ファイルがリモートのサーバにできてしまっていますが、
これを避けるにはどうすればよいでしょうか。
こういう中間ファイルは、計算を Ctrl+c
などで中断した場合にリモートのサーバにゴミを残すことになります。
処理によっては、次に計算したときに悪さをすることもあります。

 これを解決するには「シェル芸」です。
「開眼シェルスクリプト」という名前で連載をしていますが、

不要なシェルスクリプトと中間ファイルはゴミ

です。こんなもん、ワンライナーで十分です。
リスト13に示します。

  • リスト13: リモートサーバを使うワンライナー
1
2
3
4
5
uedamac:~ ueda$ time cat TESTDATA10M | ssh -p 10022 usp.usp-lab.com 'cat | msort -p 8 key=1' > TESTDATA10M.sort3

real    5m0.033s
user    0m14.077s
sys     0m9.415s

これで、sshでソートした出力は、(太字)手元のMacの標準出力から出てきます。
ssh が(リモートでなく)
手元のマシンの標準入出力に字を出し入れしてくれることは、
ssh コマンドが手元のマシンで動いているので当然と言えば当然ですが、
よくよく考えるととても便利なことです。
ワンライナーとしては難解かもしれませんが、
リモートとローカルがシームレスにつながっています。
パイプラインなので、ストレージを使う事もありません。

 ちょっとやりすぎですが、
筆者の自宅とサーバの間の通信速度がそんなに速くないので、
gzip, gunzip を使ってリスト14のようにチューンしたらさらに時短できました。

  • リスト14: 圧縮を挟み込んだワンライナー
1
2
3
4
5
uedamac:~ ueda$ time gzip < TESTDATA10M | ssh -p 10022 usp.usp-lab.com 'gunzip | msort -p 8 key=1 | gzip' | gunzip > TESTDATA10M.sort3

real    1m10.669s
user    0m42.874s
sys     0m2.806s

18.5. 終わりに

 今回も前回に引き続き作り物をさぼって、
サーバを股にかけてデータをやりとりし、処理する方法について書きました。
bashの通信機能や、 ssh, scp, nc
などのコマンドについてちょっとした使い方を紹介しました。

 マシンを複数台使うと頭の中が混乱しがちです。
その点、 ssh をパイプにつなぐことを覚えると、
あまり頭を悩ませずに複数のマシンを使いこなすことができます。
パイプラインは一方通行で順番にサーバをつなげていくだけなので、
頭の中でいろんなマシンの絵を同時に思い浮かべる負荷が不要です。
マシン間の通信速度はまだ向上していくでしょうから、
これからは使う人が増えるかもしれません。

 次回からは、とうとう禁断のお題をやる覚悟ができました。
「シェルスクリプトでCGI」というお題で作り物をしてみます。

Pocket
LINEで送る

脚注   [ + ]

1. n + 1