Help us understand the problem. What is going on with this article?

Rustのコマンドライン解析ライブラリ『Clap』 〜導入編〜

Clapとは

コマンドラインの引数解析を行うRust用のライブラリです。
位置引数、フラグ、オプション、サブコマンドなどの解析が可能で、それぞれ細かい設定も指定することができる多機能さが特徴です。

この記事について

Clapについてざっと確認したので個人的な備忘的意味合いも含め、導入から簡単な使い方、各機能を解説します。
また、使用している各メソッドについては一覧としてこちらにもまとめていますので参考までに。

確認した環境やバージョンは以下の通りです。

  • MacOSX 10.11.6 (El Capitan)
  • rust 1.5.1
  • clap 2.20.3

Quick start

導入手順から簡単なサンプル実装までを紹介します。
Note: rustの環境(rustcやCargoなど)は整っているものとします。

1. プロジェクトを作成してcargo.tomlを編集

まず任意の場所でcargo new clapexを実行してプロジェクトの雛形を作成します。
次にClapをCargo経由で取得する為の設定をCargo.tomlに追加します。

cargo.toml
[package]
name = "clapex"
version = "0.1.0"
authors = ["myname <myname@mail.com>"]
description = "Clap Example CLI"

[dependencies]
clap = "2.20.3"

descriptionなどは必須ではありませんが、記述しておくと自動的に内容をヘルプに記載してくれるClapのmacroが使えたりします(後述)。

2. main.rsを作ってcrateとuseを追加

プロジェクトトップのsrcディレクトリにmain.rsファイルを作成し、Clapを使うためにextern crateします。
また、この後の実装でAppArgSubCommandを使用するので、簡潔のためにuseを追記します。

main.rs
extern crate clap;

use clap::{App, Arg, SubCommand}

3. main関数に簡単なアプリケーションを定義

main関数に簡単なサンプルCLIアプリケーションを定義してみます。

main.rs続き
fn main() {
    let app = App::new("clapex")
        .version("0.1.0")                       // バージョン情報
        .author("myname <myname@mail.com>")     // 作者情報
        .about("Clap Example CLI")              // このアプリについて
        .arg(Arg::with_name("pa")               // 位置引数を定義
        .help("sample positional argument")     // ヘルプメッセージ
        .required(true)                         // この引数は必須であることを定義
        )
        .arg(Arg::with_name("flg")              // フラグを定義
            .help("sample flag")                // ヘルプメッセージ
            .short("f")                         // ショートコマンド
            .long("flag")                       // ロングコマンド
        )
        .arg(Arg::with_name("opt")              // オプションを定義
            .help("sample option")              // ヘルプメッセージ
            .short("o")                         // ショートコマンド
            .long("opt")                        // ロングコマンド
            .takes_value(true)                  // 値を持つことを定義
        )
        .subcommand(SubCommand::with_name("sub")// サブコマンドを定義
            .about("sample subcommand")         // このサブコマンドについて
            .arg(Arg::with_name("subflg")       // フラグを定義
                .help("sample flag by sub")     // ヘルプメッセージ
                .short("f")                     // ショートコマンド
                .long("flag")                   // ロングコマンド
            )
        );
}

App::newメソッドで"clapex"という名前のAppインスタンスを作成し、メソッドチェーンでヘルプ情報や引数などを定義していきます。

4. 解析部分を実装する

次に解析部分を実装します。サンプルなので入力を標準出力するだけの簡単なものを実装してみます。

main.rs続き
fn main() {
    let app = App::new("clapex")
        ...(省略)

    // 引数を解析
    let matches = app.get_matches();

    // paが指定されていれば値を表示
    if let Some(o) = matches.value_of("pa") {
        println!("Value for pa: {}", o);
    }

    // optが指定されていれば値を表示
    if let Some(o) = matches.value_of("opt") {
        println!("Value for opt: {}", o);
    }

    // flgのON/OFFで表示するメッセージを切り替え
    println!("flg is {}", if matches.is_present("flg") {"ON"} else {"OFF"});

    // subサブコマンドの解析結果を取得
    if let Some(ref matches) = matches.subcommand_matches("sub") {
        println!("used sub"); // subが指定されていればメッセージを表示
        // subflgのON/OFFで表示するメッセージを切り替え
        println!("subflg is {}", if matches.is_present("subflg") {"ON"} else {"OFF"});
    }
}

get_macthesメソッドを呼び出すことで与えられたコマンドラインの解析を行い、正常に解析できればArgMatchesという結果が返ります。コマンドラインの指定にエラーがあった場合(必須引数が指定されていないなど)、エラーメッセージを標準出力に出力し、強制的に終了コード1でメインプロセスを終了します。
引数に指定された内容はArgMatchesが持つメソッドを利用して取得可能です。
value_ofメソッドは引数に渡した引数名に該当する引数に指定された値を取得します。値がない場合はNoneが返ります。
is_presentメソッドは引数に渡した引数名に該当する引数が指定されたかどうかを判定します。bool値が返ります。
subcommand_matchesメソッドは引数に渡したサブコマンド名に該当するサブコマンドの解析結果としてArgMatchesが返ります。

Tips
エラーを検出した場合にプロセスを終了させたくない場合はget_matches_safeメソッドを使うことで可能です。これはエラー時にはエラーを返すだけでメインプロセスは終了させません。エラー時に別途実行したい処理がある場合などはこちらを使ったほうがいいでしょう。

main.rsの実装は一旦これで完了です。

5. ビルドして実行してみる

プロジェクトディレクトリ配下でcargo buildを実行します。
無事にビルドが終われば{プロジェクトルート}/target/debug/clapexという実行ファイルが出力されているはずです。
では、実行してみましょう。

まずはヘルプメッセージを表示させてみます。./clapex -hを実行します。

$ ./clapex -h
clapex 0.1.0
myname <myname@mail.com>
Clap Example CLI

USAGE:
    clapex [FLAGS] [OPTIONS] <pa> [SUBCOMMAND]

FLAGS:
    -f, --flag       sample flag
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -o, --opt <opt>    sample option

ARGS:
    <pa>    sample positional argument

SUBCOMMANDS:
    help    Prints this message or the help of the given subcommand(s)
    sub     sample subcommand

このように、定義した情報が自動的によくあるヘルプ形式で整形されて表示されます。
明示的に定義していないバージョン(-V/--version)とヘルプ(-h/--help)はClapが自動的に作成します(作成させないようにすることも可能です)。
このバージョンとヘルプの自動生成はサブコマンドにも適用されています。./clapex sub -hを実行します。

$ ./clapex sub -h
clapex-sub 
sample subcommand

USAGE:
    clapex sub [FLAGS]

FLAGS:
    -h, --help       Prints help information
    -f, --flag        sample flag by sub
    -V, --version    Prints version information

サブコマンドに限定したヘルプメッセージが出力されます。

次に引数を指定して実行してみます。./clapex pavalue -f -o optvalue sub -fを実行します。

$ ./clapex pavalue -f -o optvalue sub -f
Value for pa: pavalue
Value for opt: optvalue
flg is on
used sub
subflg is on

解析も正しく行われています。

必須の引数である"pa"を指定しなかったらどうなるでしょうか。./clapex -fを実行します。

$ ./clapex -f
error: The following required arguments were not provided:
    <pa>

USAGE:
    clapex <pa> --flag

For more information try --help

エラーになりました。エラーメッセージにはエラーの理由とUSAGE、さらに--helpへの導線も表示されています。

このように、Clapを使うと比較的簡単にコマンドラインを定義&解析することができます。
この他にも引数を複数指定可能にしたり、いくつかの引数をグループ可してそのうち1つだけ指定可能にしたり、ヘルプメッセージの出力方法を変更したりと様々な機能がClapには備わっています。
いくつかの機能をAdvancedとして紹介します。

Advanced

基本的な実装方法はQuick startで説明した通りですが、Clapにはまだまだ便利な機能がたくさんあるのでその一部を解説します。

crateマクロでCargo.tomlを参照して自動設定する

アプリ名や作成者、バージョンなどを手動で定義するのではなく、Cargo.tomlを参照して自動的に設定させることができます。
Quick startのコードを修正してみます。

main.rs
#[macro_use] // macroを使うのでmacro_useを追記
extern crate clap;
...(省略)

fn main() {
    let app = App::new(crate_name!())   // Cargo.tomlのnameを参照する
        .version(crate_version!())      // Cargo.tomlのversionを参照する
        .author(crate_authors!())       // Cargo.tomlのauthorsを参照する
        .about(crate_description!())    // Cargo.tomlのdescriptionを参照する
        .arg(Arg::with_name("pa")
        ...(省略)
}

それぞれハードコーディングからmacroを使うように変更したので、Cargo.tomlの内容を参照するようになりました。
試しにCargo.tomlを変更してみます。

Cargo.toml
[package]
name = "clapex"
version = "1.1.1"
authors = ["myname <myname@mail.com>"]
description = "Clap Example CLI ref toml"
...(省略)

ヘルプメッセージを表示すると、

$ ./crapex -h
clapex 1.1.1
myname <myname@mail.com>
Clap Example CLI ref toml

このように変更が反映されます。

これはapp_from_crateマクロを使うことでさらに簡略化することが可能です。
main.rsを次のように修正します。

main.rs
fn main() {
    let app = app_from_crate!()
        .arg(Arg::with_name("pa")
        ...(省略)
}

だいぶ簡略化されましたが、これでも同じ結果を得ることができます。

シンタックスライトにArgを定義する

Quick startではArgを定義する際、以下のように行っていました。

main.rs
...(省略)
fn main() {
    let app = app_from_crate!()
        ...(省略)
        .arg(Arg::with_name("opt")
            .help("sample option")
            .short("o")
            .long("opt")
            .takes_value(true)
        )
        ...(省略)
}

少し冗長に感じる方もいるかと思います。
しかしこれはfrom_usageメソッドを使ってある程度シンタックスライトに定義することが可能です。
from_usageを使った書き方に変更します。

main.rs
...(省略)
fn main() {
    let app = App::new(crate_name!())
        ...(省略)
        .arg(Arg::from_usage("-o --opt [OPT] 'sample option'"))
        ...(省略)
}

定義が1行に収まり、すっきりと記述することができました。可読性もこちらの方が良いですね。

from_usageメソッドはいくつかのシンタックスルールに則って記述することである程度の設定を一度に行うことができます。
ある程度と表現したのは、from_usageメソッドだけでは定義しきれない設定も多数あるからです。
from_usageメソッドで定義できない設定はこれに続けてメソッドチェーンで繋げれば問題なく設定可能です。

Tips
Appにも似たようなarg_from_usageというメソッドがあります。これを使うとAppのインスタンスから. arg_from_usage("...")と書き始める事ができます。ただし、この方法の場合はシンタックスで定義できるものしか設定することができません。

シンタックスルールは以下のようになります。

[explicit name] [short] [long] [values] [help]
  • explicit name: 明示的な名称(省略可能)。省略された場合はlong > short > valuesの順を優先順として、指定された名称を引数の名称とします。[]で囲うと任意、<>で囲うと必須であることを意味します。後ろに...を付けると複数指定可能であることを意味します。
  • short: ショートコマンド。-の後に1文字付けることで設定されます。後ろに...を付けると複数指定可能であることを意味します。設定の最後に,を付けることもできますが、機能には影響しません。
  • long: ロングコマンド。--の後にスペースなしの文字列を付けることで設定されます。後ろに...を付けると複数指定可能であることを意味します。
  • values: 引数の値。[]または<>で囲った文字列を1つ以上記述することで設定されます。[]で囲うと任意、<>で囲うと必須であることを意味します。後ろに...を付けると複数指定可能であることを意味します。1つの引数に複数の値を持たせることもできます。
  • help: ヘルプメッセージ。''で囲った文字列を記述することで設定されます。

Quick startの定義を全てfrom_usageメソッドを使うと以下のようになります。

main.rs
...(省略)
fn main() {
    let app = App::new(crate_name!())
        .arg(Arg::from_usage("<pa> 'sample positional argument'"))
        .arg(Arg::from_usage("[flg] -f --flag 'sample flag'"))
        .arg(Arg::from_usage("-o --opt [OPT] 'sample option'"))
        .subcommand(SubCommand::with_name("sub")
            .about("sample subcommand") 
            .arg(Arg::from_usage("[subflg] -f --flag 'sample flag by sub'"))
        );

        ...(省略)
}

かなりすっきり設定できますね。

複数の引数をグループ化する

Clapには複数の引数をグループ化する機能があります。引数をグループ化することにより、グループのうちどれか1つしか指定できないようになります(グループから複数指定させることも可能です)。

引数をグループ化するにはArgGroupを使います。
main.rsに幾つか引数を追加してそれらをグループ化してみます。

main.rc
#[macro_use]
extern crate clap;

use clap::{App, Arg, SubCommand, ArgGroup}; // ArgGroupを追加

fn main() {
    let app = App::new(crate_name!())
        ...(省略)
        // 定義部分
        // levelグループに所属させる引数を定義
        .args_from_usage("--verb 'verbose mode: level group'
                            --debug 'debug mode: level group'
                            --info 'info mode: level group'")
        // ArgGroupを定義
        .group(ArgGroup::with_name("level") // グループ名
            .args(&["verb", "debug", "info"]) // グループに所属させる引数を設定
        )
        ...(省略)

        // 解析部分
        // levelグループのいずれかが指定されていれば内容を表示
        if matches.is_present("level") {
            let (verb, debug, _) = (matches.is_present("verb"),
                                    matches.is_present("debug"),
                                    matches.is_present("info"));
            println!("level is {}", if verb {"verb"} else if debug {"debug"} else {"info"});
        }
}

ArgGroup::with_nameメソッドで"level"という名前のグループを作成し、argsメソッドで所属させる引数そ設定しています。それをgroupメソッドでAppに設定します。
解析部分ではグループのいずれかが指定されたことをis_presentメソッドで判定しています。

Tips
args_from_usageメソッドは複数の引数の定義を1つ1行としてシンタックスライトに一度に定義することができます。シンタックスルールはfrom_usageメソッドと同じです。ただし、この方法の場合はシンタックスで定義できるものしか設定することができません。

それでは、これを実行してみます。./clapex pavalue --verbを実行します。

$ ./clapex pavalue --verb
Value for pa: pavalue
flg is OFF
level is verb

期待通り"level is verb"が出力されました。

グループ化しているので同時にverbとinfoは指定できないはずです。./clapex pavalue --verb --infoを実行してみましょう。

$ ./clapex pavalue --verb --info
error: The argument '--verb' cannot be used with one or more of the other specified arguments

USAGE:
    clapex <pa> <--verb|--debug|--info>

For more information try --help

こちらも期待通りエラーになりました。

ArgGroupはこの他にもグループ単位で必須にしたり、他の引数が指定されいる場合に限り指定可能にするなど様々な設定が可能です。

ヘルプに表示される引数の順序を定義順にする

ここで一度ヘルプを表示してみます。

$ ./clapex -h
clapex 1.1.1
myname <myname@mail.com>
Clap Example CLI ref toml

USAGE:
    clapex [FLAGS] [OPTIONS] <pa> [SUBCOMMAND]

FLAGS:
        --debug      debug mode: level group
    -f, --flag       sample flag
    -h, --help       Prints help information
        --info       info mode: level group
    -V, --version    Prints version information
        --verb       verbose mode: level group

OPTIONS:
    -o, --opt <OPT>    sample option

ARGS:
    <pa>    sample positional argument

SUBCOMMANDS:
    sub     sample subcommand
    help    Prints this message or the help of the given subcommand(s)

先程定義したlevelグループがバラバラで見づらくなってしまっています。これはClapではデフォルトで引数の並び順はアルファベットの降順になるためです。

では次はこの順序を変更してみます。引数ひとつひとつに順序を付けるArg::display_orderというメソッドもありますが、それぞれの引数に設定しないといけないのと運用にも若干手間がかかるので、ここではもっとシンプルに、定義順で表示するAppSettings::DeriveDisplayOrderという設定を有効にします。

main.rsを以下の様に変更します。

main.rs
#[macro_use]
extern crate clap;

use clap::{App, AppSettings, Arg, ArgGroup, SubCommand}; // AppSettingsを追加

fn main() {
    let app = app_from_crate!()
        .setting(AppSettings::DeriveDisplayOrder) // この1行を追加
    ...(省略)
}

これだけです。
settingメソッドはAppSettingsの設定を有効にするメソッドで、AppSettings::DeriveDisplayOrderはヘルプに表示する引数の順序を定義順にする設定です。

では、再度ヘルプを表示してみます。

$ ./clapex -h
clapex 1.1.1
myname <myname@mail.com>
Clap Example CLI ref toml

USAGE:
    clapex [FLAGS] [OPTIONS] <pa> [SUBCOMMAND]

FLAGS:
    -f, --flag       sample flag
        --verb       verbose mode: level group
        --debug      debug mode: level group
        --info       info mode: level group
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -o, --opt <OPT>    sample option

ARGS:
    <pa>    sample positional argument

SUBCOMMANDS:
    sub     sample subcommand
    help    Prints this message or the help of the given subcommand(s)

無事に順序が定義順に表示されるようになりました。

指定可能な値を限定する & デフォルト値を設定する

Clapでは指定可能な値をあらかじめ設定しておくことができます。また、引数が指定されなかった場合に取るデフォルト値を設定することもできます。

main.rsを以下のように追記します。

main.rs
...(省略)
fn main() {
    let app = app_from_crate!()
        ...(省略)
        // 以下の定義を追加
        .arg(Arg::from_usage("-m --mode [MODE] 'sample mode'")
            .possible_values(&["fast", "normal", "slow"])   // 指定可能な値を設定
            .default_value("normal")                        // デフォルト値を設定
        )
        ...(省略)

    // 以下の解析部分を追加
    // modeが指定されていれば値を表示
    if let Some(o) = matches.value_of("mode") {
        println!("Value for mode: {}", o);
    }
    ...(省略)
}

新たにmodeという引数を追加し、possible_valuesメソッドで指定可能な値を列挙しています。また、default_valueメソッドで引数が指定されなかった場合のデフォルト値を設定しています。

ヘルプを確認してみます。

 $ ./clapex -h
...(省略)
OPTIONS:
    -o, --opt <OPT>      sample option
    -m, --mode <MODE>    sample mode [default: normal]  [values: fast, normal, slow]
...(省略)

modeが追加され、デフォルト値と指定可能値が明示されています。

では、実際にコマンドライン実行してみます。

$ ./clapex pa -m fast
Value for pa: pa
flg is OFF
Value for mode: fast

指定可能値なので期待通りに処理が正常終了しています。

modeを指定しない場合はどうでしょうか。

$ ./clapex pa
Value for pa: pa
flg is OFF
Value for mode: normal

こちらも期待通りにデフォルト値が設定されています。

最後に指定可能値以外の値を指定してみましょう。

$ ./clapex pa -m veryfast
error: 'veryfast' isn't a valid value for '--mode <MODE>'
        [values: fast, normal, slow]


USAGE:
    clapex <pa> --mode <MODE>

For more information try --help

エラーになりました。
エラーメッセージからも"veryfast"は無効な値で、"fast"/"normal"/"slow"が指定可能であることが分かります。

カスタムバリデーションを実装する

Clapは引数のバリデーションに独自のバリデータを設定することができます。

main.rsを以下のように変更します。

main.rs
...(省略)
// 値に'@'が含まれてないことをチェックするカスタムバリデータ
fn not_at(v: String) -> Result<(), String> {
    if v.contains("@") {
        return Err(String::from("The value can not contain the '@' character"));
    }
    Ok(())
}

fn main() {
    let app = app_from_crate!()
        ...(省略)
        .arg(Arg::from_usage("<pa> 'sample positional argument [value: without '@']'") // '@'を含んではいけないことを明記
            .validator(not_at) // '@'をチェックするカスタムバリデータを設定
        )
        ...(省略)
}

validatorメソッドを使うことで引数のバリデーションに独自のバリデータを設定できます。引数に渡すのは特定のシグネチャFn(String) -> Result<(), String>を満たすstaticな関数です。
ここではサンプルとして'@'文字がふくまれていないことをチェックするバリデータを実装しています。エラー時の文言はそのまま出力されるので分かりやすい文言を設定した方がいいでしょう。
また、該当の引数のヘルプメッセージにもバリデーションのことを明記しておく方が親切ですので、ここでもその旨を追記しています。

では実際にバリデーションエラーにしてみます。

$ ./clapex pa@
error: Invalid value for '<pa>': The value can not contain the '@' character

カスタムバリデーションが動作してエラーになりました。
メッセージ前半部分(":"より前)はClapが自動的に出力する文言で、どの引数でエラーになったのかが出力されます。メッセージ後半部分(":"より後)がカスタムバリデータで設定したエラーメッセージになります。

シェルの補完ファイルを生成する

Clapはシェルの補完ファイル(bash-completionなど)を生成する機能も持っています。
この機能はAppインスタンスが持つgen_completions_toメソッドを使って生成することができますが、これまでの実行ファイルとは別に動作する方が良いでしょう。その手順はClapのドキュメントに記載されているので、それに従ってやってみます。

まずはAppの定義部分を別モジュールに切り分けます。src/cli.rsファイルを作成し、そこにAppの定義を移します。

cli.rs
use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};

pub fn build_cli() -> App<'static, 'static> {
    app_from_crate!()
        .setting(AppSettings::DeriveDisplayOrder)
        .arg(Arg::from_usage("<pa> 'sample positional argument [value: without '@']'")
            .validator(not_at)
        )
        .arg(Arg::from_usage("[flg] -f --flag 'sample flag'"))
        .arg(Arg::from_usage("-o --opt [OPT] 'sample option'"))
        .arg(Arg::from_usage("-m --mode [MODE] 'sample mode'")
            .possible_values(&["fast", "normal", "slow"])
            .default_value("normal")
        )
        .args_from_usage("--verb 'verbose mode: level group'
                            --debug 'debug mode: level group'
                            --info 'info mode: level group'")
        .group(ArgGroup::with_name("level")
            .args(&["verb", "debug", "info"])
        )
        .subcommand(SubCommand::with_name("sub")
            .about("sample subcommand")
            .arg(Arg::from_usage("[subflg] -f --flag 'sample flag by sub'"))
        )
}

fn not_at(v: String) -> Result<(), String> {
    if v.contains("@") {
        return Err(String::from("The value can not contain the '@' character"));
    }
    Ok(())
}

今までmain.rsのmain関数内で行っていたAppの定義をbuild_cliというパブリックな関数に移しました。定義内容は特に変えていません。

これによってmain.rsのApp定義部分も以下のように変更できます。

main.rs
#[macro_use]
extern crate clap;

// clap::Appなどはここでは使わなくなったのuseは削除

mod cli; //代わりにcliモジュールをインポート

fn main() {
    // Appを定義する関数を呼び出し、解析する
    let matches = cli::build_cli().get_matches();
    ...(省略)
}

App定義部分は別関数からの呼び出しにリファクタリングします。

次に、補完ファイル作成用にbuild.rsをプロジェクトトップに作成し・実装します。

build.rs
#[macro_use]
extern crate clap;

use clap::{Shell};

include!("src/cli.rs");

fn main() {
    let mut app = build_cli();
    app.gen_completions("clapex", Shell::Bash, "./");
}

cli::build_cliを呼び出してApp定義を取得した後、gen_completionsメソッドで補完ファイルを生成します。gen_completionsメソッドの第1引数にはCLIアプリのバイナリ名、第2引数には対象のシェル、第3引数には補完ファイルを書き出すディレクトリを指定します。
対象にできるシェルは現状以下の4つです。

  • Bash
  • Fish
  • Zsh
  • PowerShell

ここではBashを生成していますが、自身の環境に合わせて変えて下さい。

最後にビルド時に補完ファイルを生成させるようにCargo.tomlを修正します。

Cargo.toml
[package]
...(省略)
build = "build.rs"

[build-dependencies]
clap = "2.20.3"

packageにbuild = "build.rs"を指定してビルド時にbuild.rsを実行するように設定します。また、build.rsでもclapを使うのでbuild-dependenciesを追加してClapを解決できるようにします。

これで準備が整いました。cargo buildするとプロジェクトトップにclapex.bash-completionファイル(選択したシェルによって変わるのでBash以外を指定した場合は読み替えて下さい)が生成されます。

あとは自身の環境のcompletionにこのファイルを適用すればコマンド補完されるようになります。

まとめ

簡単な導入からいくつかの機能を紹介しましたが、Clapにはここでは紹介しきれないほどまだまだ多くの設定があり、それらを駆使すればより柔軟なコマンドラインアプリケーションを作成することができます。
各種設定の詳細については本家ドキュメントを参照して頂くのが一番良いと思いますが、こちらにも簡単にではありますがほぼ全ての設定についてまとめてありますので参考にしてもらえればと思います。

emonuh
しがないシステムエンジニアです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした