2014年8月6日水曜日

[Swift] コマンドライン(Command Line)プログラムとして使う [1]

そう言えばコマンドラインとして使える話があったので、今更試してみたメモ。

コマンドラインツール群はコンパイラ等と同様にアプリケーションパッケージ内にある。
通常は xcode-select -print-path で表示されている Xcode 内のツールが使われるが、現状ベータなので xcode-select -switch で変更せずに直接パスを叩く方針にする。

REPL(Read-Eval-Print-Loop)を使ってみる


まずは REPL を立ち上げてみる。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
Welcome to Swift!  Type :help for assistance.
  1>  
または以下でも同じものが立ち上がる。
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/lldb --repl
% /Applications/Xcode6-Beta5.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
終了するには Ctrl-D を押すか、REPL に :q (または :quit) とタイプする。

一通りのことは出来るようだが、出来ることに若干の違いがあるような気がする。


ファイルを実行する


コマンドラインなので、REPL よりもファイルから実行できた方が良い。
以下のようなフツーの swift ファイルを書いて実行することが出来た。
func hello(name: String) -> String {
    return "Hello, \(name)"
}

println(hello("Swift"))
% /Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift command-1.swift
Hello, Swift
古いサンプルでは xcrun swift -i の指定があるものも残っているが、Beta5 から -i オプションは必要なくなっている。

通常のアプリケーションコードと違うのは、コマンドラインや REPL の場合は Playground と同様に order-dependent なので定義順にしか解釈されないこと。
アプリケーションコードであっても main.swift のようにトップレベルに実行可能コードが書けるものは order-dependent となるのだが、この辺の話は Apple Swift Blog の Files and Initialization に書いてある。

当たり前だが REPL や Playground とは違い、コマンドラインでは標準出力に出力しなければ結果は表示できない。


よりコマンドラインらしく、shebang を書いて実行権限をつければ単体で実行可能となる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift

func hello(name: String) -> String {
    return "Hello, \(name)"
}

println(hello("Swift"))
% chmod +x command-1.swift
% ./command-1.swift
Hello, Swift
まるでスクリプトのように実行できる。


引数を取る


さて、コマンドラインとして使うなら引数が取れないと意味が無い。

Foundation を使えば Cocoa の流儀で取得できる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Foundation

let  keyName = "keyName"
let  args = NSUserDefaults.standardUserDefaults()

println("\(keyName): \(args.objectForKey(keyName))")
% ./command-2.swift -keyName value
keyName: value

% ./command-2.swift -keyName '("a", "b")'
keyName: (
    a,
    b
)

% ./command-2.swift -keyName '{ "k1"=1; "k2"=2; }'
keyName: {
    k1 = 1;
    k2 = 2;
}
Array や Dictionary の指定が Swift-Style ではなく ASCII Property List Style になってしまうのは NSUserDefaults の都合上、致し方ない。

特定の引数を取得するのは NSUserDefaults で良いが、引数一覧を取得する場合は NSProcessInfo を使う。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Foundation

for arg in NSProcessInfo.processInfo().arguments {
    println("\(arg)")
}
% ./command-3.swift a b c
/Applications/Xcode6-Beta5.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
-frontend
-interpret
./command-3.swift
-enable-objc-attr-requires-objc-module
-target
x86_64-apple-darwin13.3.0
-target-cpu
core2
-module-name
main
-sdk
/Applications/Xcode6-Beta5.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk
-color-diagnostics
--
a
b
c
実はたくさんの引数がついていた。

Foundation を使わずに Pure Swift で引数を取ることが出来ないか調べたところ、practicalswift.com がヒットした。どうやら Process というのが定義されているらしい。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift

for arg in Process.arguments {
    println("\(arg)")
}
% ./command-4.swift a b c
./command-4.swift
a
b
c
これなら Pure Swift だし、NSProcessInfo を使うよりも実用的な引数一覧になった。

しかし、Process の実態には Xcode 上で定義にジャンプすることが出来ず、どこにどのように定義されているものかわからない。

この定義をどうやって見つけたのだろう?


他のモジュールを使う


先の例までで Foundation を使っていたが、当然 AppKit も使えるだろうということで、GUI テストとしてアラートを表示してみる。
#!/Applications/Xcode6-Beta5.app/Contents/Developer/usr/bin/xcrun swift
import Cocoa

var  alert = NSAlert()
alert.messageText = "Hello, Swift"
alert.runModal()
% ./command-5.swift
問題なく表示できた。これは素晴らしい。


これなら GUI が必要な時はシェルスクリプト代わりに swift を使うのもアリだ。


では自作のモジュールを取り込むときは?
シェルスクリプトで使い回し可能コードを source して取り込むように、swift コマンドラインで使い回し可能コードを import で取り込めないものか。

これを行う為には、使い回す swift コードをモジュール化する必要がある。


続きは次回


0 件のコメント:

コメントを投稿