83557


# やりたいこと

CLIで動作するコマンドを作っています。  
以下のコマンドラインオプションを用意しいたいです。

  * JSON文字列またはJSONファイルを受け取るオプション
  * スペース区切りの文字列、またはファイルを受け取るオプション

### 文字列を渡す場合

[code]

    $ cli --json '{"foo":1}'
    
    $ cli --user alice bob
    
[/code]

### ファイルを渡す場合

[code]

    $ cat foo.json
    {"foo":1}
    
    $ cli --json file://foo.json
    
    
    $ cat user.txt
    alice
    bob
    
    $ cli --user file://user.txt
    
[/code]

# 悩んでいること

awscliを参考にして、ファイルを指定するときは、`file://` というプレフィックスを利用しました。  
ただこのスキーマだと、`http://` など別のスキーマが使えることを期待してしましまいます。  
今作っているコマンドは、ファイル以外のリソースを指定する予定はありません。  
したがって、ファイルを指定するときのプレフィックスに、`file://`を使うことは良くないと考えるようになりました。

# 質問

ファイルパスを指定するときのプレフィックスは何がよいでしょうか？  
たとえば、curlは`@`をプレフィックスとしていますが、`@`がよいのでしょうか？

# 補足

CLIはPythonのargparseモジュールを使って、以下のように実装する予定でした。  
<https://docs.python.org/ja/3/howto/argparse.html>

[code]

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--user", nargs="+", type=str)
    parser.add_argument("--json", type=str)
    args = parser.parse_args()
    
    print(f"{args.user=}")
    print(f"{args.json=}")
    
[/code]

[code]

    $ python --version
    Python 3.9.7
    
    $ python test.py --help
    usage: test.py [-h] [--user USER [USER ...]] [--json JSON]
    
    optional arguments:
      -h, --help            show this help message and exit
      --user USER [USER ...]
      --json JSON
    
    $ python test.py --user alice bob --json '{"foo":1}'
    args.user=['alice', 'bob']
    args.json='{"foo":1}'
    
[/code]


CC BY-SA 4.0
2021-11-08T01:55:09.747
83483
2021-11-12T03:29:40.167
2021-11-11T14:55:35.767
19524
19524
question
linux command-line
文字列またはファイルを受け取るコマンドを作りたいです。ファイルを受け取る際のプレフィックスは何がよいでしょうか？


タグのlinuxで考えるなら、ファイルだけでなく標準入出力も考慮すると使い勝手が拡がります。  
この場合、プレフィクスはなしでファイル名指定だけでいいとおもいます

標準のオプションだと

`cli` 標準入力からjsonを入力  
`cli <ファイル名>` ファイルからjsonを入力  
`cli -- <ファイル名>` \-- のあとはすべてファイルとして扱う

だったと思います


CC BY-SA 4.0
2021-11-08T12:52:02.260
83492
2021-11-08T12:52:02.260
44341
83483
answer


もし「ファイルまたは文字列の入力」が1つだけだったら、

  * コマンドラインで指定するのはファイル名
  * ファイル名が `-` の場合は標準入力を用いる

のような挙動をするコマンドが多いのですが、そういう入力が2つだとこの手は取れませんね。

文字列指定のみサポートして、ファイル入力したいときはシェルのコマンド代入(バッククオートや `$(...)`)を使ってもらうのも手です。  
逆に、bashやzshしか使わないという前提でも良いなら、ファイル名指定のみサポートして、文字列の場合はプロセス置換 `cli --user <(echo
alice bob)` してもらうこともできます。

どうしてもコマンドラインでファイルと文字列の両方のサポートしたいなら、私ならプレフィックスは使わずに、別のフラグにします。例: `--userfile
ファイル名` と `--user 文字列`  
プレフィックス方式だとシェルのファイル名補完が面倒になるからです。


CC BY-SA 4.0
2021-11-09T00:32:46.140
83499
2021-11-09T00:52:09.350
2021-11-09T00:52:09.350
3475
3475
83483
answer


「複数のファイル（および文字列）を、個別の形式を指定しつつ読み取る」ようなインターフェースを持つコマンドとして、例に挙げられているcurlや、ffmpeg/ffplay、ImageMagickなどがよく知られているように思います。  
もちろん、これらのコマンドでの、複数形式の文字列・ファイルの同時指定の方法が最良のインターフェースであるとは限りませんが、とりあえずはこれらのオプション体系を参考にするのが、もっとも無難なやり方だと思われます。

curlでは、-dまたは--
dataというオプションの引数について、その先頭が@である場合に、続く文字列をファイル名だとして処理します。ただしこのような実装の場合、「先頭に@があるが、文字列として扱う」ことを実現するために、適宜対応しなくてはなりません。curlでは、--data-
rawというオプションを用意し、--data-rawオプションの引数は常に文字列とみなす、という仕様にすることでこの状況に対応しています。  
データの形式は、--data-ascii、--data-
binaryといったオプションを用いて明示できます。これらのオプションについても、「先頭に@があれば続く文字列をファイル名とみなす」という仕様が定められています。

curlのオプション体系に素直に従うなら、

[code]

    $ cli --data-json string --data-json @file --data-json-raw @string
    # string, @stringはそのままJSON文字列として読み取り、
    # @fileはfileというファイル名のJSONファイルとして読み取る。
    $ cli \
      --data-json '{"foo":1}' --data-user 'alice bob' \
      --data-json @foo.txt    --data-user @user.txt
    # 混合した例
    
[/code]

のような仕様にするのがよいでしょう。

参考:  
<https://curl.se/docs/manpage.html#-d>

* * *

ffmpegでは、文字列とファイルをそれぞれ違うオプションで指定するのが基本です。たとえばメタデータ（作曲者やチャプターなどの情報）を指定する場合、メタデータを文字列で入力する際には
--metadataというオプションの引数として指定し、ファイルで入力する際には`-f ffmetadata -i
file`というように、「入力するファイルの形式」と「入力するファイルの名前」を二つのオプションで指定します。

ffmepgのオプション体系に素直に従うなら、

[code]

    $ cli --json string [-f|--format] json [-i|--input] file
    # stringはそのままJSON文字列として読み取り、
    # @fileはfileというファイル名のJSONファイルとして読み取る。
    $ cli \
      --json '{"foo":1}'  --user 'alice bob' \
      -f json -i foo.txt -f user -i user.txt
    # 混合した例
    
[/code]

のような仕様にするのがよいでしょう。

参考:  
<http://ffmpeg.org/ffmpeg.html#Main-options>  
<http://ffmpeg.org/ffmpeg-formats.html#Metadata-1>

* * *

ImageMagickでは、複数の入力をオプション引数ではなく非演算子として受け取り、それらの先頭に指定されている固有の書式に基づいて処理が行われます。  
例えば「標準入力のgif形式とファイル記述子3のPNG形式」という入力なら、`gif:- png:fd:3`というように指定します。

ImageMagickのオプション体系に素直に従うなら、

[code]

    $ cli json:str:string json:file
    # json:str:stringはstringというJSON文字列として読み取り、
    # json:fileはfileというファイル名のJSONファイルとして読み取る。
    $ cli \
      json:str:'{"foo":1}' user:str:'alice bob' \
      json:foo.txt         user:user.txt
    # 混合した例
    
[/code]

のような仕様にするのがよいでしょう。

参考:  
<https://imagemagick.org/script/command-line-processing.php#input>

* * *

ところで、質問で例示なさっているコマンドラインにおいて、空白を含むテキスト形式の文字列を、引用符や逆斜線などで空白をエスケープすることなくオプション引数に指定してありますが、一つのオプションに対して複数個のオプション引数を用意するのはあまり適切とは言えません。  
Unix系OSの最低限の共通インターフェースを定めているPOSIXでも、そのような仕様のオプション体系は禁止されています。なお、LinuxはUnixではありませんが、[POSIXへの準拠と互換性向上を開発の一目的に据えている](https://www.kernel.org/doc/html/latest/admin-
guide/README.html?highlight=posix%20compliance)ので、従っておくべきだと思います。

[code]

    $ cli --user alice bob
    # ではなく
    $ cli --user 'alice bob'
    
[/code]

参考  
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02>

* * *

【追記】

[いただいたコメント](https://ja.stackoverflow.com/questions/83483/83557#comment94850_83557)をうけて追記します。

> 補足に書きましたが、pythonのargparseモジュールを使ってCLIを作る予定でした。
> argparseを使うと、1つのオプションに複数の値を指定できます。 POSIXに準拠するつもりはなかったです。

  1. POSIXへの準拠について

質問に「Linux」というタグをつけられていることから，このプログラムはLinux上の端末で動かすものだと想定しました。  
また，「プログラムのCLI設計について質問する」ということは，ある程度の利用性を持たせたいと思ってらっしゃるのではないですか。どのような場面で用いるプログラムかは分かりませんが，たとえば「同僚に使わせる」程度の利用性は確保したいのではないですか。

そのような場面では，できるだけ現地の慣習に従うことで，無用な混乱を避けられるでしょう。

また，「Pythonのargparseライブラリは基本的に[POSIXでの慣習](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html)に倣う」というargparseライブラリ開発者（paul.j3氏）の[意見もあります](https://bugs.python.org/msg299609)。もちろん，これは一つの意見にすぎず，argparse開発陣の総意はまったく異なるものである可能性は多いにあります。しかしながら，オプション前置子の既定が-
であることなどを鑑みると，argparseを用いる際にPOSIXへの準拠を考える，という指針はそれほど極端なものではないと言えるのではないでしょうか。

  2. Pythonのargparseライブラリを使うことを前提した回答

argparseライブラリのArgumentParser構築子にはfromfile_prefix_charsという属性があるようですね（私事ですが，この回答を書いていて知りました）。

参考  
<https://docs.python.org/3/library/argparse.html#fromfile-prefix-chars>

おそらくですが，これを用いて「オプション引数の文字列をファイル名とみなして処理する」挙動を実装しようと考えてらしたのではないですか。

fromfile_prefix_chars属性の解説には，例として「先頭に@がある場合にファイル名としてみなす」という，curlの仕様とそっくりなコード例が載っています。  
そういう理由からも，curl風のオプション体系を採用するのがいいですね。


CC BY-SA 4.0
2021-11-11T05:29:49.143
83557
2021-11-12T03:29:40.167
2021-11-12T03:29:40.167
19601
19601
83483
answer