I have constructed the following program to try to pipe in my own shell. A StringArray is simply a char** I have constructed. The code runs fine, but when I put in cat txt.txt | grep a, nothing prints back to the screen. When debugging, I saw that the code seems to stop around like 152 (where the print-out command is), where pid==0 and i==0.
For context, I'm calling this function in another function after a pipe has been detected.
void doPipe(StringArray sa)
{
printf("In 69\n");
int filedes[2]; // pos. 0 output, pos. 1 input of the pipe
int filedes2[2];
int num_cmds = 0;
char *command[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int i = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
while (sa[l] != NULL){
if (strcmp(sa[l],"|") == 0){
num_cmds++;
}
l++;
}
num_cmds++;
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1){
k = 0;
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
while (strcmp(sa[j],"|") != 0){
command[k] = sa[j];
j++;
if (sa[j] == NULL){
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
k++;
break;
}
k++;
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
printf("In 121\n");
// Depending on whether we are in an iteration or another, we
// will set different descriptors for the pipes inputs and
// output. This way, a pipe will be shared between each two
// iterations, enabling us to connect the inputs and outputs of
// the two different commands.
if (i % 2 != 0){
pipe(filedes); // for odd i
}else{
pipe(filedes2); // for even i
}
pid=fork();
if(pid==-1){
if (i != num_cmds - 1){
if (i % 2 != 0){
close(filedes[1]); // for odd i
}else{
close(filedes2[1]); // for even i
}
}
printf("Child process could not be created\n");
return;
}
if(pid==0){
printf("In 148\n");
// If we are in the first command
if (i == 0){
printf("In 152\n");
dup2(filedes2[1], STDOUT_FILENO);
}
// If we are in the last command, depending on whether it
// is placed in an odd or even position, we will replace
// the standard input for one pipe or another. The standard
// output will be untouched because we want to see the
// output in the terminal
else if (i == num_cmds - 1){
printf("In 162\n");
if (num_cmds % 2 != 0){ // for odd number of commands
dup2(filedes[0],STDIN_FILENO);
printf("In 166\n");
}else{ // for even number of commands
dup2(filedes2[0],STDIN_FILENO);
printf("In 166\n");
}
// If we are in a command that is in the middle, we will
// have to use two pipes, one for input and another for
// output. The position is also important in order to choose
// which file descriptor corresponds to each input/output
}else{ // for odd i
if (i % 2 != 0){
dup2(filedes2[0],STDIN_FILENO);
dup2(filedes[1],STDOUT_FILENO);
}else{ // for even i
dup2(filedes[0],STDIN_FILENO);
dup2(filedes2[1],STDOUT_FILENO);
}
}
if (execvp(command[0],command)==err){
kill(getpid(),SIGTERM);
}
}
// CLOSING DESCRIPTORS ON PARENT
if (i == 0){
close(filedes2[1]);
}
else if (i == num_cmds - 1){
if (num_cmds % 2 != 0){
close(filedes[0]);
}else{
close(filedes2[0]);
}
}else{
if (i % 2 != 0){
close(filedes2[0]);
close(filedes[1]);
}else{
close(filedes[0]);
close(filedes2[1]);
}
}
waitpid(pid,NULL,0);
i++;
}
}
One of your big problems may be doing waitpid on each iteration of the pipeline construction. The waiting should be done at the end (remembering the pids in a list).
I had some difficulty understanding your code, so I did some simplification and cleanup. In particular, doing if (i % 2 ...) everywhere made things harder.
I've cleaned up and fixed the code. I added a struct to make things easier to manage [please pardon the gratuitous style cleanup]:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef struct {
int pipe_fildes[2];
} pipectl_t;
#define CLOSEME(_fd) \
do { \
close(_fd); \
_fd = -1; \
} while (0)
void
doPipe(char **sa)
{
pipectl_t pipes[2];
pipectl_t *pipein;
pipectl_t *pipeout;
pipectl_t *pipetmp;
int num_cmds = 0;
char *command[256];
pid_t pidlist[256];
pid_t pid;
int err = -1;
int end = 0;
// Variables used for the different loops
int icmd = 0;
int j = 0;
int k = 0;
int l = 0;
// First we calculate the number of commands (they are separated
// by '|')
for (int l = 0; sa[l] != NULL; ++l) {
if (strcmp(sa[l], "|") == 0)
num_cmds++;
}
num_cmds++;
for (int ipipe = 0; ipipe <= 1; ++ipipe) {
pipes[ipipe].pipe_fildes[0] = -1;
pipes[ipipe].pipe_fildes[1] = -1;
}
pipein = &pipes[0];
pipeout = &pipes[1];
// Main loop of this method. For each command between '|', the
// pipes will be configured and standard input and/or output will
// be replaced. Then it will be executed
while (sa[j] != NULL && end != 1) {
// We use an auxiliary array of pointers to store the command
// that will be executed on each iteration
k = 0;
while (strcmp(sa[j], "|") != 0) {
command[k] = sa[j];
j++;
k++;
if (sa[j] == NULL) {
// 'end' variable used to keep the program from entering
// again in the loop when no more arguments are found
end = 1;
break;
}
}
// Last position of the command will be NULL to indicate that
// it is its end when we pass it to the exec function
command[k] = NULL;
j++;
// swap input and output, so previous child's output becomes the new
// child's input
// NOTE: by doing this here, in one place, we eliminate all the i % 2
// if statements
pipetmp = pipein;
pipein = pipeout;
pipeout = pipetmp;
// are we the last command?
int lastflg = (icmd == (num_cmds - 1));
// last command does _not_ have an output pipe, so don't create one
if (! lastflg)
pipe(pipeout->pipe_fildes);
pid = fork();
// NOTE: fork failure almost never happens and is fatal
if (pid == -1) {
printf("Child process could not be created\n");
return;
}
// process child
if (pid == 0) {
// NOTE: after we've dup'ed a file descriptor, we close it
// first command does _not_ have a pipe for input
if (icmd > 0)
dup2(pipein->pipe_fildes[0],STDIN_FILENO);
CLOSEME(pipein->pipe_fildes[0]);
// last command does _not_ have a pipe for output
if (! lastflg)
dup2(pipeout->pipe_fildes[1],STDOUT_FILENO);
CLOSEME(pipeout->pipe_fildes[1]);
// close the parent sides of the pipes (in this child)
// close previous child's output descriptor (the feed for our input)
CLOSEME(pipein->pipe_fildes[1]);
// close next child's input descriptor (our feed for its input)
CLOSEME(pipeout->pipe_fildes[0]);
if (execvp(command[0], command) == err) {
#if 0
kill(getpid(), SIGTERM);
#else
exit(1);
#endif
}
}
// close all input descriptors for _this_ child
CLOSEME(pipein->pipe_fildes[0]);
CLOSEME(pipein->pipe_fildes[1]);
// close output side of _this_ child's output pipe [which becomes next
// child's input pipe]
CLOSEME(pipeout->pipe_fildes[1]);
pidlist[icmd] = pid;
icmd++;
}
// wait for all pids _after_ the entire pipeline is constructed
for (int icmd = 0; icmd < num_cmds; ++icmd)
waitpid(pidlist[icmd], NULL, 0);
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
char *bp;
char buf[1000];
char **av;
char *avlist[256];
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
default:
break;
}
}
while (1) {
printf("> ");
fflush(stdout);
cp = fgets(buf,sizeof(buf),stdin);
if (cp == NULL)
break;
av = avlist;
bp = buf;
while (1) {
cp = strtok(bp," \t\r\n");
bp = NULL;
if (cp == NULL)
break;
*av++ = cp;
}
*av = NULL;
doPipe(avlist);
}
return 0;
}
UPDATE:
When I run this code, the same command cat txt.txt | grep a only appears to do the first command, and not the second after the pipe. (It cats out the txt file but does not grep)
I tested the entire program before I posted. I just retested using a cat/grep command. It worked, but that was my program unchanged.
Any ideas why this could be happening? I implemented your doPipe method in my code and passed in my StringArray sa which is just a char ** as well.
My suggestions are:
Verify that my unchanged version works for you.
Using gdb breakpoint on doPipe and look at the arguments. For both programs, they should be the same.
If StringArray is truly char **, replace it in your version to ensure it makes no difference. That is void doPipe(char **sa) and see if your code still compiles. In gdb at the breakpoint, you should be able to do ptype sa on both programs
The StringArray looks a bit "Java-esque" to me :-) I'd avoid it, particularly here since execvp wants a char **
Verify that sa is properly NULL terminated. If it isn't the last command in the pipeline may be bogus/garbage and the error checking for a failed execvp isn't that robust.
Verify that num_cmds is the same.
Try cat txt.txt | grep a | sed -e s/a/b/. If you get the cat and grep, but not the sed, this means num_cmds is not correct
Verify that caller's parsing of the buffer puts the "|" in a separate token. That is, this code works with cat txt.txt | grep a but it will not work with: cat txt.txt|grep a
UPDATE #2:
BTW, if your pipe code still isn't working (e.g. the last command is not executed), check to see if the last token has newline on it (i.e. the newline wasn't stripped correctly).
I've tried all of this but still can't get my redirection code to work with this. Essentially, I'm confused as to where in this code I should be checking for '<' or '>'
Doing the general parsing to support redirection (e.g. < or >), pipes (e.g. |), multiple commands per line (e.g. ;), embedded sub-shells (e.g. (echo the date is ; date), and detached jobs (e.g. &) can require a bit of care and you need a multilevel approach.
I suspect that after you get pipes and/or redirection working, you're tasked with implementing even more shell syntax. I've done this before, so, rather than you figuring it out piecemeal, here is what you'll need to do ...
You'll need to scan the input buffer char-by-char and save off tokens into a "token" struct that also has a type. You'll need a linked list of those structs. More on this below.
When you encounter a quoted string, you'll need to strip off the quotes: "abc" --> abc, being mindful of escaped quotes: "ab\"c --> ab"c.
Also, you have to be careful about quoted strings abutting [what perl calls] "bareword" strings: echo abc. If we have abc"d ef"ghi, this needs to be concatenated into a single string token: abcd efghi
Backslashes on redirectors must also be accounted for. echo abc > def is a redirection that will put abc into the file def. But, echo abc \> def should just output abc > def literally to stdout. Backslash on the other "punctuation" is similar.
You'll also have to handle the fact that punctuation doesn't have to have whitespace around it. That is, echo abc>def has to be handled just as if it were echo abc > def.
Also, punctuation inside a quoted string should be treated as if it were escaped above. That is, echo abc ">" def is not a redirection and [again] should be treated as a simple command.
Also, if the current line ends in a backslash (e.g. \<newline>), this means that the next line is a "continuation" line. You should strip the backslash and the newline. Then, read another line and continue to build up the token list.
Further, while & can be for detached jobs, as in: date &, it can also be part of a redirection, as in gcc -o myshell myshell.c 2>&1 >logfile
Okay, so to manage all this we need types for tokens and a token struct:
// token types
typedef enum {
TOKEN_NORMAL, // simple token/string
TOKEN_QUO1, // quoted string
TOKEN_QUO2, // quoted string
TOKEN_SEMI, // command separater (e.g. ;)
TOKEN_OREDIR, // output redirector (e.g. >)
TOKEN_IREDIR, // input redirector (e.g. <)
TOKEN_PIPE, // pipe separater (e.g. |)
TOKEN_AMP // an & (can be detach or redirect)
} toktype_t;
// token control
typedef struct token token_t;
struct token {
token_t *tok_next; // forward link
token_t *tok_prev; // backward link
toktype_t tok_type; // token type
char tok_str[256]; // token value
};
// token list
typedef struct tlist tlist_t;
struct token {
tlist_t *tlist_next; // forward link
tlist_t *tlist_prev; // backward link
token_t *tlist_head; // pointer to list head
token_t *tlist_tail; // pointer to list tail
};
Initially, after parsing an input line [being mindful of the continuations], we have a single tlist.
If the list has ; separaters in it, we split on them to create sublists. We then loop on the sublists and execute the commands in order.
When looking at a subcommand, if it ends in &, the command must be run detached. We note that and pop it off the back of the list.
Okay, now we have a list that might be of the form:
cat < /etc/passwd | grep root | sed -e s/root/admin/ > /tmp/out
Now, we do a further split on the | so we have a list that has three elements:
cat < /etc/passwd
grep root
sed -e s/root/admin/ > /tmp/out
Actually, each of those "lines" is a tlist and this is a two dimensional list of lists:
list_of_tlists:
|
|
tlist[0] --> cat --> < --> /etc/passwd
|
|
tlist[1] --> grep --> root
|
|
tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out
As we create the pipeline, we note the redirections and do file open instead of pipe as needed.
Okay, that's the abstract.
See my answer here: Implementing input/output redirection in a Linux shell using C for a full and complete implementation.
On that page, there is code for just doing redirections. It could probably be adapted to include pipes by merging that code with the code I've posted here.
That OP asked for help in doing redirections and pipes.
Side note: At that time, there was a spate of shell implementation questions. So, I ended up producing a full shell that does pretty much everything. But, that version was too large to post on SO. So, in that page, find the pastebin link I posted. It has full source code. It can be downloaded, built, and run.
You may not want to use that code directly, but it should give you some ideas. Also, the full version may do things a little bit differently than what I've described above.
Related
I am struggling to fix this problem for 2 days now, but nothing seems to work!
I am making a shell in C, and I am trying to implement the history command( which will keep a history of all commands given by the user). This is a simplified version of my code (removed unnecessary code, and functions).
#include <stdio.h>
#include <string.h>
int main()
{
int doLoop = 1;
int i=0;
int c=0;
char givenCommand[100];
char *history[20];
char *newlinePos; /* pointer to the '\n' character in the input C string */
/* Print the header */
printf("Operating Systems Shell (Fall 2013)\n");
printf("Iacovos Hadjicosti\n");
printf("\n");
while(doLoop==1) /* Check if it should do the loop again */
{
printf("CSC327>"); /* Print a new prompt line */
fgets(givenCommand, sizeof(givenCommand), stdin); /* get input */
newlinePos = strchr(givenCommand,'\n'); /* point newlinePos to the '\n' character */
if(newlinePos!=NULL)
{
*newlinePos = '\0'; /* replace it with the null character */
}
if(strcmp(givenCommand,"exit")==0)
{
doLoop = 0; /* Do not do the loop again */
}
else if(strcmp(givenCommand,"history")==0)
{
for(i=0; i<c; i++)
{
printf("%d. %s\n", i+1, history[i]);
}
}
else
{
if(strcmp(givenCommand,"")!=0) /* if input was not empty.. */
{
printf("Command not found!\n"); /* show wrong command message */
}
}
history[c] = givenCommand;
c++;
}
return 0;
}
This gets the input, put it in givenCommand, check which command it was, and then put it in the history array. when the user give the "history" command, it should print all commands in the history array. Instead, it prints only the last command given, c times (c is the total number of commands given).
for example, if the user give input "Test1", and then second input "Test2", when he give the input "history" the third time, it will output the following:
1.Test2
2.Test2
Any opinions how to solve this? (I am using TCC to compile)
Modify this part
else if(strcmp(givenCommand,"")==0) /* if input was empty.. */
{
printf("Command not found!\n"); /* show wrong command message */
}
else
{
history[c]=malloc(strlen(givenCommand)+1); //allocate memory
//check malloc allocated memory or failed
if(history[c]==NULL)
{
printf("malloc function failed \n");
perror("ERROR");
exit(EXIT_FAILURE);
// exit(1); //if you don't want to exit then break loop with break;
// As alk suggested, The use of EXIT_SUCCESS and EXIT_FAILURE is
// slightly more portable (to non-UNIX environments)
// than the use of 0 and some nonzero value like 1 or -1.
}
strcpy(history[c], givenCommand); // if you can able to use Unix platform you can also use this instead of allocating memory copyinghistory[c] =strdup( givenCommand );
c++;
}
Edited strdup() would not work on windows because it is POSIX specific.
what happens in your case
char givenCommand[100]; is static declaration address is same.
when you enter "test1"
getcommandname starting address
test1
^
|
history[0]----------|
when you enter "test2"
getcommandname starting address
test2
^
|
history[0]----------|
history[1]----------|
When you enter "history"
getcommandname starting address
history
^
|
history[0]---------|
history[1]---------|
history[2]---------|
You need to copy givenCommand into the history, instead of assigning its pointer:
strcpy(history[c], givenCommand);
For this you must change history so it has space for the commands themselves:
char history[20][100];
As a stylistic note: you could put continue; at the end of the if-body for each command so you can omit the else part. For "exit" you can use break; to end the loop immediately:
if(strcmp(givenCommand, "exit") == 0){
break; /* end the loop now */
}
if(strcmp(givenCommand, "history") == 0){
/* do history stuff */
continue; /* go back to the beginning of the loop */
}
Because every time after the input givenCommand pointing to content are changing, so the content of the need to save the input, rather than save the pointer to the input.so may be you could use strcopy function to save the input string
I would like to preface this question by saying I'm new to C and therefore terrible at it, so I apologize in advance for any blatant errors or bad style. Also, I'm not sure how to introduce the problem before showing you my code, so here it is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int MAX_INPUT_SIZE = 200;
volatile int running = 1;
while (running)
{
char input[MAX_INPUT_SIZE];
char *tokens[100];
const char *cmds[] = { "wait", "pwd", "cd", "exit" };
char *cmdargs[100];
printf("shell> ");
fgets(input, MAX_INPUT_SIZE, stdin);
// remove newline character at end
int nl = strlen(input) - 1;
if (input[nl] == '\n')
{
input[nl] = '\0';
}
// tokenize input string, put each token into an array
char *space;
space = strtok(input, " ");
tokens[0] = space;
int i = 1;
while (space != NULL)
{
space = strtok(NULL, " ");
tokens[i] = space;
++i;
}
// copy tokens after first one into string
int noargscheck;
if (tokens[1] != NULL)
{
noargscheck = 0;
strcpy((char *)cmdargs, tokens[1]);
for (i = 2; tokens[i] != NULL; i++)
{
strcat((char *)cmdargs, " ");
strcat((char *)cmdargs, tokens[i]);
}
}
else
{
noargscheck = 1;
}
// compare tokens[0] to list of internal commands
int isInternal = -1;
for (i = 0; i < 4; i++)
{
if (strcmp(tokens[0], cmds[i]) == 0)
{
isInternal = i;
}
}
// internal commands
char wd[200];
if (isInternal != -1)
{
switch (isInternal)
{
case 0:
// wait
break;
case 1:
// pwd
if (getcwd(wd, sizeof(wd)) == NULL)
{
perror("getcwd() error!");
}
else
{
printf("%s\n", wd);
}
break;
case 2:
// cd
if (noargscheck)
{
chdir("/home");
}
else if (chdir((const char *)cmdargs) != 0)
{
perror("cd failed");
}
break;
case 3:
// exit
exit(1);
break;
}
}
else
{
// external commands
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
break;
}
}
}
}
When I run this code with the input echo hello world, the program successfully enters the case 0 case in the second switch statement that starts with switch (child_pid=fork()) but the output, which is unexpected, is as follows:
OUTPUT: (including one line that shows my input at the prompt)
shell> echo hello world (my input)
shell> (this is the part I don't understand)
HERE
shell> (the program now waits here at the prompt for the next user input)
I cannot figure out why the extra shell> prompt is printing. Can anybody see the problem?
EDIT: fixed the first parameter of execvp. Changed from "echo" (which was there because I'm silly) to tokens[0].
When you fork, you now have two processes at that point. Your child will print the HERE message and then call execvp. You do not check the return value of execvp and so it may be returning an error. cmdargs should be a vector - that is, a null-pointer-terminated array of string pointers. Instead, you are passing execvp a string. In other words, it expects a char* [], which is what cmdargs is, however you have previously treated cmdargs incorrectly.
For example, you say strcpy((char*)cmdargs, tokens[1]);. This places a string at *cmdargs. A string is an array of zero or more non-zero characters followed by an ascii NUL which is 8 bits wide:
char* cmdargs[] is a double pointer
you treat cmdargs as a single pointer and feed it to strcpy
cmdargs points to: 'H' 'e' 'l' 'l' 'o' '\0'
However, this is not what execvp wants. Execvp wants a vector, which looks somewhat more like this:
char* cmdargs[] is a double pointer
cmdargs[0] is a single pointer
cmdargs[0] points to: 'H' 'e' 'l' 'l' 'o' '\0'
cmdargs[1] points to: 'w' 'o' 'r' 'l' 'd' '\0'
cmdargs[2] is a null pointer, indicating that it is the end of the vector
Therefore, execvp is unable to find the end of the vector and fails, returning -1. You do not test for this, so the child process continues back to the top of the loop, as does the parent, and both processes print shell>.
EDIT: By the way, the FIRST string in the argv vector SHOULD be the name of the file being executed - in this case, echo, and then the second and third strings should be the first and second 'arguments' - here hello and world. This is the argv vector fed to the program you call, and by convention the first element in that vector is the name of the called program. If you ignore that convention, echo will get terribly confused.
cmdargs is defined as an array of 100 string-pointers, but you seem to use it as one 100-byte buffer for a single string. I also don't get why you handle token[1] specially. Only token[0] is special, that's the command, all the others are arguments. Processing the arguments should be a loop of
while (cmdargs[i++] = strtok(NULL, " "))
and then the closing NULL pointer in cmdargs for execvp():
cmdargs[i] = NULL;
This being a shell, you also forgot to wait for child processes. You will prompt the user for input before the last child process has finished. The final switch-case should look line this:
pid_t child_pid;
switch (child_pid = fork())
{
case -1:
perror("Fork failed");
return 1;
case 0:
// child
printf("\nHERE\n"); // for debugging
execvp(tokens[0], cmdargs);
perror("Exec failed");
exit(1);
default:
// parent
int status;
wait(&status);
break;
}
I am making a simple shell. It also needs to be able to read text files by lines. This is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
// Exit when called, with messages
void my_exit() {
printf("Bye!\n");
exit(0);
}
int main(void) {
setvbuf(stdout, NULL, _IONBF, 0);
// Char array to store the input
char buff[1024];
// For the fork
int fid;
// Get all the environment variables
char dir[50];
getcwd(dir,50);
char *user = getenv("USER");
char *host = getenv("HOST");
// Issue the prompt here.
printf("%s#%s:%s> ", user, host, dir);
// If not EOF, then do stuff!
while (fgets(buff, 1024, stdin) != NULL) {
// Get rid of the new line character at the end
// We will need more of these for special slash cases
int i = strlen(buff) - 1;
if (buff[i] == '\n') {
buff[i] = 0;
}
// If the text says 'exit', then exit
if (!strcmp(buff,"exit")) {
my_exit();
}
// Start forking!
fid = fork();
// If fid == 0, then we have the child!
if (fid == 0) {
// To keep track of the number of arguments in the buff
int nargs = 0;
// This is a messy function we'll have to change. For now,
// it just counts the number of spaces in the buff and adds
// one. So (ls -a -l) = 3. AKA 2 spaces + 1. Really in the
// end, we should be counting the number of chunks in between
// the spaces.
for (int i = 0; buff[i] != '\0'; i++) {
if (buff[i] == ' ') nargs ++;
}
// Allocate the space for an array of pointers to args the
// size of the number of args, plus one for the NULL pointer.
char **args = malloc((sizeof(char*)*(nargs + 2)));
// Set the last element to NULL
args[nargs+1] = NULL;
// Split string into tokens by space
char *temp = strtok (buff," ");
// Copy each token into the array of args
for (int i = 0; temp != NULL; i++) {
args[i] = malloc (strlen(temp) + 1);
strcpy(args[i], temp);
temp = strtok (NULL, " ");
}
// Run the arguments with execvp
if (execvp(args[0], args)) {
my_exit();
}
}
// If fid !=0 then we still have the parent... Need to
// add specific errors.
else {
wait(NULL);
}
// Issue the prompt again.
printf("%s#%s:%s> ", user, host, dir);
}
// If fgets == NULL, then exit!
my_exit();
return 0;
}
When I run it alone as a shell, it works great. When I run ./myshell < commands.txt, it does not work.
commands.txt is:
ls -l -a
pwd
ls
But the output is:
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!
>Bye!>Bye!
>Bye!
>Bye!
>Bye!
Doesn't even run my commands. Any ideas? I thought my while loop was pretty simple.
I don't know if this is the problem, but you (correctly) mention in a comment that you have to allocate "plus one for the NULL pointer" in the *args array.
However, you don't actually set the last pointer in *args to NULL.
execvp() won't like that.
That doesn't explain why there might be a difference between redirected vs. non-redirected input, other than undefined behavior is a bastard.
Sorry everyone - turns out my text file was in some sort of demented format from Mac's TextEdit GUI. Everything is working great.
I really appreciate all of the helpful responses
[...] Preprocesser directives
void read_command()
{
int i; //index to the arrays stored in parameter[]
char *cp; //points to the command[]
const char *hash = " "; //figures out the strings seperated by spaces
memset(command, 0, 100); //Clear the memory for array
parameter[0] = "/bn/"; //Initialize the path
//Get the user input and check if an input did occur
if(fgets(command, sizeof(command), stdin) == NULL)
{
printf("Exit!\n");
exit(0);
}
//Split the command and look store each string in parameter[]
cp = strtok(command, " "); //Get the initial string (the command)
strcat(parameter[0], cp); //Append the command after the path
for(i = 1; i < MAX_ARG; i++)
{
cp = strtok(NULL, " "); //Check for each string in the array
parameter[i] = cp; //Store the result string in an indexed off array
if(parameter[i] == NULL)
{
break;
cp = NULL;
}
}
//Exit the shell when the input is "exit"
if(strcmp(parameter[0], "exit") == 0)
{
printf("Exit!\n");
exit(0);
}
}
int main()
{
[...]
read_command();
env = NULL; //There is no environment variable
proc = fork();
if(proc == -1) //Check if forked properly
{
perror("Error");
exit(1);
}
if (proc == 0) //Child process
{
execve(parameter[0], parameter, env); //Execute the process
}
else //Parent process
{
waitpid(-1, &status, 0); //Wait for the child to be done
}
[...]
}
The basic idea of the code is to read the input command by the user (done in the read_command() function) (ex: ls -l). Then I divide the input string in little strings and store them in an array. The point is to store the command in parameter[0] (ex: ls) and the parameters in parameter[1,2,3 etc.] (ex: -l). However, I think I executing the execve() function incorrectly.
There are all types of issues with your code including the following (some of them are correctly pointed out by Jonathan Leffler):
"/bin/" is misspelled as "/bn/"
Since parameter[0] points to a string literal ("/bn/") in strcat(parameter[0], cp); you are trying to append to this string literal which is incorrect. You should allocate a buffer to hold the concatenated string instead.
Your tokenizing code doesn't handle the trailing newline in command properly.
env should point to a NULL-terminated array of strings.
In general, I think you should focus on implementing and testing parts of your code properly before integrating them in a larger program. If you tested the read_command before trying to pass its results to execve, you would notice that it doesn't work.
I love the ideas presented in Brian Kernighan and Rob Pike's book, "The UNIX Programming Environment," where they focus on the point of working within an environment where you can put together many (small, precise, well understood) programs on the command line to accomplish many programming tasks.
I'm brushing up on strict ANSI C conventions and trying to stick to this philosophy. Somewhere in this book (I can get an exact page number if needed) they suggest that all programs in this environment should adhere to the following principles:
If input is presented on the command line, as an argument to the program itself, process that input.
If no input is presented on the command line, process input from stdin.
Here's a C program I wrote that will echo any input (numeric or alphabetic) that is a palindrome. My question specifically:
Is this a well behaved C program? In other words, is this what Kernighan and Pike were suggesting is the optimal behavior for a command line application like this?
#include <stdio.h>
#include <string.h> /* for strlen */
int main(int argc, char* argv[]) {
char r_string[100];
if (argc > 1) {
int length = (int)strlen(argv[1]);
int i = 0;
int j = length;
r_string[j] = (char)NULL;
j--;
for (i = 0; i < length; i++, j--) {
r_string[j] = argv[1][i];
}
if (strcmp(argv[1], r_string) == 0) {
printf("%s\n", argv[1]);
}
} else {
char* i_string;
while (scanf("%s", i_string) != EOF) {
int length = (int)strlen(i_string);
int i = 0;
int j = length;
r_string[j] = (char)NULL;
j--;
for (i = 0; i < length; i++, j--) {
r_string[j] = i_string[i];
}
if (strcmp(i_string, r_string) == 0) {
printf("%s\n", i_string);
}
}
}
return 0;
}
Yes, I think that you are following the R&K advice. As Hugo said, you could take the argumentas a filename, bu,t IMHO, for this simple program, I'd say that taking the parameter as the palindrome itself may make more sense.
Also, if you allow me extra advice, I would separate the functionality of reading a string from checking whether it is a palindrome or not, because you have that code duplicated right now.
int ispalindrome(const char* c) {
size_t len = strlen(c);
size_t limit = len/2;
size_t i;
for (i = 0; i < limit; i++) {
if(c[i]!=c[len-i-1]) break; /* Different character found */
}
return i==limit; /* If we reached limit, it's a palyndrome */
}
Of course, I am pretty sure this can be improved (it may even have a bug, I am typping quite fast), but once that you have your string, be either from command line or user input, you can call this function or a functiom like this.
NOTE: Edited to reflect comment from Mark, thanks a lot, Mark!
One problem that you have is a potential buffer overflow because you are writing an input of arbitrary length into a buffer with a fixed size. You can fix this by rejecting too long inputs or creating an array of the correct size dynamically. I would avoid using scanf.
Regarding the actual algorithm, you don't need to copy the string reversed and then compare the two strings. You could do the check using only a single copy of the string and a pointer at both ends, both moving in towards the middle.
Here is some code to show the principle:
char* a = /* pointer to first character in string */;
char* b = /* pointer to last character in string (excluding the null terminator) */;
while (a < b && *a == *b)
{
a++;
b--;
}
if (a >= b)
{
// Is palindrome.
}
I agree with Javier that you factor the palindrome checking code out into a separate function.
Regarding the principles you specified, I believe that these tools usually take their arguments as filenames whose content is to be processed. Instead, you are treating them like the input itself.
Take sort, for example. If you don't specify any arguments, the contents from stdin will be sorted. Otherwise, the contents in the file whose filename you specified will be sorted. It is not the arguments themselves that are processed.
The code for this would be something along these lines:
FILE * input = stdin;
if (argc > 1)
{
input = fopen(argv[1], "r");
// handle possible errors from the fopen
}
while (fscanf(input, "%s", i_string) != EOF)
// check if i_string is a palindrome and output to stdout
Also, you should be careful with the buffer overflow specified by Mark Byers.
You're not handling the string reading correctly. The i_string buffer is not initialized, and even if it were, you're should limit the number of bytes that scanf reads to avoid the mentioned overflow:
char i_string[1000];
while (scanf("999%s", i_string) != EOF)
if (is_palindrome(i_string)) /* Use any function defined in the other answers */
printf("%s\n", i_string);
You must always reserve one more byte (1000 vs 999) to account for the NULL string terminator. If you want to allow arbitrary length strings, I think you'll have to dinamically allocate the buffer, and resize it in case bigger strings are present. This would be slightly more complicated.
It is useful for text filters such as a program that prints only lines with palindromes to specify input files via command line arguments e.g., it allows:
$ palindromes input*.txt # file patterns
$ find -name '*.txt' -print0 | xargs -0 palindromes
It is common convention that is supported by many languages. Below are scripts in Perl, Python, C that has the same usage:
Usage: palindromes [FILE]
Print lines that are polindromes in each FILE.
With no FILE, or when FILE is -, read standard input.
in Perl
#!/usr/bin/perl -w
while (<>) { # read stdin or file(s) specified at command line
$line = $_;
s/^\s+//; # remove leading space
s/\s+$//; # remove trailing space
print $line if $_ eq reverse $_; # print line with a palindrome
}
in Python
#!/usr/bin/env python
import fileinput, sys
for line in fileinput.input(): # read stdin or file(s) specified at command line
s = line.strip() # strip whitespace characters
if s == s[::-1]: # is palindrome
sys.stdout.write(line)
in C
#!/usr/local/bin/tcc -run -Wall
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
enum {
MATCH,
NO_MATCH,
ERROR
};
bool is_palindrome(char *first, char *last) {
/** Whether a line defined by range [first, last) is a palindrome.
`last` points either to '\0' or after the last byte if there is no '\0'.
Leading and trailing spaces are ignored.
All characters including '\0' are allowed
*/
--last; // '\0'
for ( ; first < last && isspace(*first); ++first); // skip leading space
for ( ; first < last && isspace(*last); --last); // skip trailing space
for ( ; first < last; ++first, --last)
if (*first != *last)
return false;
return true;
}
int palindromes(FILE *fp) {
/** Print lines that are palindromes from the file.
Return 0 if any line was selected, 1 otherwise;
if any error occurs return 2
*/
int ret = NO_MATCH;
char *line = NULL;
size_t line_size = 0; // line size including terminating '\0' if any
ssize_t len = -1; // number of characters read, including '\n' if any,
// . but not including the terminating '\0'
while ((len = getline(&line, &line_size, fp)) != -1) {
if (is_palindrome(line, line + len)) {
if (printf("%s", line) < 0) {
ret = ERROR;
break;
}
else
ret = MATCH;
}
}
if (line)
free(line);
else
ret = ERROR;
if (!feof(fp))
ret = ERROR;
return ret;
}
int main(int argc, char* argv[]) {
int exit_code = NO_MATCH;
if (argc == 1) // no input file; read stdin
exit_code = palindromes(stdin);
else {
// process each input file
FILE *fp = NULL;
int ret = 0;
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-") == 0)
ret = palindromes(stdin);
else if ((fp = fopen(argv[i], "r")) != NULL) {
ret = palindromes(fp);
fclose(fp);
} else {
fprintf(stderr, "%s: %s: could not open: %s\n",
argv[0], argv[i], strerror(errno));
exit_code = ERROR;
}
if (ret == ERROR) {
fprintf(stderr, "%s: %s: error: %s\n",
argv[0], argv[i], strerror(errno));
exit_code = ERROR;
} else if (ret == MATCH && exit_code != ERROR)
// return MATCH if at least one line is a MATCH, propogate error
exit_code = MATCH;
}
}
return exit_code;
}
Exit status is 0 if any line was selected, 1 otherwise;
if any error occurs, the exit status is 2. It uses GNU getline() that allows arbitrary large lines as an input.