web-dev-qa-db-ja.com

コマンドラインパラメータを含む文字列をJava)のString []に分割します

C#のこのスレッド と同様に、ユーザーが複数のコマンドを簡単に実行できるように、プログラムへのコマンドライン引数を含む文字列を分割する必要があります。たとえば、次の文字列があるとします。

_-p /path -d "here's my description" --verbose other args
_

上記の場合、Javaは通常、以下をmainに渡します。

_Array[0] = -p
Array[1] = /path
Array[2] = -d
Array[3] = here's my description
Array[4] = --verbose
Array[5] = other
Array[6] = args
_

シェルの拡張について心配する必要はありませんが、一重引用符と二重引用符、および文字列内に存在する可能性のあるエスケープを処理するのに十分スマートである必要があります。これらの条件下でシェルが行うように文字列を解析する方法を知っている人はいますか?

[〜#〜]注[〜#〜]:私は[〜#〜]しない[〜# 〜]コマンドライン解析を行う必要があります。私はすでに joptsimple を使用しています。むしろ、プログラムを簡単にスクリプト化できるようにしたいのです。たとえば、ユーザーが1つのファイル内に、それぞれがコマンドラインで有効になる一連のコマンドを配置できるようにしたい場合があります。たとえば、ファイルに次のように入力する場合があります。

_--addUser admin --password Admin --roles administrator,editor,reviewer,auditor
--addUser editor --password Editor --roles editor
--addUser reviewer --password Reviewer --roles reviewer
--addUser auditor --password Auditor --roles auditor
_

次に、ユーザーは次のように私の管理ツールを実行します。

_adminTool --script /path/to/above/file
_

次に、main()は_--script_オプションを見つけて、ファイル内のさまざまな行を繰り返し処理し、各行を配列に分割して、joptsimpleインスタンスで再起動します。アプリケーションドライバー。

joptsimpleには、 parse method を持つパーサーが付属していますが、サポートするのはString配列のみです。同様に、 GetOpt コンストラクターにも_String[]_が必要です。したがって、パーサーが必要です。

27
Kaleb Pederson

これは、ファイルのテキスト行を引数ベクトルに分割して、オプションパーサーにフィードできるようにするための非常に簡単な代替手段です。

これが解決策です:

public static void main(String[] args) {
    String myArgs[] = Commandline.translateCommandline("-a hello -b world -c \"Hello world\"");
    for (String arg:myArgs)
        System.out.println(arg);
}

魔法のクラス Commandlineantの一部です。したがって、使用されるメソッドは静的であるため、クラスパスにantを配置するか、コマンドラインクラスを取得する必要があります。

24
Andreas_D

フル機能のmodernオブジェクト指向のコマンドライン引数パーサーを使用する必要があります。私のお気に入りの Java Simple Argument Parser をお勧めします。そして JSAPの使い方 、これは例としてGroovyを使用していますが、ストレートJavaでも同じです。 args4j もあります。これは、アノテーションを使用し、Apache.commons.cliのものに近づかないため、JSAPよりもいくつかの点で最新です。古くて破壊されており、非常に手続き型で非Java風です。そのAPIで。しかし、独自のカスタム引数ハンドラーを作成するのはとても簡単なので、私はまだJSAPに頼っています。

URL、Numbers、InetAddress、Color、Date、File、Classにはデフォルトのパーサーがたくさんあり、独自のパーサーを追加するのは非常に簡単です。

たとえば、引数を列挙型にマップするハンドラーは次のとおりです。

_import com.martiansoftware.jsap.ParseException;
import com.martiansoftware.jsap.PropertyStringParser;

/*
This is a StringParser implementation that maps a String to an Enum instance using Enum.valueOf()
 */
public class EnumStringParser extends PropertyStringParser
{
    public Object parse(final String s) throws ParseException
    {
        try
        {
            final Class klass = Class.forName(super.getProperty("klass"));
            return Enum.valueOf(klass, s.toUpperCase());
        }
        catch (ClassNotFoundException e)
        {
            throw new ParseException(super.getProperty("klass") + " could not be found on the classpath");
        }
    }
}
_

私はXMLを介した構成プログラミングのファンではありませんが、JSAPにはコードの外部でオプションと設定を宣言するための非常に優れた方法があるため、実際の関数型コードを乱雑にしたり覆い隠したりする数百行のセットアップがコードに散らばることはありません。 JSAPの使用方法 のリンクを参照してください。例として、私が試した他のどのライブラリよりも少ないコードです。

これは、更新で明確にされているように、問題の方向性の解決策です。 "ファイルはまだコマンドラインです。それらをファイルから1行ずつ読み取り、 JSAP.parse(String); を呼び出します。

私はこの手法を使用して、Webアプリに「コマンドライン」機能を常に提供しています。特定の用途の1つは、Director/Flashフロントエンドを備えたMassivelyMultiplayer Online Gameで、チャットのように「コマンド」を実行できるようにし、バックエンドでJSAPを使用してそれらを解析し、解析内容に基づいてコードを実行しました。ソケットではなくファイルから「コマンド」を読み取ることを除けば、やりたいことと非常によく似ています。私はjoptsimpleを捨てて、JSAPを使用するだけですが、その強力な拡張性に本当に甘やかされてしまいます。

9
user177800

UNIXライクなOSのみをサポートする必要がある場合は、さらに優れたソリューションがあります。 antのCommandlineとは異なり、DrJavaの ArgumentTokenizershに似ています。エスケープをサポートしています!

真剣に、sh -c 'echo "\"un'\''kno\"wn\$\$\$'\'' with \$\"\$\$. \"zzz\""'のような非常識なでさえ[bash, -c, echo "\"un'kno\"wn\$\$\$' with \$\"\$\$. \"zzz\""]に適切にトークン化されます(ちなみに、このコマンドを実行すると、"un'kno"wn$$$' with $"$$. "zzz")。

8
nvamelichev
/**
 * [code borrowed from ant.jar]
 * Crack a command line.
 * @param toProcess the command line to process.
 * @return the command line broken into strings.
 * An empty or null toProcess parameter results in a zero sized array.
 */
public static String[] translateCommandline(String toProcess) {
    if (toProcess == null || toProcess.length() == 0) {
        //no command? no string
        return new String[0];
    }
    // parse with a simple finite state machine

    final int normal = 0;
    final int inQuote = 1;
    final int inDoubleQuote = 2;
    int state = normal;
    final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
    final ArrayList<String> result = new ArrayList<String>();
    final StringBuilder current = new StringBuilder();
    boolean lastTokenHasBeenQuoted = false;

    while (tok.hasMoreTokens()) {
        String nextTok = tok.nextToken();
        switch (state) {
        case inQuote:
            if ("\'".equals(nextTok)) {
                lastTokenHasBeenQuoted = true;
                state = normal;
            } else {
                current.append(nextTok);
            }
            break;
        case inDoubleQuote:
            if ("\"".equals(nextTok)) {
                lastTokenHasBeenQuoted = true;
                state = normal;
            } else {
                current.append(nextTok);
            }
            break;
        default:
            if ("\'".equals(nextTok)) {
                state = inQuote;
            } else if ("\"".equals(nextTok)) {
                state = inDoubleQuote;
            } else if (" ".equals(nextTok)) {
                if (lastTokenHasBeenQuoted || current.length() != 0) {
                    result.add(current.toString());
                    current.setLength(0);
                }
            } else {
                current.append(nextTok);
            }
            lastTokenHasBeenQuoted = false;
            break;
        }
    }
    if (lastTokenHasBeenQuoted || current.length() != 0) {
        result.add(current.toString());
    }
    if (state == inQuote || state == inDoubleQuote) {
        throw new RuntimeException("unbalanced quotes in " + toProcess);
    }
    return result.toArray(new String[result.size()]);
}
3
qiangbro

Andreas_Dの答え を拡張すると、コピーする代わりに、優れた Plexus Common Utilities ライブラリのCommandLineUtils.translateCommandline(String toProcess)を使用します。

1
Sagi