SwiftというとiOS開発(あるいはiPad/Macアプリ)でしか使いませんが、コマンドラインからの実行もできます。
(当たり前っちゃ当たり前ですが)
Swiftをシェルスクリプトみたいに使えないかな? とふと思って、調べてみたら普通に色々できたので、書いてみます。
実行
「script.swift」というSwiftファイルを動かしたいとしたら、下記で実行できます。
$ swift script.swift
コマンドライン引数も指定できます。
$ swift script.swift arg1 arg2 ...
できること
- 標準入出力
- ファイル操作
- WebのAPIを叩く
スクリプトでやらせたいことはだいたいできる気がします。
慣れ親しんだSwiftでツール作成もできるとなれば嬉しいですね。
AtCoderやる人だったら、Playgroundだと標準入力受け取れないので、
コマンドラインで実行すると捗ると思います。
コマンドライン引数の受け取り方
let arg1 = CommandLine.arguments[1]
標準入力
let input = readLine() //これでコマンドライン上では入力待ち状態になります
標準出力
print()です。
APIを叩く
自分では試してませんが、下記でできるとのこと。
各種ディレクトリパスの取得
FileManagerを使います。
なおFoundationのクラスなので、import必須です。
let filemanager = FileManager.default
print(filemanager.homeDirectoryForCurrentUser)
print(filemanager.temporaryDirectory)
print(filemanager.currentDirectoryPath)
ファイル読み込み
//ファイル名をコマンドライン引数の1番目でもらっている前提
let fileName = "./" + CommandLine.arguments[1]
if let text = try? String(contentsOfFile: fileName, encoding: String.Encoding.utf8) {
print(text)
}
ファイル書き込み
do {
try text?.write(toFile: "./hoge.txt", atomically: true, encoding: String.Encoding.utf8)
} catch let message {
print("error!")
print(message)
}
スクリプトが動いているディレクトリ直下にhoge.txtというファイルが作られます。
サンプルツール
やり方を紹介するだけだとつまらないので、スクリプトを書いてみました。
$ swift script.swift input.txt output.txt
こんな感じで実行してやると、インプットファイルの「。」の位置で改行してくれます。
つまり、
二人の若い紳士しんしが、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲てっぽうをかついで、白熊しろくまのような犬を二疋ひきつれて、だいぶ山奥やまおくの、木の葉のかさかさしたとこを、こんなことを云いいながら、あるいておりました。「ぜんたい、ここらの山は怪けしからんね。鳥も獣けものも一疋も居やがらん。なんでも構わないから、早くタンタアーンと、やって見たいもんだなあ。」「鹿しかの黄いろな横っ腹なんぞに、二三発お見舞みまいもうしたら、ずいぶん痛快だろうねえ。くるくるまわって、それからどたっと倒たおれるだろうねえ。」
これが、
二人の若い紳士しんしが、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲てっぽうをかついで、白熊しろくまのような犬を二疋ひきつれて、だいぶ山奥やまおくの、木の葉のかさかさしたとこを、こんなことを云いいながら、あるいておりました。
「ぜんたい、ここらの山は怪けしからんね。
鳥も獣けものも一疋も居やがらん。
なんでも構わないから、早くタンタアーンと、やって見たいもんだなあ。
」「鹿しかの黄いろな横っ腹なんぞに、二三発お見舞みまいもうしたら、ずいぶん痛快だろうねえ。
くるくるまわって、それからどたっと倒たおれるだろうねえ。
」
こうなります。
ツールの仕様上、インプットの末尾に「。」が来た場合、出力されません。
import Foundation
let filemanager = FileManager.default
let currentPath = filemanager.currentDirectoryPath
var text = ""
func check() {
guard CommandLine.arguments.count == 3 else {
print("""
This tool make a new file separated the words on different lines each Japanese period(。).
This tool need two arguments.
(example:) > swift script.swift input.txt output.txt
argument 0: script file name
argument 1: input file name
argument 2: output file name
""")
exit(0) //アプリ開発だと使っちゃダメ
}
}
func input() {
let fileName = currentPath + "/" + CommandLine.arguments[1]
let inputFile = try? String(contentsOfFile: fileName, encoding: String.Encoding.utf8)
guard let t = inputFile else {
print("input file is not found!!!")
return
}
text = t
}
func output() {
text = text.split(whereSeparator: { $0 == "。" }).joined(separator: "。\n")
//フルパスでなくても、1) ./hoge.txt 2) hoge.txt でもカレントディレクトリ直下にファイルが作成される
let ouputFileName = currentPath + "/" + CommandLine.arguments[2]
do {
try text.write(toFile: ouputFileName, atomically: true, encoding: String.Encoding.utf8)
} catch let e {
print("write file failed!!!")
print(e)
}
}
check()
input()
output()
このサンプルは著作権フリーなので、お好きに改変してお使いください。
試行錯誤のメモ
- 最初句点の置換を
replaceSubrange(_:with:)
でやろうとしたら、最初にヒットした文字しかできなくて断念 - 更にString → [Character] → map使って"。"と一致したら"。\n"としたらCharacterに二文字は入れられません、でエラー(そりゃそうだ)
書いてから気づいたこと
上記のサンプルツール書くとき、新しいSwiftファイルとして作成して、
開発ディレクトリを直で編集していたんですが、これでやると入力補完が十分効かなくなって辛かったです。
ちょっと大袈裟にはなりますが、Command Line ToolとしてProjectつくってやるとキー補完も効くし、git管理もできるし、ビルドもできるんで便利ですね。
コマンドライン引数は流石に無理やろ……? と思ったら、それもイケるみたいです。すごいぞXcode。