S-JIS[2011-10-01/2012-09-02] 変更履歴
ScalaのREPL上でHadoopのHDFSを操作してみた。
|
PigでHadoopのHDFS上のファイルを操作していて、ファイル名を扱うのにローカル変数が使えなかったりして不便に思った。
で、ScalaのREPLも対話型ツールなので、この上でHadoopのHDFSを操作することが出来るんじゃないか?と思って試してみた。
REPL上でHadoopのクラスをインポートすれば、そのクラスのメソッドを呼び出すだけなので、何でも出来るはず。
REPL(scalaコマンド)起動時にクラスパスを指定できるので、Hadoopのライブラリーを指定すればいけるはず。
しかしHadoopはコンパイルするだけなら2つくらいのjarファイルを指定すればいいが、実行するとなるとたくさんjarファイルを指定しないといけなくなる。
しかもWindowsではCygwinを使って起動することになるので、そのパスをどうするか…なんて考えたくもない(爆)
一方、Hadoopの起動はhadoopシェルで、中はjavaコマンドを起動しているだけなので、こちらもクラスパスを追加できる。
しかも起動するプログラム(クラス)を指定することも出来る。
Scalaのライブラリー(jarファイル)をクラスパスに追加し、REPLのメインクラスを実行すれば動くんじゃなかろうか。
hadoopシェルの場合、環境変数HADOOP_CLASSPATHにクラスパスを入れておくと、実行時に参照してくれる。
そこで、ScalaのライブラリーをHADOOP_CLASSPATHに入れる。
export HADOOP_CLASSPATH="$SCALA_HOME/lib/*"
JDK1.6では、クラスパスのファイル名部分に「*」があると その位置の全jarファイルを読み込んでくれるので、自分で展開する必要は無い。
ただし、Cygwinの場合はSCALA_HOMEはWindowsのパス形式だろうから、UNIX形式に直しておく必要がある。
export HADOOP_CLASSPATH=$(cygpath -u "$SCALA_HOME/lib/*")
ちなみに、「*」入りのパスを確認しようと思ってechoを使うと、シェルの機能によって各ファイル(スペース区切り)に展開されてしまう。環境変数の中(値)は「*」のままなので、勘違いしないように。ってゆーかずっと「上手くいかないなー」って悩んでたよorz
ダブルクォーテーションで囲むと「*」のまま表示される。$ echo $HADOOP_CLASSPATH /cygdrive/c/scala/scala-2.9.1.final/lib/jline.jar /cygdrive/c/scala/scala-2.9.1.〜 $ echo "$HADOOP_CLASSPATH" /cygdrive/c/scala/scala-2.9.1.final/lib/*
ScalaのREPLは、scala.tools.nsc.MainGenericRunnerというオブジェクトから実行される。
したがって、hadoopシェルの引数にそれを書けばいい。
$ hadoop scala.tools.nsc.MainGenericRunner
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Client VM, Java 1.6.0_27).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
Failed to initialize compiler: object scala not found.
** Note that as of 2.8 scala does not assume use of the java classpath.
** For the old behavior pass -usejavacp to scala, or if using a Settings
** object programatically, settings.usejavacp.value = true.
と思ったら、なんかエラーが出た。
どうも、通常のscalaコマンドの場合、Scalaのライブラリー(jarファイル)はブートストラップのような形で読み込んでいるが、
hadoopシェルでは通常のjarファイルと同じように読み込まれているので、問題があるらしい。
エラーメッセージに書かれている通り、settings.usejavacp.valueというシステムプロパティーにtrueを指定すれば解決する。
Hadoopの場合は環境変数HADOOP_OPTSにオプションを指定することが出来る。
$ export HADOOP_OPTS=-Dscala.usejavacp=true $ hadoop scala.tools.nsc.MainGenericRunner Welcome to Scala version 2.9.1.final (Java HotSpot(TM) Client VM, Java 1.6.0_27). Type in expressions to have them evaluated. Type :help for more information.
scala> import org.apache.hadoop.conf._ import org.apache.hadoop.conf._ scala> import org.apache.hadoop.fs._ import org.apache.hadoop.fs._ scala> val conf = new Configuration conf: org.apache.hadoop.conf.Configuration = Configuration: core-default.xml, core-site.xml scala> val fs = FileSystem.getLocal(conf) fs: org.apache.hadoop.fs.LocalFileSystem = org.apache.hadoop.fs.LocalFileSystem@1a15597 scala> fs.listStatus(new Path("/temp")).map(_.getPath) res1: Array[org.apache.hadoop.fs.Path] = Array(file:/temp/asakusa, file:/temp/scala)
上手くいった!^^
scalaコマンドに渡す引数(-cp等)は、scala.tools.nsc.MainGenericRunnerの引数として指定する。
$ hadoop scala.tools.nsc.MainGenericRunner -cp mylibrary.jar
scalaコマンドでは、「-i」オプションを付けると、REPL起動時にスクリプトファイルを読み込んで実行してくれる。
インポート文なんかを書いたファイルを用意しておけば便利だろう。
$ scala -i スクリプト
が、Scala2.9.1では「-i」オプションにバグがあり、使うと固まる。(SI-4945。Scala2.9.0.1までは大丈夫だったらしい)
こればっかりは直るのを待つしか仕方ない…。
一応、REPLコマンドに「:load
」というのがあって、スクリプトファイルを読み込んで実行できる。ちょっと手間だが、これで我慢。
SI-4945は2012-08-07にFIXされた(1年くらいかかってるよorz)ので、たぶんScala2.10からは大丈夫になると思われる。[2012-09-02]
Scala2.9.1およびScala2.9.2では直っていないが、「-Yrepl-sync」を付ければ実行できるようだ。
$ scala -Yrepl-sync -i スクリプト
さて、REPLを終わるときは「:q
」を入力する。
Cygwinだとすんなり終了できて問題ないが、通常のUNIX(CentOSのGnomeターミナル)では、終了後のキー入力がおかしくなる。(打ったキーが表示されないし、改行できない(見えないだけで、コマンドは実行できる))
こういうとき(コンソールが文字化けしている時など)は、resetコマンドを試すのが常套手段。
今回もresetを実行すると正常に戻った。
ちなみにCygwinにはresetは無いので、実行しようとしたらエラーになる。