Existe-t-il un moyen en C d’analyser un texte et d’obtenir des valeurs pour argv et argc, comme si le texte avait été transmis à une application sur la ligne de commande?
Cela ne doit pas forcément fonctionner sous Windows, mais uniquement sous Linux - je ne me soucie pas non plus de citer des arguments.
Si la solution Glib est excessive pour votre cas, vous pouvez envisager de la coder vous-même.
Ensuite vous pouvez:
Le diagramme ci-dessous devrait clarifier (espérons-le):
aa bbb ccc "dd d" ee <- original string
aa0bbb0ccc00dd d00ee0 <- transformed string
| | | | |
argv[0] __/ / / / /
argv[1] ____/ / / /
argv[2] _______/ / /
argv[3] ___________/ /
argv[4] ________________/
Une API possible pourrait être:
char **parseargs(char *arguments, int *argc);
void freeparsedargs(char **argv);
Vous aurez besoin de considérations supplémentaires pour mettre en œuvre freeparsedargs () en toute sécurité.
Si votre chaîne est très longue et que vous ne voulez pas balayer deux fois, vous pouvez envisager des alternatives, telles que l'allocation de davantage d'éléments pour les tableaux argv (et la réaffectation si nécessaire).
EDIT: Solution proposée (ne pas gérer l'argument cité).
#include <stdio.h>
static int setargs(char *args, char **argv)
{
int count = 0;
while (isspace(*args)) ++args;
while (*args) {
if (argv) argv[count] = args;
while (*args && !isspace(*args)) ++args;
if (argv && *args) *args++ = '\0';
while (isspace(*args)) ++args;
count++;
}
return count;
}
char **parsedargs(char *args, int *argc)
{
char **argv = NULL;
int argn = 0;
if (args && *args
&& (args = strdup(args))
&& (argn = setargs(args,NULL))
&& (argv = malloc((argn+1) * sizeof(char *)))) {
*argv++ = args;
argn = setargs(args,argv);
}
if (args && !argv) free(args);
*argc = argn;
return argv;
}
void freeparsedargs(char **argv)
{
if (argv) {
free(argv[-1]);
free(argv-1);
}
}
int main(int argc, char *argv[])
{
int i;
char **av;
int ac;
char *as = NULL;
if (argc > 1) as = argv[1];
av = parsedargs(as,&ac);
printf("== %d\n",ac);
for (i = 0; i < ac; i++)
printf("[%s]\n",av[i]);
freeparsedargs(av);
exit(0);
}
Je suis surpris que personne n'ait fourni la réponse la plus simple en utilisant les fonctionnalités POSIX standard:
http://www.opengroup.org/onlinepubs/9699919799/functions/wordexp.html
Voici ma contribution. C'est sympa et court, mais il faut se méfier:
Le code:
enum { kMaxArgs = 64 };
int argc = 0;
char *argv[kMaxArgs];
char *p2 = strtok(commandLine, " ");
while (p2 && argc < kMaxArgs-1)
{
argv[argc++] = p2;
p2 = strtok(0, " ");
}
argv[argc] = 0;
Vous pouvez maintenant utiliser argc et argv, ou les transmettre à d'autres fonctions déclarées comme "foo (int argc, char ** argv)".
Le toujours-merveilleux glib a g_Shell_parse_args()
qui sonne comme ce que vous êtes après.
Si vous ne souhaitez même pas citer, cela pourrait être exagéré. Tout ce que vous avez à faire est de créer une marque, en utilisant un espace comme caractère de marque. Écrire une routine simple pour faire cela ne devrait pas prendre longtemps, vraiment.
Si vous n'êtes pas très avare de mémoire, le faire en un seul passage sans réaffectation devrait être facile; Supposons simplement que chaque deuxième caractère représente un espace, dans le cas le plus défavorable, supposant ainsi qu'une chaîne de caractères n
contient au plus __ arguments (n + 1) / 2
et (bien entendu) au plus n
octets de texte d'argument (à l'exclusion des terminateurs).
Voici une solution pour Windows et Unix (testée sur Linux, OSX et Windows). Testé avec Valgrind et Dr. Memory .
Il utilise wordexp pour les systèmes POSIX et CommandLineToArgvW pour Windows.
Notez que pour la solution Windows, la plupart du code est converti entre char **
et wchar_t **
avec la belle API Win32, car il n’existe pas de variable CommandLineToArgvA
(version ANSI).
#ifdef _WIN32
#include <windows.h>
#else
#include <wordexp.h>
#endif
char **split_commandline(const char *cmdline, int *argc)
{
int i;
char **argv = NULL;
assert(argc);
if (!cmdline)
{
return NULL;
}
// Posix.
#ifndef _WIN32
{
wordexp_t p;
// Note! This expands Shell variables.
if (wordexp(cmdline, &p, 0))
{
return NULL;
}
*argc = p.we_wordc;
if (!(argv = calloc(*argc, sizeof(char *))))
{
goto fail;
}
for (i = 0; i < p.we_wordc; i++)
{
if (!(argv[i] = strdup(p.we_wordv[i])))
{
goto fail;
}
}
wordfree(&p);
return argv;
fail:
wordfree(&p);
}
#else // WIN32
{
wchar_t **wargs = NULL;
size_t needed = 0;
wchar_t *cmdlinew = NULL;
size_t len = strlen(cmdline) + 1;
if (!(cmdlinew = calloc(len, sizeof(wchar_t))))
goto fail;
if (!MultiByteToWideChar(CP_ACP, 0, cmdline, -1, cmdlinew, len))
goto fail;
if (!(wargs = CommandLineToArgvW(cmdlinew, argc)))
goto fail;
if (!(argv = calloc(*argc, sizeof(char *))))
goto fail;
// Convert from wchar_t * to ANSI char *
for (i = 0; i < *argc; i++)
{
// Get the size needed for the target buffer.
// CP_ACP = Ansi Codepage.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
NULL, 0, NULL, NULL);
if (!(argv[i] = malloc(needed)))
goto fail;
// Do the conversion.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
argv[i], needed, NULL, NULL);
}
if (wargs) LocalFree(wargs);
if (cmdlinew) free(cmdlinew);
return argv;
fail:
if (wargs) LocalFree(wargs);
if (cmdlinew) free(cmdlinew);
}
#endif // WIN32
if (argv)
{
for (i = 0; i < *argc; i++)
{
if (argv[i])
{
free(argv[i]);
}
}
free(argv);
}
return NULL;
}
Je viens de faire cela pour un projet intégré en langage C où j'ai un petit CLI qui analyse l'entrée du port série et exécute un ensemble limité de commandes avec les paramètres.
Ce n’est probablement pas le mieux, mais il est aussi petit et efficace que possible:
int makeargs(char *args, int *argc, char ***aa) {
char *buf = strdup(args);
int c = 1;
char *delim;
char **argv = calloc(c, sizeof (char *));
argv[0] = buf;
while (delim = strchr(argv[c - 1], ' ')) {
argv = realloc(argv, (c + 1) * sizeof (char *));
argv[c] = delim + 1;
*delim = 0x00;
c++;
}
*argc = c;
*aa = argv;
return c;
}
tester:
int main(void) {
char **myargs;
int argc;
int numargs = makeargs("Hello world, this is a test", &argc, &myargs);
while (numargs) {
printf("%s\r\n", myargs[argc - numargs--]);
};
return (EXIT_SUCCESS);
}
LIBTINYC de Matt Peitrek a un module appelé argcargv.cpp qui prend une chaîne et l'analyse dans le tableau d'arguments en prenant en compte les arguments cités. Notez que cela est spécifique à Windows, mais que c'est assez simple et qu'il devrait donc être facile de passer à la plate-forme de votre choix.
J'ai fini par écrire une fonction pour le faire moi-même, je ne pense pas que ce soit très bien, mais cela fonctionne pour mon propos - n'hésitez pas à suggérer des améliorations pour ceux qui en ont besoin à l'avenir:
void parseCommandLine(char* cmdLineTxt, char*** argv, int* argc){
int count = 1;
char *cmdLineCopy = strdupa(cmdLineTxt);
char* match = strtok(cmdLineCopy, " ");
// First, count the number of arguments
while(match != NULL){
count++;
match = strtok(NULL, " ");
}
*argv = malloc(sizeof(char*) * (count+1));
(*argv)[count] = 0;
**argv = strdup("test"); // The program name would normally go in here
if (count > 1){
int i=1;
cmdLineCopy = strdupa(cmdLineTxt);
match = strtok(cmdLineCopy, " ");
do{
(*argv)[i++] = strdup(match);
match = strtok(NULL, " ");
} while(match != NULL);
}
*argc = count;
}
Celui-ci que j'ai écrit considère également les guillemets
N'hésitez pas à contribuer.
/*
Tokenize string considering also quotes.
By Zibri <zibri AT zibri DOT org>
https://github.com/Zibri/tokenize
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
int main(int argc, char *argv[])
{
char *str1, *token;
int j;
char *qstart = NULL;
bool quoted = false;
if (argc != 2) {
fprintf(stderr, "Usage: %s string\n", argv[0]);
exit(EXIT_FAILURE);
}
for (j = 1, str1 = argv[1];; j++, str1 = NULL) {
token = strtok(str1, " ");
if (token == NULL)
break;
if ((token[0] == 0x27) || (token[0] == 0x22)) {
qstart = token + 1;
quoted = true;
}
if ((token[strlen(token) - 1] == 0x27) || (token[strlen(token) - 1] == 0x22)) {
quoted = false;
token[strlen(token) - 1] = 0;
printf("%d: %s\n", j, qstart);
} else {
if (quoted) {
token[strlen(token)] = 0x20;
j--;
} else
printf("%d: %s\n", j, token);
}
}
if (quoted) {
fprintf(stderr, "String quoting error\n");
return EXIT_FAILURE;
} else
return EXIT_SUCCESS;
}
Exemple de sortie:
$ ./tokenize "1 2 3 '4 5 6' 7 8 \"test abc\" 10 11"
1: 1
2: 2
3: 3
4: 4 5 6
5: 7
6: 8
7: test abc
8: 10
9: 11
Solution pour ceux qui ne veulent pas utiliser l'allocation de mémoire dynamique (par exemple, intégrée)
J'ai écrit tokenise_to_argc_argv()
pour un projet incorporé, qui utilise strtok_r()
comme base pour la création d'une chaîne de commande sous la forme argc et argv. Contrairement à la plupart des réponses ici, j'alloue généralement de la mémoire de manière statique. Ainsi, mon implémentation suppose que vous avez une limite supérieure de argv_length
. Pour la plupart des applications intégrées classiques, cela est largement suffisant. J'ai inclus un exemple de code ci-dessous pour que vous puissiez l'utiliser rapidement.
int tokenise_to_argc_argv(
char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise
int *argc, ///< Out : Argument Count
char *argv[], ///< Out : Argument String Vector Array
const int argv_length ///< In : Maximum Count For `*argv[]`
)
{ /* Tokenise string buffer into argc and argv format (req: string.h) */
int i = 0;
for (i = 0 ; i < argv_length ; i++)
{ /* Fill argv via strtok_r() */
if ( NULL == (argv[i] = strtok_r( NULL , " ", &buffer)) ) break;
}
*argc = i;
return i; // Argument Count
}
\0
dans le tampon pour délimiter les jetons de chaîne)." "
espace comme seul séparateur. Ceci émule le comportement main(int argc, char *argv[])
dans les interfaces de ligne de commande typiques.strtok_r()
est utilisé parce qu'il est threadsafe (étant donné que strtok()
utilise un pointeur statique interne). En outre, il fait partie de la bibliothèque standard C string.h
est donc très portable.Vous trouverez ci-dessous le code de démonstration ainsi que sa sortie. De plus, cela montre que tokenise_to_argc_argv () peut gérer la plupart des cas de chaîne et a donc été testé. De plus, cette fonction ne repose pas sur malloc ou calloc et convient donc à une utilisation intégrée (après utilisation de types stdint.h
).
Code de démonstration
/*******************************************************************************
Tokenise String Buffer To Argc and Argv Style Format
Brian Khuu 2017
*******************************************************************************/
#include <stdio.h> // printf()
#include <ctype.h> // isprint()
#include <string.h> // strtok_r()
/**-----------------------------------------------------------------------------
@brief Tokenise a string buffer into argc and argv format
Tokenise string buffer to argc and argv form via strtok_r()
Warning: Using strtok_r will modify the string buffer
Returns: Number of tokens extracted
------------------------------------------------------------------------------*/
int tokenise_to_argc_argv(
char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise
int *argc, ///< Out : Argument Count
char *argv[], ///< Out : Argument String Vector Array
const int argv_length ///< In : Maximum Count For `*argv[]`
)
{ /* Tokenise string buffer into argc and argv format (req: string.h) */
int i = 0;
for (i = 0 ; i < argv_length ; i++)
{ /* Fill argv via strtok_r() */
if ( NULL == (argv[i] = strtok_r( NULL, " ", &buffer)) ) break;
}
*argc = i;
return i; // Argument Count
}
/*******************************************************************************
Demonstration of tokenise_to_argc_argv()
*******************************************************************************/
static void print_buffer(char *buffer, int size);
static void print_argc_argv(int argc, char *argv[]);
static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size);
int main(void)
{ /* This shows various string examples */
printf("# `tokenise_to_argc_argv()` Examples\n");
{ printf("## Case0: Normal\n");
char buffer[] = "tokenising example";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case1: Empty String\n");
char buffer[] = "";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case2: Extra Space\n");
char buffer[] = "extra space here";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case3: One Word String\n");
char buffer[] = "one-Word";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
}
static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size)
{ /* This demonstrates usage of tokenise_to_argc_argv */
int argc = 0;
char *argv[10] = {0};
printf("* **Initial State**\n");
print_buffer(buffer, buffer_size);
/* Tokenise Command Buffer */
tokenise_to_argc_argv(buffer, &argc, argv, sizeof(argv));
printf("* **After Tokenizing**\n");
print_buffer(buffer, buffer_size);
print_argc_argv(argc,argv);
printf("\n\n");
}
static void print_buffer(char *buffer, int size)
{
printf(" - Buffer Content `");
for (int i = 0 ; i < size; i++) printf("%c",isprint(buffer[i])?buffer[i]:'0');
printf("` | HEX: ");
for (int i = 0 ; i < size; i++) printf("%02X ", buffer[i]);
printf("\n");
}
static void print_argc_argv(int argc, char *argv[])
{ /* This displays the content of argc and argv */
printf("* **Argv content** (argc = %d): %s\n", argc, argc ? "":"Argv Is Empty");
for (int i = 0 ; i < argc ; i++) printf(" - `argv[%d]` = `%s`\n", i, argv[i]);
}
Sortie
tokenise_to_argc_argv()
Exemplestokenising example0
| HEX: 74 6F 6B 65 6E 69 73 69 6E 67 20 65 78 61 6D 70 6C 65 00 tokenising0example0
| HEX: 74 6F 6B 65 6E 69 73 69 6E 67 00 65 65 61 61 6 D 70 6 C 65 00 argv[0]
= tokenising
argv[1]
= example
0
| HEX: 00 0
| HEX: 00 extra space here0
| HEX: 65 78 74 72 61 20 20 73 70 61 63 65 20 68 65 72 65 00 extra0 space0here0
| HEX: 65 78 74 72 61 00 20 73 70 61 63 65 00 68 65 72 65 00 argv[0]
= extra
argv[1]
= space
argv[2]
= here
one-Word0
| HEX: 6F 6E 65 2D 77 6F 72 64 00 one-Word0
| HEX: 6F 6E 65 2D 77 6F 72 64 00 argv[0]
= one-Word
String.h ou strtok_r () manquants dans votre chaîne d'outils?
Si pour une raison quelconque votre chaîne d’outils n’a pas strtok_r (). Vous pouvez utiliser cette version simplifiée de strtok_r () . Il s'agit d'une version modifiée de l'implémentation GNU C de strtok_r (), mais simplifiée pour ne prendre en charge que les caractères d'espace.
Pour l'utiliser, placez-le simplement au-dessus de tokenise_to_argc_argv()
, puis remplacez strtok_r( NULL, " ", &buffer)
Par strtok_space(&buffer)
/**-----------------------------------------------------------------------------
@brief Simplied space deliminated only version of strtok_r()
- save_ptr : In/Out pointer to a string. This pointer is incremented by this
function to find and mark the token boundry via a `\0` marker.
It is also used by this function to find mutiple other tokens
via repeated calls.
Returns:
- NULL : No token found
- pointer to start of a discovered token
------------------------------------------------------------------------------*/
char * strtok_space(char **save_ptr)
{ /* strtok_space is slightly modified from GNU C Library `strtok_r()` implementation.
Thus this function is also licenced as GNU Lesser General Public License*/
char *start = *save_ptr;
char *end = 0;
if (*start == '\0') {
*save_ptr = start;
return NULL;
}
/* Scan leading delimiters. */
while(*start == ' ') start++;
if (*start == '\0') {
*save_ptr = start;
return NULL;
}
/* Find the end of the token. */
end = start;
while((*end != '\0') && (*end != ' ')) end++;
if (*end == '\0') {
*save_ptr = end;
return start;
}
/* Terminate the token and make *SAVE_PTR point past it. */
*end = '\0';
*save_ptr = end + 1;
return start;
}
#include <cctype> // <ctype.h> for isspace()
/**
* Parse out the next non-space Word from a string.
* @note No nullptr protection
* @param str [IN] Pointer to pointer to the string. Nested pointer to string will be changed.
* @param Word [OUT] Pointer to pointer of next Word. To be filled.
* @return pointer to string - current cursor. Check it for '\0' to stop calling this function
*/
static char* splitArgv(char **str, char **Word)
{
constexpr char QUOTE = '\'';
bool inquotes = false;
// optimization
if( **str == 0 )
return NULL;
// Skip leading spaces.
while (**str && isspace(**str))
(*str)++;
if( **str == '\0')
return NULL;
// Phrase in quotes is one arg
if( **str == QUOTE ){
(*str)++;
inquotes = true;
}
// Set phrase begining
*Word = *str;
// Skip all chars if in quotes
if( inquotes ){
while( **str && **str!=QUOTE )
(*str)++;
//if( **str!= QUOTE )
}else{
// Skip non-space characters.
while( **str && !isspace(**str) )
(*str)++;
}
// Null terminate the phrase and set `str` pointer to next symbol
if(**str)
*(*str)++ = '\0';
return *str;
}
/// To support standart convetion last `argv[argc]` will be set to `NULL`
///\param[IN] str : Input string. Will be changed - splitted to substrings
///\param[IN] argc_MAX : Maximum a rgc, in other words size of input array \p argv
///\param[OUT] argc : Number of arguments to be filled
///\param[OUT] argv : Array of c-string pointers to be filled. All of these strings are substrings of \p str
///\return Pointer to the rest of string. Check if for '\0' and know if there is still something to parse. \
/// If result !='\0' then \p argc_MAX is too small to parse all.
char* parseStrToArgcArgvInsitu( char *str, const int argc_MAX, int *argc, char* argv[] )
{
*argc = 0;
while( *argc<argc_MAX-1 && splitArgv(&str, &argv[*argc]) ){
++(*argc);
if( *str == '\0' )
break;
}
argv[*argc] = nullptr;
return str;
};
#include <iostream>
using namespace std;
void parseAndPrintOneString(char *input)
{
constexpr size_t argc_MAX = 5;
char* v[argc_MAX] = {0};
int c=0;
char* rest = parseStrToArgcArgvInsitu(input,argc_MAX,&c,v);
if( *rest!='\0' ) // or more clear `strlen(rest)==0` but not efficient
cout<<"There is still something to parse. argc_MAX is too small."<<endl;
cout << "argc : "<< c << endl;
for( int i=0; i<c; i++ )
cout<<"argv["<<i<<"] : "<<v[i] <<endl;
/*//or condition is `v[i]`
for( int i=0; v[i]; i++ )
cout<<"argv["<<i<<"] : "<<v[i] <<endl;*/
}
int main(int argc, char* argv[])
{
char inputs[][500] ={
"Just another TEST\r\n"
, " Hello my world 'in quotes' \t !"
, "./hi 'Less is more'"
, "Very long line with \"double quotes\" should be parsed several times if argv[] buffer is small"
, " \t\f \r\n"
};
for( int i=0; i<5; ++i ){
cout<<"Parsing line \""<<inputs[i]<<"\":"<<endl;
parseAndPrintOneString(inputs[i]);
cout<<endl;
}
}
Parsing line "Just another TEST\r\n":
argc : 3
argv[0] : Just
argv[1] : another
argv[2] : TEST
Parsing line " Hello my world 'in quotes' !":
There is still something to parse. argc_MAX is too small.
argc : 4
argv[0] : Hello
argv[1] : my
argv[2] : world
argv[3] : in quotes
Parsing line "./hi 'Less is more'":
argc : 2
argv[0] : ./hi
argv[1] : Less is more
Parsing line "Very long line with "double quotes" should be parsed several times if argv[] buffer is small":
There is still something to parse. argc_MAX is too small.
argc : 4
argv[0] : Very
argv[1] : long
argv[2] : line
argv[3] : with
Parsing line "
":
argc : 0
Malheureusement, C++, mais pour les autres qui pourraient rechercher ce type de bibliothèque, je recommande:
ParamContainer - Analyseur de paramètres en ligne de commande facile à utiliser
Vraiment petit et très facile.
p.addParam("long-name", 'n', ParamContainer::regular,
"parameter description", "default_value");
nom du programme - long-nom = valeur
cout << p["long-name"];
>> value
Selon mon expérience: