Swiftでちょっとしたスクリプトを書く

SwiftというとiOS開発（あるいはiPad/Macアプリ）でしか使いませんが、コマンドラインからの実行もできます。
（当たり前っちゃ当たり前ですが）
Swiftをシェルスクリプトみたいに使えないかな？　とふと思って、調べてみたら普通に色々できたので、書いてみます。

# 実行
「script.swift」というSwiftファイルを動かしたいとしたら、下記で実行できます。

```
$ swift script.swift
```

コマンドライン引数も指定できます。

```
$ swift script.swift arg1 arg2 ...
```

# できること
- 標準入出力
- ファイル操作
- WebのAPIを叩く

スクリプトでやらせたいことはだいたいできる気がします。
慣れ親しんだSwiftでツール作成もできるとなれば嬉しいですね。

AtCoderやる人だったら、Playgroundだと標準入力受け取れないので、
コマンドラインで実行すると捗ると思います。

# コマンドライン引数の受け取り方
```swift
let arg1 =  CommandLine.arguments[1]
```

# 標準入力
```swift
let input = readLine() //これでコマンドライン上では入力待ち状態になります
```

# 標準出力
print()です。

# APIを叩く
自分では試してませんが、下記でできるとのこと。

[Swiftをシェルスクリプトのように使う一番簡単な方法](https://qiita.com/naokits/items/8f09ffc8bbc78ade366c)

# 各種ディレクトリパスの取得
[FileManager](https://developer.apple.com/documentation/foundation/filemanager)を使います。
なおFoundationのクラスなので、import必須です。

```swift
let filemanager = FileManager.default

print(filemanager.homeDirectoryForCurrentUser)
print(filemanager.temporaryDirectory)
print(filemanager.currentDirectoryPath)
```

# ファイル読み込み
```swift
//ファイル名をコマンドライン引数の１番目でもらっている前提
let fileName = "./" + CommandLine.arguments[1]
if let text = try? String(contentsOfFile: fileName, encoding: String.Encoding.utf8) {
    print(text)
}
```

# ファイル書き込み
```swift
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
```

こんな感じで実行してやると、インプットファイルの「。」の位置で改行してくれます。
つまり、

```swift:input.txt
　二人の若い紳士しんしが、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲てっぽうをかついで、白熊しろくまのような犬を二疋ひきつれて、だいぶ山奥やまおくの、木の葉のかさかさしたとこを、こんなことを云いいながら、あるいておりました。「ぜんたい、ここらの山は怪けしからんね。鳥も獣けものも一疋も居やがらん。なんでも構わないから、早くタンタアーンと、やって見たいもんだなあ。」「鹿しかの黄いろな横っ腹なんぞに、二三発お見舞みまいもうしたら、ずいぶん痛快だろうねえ。くるくるまわって、それからどたっと倒たおれるだろうねえ。」
```

これが、

```swift:output.txt
　二人の若い紳士しんしが、すっかりイギリスの兵隊のかたちをして、ぴかぴかする鉄砲てっぽうをかついで、白熊しろくまのような犬を二疋ひきつれて、だいぶ山奥やまおくの、木の葉のかさかさしたとこを、こんなことを云いいながら、あるいておりました。
「ぜんたい、ここらの山は怪けしからんね。
鳥も獣けものも一疋も居やがらん。
なんでも構わないから、早くタンタアーンと、やって見たいもんだなあ。
」「鹿しかの黄いろな横っ腹なんぞに、二三発お見舞みまいもうしたら、ずいぶん痛快だろうねえ。
くるくるまわって、それからどたっと倒たおれるだろうねえ。
」
```
こうなります。
ツールの仕様上、インプットの末尾に「。」が来た場合、出力されません。

```swift:script.swift
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。

[XcodeでCommand Line Tool実行時にコマンドライン引数を渡す](https://ys4145.hatenadiary.org/entry/20120615/p1)