【问题标题】:Split a string containing command-line parameters into a String[] in Java将包含命令行参数的字符串拆分为 Java 中的 String[]
【发布时间】:2011-03-16 14:33:57
【问题描述】:

类似于this thread for 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

我不需要担心任何 shell 扩展,但它必须足够聪明以处理单引号和双引号以及字符串中可能存在的任何转义。有人知道在这些条件下解析字符串的方法吗?

注意:我确实需要进行命令行解析,我已经在使用joptsimple 来做到这一点。相反,我想让我的程序易于编写脚本。例如,我希望用户能够在单个文件中放置一组命令,每个命令在命令行上都是有效的。例如,他们可能会在文件中键入以下内容:

--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[]——因此需要解析器。

【问题讨论】:

  • 你不能直接使用 main() 中给你的 args 数组而不是自己去解析它吗?
  • 我已经更新了我的问题,以描述为什么我需要解析字符串以及这与命令行解析有何不同。
  • 我认为它与命令行解析没有什么不同,请参阅我的答案的附录,了解我过去如何处理与此非常相似的事情。
  • 刚刚添加了一个您可能会觉得有用的简短答案 - 现在您已经为您的问题添加了一些解释:-)

标签: java parsing command-line-arguments


【解决方案1】:

Andreas_D's answer 上扩展,而不是复制,使用来自优秀的Plexus Common Utilities 库的CommandLineUtils.translateCommandline(String toProcess)

【讨论】:

    【解决方案2】:
    /**
     * [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】:

      如果您只需要支持类 UNIX 操作系统,还有一个更好的解决方案。与 ant 的 Commandline 不同,DrJava 的 ArgumentTokenizer 更像 sh-like:它支持转义!

      说真的,即使是像sh -c 'echo "\"un'\''kno\"wn\$\$\$'\'' with \$\"\$\$. \"zzz\""' 这样的疯狂 也会被正确标记为[bash, -c, echo "\"un'kno\"wn\$\$\$' with \$\"\$\$. \"zzz\""](顺便说一下,运行时,此命令会输出"un'kno"wn$$$' with $"$$. "zzz")。

      【讨论】:

        【解决方案4】:

        这是一个非常简单的替代方法,可以将文件中的文本行拆分为参数向量,以便您可以将其输入到选项解析器中:

        这是解决方案:

        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 放在类路径上,要么只使用 Commandline 类,因为使用的方法是静态的。

        【讨论】:

        • 作为文档,translateCommandline 处理单引号和双引号字符串并在其中转义,但由于在基于 DOS 的系统上导致的问题,它不能像 POSIX shell 那样识别反斜杠。跨度>
        • 有一个ant的源码分布。在这一点上,我将实现translateCommandline 并对其进行修改以满足我的需要。
        • 小心,\t\r\n 不是这个方法的空格
        • 还是唯一的办法吗?核心库中的任何内容?
        • 实现(第 337 行):translateCommandline
        【解决方案5】:

        你应该使用一个功能齐全的现代面向对象的命令行参数解析器我推荐我最喜欢的Java Simple Argument Parser。还有how to use JSAP,这是以Groovy 为例,但对于纯Java 也是如此。还有args4j,它在某些方面比 JSAP 更现代,因为它使用注解,远离 apache.commons.cli 的东西,它是旧的和破败的,它的 API 非常程序化和非 Java-eques。但我仍然依赖 JSAP,因为构建自己的自定义参数处理程序非常容易。

        有很多用于 URL、数字、InetAddress、颜色、日期、文件、类的默认解析器,添加您自己的非常容易。

        例如,这是一个将 args 映射到 Enums 的处理程序:

        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 有一种非常好的方式来声明代码之外的选项和设置,因此您的代码不会被数百行设置混乱和模糊真实功能代码,请参阅我在how to use JSAP 上的链接作为示例,代码比我尝试过的任何其他库都少。

        这是您在更新中阐明的问题的方向解决方案,“脚本”文件中的行仍然是命令行。从文件中逐行读取它们并调用JSAP.parse(String);

        我一直使用这种技术为网络应用程序提供“命令行”功能。一个特殊的用途是在具有 Director/Flash 前端的大型多人在线游戏中,我们可以像执行聊天一样从聊天中执行“命令”,并在后端使用 JSAP 来解析它们并根据解析的内容执行代码。非常像您想要做的,除了您从文件而不是套接字读取“命令”。我会放弃 joptsimple 而只使用 JSAP,你真的会被它强大的可扩展性所宠坏。

        【讨论】:

        • JSAP 是我见过的第一个接受字符串的解析器,但不幸的是,它返回 JSAPResult 而不是 String[],所以如果不切换,我将无法使用它我的命令行解析库:(.
        • a String[] 非常没用,JSAP 结果的全部原因是它为您完成了所有的解析和规则执行和检查。我认为,如果您真的退后一步,重新考虑您的方法,并且进行一些重构将确实是有益的。根据您上次的编辑查看我的更新。
        • 我不想构建一个 shell 字符串解析器。 line.split(" ") 不够聪明。正如我在帖子中指出的那样,它会在创建Array[3] 的参数上死掉,因为参数中可能同时包含空格和转义序列。我需要一个完整的解析器来处理所有的可能性——但我需要一个字符串到 String[] 解析器,而不是命令行解析器。
        • JSAP 可能需要花费一些时间阅读文档以了解它提供的选项,但它是一个非常好的解决命令行解析需求的解决方案,并且效果很好 - 绝对推荐...跨度>
        • 也许切换是你能做的最好的事情 joptsimple 可能对你的要求来说太“简单”了。
        【解决方案6】:

        我使用Java Getopt port 来做到这一点。

        【讨论】:

        • 除非我遗漏了什么,否则 getopt 端口不会接收字符串,只有 String[]
        • 能否详细说明如何使用?只是一个链接不太好。
        猜你喜欢
        • 2014-09-18
        • 2015-12-17
        • 1970-01-01
        • 2017-12-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多