Create a shell in c that recognizes comments - c

I am trying to write a shell in c that will recognize # as a comment character. For example, if I type the command "ls # This is a comment", the program should not recognize the characters after the #.
Here is the code I currently have:
/* See Chapter 5 of Advanced UNIX Programming: http://www.basepath.com/aup/
* for further related examples of systems programming. (That home page
* has pointers to download this chapter free.
*
* Copyright (c) Gene Cooperman, 2006; May be freely copied as long as this
* copyright notice remains. There is no warranty.
*/
/* To know which "includes" to ask for, do 'man' on each system call used.
* For example, "man fork" (or "man 2 fork" or man -s 2 fork") requires:
* <sys/types.h> and <unistd.h>
*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#define MAXLINE 200 /* This is how we declare constants in C */
#define MAXARGS 20
/* In C, "static" means not visible outside of file. This is different
* from the usage of "static" in Java.
* Note that end_ptr is an output parameter.
*/
static char * getword(char * begin, char **end_ptr) {
char * end = begin;
while ( *begin == ' ' )
begin++; /* Get rid of leading spaces. */
end = begin;
while ( *end != '\0' && *end != '\n' && *end != ' ' )
end++; /* Keep going. */
if ( end == begin )
return NULL; /* if no more words, return NULL */
*end = '\0'; /* else put string terminator at end of this word. */
*end_ptr = end;
if (begin[0] == '$') { /* if this is a variable to be expanded */
begin = getenv(begin+1); /* begin+1, to skip past '$' */
if (begin == NULL) {
perror("getenv");
begin = "UNDEFINED";
}
}
return begin; /* This word is now a null-terminated string. return it. */
}
/* In C, "int" is used instead of "bool", and "0" means false, any
* non-zero number (traditionally "1") means true.
*/
/* argc is _count_ of args (*argcp == argc); argv is array of arg _values_*/
static void getargs(char cmd[], int *argcp, char *argv[])
{
char *cmdp = cmd;
char *end;
int i = 0;
/* fgets creates null-terminated string. stdin is pre-defined C constant
* for standard intput. feof(stdin) tests for file:end-of-file.
*/
if (fgets(cmd, MAXLINE, stdin) == NULL && feof(stdin)) {
printf("Couldn't read from standard input. End of file? Exiting ...\n");
exit(1); /* any non-zero value for exit means failure. */
}
while ( (cmdp = getword(cmdp, &end)) != NULL ) { /* end is output param */
/* getword converts word into null-terminated string */
if (strchr(cmdp, '#') != NULL) {
}
argv[i++] = cmdp;
/* "end" brings us only to the '\0' at end of string */
cmdp = end + 1;
}
argv[i] = NULL; /* Create additional null word at end for safety. */
*argcp = i;
}
static void execute(int argc, char *argv[])
{
pid_t childpid; /* child process ID */
childpid = fork();
if (childpid == -1) { /* in parent (returned error) */
perror("fork"); /* perror => print error string of last system call */
printf(" (failed to execute command)\n");
}
if (childpid == 0) { /* child: in child, childpid was set to 0 */
/* Executes command in argv[0]; It searches for that file in
* the directories specified by the environment variable PATH.
*/
if (-1 == execvp(argv[0], argv)) {
perror("execvp");
printf(" (couldn't find command)\n");
}
/* NOT REACHED unless error occurred */
exit(1);
} else /* parent: in parent, childpid was set to pid of child process */
waitpid(childpid, NULL, 0); /* wait until child process finishes */
return;
}
int main(int argc, char *argv[])
{
char cmd[MAXLINE];
char *childargv[MAXARGS];
int childargc;
while (1) {
printf("%% "); /* printf uses %d, %s, %x, etc. See 'man 3 printf' */
fflush(stdout); /* flush from output buffer to terminal itself */
getargs(cmd, &childargc, childargv); /* childargc and childargv are
output args; on input they have garbage, but getargs sets them. */
/* Check first for built-in commands. */
if ( childargc > 0 && strcmp(childargv[0], "exit") == 0 )
exit(0);
else if ( childargc > 0 && strcmp(childargv[0], "logout") == 0 )
exit(0);
else
execute(childargc, childargv);
}
/* NOT REACHED */
}

You have to put the test outside of the while loop.
static void getargs(char cmd[], int *argcp, char *argv[])
{
char *cmdp = cmd;
char *end, *hash;
int i = 0;
/* fgets creates null-terminated string. stdin is pre-defined C constant
* for standard intput. feof(stdin) tests for file:end-of-file.
*/
if (fgets(cmd, MAXLINE, stdin) == NULL && feof(stdin)) {
printf("Couldn't read from standard input. End of file? Exiting ...\n");
exit(1); /* any non-zero value for exit means failure. */
}
// check if we have a comment
hash = strchr(cmd,'#');
if(hash != NULL){
// just overwrite it with NULs
while(*hash != '\0'){
*hash = '\0';
hash++;
}
}
while ( (cmdp = getword(cmdp, &end)) != NULL ) { /* end is output param */
/* getword converts word into null-terminated string */
argv[i++] = cmdp;
/* "end" brings us only to the '\0' at end of string */
cmdp = end + 1;
}
argv[i] = NULL; /* Create additional null word at end for safety. */
*argcp = i;
}

Related

What's the best way to insert the number of lines in the file at the beginning of the file in C?

I have a file in which few lines are being dumped. I want to post process the file and insert a header at the beginning of this file. The header is basically the number of lines in the file before inserting the header. I need to write this in C preferable irrespective of the platform. I was thinking of using system command in C code as follows to calculate the number of lines in the file:
int header = system("wc -l myfile");
save header in the temp file;
append myfile in temp file;
replace or move tempfile with myfile
Looking for a better way.
Use diff ( https://unix.stackexchange.com/questions/252927/what-do-the-numbers-in-the-line-mean-in-output-of-diff ) then you know which lines were dumped
To write text to the begin of a file use shell commands like sed -i '1s/^/task goes here\n/' todo.txt (https://superuser.com/questions/246837/how-do-i-add-text-to-the-beginning-of-a-file-in-bash), in C you can execute shell commands using sh or system depends if the shell command is built-in.
If you want this platform independent you have to count the lines in C like in https://www.geeksforgeeks.org/c-program-count-number-lines-file/
The straightforward option is to do the postprocessing and line counting in one pass (counting the number of lines written to a temporary file), then creating an another temporary file but this time in the same directory as the target file, writing the header to the latter temporary file, copying the contents of the first temporary file to the latter temporary file, removing the first temporary file, and finally renaming the latter temporary file as the final file.
This would be very simple to do using POSIX C getline() (which is included in standard C libraries in Linux, BSDs, and Mac OS), but it is not available on Windows. We'd need to first write a wrapper function, that on non-Windows systems uses POSIX getline() to read unlimited-length lines; and uses fgetc() or some other function to implement same/similar function Windows.
Another approach would be to read the file using fread() into a dynamically allocated buffer, and call a callback/filtering function for each line read. This approach has the benefit of being able to support any newline encoding (NUL, LF, CR, LF CR, or CR LF) and minimizing buffer copies, but the downside of being unfamiliar approach to many programmers.
Consider the following example implementation of for_each_line():
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
typedef enum {
NEWLINE_INVALID = -1, /* Invalid NEWLINE_ enum value */
NEWLINE_NONE = 0, /* No newline at end of line; ended at end of stream */
NEWLINE_CR = 1, /* "\r" */
NEWLINE_LF = 2, /* "\n" */
NEWLINE_CRLF = 3, /* "\r\n" */
NEWLINE_LFCR = 4, /* "\n\r" */
NEWLINE_NUL = 5 /* "", or '\0', */
} newline_type;
int for_each_line(FILE *source,
int (*filter)(char *line, size_t len, void *context),
void *context)
{
char *data = NULL; /* source data buffer */
size_t size = 0; /* size of source data buffer in chars */
size_t head = 0; /* start of next line to pass to filter */
size_t tail = 0; /* next char to read into buffer */
int newline = NEWLINE_INVALID;
/* Neither source nor filter can be NULL. */
if (!source || !filter)
return -1; /* -1: Invalid parameters */
/* source stream should not be in error state. */
if (ferror(source))
return -2; /* -2: Error reading source. */
while (1) {
size_t next = head;
size_t n;
int retval;
/* Check if we have a complete line to supply to filter. */
if (tail > head) {
/* To be able to safely use strcspn(), we need a terminating nul at end of buffer. */
data[tail] = '\0';
/* Find the first occurrence of any newline character. */
n = strcspn(data + head, "\r\n");
if (n < tail - head) {
/* Found, and it wasn't the nul we added. */
if (data[head + n] == '\r') {
/* CR or CR LF */
if (data[head + n + 1] == '\n') {
newline = NEWLINE_CRLF;
next = head + n + 2;
} else {
newline = NEWLINE_CR;
next = head + n + 1;
}
} else
if (data[head + n] == '\n') {
/* LF or LF CR */
if (data[head + n + 1] == '\r') {
newline = NEWLINE_LFCR;
next = head + n + 2;
} else {
newline = NEWLINE_LF;
next = head + n + 1;
}
} else {
/* Must have been NUL */
newline = NEWLINE_NUL;
next = head + n + 1;
}
/* Note: A two-character newline might have been split at the end of a buffer,
so at this point, 'newline' is *tentative*, not exact. */
if (newline != NEWLINE_INVALID) {
/* We replace the newline with end-of-string NUL mark. */
data[head + n] = '\0';
retval = filter(data + head, n, context);
if (retval) {
free(data);
return retval;
}
head = next;
/* Check for next line, before trying to refill the buffer. */
continue;
}
}
}
/* If the buffer is at least half full, move the data to beginning of buffer. */
if (head >= tail) {
/* Buffer is completely empty. */
head = 0;
tail = 0;
} else
if (head > 0 && tail >= size / 2) {
/* Buffer is at least half full, and not aligned at start of buffer. Move. */
tail -= head;
memmove(data, data + head, tail);
head = 0;
}
/* Need to grow the buffer? */
if (tail + 2 >= size) {
/* TODO: Improve on this linear growth policy. */
const size_t new_size = (tail | 4095) + 4097 - 32;
char *new_data;
new_data = realloc(data, new_size);
if (!new_data) {
free(data);
return -3; /* Not enough memory. */
}
data = new_data;
size = new_size;
}
/* Read some data into the buffer. */
n = fread(data + tail, 1, size - tail - 1, source);
if (n > 0) {
tail += n;
/* Consume partial newline, since now we know we can. */
if (newline == NEWLINE_CR && data[head] == '\n') {
newline = NEWLINE_CRLF;
head++;
} else
if (newline == NEWLINE_LF && data[head] == '\r') {
newline = NEWLINE_LFCR;
head++;
}
} else
if (ferror(source)) {
free(data);
return -2; /* Error reading source. */
} else {
/* End of input. */
if (tail > head) {
data[tail] = '\0';
retval = filter(data + head, tail - head, context);
free(data);
return retval;
} else {
free(data); /* Note: free(NULL) is safe. */
return 0;
}
}
}
/* Execution never reaches here. */
}
struct context {
FILE *out;
const char *newline;
unsigned long long lines;
};
static int do_copy_count_lines(char *line, size_t len, void *ctxptr)
{
struct context *const ctx = ctxptr;
(void)len; /* Silence warning about unused parameter 'len'. Does nothing. */
fputs(line, ctx->out);
fputs(ctx->newline, ctx->out);
ctx->lines++;
return ferror(ctx->out);
}
int copy_count_lines(FILE *source, FILE *target, unsigned long long *linecount)
{
struct context ctx;
int retval;
if (!source || !target)
return -1; /* Invalid parameters */
ctx.out = target;
ctx.newline = "\n";
ctx.lines = 0;
retval = for_each_line(source, do_copy_count_lines, &ctx);
if (linecount)
*linecount = ctx.lines;
return retval;
}
int main(void)
{
unsigned long long count = 0;
int err;
err = copy_count_lines(stdin, stdout, &count);
if (err) {
fflush(stdout);
fprintf(stderr, "Failed: for_each_line() returned %d.\n", err);
return EXIT_FAILURE;
}
fflush(stdout);
fprintf(stderr, "Copied %llu lines from standard input to standard output.\n", count);
return EXIT_SUCCESS;
}
The above example program simply reads from standard input, writing the lines (replacing any newline convention with the default newline convention for standard output), and the number of lines copied to standard error.
Because this uses fread(), it is very fast, but not suitable for interactive input (from terminals or terminal emulators), because it reads the input in large-ish blocks.
A variant that uses fgetc() is easy to implement, well suited for interactive input, but because of the large number of function calls, is somewhat slower.
Here is an example variant for use with wide character strings. (Do note that Windows' wide character support may be broken; the following is strictly C standard compliant code.)
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <wchar.h>
#include <errno.h>
/* Only EDOM, EILSEQ, and ERANGE are guaranteed to be known,
so errno constants may need to be mapped. */
/* getline(): End of stream */
#ifndef WGETLINE_EOF
#define WGETLINE_EOF 0
#endif
/* getline(): Invalid parameters */
#ifndef WGETLINE_EINVAL
#ifdef EINVAL
#define WGETLINE_EINVAL EINVAL
#else
#define WGETLINE_EINVAL EILSEQ
#endif
#endif
/* getline(): Not enough memory */
#ifndef WGETLINE_ENOMEM
#ifdef ENOMEM
#define WGETLINE_ENOMEM ENOMEM
#else
#define WGETLINE_ENOMEM ERANGE
#endif
#endif
/* getline(): Read error */
#ifndef WGETLINE_EIO
#ifdef EIO
#define WGETLINE_EIO EIO
#else
#define WGETLINE_EIO EDOM
#endif
#endif
size_t wgetline(wchar_t **lineptr, size_t *sizeptr, FILE *handle)
{
if (!lineptr || !sizeptr || !handle) {
errno = WGETLINE_EINVAL;
return 0;
} else
if (ferror(handle)) {
errno = WGETLINE_EIO;
return 0;
} else
if (feof(handle)) {
errno = WGETLINE_EOF;
return 0;
}
wchar_t *line = *lineptr;
size_t size = *sizeptr;
size_t used = 0;
wint_t wc;
if (!size)
line = NULL;
while (1) {
/* Make sure there are is room for at least three wide chars in the buffer. */
if (used + 3 > size) {
/* TODO: Better buffer growth policy? size is in wchar_t's. */
size = (used | 1023) + 1025 - 16;
line = realloc(line, size * sizeof line[0]); /* realloc(NULL, ..) is safe. */
if (!line) {
errno = WGETLINE_ENOMEM;
return 0;
}
*lineptr = line;
*sizeptr = size;
}
wc = getwc(handle);
if (wc == WEOF) {
line[used] = L'\0';
if (!used)
errno = WGETLINE_EOF;
return used;
} else {
line[used++] = wc;
if (wc == L'\n') {
line[used] = L'\0';
return used;
}
}
}
/* Never reached. */
}
int main(void)
{
wchar_t *line = NULL;
size_t size = 0;
size_t len;
unsigned long long lines = 0uLL, wchars = 0uLL;
setlocale(LC_ALL, "");
if (fwide(stdin, 1) != 1)
fprintf(stderr, "Warning: Your C library or locale does not support wide character standard input.\n");
if (fwide(stdout, 1) != 1)
fprintf(stderr, "Warning: Your C library or locale does not support wide character standard output.\n");
while (1) {
len = wgetline(&line, &size, stdin);
if (!len)
break;
lines++;
wchars += len;
fputws(line, stdout);
}
if (ferror(stdin) || !feof(stdin)) {
fprintf(stderr, "Error reading from standard input.\n");
return EXIT_FAILURE;
}
fflush(stdout);
if (ferror(stdout)) {
fprintf(stderr, "Error writing to standard output.\n");
return EXIT_FAILURE;
}
fprintf(stderr, "Copied %llu lines (%llu wide characters).\n", lines, wchars);
return EXIT_SUCCESS;
}
To give you an idea on how performant these are, time wc -l /usr/share/dict/american-english takes 5ms real time on my system; time ./ex1 < /usr/share/dict/american-english > /dev/null takes 12ms; and time ./ex2 < /usr/share/dict/american-english > /dev/null takes 85ms. All three agree it has 102,305 lines. It has 971,304 wide characters, using UTF-8 character set (with file size 971,578 bytes).

Understanding expected behavior of c language code snippet

I have been given a c language code
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_BUFFER 256
#define QUIT_STRING "q"
int makeargv(const char *s, const char *delimiters, char ***argvp);
int main (void) {
char **chargv;
char inbuf[MAX_BUFFER];
for( ; ; ) {
gets(inbuf);
if (strcmp(inbuf, QUIT_STRING) == 0)
return 0;
if ((fork() == 0) && (makeargv(inbuf, " ", &chargv) > 0))
execvp(chargv[0], chargv);
wait(NULL);
}
}
makeargv function which makes tokens out of the string passed as 1st argument (using delimiters passed in 2nd argument) and stores these tokens in the array pointed to by the 3rd argument.
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int makeargv(const char *s, const char *delimiters, char ***argvp) {
int error;
int i;
int numtokens;
const char *snew;
char *t;
if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
errno = EINVAL;
return -1;
}
*argvp = NULL;
snew = s + strspn(s, delimiters); /* snew is real start of string */
if ((t = malloc(strlen(snew) + 1)) == NULL)
return -1;
strcpy(t, snew);
numtokens = 0;
if (strtok(t, delimiters) != NULL) /* count the number of tokens in s */
for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
/* create argument array for ptrs to the tokens */
if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
error = errno;
free(t);
errno = error;
return -1;
}
/* insert pointers to tokens into the argument array */
if (numtokens == 0)
free(t);
else {
strcpy(t, snew);
**argvp = strtok(t, delimiters);
for (i = 1; i < numtokens; i++)
*((*argvp) + i) = strtok(NULL, delimiters);
}
*((*argvp) + numtokens) = NULL; /* put in final NULL pointer */
return numtokens;
}
and i need to answer 3 questions which are mentioned below
How would the shell implemented in above code will behave when the user gives an invalid command (i.e. a command for which no executable exists)?
What would happen if the user gives multiple invalid commands?
What happens when the user tries to quit the shell after giving multiple invalid commands.
Here's what i think are the answers to these questions
The execv will return an error, but I do not think it will break the code so it will result in two forks trying to read inputs
More forks will be created
Only one of the forks will quit
Question
Are all of the answers correct? Could any of the answers be improved?
Never have two processes trying to read stdin at once. Race conditions make the resulting environment unusable.
if (fork() ==0){
if(...)
execvp();
_exit(255); /* don't fall back to parent code */
}

Finding and printing the last word of a string with a space or tab delimiter

I'm trying to get the last word of a string as an argv parameter in C. How can I find the last word and delimit the strings that contain a space/tabs?
The definition of a word is "a section of string delimited by spaces/tabs or by the start/end of the string." I initially made a function to identify it is whitespace or tab; however, the solution I have does not for all cases. Most of it works, but when my argv has a string like "asdkBakjsdhf1785 ", the expected output should be "asdkBakjsdhf1785", not "asdkBakjsdhf1785 ". All of these functions has to made from scratch and I'm only allowed to use the write function from the <unistd.h> library.
int my_isspace(char c)
{
if (c == ' ' || c == '\t')
return (1);
return (0);
}
void my_putstr(char *str)
{
while (*str)
write(1, str++, 1);
}
void last_word(char *str)
{
char *last;
int i;
i = 0;
last = &str[i];
while (str[i])
{
if (my_isspace(str[i]))
{
if (str[i + 1] >= '!' && str[i + 1] <= '~')
last = &str[i + 1];
}
i++;
}
if (last)
my_putstr(last);
}
int main(int argc, char *argv[])
{
if (argc == 2)
last_word(argv[1]);
my_putstr("\n");
return (0);
}
For now, what it does is that it parses through the string and checks if it has a whitespace/tab. If it has, it will ignore it and move to the next character until it hits a character between ! and ~ (including those). Once it hits to any of the characters in between, it will store the parsing into another empty string pointer to preserve what was the "last word". With the way that I'm using, where can I improve the tiny bit to delimit my space/tab at the end?
You have a couple of problems. First it would help to make your my_isspace() roughly equivalent to isspace() so that it correctly considers line-ends as whitespace as well, e.g.
int my_isspace (const char c)
{
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
return 1;
return 0;
}
Next, you can make last_word() (and many other similar classification functions) much simpler by taking a State Loop approach and keeping a simple flag to help you with your classification. For instance here, it helps to know whether you are reading within a word or not. That lends itself to a simple in/out state, which you can track with a simple flag of int in = 0; for outside a word, and in = 1; when within a word.
Putting that in use, your last_word() can be written as follows for mutable strings (the arguments to main() being mutable):
void last_word (char *str)
{
char *p = NULL, /* your last */
*ep = NULL; /* end pointer to end of last */
int in = 0; /* flag - in/out of a word */
while (*str) { /* loop over each char */
if (my_isspace (*str)) { /* if a space */
if (in) /* if in a word */
ep = str; /* set endptr to current char */
in = 0; /* set out of word */
}
else { /* not a space */
if (!in) /* if not in a word */
p = str; /* set start pointer */
in = 1; /* set in word */
}
str++; /* increment pointer */
}
if (p) { /* if we have start of word */
if (ep) /* if we have endptr set */
*ep = 0; /* nul-terminate at endptr */
my_putstr (p); /* output last */
}
}
(note: if dealing with non-mutable string input, you could use ep - p to get the number of characters to output and output them one at a time)
A complete example (with a few cleanups) could be:
#include <unistd.h>
int my_isspace (const char c)
{
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
return 1;
return 0;
}
void my_putstr (const char *str)
{
size_t n = 0;
for (n = 0; str[n]; n++) {}
write (1, str, n);
}
void last_word (char *str)
{
char *p = NULL, /* your last */
*ep = NULL; /* end pointer to end of last */
int in = 0; /* flag - in/out of a word */
while (*str) { /* loop over each char */
if (my_isspace (*str)) { /* if a space */
if (in) /* if in a word */
ep = str; /* set endptr to current char */
in = 0; /* set out of word */
}
else { /* not a space */
if (!in) /* if not in a word */
p = str; /* set start pointer */
in = 1; /* set in word */
}
str++; /* increment pointer */
}
if (p) { /* if we have start of word */
if (ep) /* if we have endptr set */
*ep = 0; /* nul-terminate at endptr */
my_putstr (p); /* output last */
}
}
int main (int argc, char **argv) {
my_putstr ("'");
if (argc >= 2)
last_word (argv[1]);
my_putstr ("'");
my_putstr ("\n");
return 0;
}
Example Use/Output
Single word:
$ ./bin/write_last_argv1 "fleas"
'fleas'
Single word with trailing whitespace:
$ ./bin/write_last_argv1 "fleas "
'fleas'
Multiple words:
$ ./bin/write_last_argv1 "my dog has fleas"
'fleas'
Multiple words with leading, trailing and multiple intervening whitespace:
$ ./bin/write_last_argv1 " my dog has fleas "
'fleas'
Look things over and let me know if you have further questions.

receiving Seg Fault for my minishell program

I am writing a mini-shell program. It gives me seg fault in the line "args = my_str2vect(line);". The "my_str2vect" function works fine though, as well as the my_strncpy function and my_strlen function(returns the length of the char array). So what's wrong with that line?
#define _POSIX_SOURCE
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include "../../include/my.h"
pid_t pid;
int childrunning = 0;
void minishell_loop();
/*
Your shell must also support CTRL+C by using UNIX signals.
If you type CTRL+C your prompt must not exit
and instead kill the currently running process (or do nothing if there is none).
*/
void int_handler(int sig){
if(childrunning == 1){
kill(pid, SIGUSR2);
}else{
my_str("\n");
minishell_loop();
}
}
void killkillkill(int sig){
exit(0);
}
/*
Function Declarations for builtin shell commands:
*/
int minishell_cd(char **args);
int minishell_help(char **args);
int minishell_exit(char **args);
/*
List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
"cd",
"help",
"exit"
};
int (*builtin_func[]) (char **) = {
&minishell_cd,
&minishell_help,
&minishell_exit
};
int minishell_mum_bultins() {
return sizeof(builtin_str) / sizeof(char *);
}
/*
Builtin function implementations.
*/
/**
#brief Bultin command: change directory.
#param args List of args. args[0] is "cd". args[1] is the directory.
#return Always returns 1, to continue executing.
*/
int minishell_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "minishell: expected argument to \"cd\"\n");
} else {
if (chdir(args[1]) != 0) {
perror("minishell");
}
}
return 1;
}
/**
#brief Builtin command: print help.
#param args List of args. Not examined.
#return Always returns 1, to continue executing.
*/
int minishell_help(char **args)
{
int i;
printf("Tianwei's minishell, version 1.01\n");
printf("These shell commands are defined internally.\n");
for (i = 0; i < minishell_mum_bultins(); i++) {
printf(" %s\n", builtin_str[i]);
}
return 1;
}
/**
#brief Builtin command: exit.
#param args List of args. Not examined.
#return Always returns 0, to terminate execution.
*/
int minishell_exit(char **args)
{
return 0;
}
/**
#brief Launch a program and wait for it to terminate.
#param args Null terminated list of arguments (including program).
#return Always returns 1, to continue execution.
*/
int minishell_launch(char **args)
{
signal(SIGUSR2, killkillkill);
int status;
pid = fork();
if (pid == 0) {
// Child process
childrunning = 1;
if (execvp(args[0], args) == -1) {
perror("minishell");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("minishell");
} else {
// Parent process
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
/**
#brief Execute shell built-in or launch program.
#param args Null terminated list of arguments.
#return 1 if the shell should continue running, 0 if it should terminate
*/
int minishell_execute(char **args)
{
int i;
my_str(args[0]);
if (args[0] == NULL) {
my_str("args[0] is NULL\n");
// An empty command was entered.
return 1;
}
for (i = 0; i < minishell_mum_bultins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
my_str("1\n");
return (*builtin_func[i])(args);
}
}
my_str("0\n");
return minishell_launch(args);
}
#define MINISHELL_RL_BUFSIZE 1024
/**
#brief Read a line of input from stdin.
#return The line from stdin.
*/
char *minishell_readln(void)
{
int bufsize = MINISHELL_RL_BUFSIZE;
int position = 0;
char* buffer = malloc(1024 * sizeof(char));
int c;
while (1) {
// Read a character
c = getchar();
// If we hit EOF, replace it with a null character and return.
if (c == EOF || c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
fprintf(stderr, "minishell: Command line too long!\n");
exit(EXIT_FAILURE);
}
}
}
/**
#brief Loop getting input and executing it.
*/
void minishell_loop(void)
{
signal(SIGINT, int_handler);
char* line;
char** args;
int status;
do {
printf("MINISHELL: /home/tianwei/$ ");
line = minishell_readln();
my_str("0\n");
my_str(line);
args = my_str2vect(line);
my_str(args[0]);
my_str("0\n");
status = minishell_execute(args);
my_str("1\n");
free(line);
free(*args);
free(args);
} while (status);
}
/**
#brief Main entry point.
#param argc Argument count.
#param argv Argument vector.
#return status code
*/
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
minishell_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}
This is the code for my_str2vect function
#include "../../include/my.h"
char** my_str2vect(char* str){
// Takes a string
// Allocates a new vector (array of string ended by a NULL),
// Splits apart the input string x at each space character
// Returns the newly allocated array of strings
// Any number of ' ','\t', and '\n's can separate words.
// I.e. "hello \t\t\n class,\nhow are you?" -> {"hello", "class,", "how", "are","you?", NULL}
int max_num_words = 0;
int a = 0;
int b = 0;
int max_num_char = 0;
while(str[a] != '\0'){ // find the number of words and the length of the longest word
if(str[a] != ' ' && str[a] != '\t' && str[a] != '\n'){
++max_num_words;
++a;
++b;
while((str[a] != ' ' && str[a] != '\t' && str[a] != '\n') && str[a] != '\0'){
++a;
++b;
}
if(b > max_num_char){
max_num_char = b;
}
b = 0;
while((str[a] == ' ' || str[a] == '\t' || str[a] == '\n') && str[a] != '\0'){
++a;
}
}
}
char** output = (char **)malloc(sizeof(char *) * (max_num_words + 1)); // Allocate a 2D array first.
for(int c = 0; c < max_num_words + 1; ++c){
output[c] = (char *)malloc(sizeof(char) * (max_num_char + 1));
}
int i = 0;
int j = 0;
while(i < my_strlen(str) && j < max_num_words){ // Put the characters into the 2D array.
int k = 0;
while(i < my_strlen(str) && (str[i] == ' ' || str[i] == '\t' || str[i] == '\n')){
++i;
}
while(i < my_strlen(str) && (!(str[i] == ' ' || str[i] == '\t' || str[i] == '\n'))){
++i;
++k;
}
if(i-k < my_strlen(str) && j < max_num_words){
my_strncpy(output[j], &str[i-k], k);
}
++j;
}
output[j] = NULL;
return output;
}
This is my_strncpy function
#include "../../include/my.h"
char *my_strncpy(char *dst, char *src, int n)
{
/* Same as my_strcpy except:
* Only copies n chars or until the end of src*/
if(src != NULL && dst != NULL){
int i = 0;
while(i < n && src[i] != '\0'){
dst[i] = src[i];
++i;
}
dst[i] = '\0';
}
return dst;
}
I didn't try to execute your code but here is what strikes me at first sight in the my_str2vect function:
in the first while loop, a is only incremented in the if. You are likely to meet a forever loop if your line begins with a space
in the second while loop, you don't check for the end of string '\0'. This may well be the reason for your crash, but there may be others.
PS: since you are parsing a string, you should take a look at strtok and sscanf, they would ease your life

Split data from ethernet

I need split data from ethernet. Data is in this format:
ZMXXX,angle*CHCK
Where angle is number. For example: ZMXXX,900*5A
And I need separated ZMXXX,900 and 5A. I wrote this function:
void split_data(char analyze[])
{
char *words[5]; uint8_t i=0;
words[i] = strtok(analyze,"*");
while(words[i]!=NULL)
{
words[++i] = strtok(NULL,"*");
}
}
And result is here:
And now, how I can get this data from variable:
words[0]
words[1]
Assuming the format you mention to be fixed, there is no need for the expensive and error-prone strtok().
Use the good old strchr():
int parse(char * input, char ** output)
{
int result = -1; /* Be pessimistic. */
if ((NULL == inout) || (NULL == output))
{
errno = EINVAL;
goto lblExit;
}
char * pc = strchr(analyze, '*');
if (NULL == pc);
{
errno = EINVAL;
goto lblExit;
}
*pc = '\0'; /* Set a temporary `0`-terminator. */
output[0] = strdup(analyze); /* Copy the 1st token. */
if (NULL == output[0])
{
goto lblExit;
}
*pc = '*'; /* Restore the original. */
output[1] = strdup(pc + 1); /* Seek beyond the `*` and copy the 2nd token. */
if (NULL == output[1])
{
free(outout[0]); /** Clean up. */
goto lblExit;
}
result = 0; /* Indicate success. */
lblExit:
return result;
}
Use it like this:
#define _POSIX_C_SOURCE 200809L /* To make strdup() available. */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int parse(char *, char **);
int main(void)
{
char data[] = "ZMXXX,900*5A";
char * words[2];
if (-1 == parse(data, words))
{
perror("parse() failed");
exit(EXIT_FAILURE);
}
printf("word 1 = '%s'\n", words[0]);
printf("word 2 = '%s'\n", words[1]);
free(words[0]);
free(words[1]);
return EXIT_SUCCESS;
}
The above code is expected to print:
word 1 = 'ZMXXX,900'
word 2 = '5A'
Note that strdup() isn't Standard C, but POSIX. It might need to be activated using one of the appropriate defines.

Resources