2010-07-15 3 views
23

Similaire à this thread for C#, je dois diviser une chaîne contenant les arguments de la ligne de commande dans mon programme pour permettre aux utilisateurs d'exécuter facilement plusieurs commandes. Par exemple, je pourrais avoir la chaîne suivante:Fractionner une chaîne contenant des paramètres de ligne de commande en une chaîne [] en Java

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

Compte tenu de ce qui précède, Java passerait normalement les éléments suivants pour principal:

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

Je ne ai pas besoin de vous soucier de l'expansion du shell, mais il doit être assez intelligent pour gérer les guillemets simples et doubles et les échappements qui peuvent être présents dans la chaîne. Est-ce que quelqu'un connaît un moyen d'analyser la chaîne comme le shell dans ces conditions?

NOTE: Je ne pas besoin de faire l'analyse syntaxique de la ligne de commande, j'utilise déjà joptsimple pour le faire. Au contraire, je veux rendre mon programme facilement scriptable. Par exemple, je souhaite que l'utilisateur puisse placer dans un seul fichier un ensemble de commandes dont chacune serait valide sur la ligne de commande. Par exemple, ils pourraient taper la commande suivante dans un fichier:

--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 

Ensuite, l'utilisateur courraient mon outil d'administration comme suit:

adminTool --script /path/to/above/file 

main() vous trouverez alors l'option --script et itérer sur les différentes lignes dans le fichier, en séparant chaque ligne dans un tableau que je déclencherais alors à une instance de joptsimple qui serait alors passée dans mon pilote d'application. Joptsimple est livré avec un analyseur qui a un parse method, mais il ne prend en charge qu'un tableau String. De même, les constructeurs GetOpt nécessitent également un String[] - d'où la nécessité d'un analyseur.

+3

Ne pourriez-vous pas simplement utiliser le tableau args qui vous est donné dans main() au lieu d'essayer de l'analyser vous-même? –

+0

J'ai mis à jour ma question pour décrire pourquoi j'ai besoin d'analyser la chaîne et comment c'est différent de l'analyse en ligne de commande. –

+0

Je ne pense pas que ce soit différent de l'analyse en ligne de commande, voir l'addendum à ma réponse sur la façon dont j'ai abordé quelque chose de très similaire à cela dans le passé. –

Répondre

23

Voici une alternative assez facile pour diviser une ligne de texte à partir d'un fichier dans un vecteur d'argument afin que vous pouvez introduire dans votre analyseur d'options:

Ceci est la solution:

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); 
} 

Le classe magique Commandline fait partie de ant. Donc vous devez soit mettre fourmi sur le classpath ou simplement prendre la classe Commandline car la méthode utilisée est statique.

+0

Comme documentation, 'translateCommandline' gère les chaînes simples et doubles sont quotées et s'échappent en leur sein, mais ne reconnaissent pas les barres obliques inverses de la même manière qu'un shell POSIX en raison de problèmes provoqués par les systèmes basés sur DOS. –

+0

Il y a une distribution de source de fourmi. À ce stade, je prendrais l'implémentation de 'translateCommandline' et je le modifierais pour répondre à mes besoins. –

+0

Solution vraiment merveilleuse. Merci un million. Très propre. –

9

Moderne Parser d'argument de ligne de commande orienté objet Je suggère mes Java Simple Argument Parser préférés. Et how to use JSAP, cela utilise Groovy à titre d'exemple, mais il en est de même pour Java direct. Il y a aussi args4j qui est à certains égards plus moderne que JSAP car il utilise des annotations, reste à l'écart des choses apache.commons.cli, il est vieux et cassé et très procédural et non-Java-eques dans son API. Mais je retombe toujours sur JSAP parce qu'il est si facile de construire vos propres gestionnaires d'arguments personnalisés.

Il existe de nombreux analyseurs par défaut pour les URL, les numéros, InetAddress, Color, Date, File, Class, et il est très facile d'ajouter les vôtres.

Par exemple ici est un gestionnaire de carte args à énumérations:

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"); 
     } 
    } 
} 

et je ne suis pas fan de la programmation de configuration via XML, mais JSAP a une façon très agréable de déclarer les options et les paramètres en dehors de votre code , donc votre code n'est pas jonché de centaines de lignes de configuration qui encombre et obscurcit le vrai code fonctionnel, voir mon lien sur how to use JSAP pour un exemple, moins de code que toutes les autres bibliothèques que j'ai essayées.

C'est une solution de direction à votre problème tel que précisé par votre mise à jour, les lignes dans votre fichier « script » sont toujours commander des lignes. Lisez-les à partir du fichier ligne par ligne et appelez JSAP.parse(String);. J'utilise cette technique pour fournir des fonctionnalités de «ligne de commande» aux applications Web tout le temps. Une utilisation particulière était dans un jeu en ligne massivement multijoueur avec un front office Director/Flash que nous avons activé l'exécution de "commandes" du chat comme et utilisé JSAP sur le backend pour les analyser et exécuter du code basé sur ce qu'il analyse. Tout à fait comme ce que vous voulez faire, sauf que vous lisez les "commandes" d'un fichier au lieu d'un socket. Je jetterais joptsimple et juste utiliser JSAP, vous serez vraiment gâté par son extensibilité puissante.

+0

JSAP est le premier analyseur que j'ai vu accepter une chaîne mais, malheureusement, il renvoie un 'JSAPResult' plutôt qu'un 'String []', donc je ne serai pas capable de l'utiliser sans passer par ma bibliothèque d'analyse de ligne de commande :(. –

+0

a 'String []' est assez inutile, la raison complète du résultat JSAP est qu'il fait tout l'analyse et l'application des règles et la vérification pour vous Je pense que si vous reculez vraiment d'où vous êtes en repensant votre approche et certains refactoring sera vraiment bénéfique.Voir ma mise à jour basée sur votre dernière édition –

+0

Je ne veux pas construire un interpréteur de chaînes shell. line.split ("") 'n'est pas assez intelligent.Il mourrait sur le paramètre qui crée 'Array [3]' comme je l'ai indiqué dans mon message car les paramètres peuvent contenir à la fois des espaces et des séquences d'échappement. J'ai besoin d'un analyseur complet pour gérer toutes les possibilités - mais j'ai besoin d'un analyseur de chaînes pour String [], plutôt que d'un analyseur de ligne de commande. –

-1

je suppose que celui-ci vous aidera à:

CLI

+0

c'est vieux et cassé et une suggestion terrible, il y a au moins 3 plus récents supportés et non alternatives buggy complet à ce sujet, ne l'utilisez pas. –

+3

@fuzzy Bon à savoir. Mais downvoting n'était pas nécessaire car cette réponse est correcte. ;-) – InsertNickHere

+0

mauvais conseil est pire que pas de conseils :-( –

-1

J'utilise le Java Getopt port pour le faire.

+1

À moins d'avoir manqué quelque chose, le port getopt ne fonctionne pas prendre une chaîne, seulement un 'String []'. –

+0

pourriez-vous élaborer sur la façon de l'utiliser? Juste un lien n'est pas si bon –

8

Si vous ne devez prendre en charge que les systèmes d'exploitation de type UNIX, il existe une solution encore meilleure:. Contrairement à Commandline de fourmi, ArgumentTokenizer de DrJava est plus sh-like: il prend en charge les évasions!

Sérieusement, même quelque chose fou comme sh -c 'echo "\"un'\''kno\"wn\$\$\$'\'' with \$\"\$\$. \"zzz\""' se correctement dans tokenisé [bash, -c, echo "\"un'kno\"wn\$\$\$' with \$\"\$\$. \"zzz\""] (Soit dit en passant, lorsqu'il est exécuté, cette commande affiche "un'kno"wn$$$' with $"$$. "zzz").

1
/** 
* [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()]); 
} 
Questions connexes