[Mac] ターミナルから指定したファイルをファインダーに表示するコマンド

その前に、

# ターミナルから特定のフォルダのファインダーを開く

[こちらの記事](https://qiita.com/ShinichiOkayama/items/53ddd4cdeb82b3e37efb)にあるように、

```terminal
$ open フォルダパス
```

ですね。これは有名なので、みなさんご存知かと思います。

次に、


# ターミナルから指定したファイルをファインダーに表示

`-R`オプションを指定します。

```terminal
$ open -R ファイルパス
```

ところが、
複数のファイルを指定した場合は、それらすべてのファイルが選択された状態で表示して欲しいのですが、最後の1ファイルしか選択されていません。

_しかも、複数のファイルを指定した場合、極端に動作が遅くなります。_

そこで、同機能のコマンドを自作することにします。


# コマンド自作

複数のファイルを指定した場合は、すべてのファイルが選択された状態で表示するコマンドを作成します。


## 使用するAPI

使用するAPIは、[`activateFileViewerSelecting(_:)`](
https://developer.apple.com/documentation/appkit/nsworkspace/1524549-activatefileviewerselecting) 。

:::note
<i><b>activateFileViewerSelecting(_:)</b></i>
Activates the Finder, and opens one or more windows selecting the specified files.
:::

これ、実現したいことが、まんま叶うAPIです。
ファインダーに表示したいファイルのURLを配列にして引数に渡すだけ。複数のファイルを指定できます。必要により、複数のFinderウインドウが開きます。

## Swiftコード

自分はコマンド名を`ShowInFinder`としましたが、もっと短く全部小文字の方が使い易いと思います。

コードは以下の通り。

```swift:ShowInFinder.swift
import System
import AppKit

let main: () = {

    var urls = [URL]()
    let currentDirectory = FilePath(FileManager.default.currentDirectoryPath)
    for argv in CommandLine.arguments.dropFirst() {
        let path = if argv.first! != "/" {
            currentDirectory.appending(argv).string
        } else {
            argv
        }
        let url = URL(fileURLWithPath: path)
        //print(url.path)
        if FileManager.default.fileExists(atPath: url.path) {
            urls.append(url)
        } else {
            let msg = "file \(argv) not found."
            print(msg, to: &stderr)
        }
    }

    guard urls.count > 0 else {
        let commandPath = FilePath(stringLiteral: CommandLine.arguments.first!)
        let commandName = commandPath.components.last!
        let usage = """
            \(commandName): arguments missing.
            Usage: \(commandName) file1 [ file2 [ file3 ... ] ]
            """
        print(usage, to: &stderr)
        return
    }

    NSWorkspace.shared.activateFileViewerSelecting(urls)

}()

extension UnsafeMutablePointer: TextOutputStream where Pointee == FILE {
    public mutating func write(_ string: String) {
        guard let data = string.data(using: .utf8) else { return }
        _ = data.withUnsafeBytes { bytes in
#if os(Linux)
            Glibc.write(fileno(self), bytes.baseAddress!, data.count)
#else
            Darwin.write(fileno(self), bytes.baseAddress!, data.count)
#endif
        }
    }
}
```


`activateFileViewerSelecting(_:)` に渡すURLは、絶対パス（完全パス）である必要があるため、`"/"`で始まっていないパスには、カレントディレクトリパスに連結しています。

存在しないパスは無視します。

最後の`extension`は、`print`で`stderr`に出力するための汎用的なもので、本コマンドに直接は関係しません。

## ビルド

次のコマンドでビルド（コンパイル&リンカ）します。

```terminal:コンパイル&リンカ
$ swiftc ShowInFinder.swift -o ShowInFinder
```

## 使い方

ビルドして出来たコマンドに`path`を通します。もしくは`path`の通っているフォルダにコピーします。

```terminal
$ ShowInFinder
ShowInFinder: arguments missing.
Usage: ShowInFinder file1 [ file2 [ file3 ... ] ]

$ ShowInFinder hoge.png fuga.png

$ ShowInFinder *.png
```

## 環境

執筆時の環境は以下の通り

```terminal
$ uname -a
Darwin Mac-mini.local 23.4.0 Darwin Kernel Version 23.4.0: Wed Feb 21 21:44:06 PST 2024; root:xnu-10063.101.15~2/RELEASE_ARM64_T8103 arm64

$ swiftc --version
swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
```

### 以上です