備忘録: MacOS のコマンドラインからのテキストファイルの開き方

Terminal.app からテキストエディターを開く方法を試してみました.  ダブルクリックで開くアプリを環境設定用のアプリの中から起動したくて, アプリの起動法を調べ始めた中のことです. インターネットで調べていて `open -a myapp.app` と  `open -a myapp`  のどちらがよいのか混乱してきたということがきっかけで現状調査を思い立ちました.

## 調査結果の概要

* 標準ソフトは文献によくでてくる `open -a TextEdit` などで大丈夫です.
* 正式っぽい使い方は  `open -b com.apple.TextEdit` などのように `-b` オプションを使う起動の仕方です.
*  アプリのデバッグなどで複数バージョンの動作を比較する状況では, フルパス名を指定して `open -a /Appliations/SageMath-9.0.app` などとします.
* フルパス名に日本語が含まれている場合も, フルパス名でアプリ名を指定する方法は大丈夫です.
* 一部のアプリでは, `open -a SageMath-9.0` という起動の仕方ができません.
* `open -a TextEdit` と `/Applications/TextEdit.app/Contents/MacOS/TextEdit` でアプリの挙動に差異があります.

`~/MyAppli.app` や `~/マイアプリ.app` をデバッグ中で複数バージョンの動作比較を行なっている状況で, デバッグ対象をダブルクリックと同等の動作で起動したい場合について表にまとめておきます.

| コマンド| 可否 |
|:--:|:--:|
| `open -a TextEdit` | 可 |
|`open -b com.apple.TextEdit`| 可 |
| `open -a ~/MyAppli.app` | 可 |
| `open -a MyAppli.app` | 可? |
| `open -a MyAppli` | 不可 |
| `open -a ~/マイアプリ.app` | 可 |
| `open -a マイアプリ.app` | 可? |
|`/Applications/TextEdit.app/` `Contents/MacOS/TextEdit`| 不可|
|`~/MyAppli.app/Contents/MacOS/MyAppli`|不可|

(長い絶対パス名を表に押し込むために途中にスペースを入れてありますがテストしたときの絶対パス名は途中にスペースも改行も入れていません.)

 `open` コマンドで自作アプリを起動する場合には, 同じベースネームのアプリが複数配置してある状況がありますので, アプリ開発の中の動作テストの場面で, 起動中のアプリからそのアプリの絶対パスの情報を調べるテクニックが欲しくなります. これは将来の調査課題として残りました. 想定する状況だけ記しておきます.

* 同じバージョンのコピーを複数のフォルダーに配置してしまった場合
* 異なるバージョンに同じベースネームを付けてしまった場合.

## 参考資料

2017年04月14日付の macOS Mojave 10.14.6 の man-page の open の記述と [寝坊した](https://oversleptabit.com) さんの「プログラミングと海外移動の情報マガジン」のページ [便利に使えるMacのopenコマンド](https://oversleptabit.com/archives/4863) を参考にしました.

## 実際に試した使用環境

バージョン情報は以下の通りです.

* macOS Mojave 10.14.6
* CotEditor 3.8.4

### 動作テストの仕方

ここでは , カレント・ディレクトリー内に `MyTextFile.txt` というファイルを作成してある状況で  `MyTextFile.txt` を `Terminal.app` 内の `bash` から開く例を調べます.

指定するアプリは macOS 標準の `TextEdit.app` です.
`Finder` や`システム環境設定`→`言語と地域` の設定によっては `TextEdit`, `テキストエディット`, `テキストエディット.app` などと表示されていると思います.

一時的に他のアプリを例として挙げる場合も出てきます.

###  アプリを絶対パスで指定してテキストファイルを開く

絶対パス名 (バックスラッシュで始まるフルパスネーム)による `open` コマンドの起動の仕方が man-page に記載されていますので, なぞってみます.

コマンド:

```Console
myhome$ open -a /Applications/TextEdit.app MyTextFile.txt
```

動作:   `TextEdit.app` が `MyTextFile.txt` を開いて前面に出てきます.

man-page にある例から少し起動の仕方を変更して試行錯誤した結果, 注意点がでてきました.

注意点 (できないこと):

* ファイル拡張子  `.app` を取ると起動できません.
* `open` は `-a` の引数について相対パスとベースネームを区別しているようです. 例えば , `/Applications` にいるときに `open -a ./TextEdit.app` と `open -a TextEdit.app` で挙動が違います.
* アプリのパスに相対パスは使えないようです.
* アプリの名前にシェルの alias は使えません.
* アプリの名前に Locale の表示名は使えません. (オプション `-a` の引数の中で `/Applications` を `/アプリケーション` に変えたり `TextEdit` を `テキストエディット` に変えたりすると, 起動できません.)

注意点 (できること):

* シェルの変数に絶対パス名を入れておいて展開させることは可能です. (`open` コマンドに引数を送る前にシェル側で処理する形で使う場合ですので, `export` などで環境変数にしている変数以外の変数でも問題ありません.)
* アプリの絶対パス名はアプリの絶対パス名へのシンボリック・リンクの絶対パス名でも構いません.
* アプリの絶対パス名はアプリの Mac エイリアス (別名 ) の絶対パス名でも構いません.
*  下記の 3通り全てについて, アプリの絶対パス名の後ろにスラッシュ  `/` を追加して構いません.
    - アプリのパス名を直接使う場合, シェルの変数を展開して `open` コマンドに送る場合
    - アプリへのシンボリック・リンクの絶対パス名を使う場合
    - アプリへの Mac エイリアスの絶対パス名を使う場合

注意点 (できること) の例1.

```Console
myhome$ te2day=/Applications/TextEdit.app/
myhome$ open -a $te2day    MyTextFile.txt
```

注意点 (できること) の例2.

```Console
myhome$ open -a /Applications/TextEdit.app/    MyTextFile.txt
```

注意点 (工夫すればできること): アプリのファイル名に空白文字が含まれている場合は, 引用符かバックススラッシュによるエスケープを使って下さい. 下記の例はテキストファイルを開く例ではありませんがアプリの絶対パス名の扱いは同じです.

```Console
myhome$ open -a "/Applications/Font Book.app"
```

```Console
myhome$ open -a '/Applications/Font Book.app'
```

```Console
myhome$ open -a /Applications/Font\ Book.app
```

## アプリをベースネームで指定してテキストファイルを開く

ベースネームはパス名からフォルダー名をはずしたものという雰囲気のものです. `/Applications/Utilities/Terminal.app` のベースネームは `Terminal.app` です. 技術的には,  `/Applications/Utilities/Terminal.app` 自体がフォルダーだという事情で表現が難しいとろこですが

```Console
myhomt$ basename /Applications/Utilities/Terminal.app/
Terminal.app
myhome$ dirname /Applications/Utilities/Terminal.app/
/Applications/Utilities
```

とうコマンドの引数を変えて試すとわかる`basename` と `dirname` の動作に準拠します.

`/Applications/TextEdit.app` のベースネームは `TextEdit.app` です.

```Console
myhome$ open -a TextEdit.app MyTextFile.txt
```

動作:   `TextEdit.app` が `MyTextFile.txt` を開いて前面に出てきます.

注意点 (できないこと):

* アプリのベースネームの後ろにスラッシュ  `/` を付けると起動できません.
* アプリのシンボリック・リンクを `/Applications`やカレント・ディレクトリー に置いている場合でも , シンボリック・リンクのベースネームによってアプリを指定して起動することはできません.
* アプリのMac エイリアスを `/Applications`やカレント・ディレクトリー に置いている場合でも ,  Mac エイリアスのベースネームによってアプリを指定して起動することはできません.

注意点(できてしまうこと):

* アプリのベースネームを小文字にしても起動できてしまいます. (もしかすると , case-insensitive file system の問題点かもしれません. case-sensitive file system では大文字と小文字が区別されて, 下記のおかしな例が不可になるかもしれません.)

```Console
悪い例 myhome$ open -a textedit.app MyTextFile.txt
```

疑問点:

* MacOS のアプリは `XXXX.app/Contents/Info.plist` の Bundle Name や　Display Name というエントリーで名前を登録しています. この名前を使うのが MacOS Classic からの伝統につながるはずですが, 自作アプリのファイル名を変更して試してみると, アプリのファイル名のベースネームが `open` コマンドのアプリ名として通用しているようです. (`CFBundleName`があって, `CFBundleDisplayName` がない自作アプリを Automator で作成して調べました.)
* インターネットで `open` コマンドの使い方を調べていると, 「アプリのフォルダーに移動してから , `open -a アプリのベースネーム` を実行する」という内容の記述や「アプリが `/Applications` フォルダーにあるときは, アプリをベースネームで指定することができる」とう内容の記述があります. ところが, 自作アプリで調べてみると, カレント・フォルダーでもなく `/Applications` フォルダーでもない場所に置いてあるアプリでもベースネームで指定することができるようです. MacOS X のバージョンが変わってきた中で動作が変化してしまったのかもしれません . セキュリティー対策からすると怖い方向の変化です.

## アプリのベースネームから拡張子 `.app` をはずした名前でアプリを指定テキストファイルを開く

`/Applications/TextEdit.app` を例にするとベースネームは `TextEdit.app` で拡張子  `.app` をはずした名前は `TextEdit` です.

```Console
myhome$ open -a TextEdit  MyTextFile.txt
```

動作:   `TextEdit.app` が `MyTextFile.txt` を開いて前面に出てきます.

注意点 (なぜかできること):

* アプリの名前を `TextEdit/` としても起動することができます.
* インターネットで `open` コマンドの使い方を調べていると, `.app` を省略して良いというページと省略してはいけないというページが両方出てくると思います. `TextEdit` でも今回試した自作アプリでも `.app` を省略した形で `open` コマンドが使えるのですが, `SageMath-9.0.app` の場合は `open -a SageMath-9.0` も `open -a SageMath-9` も不可でした.  ベースネームの中の `.app` を省略できる条件は判然としないところがあります. もしかすると `SageMath` を複数バージョン置いている状態がトラブルを起こしているのかもしれません. (ここの調査は`TextEdit.app` 以外はファイルを開く形ではないアプリの起動で行いました.)

注意点 (できないこと ):

* アプリの名前にシンボリック・リンクや Mac エイリアスのベースネームを使うことができない状況は拡張子 `.app` をはずしても変わりません.

## アプリを Bundle Identifier で指定して起動

アプリの Bundle Identifier はアプリの Info.plist で宣言してある名前で MacOS の中でのアプリの正式名称です. 調べ方は, 後で紹介します. ここでは, `TextEdit.app` のBundle Identifier `com.apple.TextEdit` を使います.

まず, man-page にある例をなぞります.

```Console
myhome$ open -b com.apple.TextEdit MyTextFile.txt
```

動作:   `TextEdit.app` が `MyTextFile.txt` を開いて前面に出てきます.

MacOS はアプリのデータベースを作って, ファイルとアプリの関連付けやアプリの起動を行っています. そのデーターベースを調べると 
 `com.apple.textedit` が `canonical id:` として載っています. `canonical` は「標準形に変換してから比較する」というときの「標準」に近い言葉ですので, Bundle Identifier として,  `com.apple.textedit` も試しました. 

```Console
myhome$ open -b com.apple.textedit MyTextFile.txt
```

英字を小文字に変換する標準化が介在していると考えて

```Console
myhome$ open -b com.apple.Textedit MyTextFile.txt
```

 も試しました.
 
 どちらも, `com.apple.TextEdit` の場合と同じ動作でした.


## `open` コマンドのオプションでテキストエディターを指定してテキストファイルを開く

`open` コマンドに `-e` オプションを指定すると `TextEdit.app` でファイルを開きます. `-e` オプションの代わりに `-t`オプションを使うとデフォールト・テキストエディターでファイルを開きます. アプリを指定するオプションなしでファイルを開くと, そのファイルに関連付けられているアプリでファイルを開くことになります.

テキストファイルとテキストエディターの関連付けは, テキストエディター　2種 (`TextEdit.app`, `CotEditor.app`) を使って `MyTextFile.txt` に対するファイル個別の関連付けとテキストファイル全般の関連付けの組み合わせ合計 4通りを考えることにしました.

この起動の仕方 3 通りと掛け合わせて 12通りの網羅テストを行いました.
コマンドごとに表にまとめます.

```Console
myhome$ open -e  MyTextFile.txt
```

| `open -e  MyTextFile.txt` |  .txt --- TextEdit | .txt --- CotEditor |
|:-----------------:|:---------:|:------:|
| MyTextFile.txt --- TextEdit |  TextEdit | TextEdit |
| MyTextFile.txt --- CotEditor | TextEdit | TextEdit |

```Console
myhome$ open -t MyTextFile.txt
```

| `open -t MyTextFile.txt` |  .txt --- TextEdit | .txt --- CotEditor |
|:-----------------:|:---------:|:------:|
| MyTextFile.txt --- TextEdit |  TextEdit | CotEditor |
| MyTextFile.txt --- CotEditor | TextEdit | CotEditor |


```Console
myhome$ open  MyTextFile.txt
```

| `open  MyTextFile.txt` |  .txt --- TextEdit | .txt --- CotEditor |
|:-----------------:|:---------:|:------:|
| MyTextFile.txt --- TextEdit |  TextEdit |  TextEdit |
| MyTextFile.txt --- CotEditor | CotEditor | CotEditor |


動作:   以上の  12通のどの場合でも, 表に記載のテキストエディターが `MyTextFile.txt` を開いて前面に出てきました.

MacOS はインストール直後の状態では デフォールト・テキストエディターが `TextEdit.app` になっています.

注意点:

* `open` コマンドは  `-a TextEdit` とテキストファイルのファイル名の片方なら省略することができますが, 両方省略するとヘルプを表示して終了してしまいます.
* `open -e` と `open -t` はテキストエディターを起動します. このときテキストエディターは新規ファイルの編集ウィンドウで前面に出てきます.

## アプリの MacOS 実行形式を直接指定してテキストファイルを開く

アプリの MacOS 実行形式はアプリから相対パス `Contents/MacOS/` の位置のフォルダーに入れるのが慣例です. 実行形式のファイル名のベースネームは `Info.plist` に入っている推測していますが, 調査はまだです.

```Console
$ plutil -p /Applications/TextEdit.app/Contents/Info.plist | egrep -e Executable
  "CFBundleExecutable" => "TextEdit"
```

`TextEdit.app` の場合は, 下記のコマンドで開始することができます.

```Console
myhome$ /Applications/TextEdit.app/Contents/MacOS/TextEdit
```

動作：  `TextEdit.app` は起動しますが, ウィンドウは前面に出てきません.

注意点:

* この起動の仕方の `TextEdit` は挙動不審です. ファイル名を指定した場合,  起動した側のカレント・ディレクトリからではなくホーム・フォルダーから相対的にファイルを探すようです.  

## MacOS のアプリのデータベース の調べ方と Mac アプリの情報の調べ方

 [寝坊した](https://oversleptabit.com) さんの [便利に使えるMacのopenコマンド](https://oversleptabit.com/archives/4863) で教わったコマンドの出力から `id` と `com.apple.TextEdit` の行を `egrep` で抜き出してみました.

```Console
myhome$ cd  /
myhome$ cd System/Library/Frameworks/CoreServices.framework
myhome$ cd Frameworks/LaunchServices.framework/Support/
myhome$ ./lsregister -dump | egrep -i -e 'id.*com.apple.TextEdit'
	identifier:    com.apple.TextEdit (0x80003de5)
	canonical id:  com.apple.textedit (0x80003de4)
    CFBundleIdentifier = "com.apple.TextEdit";
    "com.apple.application-identifier" = "com.apple.TextEdit";
```

最初にルートへ  `cd` しているのは, 次の 2行の cd 先を両方とも相対パスにして書式を揃えるためです. `egrep` のオプション `-i` は大文字と小文字を同一視する指示です. `-e` オプションのパラメーターは, `egrep` にワイルドカードの正則表現 `.*` を渡すために引用符で囲ってあります. よく考えると, `id: com-apple-TextEdit` も引っかかる検索になっていますが, 出力に混乱が起きていないので問題ないと思います.

アプリの情報を直接見るには, `plutil -p` を使います.

```Cosolse
myhome$ cd /Applications/TextEdit.app/Contents
myhome$ plutil -p Info.plist | egrep -e CFBundleIdentifier
  "CFBundleIdentifier" => "com.apple.TextEdit"
```
### 調査結果

* 大勢が使っている標準アプリの起動は大勢に合わせておくのが無難で,  `open -a TextEdit`, `open -a Terminal`  などという形で `-a` オプションが使えます.
* 正式っぽい使い方は, アプリの名前を書く代わりに `-b` オプションに続けて Bundle Identifier を書いてアプリを指定する仕方のようです. `TextEdit.app` を起動するときは `open -b com.apple.TextEdit`,  `Terminal.app` を起動するときは `open -b com.apple.Terminal` となるようです.
* 一方, 自作ソフトや発展段階のアプリで複数バージョンの動作を比較する状況のときはアプリを絶対パス名で指定して `open -a /Applications/SageMath-9.0.app` とした方がよいようです. この場合は, `open -a SageMath-9.0` では起動でこきないことがあるようです. `open -b org.sagemath.Sage` ではアプリのバージョンが指定できません. 試してみると, LaunchPad に挙がっているバージョンが起動するようですが, SageMath-X.X.app で調べただけの状況です.
* 複数バージョンでファイル名の中のバージョンナンバーを変えてある場合は, `open -a SageMath-9.0.app` という起動の仕方でも大丈夫なようです. 他のバージョンが `SageMath-8.6.app` と `SageMath-8.9.app` というファイル名になっているような場合で調べた結果です. 
* `Finder` の日本語の表示では `TextEdit.app` は `テキストエディット`になっていて, `/Applications` フォルダーは `/アプリケーション` になっていますが, `open` コマンドではこの表示名は使えないようです. 文字コードを変えるなどのトリックで表示名による指定ができる可能性については調査していません.
* アプリのファイル名に日本語を使っている場合も, `open -a マイアプリ.app` や `open -a ~/マイアプリ.app` などは使えるようです.
* `open -a TextEdit` と `/Applications/TextEdit.app/Contents/MacOS/TextEdit` でアプリの挙動に差異があります.