-3

I'm creating a program that when launched takes in input a command and some arguments with scanf and calls execvp using those arguments. I'm doing this with strsep. I store the string in an array (char*),then I want to split it and store tokens in a new array (this time it's an array[] so I can use it with the execvp). Arguments saved with scanf should be commands for terminal (as "ls" with "-l" ecc,"pwd"... however variables saved in PATH) so they are separated by " ".

Ex : 

./mystring  
Type arguments : " ls -l "

It was an example only to specify which kind of input 'll be. I'll do the execvp alone,I need help to split the string in tokens. This is the code :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
 fflush(NULL); //to clean streams
 printf("type the command to start (and arguments if required) \n");
 char **st;
 char dest[10];
 scanf("%s",*st);
 int i;
 for (i=0;(dest[i]=strsep(st," "))!=NULL;i++)
    continue;
 for (int c=0;c<i;c++) printf(" arg %d : [%s] ",c,dest[c]);
 return 0;
}

Lines 5 and 6 required to call strsep,the 10 in dest[10] is symbolic.

Line 7 to store the input in st.

Line 9 should split on " " and store command and arguments in dest[I](that I'll pass to execvp).

Line 11 to print what dest has stored.

And this is the sad output :

./mystring 
type the command to start (and arguments if required) 
Segmentation fault: 11

I don't understand how strsep works, someone can help me?

Hackerman
  • 12,139
  • 2
  • 34
  • 45
feded
  • 109
  • 2
  • 13
  • yeah not related to strsep, `st` isn't initialized, and the pointer to the location isn't either – Jean-François Fabre Jun 18 '18 at 18:24
  • 1
    Please don't put line numbers in the code like that; it makes it a pain to work with your code. It's probably best to make them into comments, if the numbers are needed. – Jonathan Leffler Jun 18 '18 at 18:24
  • 2
    do `char st[20];` and `scanf("%19s",st)` that'll be better already – Jean-François Fabre Jun 18 '18 at 18:25
  • 2
    `fflush(NULL); //to clean streams` - Where did you read that this should be done? – Ed Heal Jun 18 '18 at 18:30
  • @EdHeal manual page of fflush : If the stream argument is NULL, fflush() flushes all open output streams – feded Jun 18 '18 at 18:56
  • @JonathanLeffler yes, you're right sorry guys – feded Jun 18 '18 at 18:58
  • 1
    Why do you need to do this? – Ed Heal Jun 18 '18 at 18:59
  • 1
    @EdHeal Well this is a good question,I thought to clean open streams first of use scanf because usually there are a lots of problems with it, it's a strange function and I don't know what it does in particular and I didn't know that it is usless at the beginning of the main. – feded Jun 18 '18 at 19:19
  • `fflush` is for output streams. You have done no output. `scanf` does not care about `fflush` – Ed Heal Jun 18 '18 at 19:21
  • You probably want `strtok`, not `strsep`. With strsep, `ls -l` is three tokens, while `ls -l` is two. With `strtok`, they are both two tokens, which is what almost everyone will expect. – rici Jun 18 '18 at 22:58
  • With `char **st; ... some_function(*st);`, `st` is not initialized, so `*st` is attempting to de-referencing an uninitialized pointer. – chux - Reinstate Monica Jun 19 '18 at 12:01

1 Answers1

3

You need to pass strsep() a pointer to the string to be analyzed. That means something more like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    printf("type the command to start (and arguments if required)\n");
    char *dest[10];
    char line[4096];
    if (fgets(line, sizeof(line), stdin) == 0)
        return 1;
    line[strcspn(line, "\n")] = '\0';  /* Zap trailing newline */
    char *st = line;
    int i;
    for (i = 0; i < 10 && (dest[i] = strsep(&st, " ")) != NULL; i++)
        ;
    for (int c = 0; c < i; c++)
        printf(" arg %d : [%s]\n", c, dest[c]);
    return 0;
}

Note the changes to dest and st — and the addition of spaces to make the code more easily readable (and newlines in the output). I removed the odd and irrelevant fflush(NULL), too — when a program starts, the buffers don't need flushing. The code removes the newline from the input; scanf() would not have included any newline. The notation scanf("%s", *st); in the question would only read one word from the input, even assuming that *st pointed to some allocated memory (which, in the question, it doesn't).

Note that description of strsep() from a macOS man page says:

char *strsep(char **stringp, const char *delim);

The strsep() function locates, in the string referenced by *stringp, the first occurrence of any character in the string delim (or the terminating '\0' character) and replaces it with a '\0'. The location of the next character after the delimiter character (or NULL, if the end of the string was reached) is stored in *stringp. The original value of *stringp is returned.

This implies that you need to pass a pointer to a char * variable, rather than the value of a char ** variable. The macOS man page also includes an example which shows similar code, passing &string to strsep() where char *string = …;. There is sometimes an art to reading a man page — and reading between the lines of the man page.

The code above (now tested) can be run to produce:

$ ./ss53
type the command to start (and arguments if required)
command-name-is-long a short arg
 arg 0 : [command-name-is-long]
 arg 1 : [a]
 arg 2 : [short]
 arg 3 : [arg]
$
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thank you very much,I have to study what you wrote,I'll be back soon :'),however I never used fgets,is it like scanf?And why return 1 below? – feded Jun 18 '18 at 19:10
  • I imagine that **line[strcspn(line, "\n")] = '\0';** replace **\n** with **\0**. I think that you do this because in this way the string is cleared by possible problems,so if this is correct **for (i = 0; i < 10 && (dest[i] = strsep(&st, " ")) != NULL; i++)** should split and store on **\0** and **" "**. – feded Jun 18 '18 at 19:22
  • 1
    `fgets()` is the standard C way to read a line of input, which seems likely to be what you want. `scanf("%s", str)` doesn't read a line; it reads a word. Since `fgets()` returns NULL on EOF (or an error), I return 1 from `main()` — without an error message since I'm lazy in this code. The `line[strcspn(line, "\n")] = '\0';` does indeed remove a trailing newline; you can use `"\r\n"` if you prefer, which will look for a CR (`'\r'`) or NL (`'\n'`) in the input, whichever comes first. However, that's usually not necessary. – Jonathan Leffler Jun 18 '18 at 19:26
  • (See also [Beginner's Guide Away From `scanf()`](http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html)). – Jonathan Leffler Jun 18 '18 at 19:27