I'm trying to create a program that runs commands from user input.
At the moment it works for multiple word commands but I'm trying to implement redirections.
I started with taking input from a file and it's not working but I'm not getting any error (I'm testing using the "wc -l < text.txt" command, the text.txt file is the the same dir as the program.)
Here is the code:
- input is the str with the user's input
- before coming to this method I already checked that it has a redirection on it
redirect(int proc, char * input){
char * comm;
if(proc == 1){ //in
comm = strsep(&input, "<");
}
else{ //out
comm = strsep(&input, ">");
}
int proc2 = check(input);
if(proc2 == 0){ //only one redirection
if(proc == 1){ //in
input = trim(input);
int fd = open(input, O_RDWR);
close(0);
dup2(fd, 0);
close(fd);
comm = trim(comm);
char ** words = parse(comm);
char str[105];
strcpy(str, "/bin/");
strcat(str, words[0]);
shrink(str);
if(!execvp(str, words)){ /*exec failed */
exit(1);
}
}
else{ //out
}
}
else{ //more than one redirection/pipe
}
}
edit
I need to use the execvp command to run the user input.
The user command "<" needs to change the stdin to be the file after it.
I changed the stdin to be the text.txt but I don't know how to pass it as an arg so the execvp can run it.
what types of commands you are trying to execute ?
if your commands are dos commands, you can read the user inputs in a string variable
, and when you want to execute the file . create a bat file then execute it
by the following code
Process.Start("your file path ");
If its just about execution of the user specified commands then to execute the shell commands using you can use the system() call. This function takes the command to be executed as argument and executes it on the command shell. You would not need to make any separate file.
you can take the command user wants to execute as string and then pass it to system() as argument to execute it.
ie
system("wc -l < text.txt");
System() works fine for both Linux and Windows.
References : Execute a Linux command in the c program
http://www.gnu.org/software/libc/manual/html_node/Running-a-Command.html
After a lot of testing and research I found that the file that I was using did not have the permissions necessary for the execvp to read it from it.
I figure it out when I wrote the code for writing into a file and then try to read that newly created file and it worked (after adding the flags).
Here is the code:
redirect(int proc, char * input){
char * comm;
if(proc == 1){ //in
comm = strsep(&input, "<");
}
else{ //out
comm = strsep(&input, ">");
}
int proc2 = check(input);
if(proc2 == 0){ //only one redirection
if(proc == 1){ //in
input = trim(input);
int fd = open(input, O_RDWR);
close(0);
dup2(fd, 0);
close(fd);
comm = trim(comm);
char ** words = parse(comm);
if(!execvp(words[0], words)){ /*exec failed */
exit(1);
}
}
else{ //out
input = trim(input);
int fd = open(input, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
dup2(fd, 1);
close(fd);
comm = trim(comm);
char ** words = parse(comm);
if(!execvp(words[0], words)){ /*exec failed */
exit(1);
}
}
}
else{ //more than one redirection/pipe
}
}
So I run the command "ls > text.txt" and it creates the text.txt file with the "ls" results in it and then run the "wc -l < text.txt" command and it returns the lines in the file.
Related
I have been working on a custom shell script and have come to a small error when redirecting output with the code given below. In its current state the code works perfectly but when passing to execvp args throws errors such as : (ls ">" no such file or directory). I know this is because it is passing the whole args[] to the parent shell which isn't working. Adding in the args[j] = NULL takes away the "<"/ ">" thus fixing the error, but also causes the redirections to not work anymore. How can I get it to not throw an error but also work properly? I have read multiple versions of this question but cant seem to find an answer. Thanks in advance for any help.
switch (fork()){
case -1:
fprintf(stderr, "error forking");
case 0://CHILD
for(int j = 0; j < size; j++){
if(!strcmp(args[j], "<")){//looking for input character
++ext;
if((in = open(args[j+1], O_RDONLY)) < 0){//open file for reading
fprintf(stderr, "error opening file\n");
}
dup2(in, STDIN_FILENO);//duplicate stdin to input file
close(in);//close after use
//args[j] = NULL;
}//end input chech
if(!strcmp(args[j],">")){//looking for output character
++ext;
out = creat(args[j+1], 0644);//create new output file
dup2(out, STDOUT_FILENO);//redirect stdout to file
close(out);//close after usere
// args[j] = NULL;
}//end output check
if(!strcmp(args[j], ">>")){//looking for append
++ext;
int append = open(args[j+1],O_CREAT | O_RDWR | O_APPEND, 0644);
dup2(append, STDOUT_FILENO);
close(append);
// args[j] = NULL;
}
}//end loop
execvp(args[0],args);//execute in parent
fprintf(stderr, "error in child execi \n");//error
exit(0);
default://PARENT
wait(&status); //wait for child to finish
}//end switch
When you are parsing redirections (e.g. <, >, >>) and doing your open/dup2, you have to strip them from the argument list you pass to execvp.
So, given your args, you need a second (e.g. args_clean) argument list that you only copy over the program name and its arguments.
And, you need an extra increment of j to skip over the redirection file in args (i.e. just doing j + 1 isn't equivalent).
Here's the cleaned up child code [please pardon the gratuitous style cleanup]:
char *args_clean[size];
int cleanidx = 0;
for (int j = 0; j < size; j++) {
if (!strcmp(args[j], "<")) { // looking for input character
++j;
if ((in = open(args[j], O_RDONLY)) < 0) { // open file for reading
fprintf(stderr, "error opening file\n");
}
dup2(in, STDIN_FILENO); // duplicate stdin to input file
close(in); // close after use
continue;
} // end input chech
if (!strcmp(args[j], ">")) { // looking for output character
++j;
out = creat(args[j], 0644); // create new output file
dup2(out, STDOUT_FILENO); // redirect stdout to file
close(out); // close after usere
continue;
} // end output check
if (!strcmp(args[j], ">>")) { // looking for append
++j;
int append = open(args[j], O_CREAT | O_RDWR | O_APPEND, 0644);
dup2(append, STDOUT_FILENO);
close(append);
continue;
}
args_clean[cleanidx++] = args[j];
} // end loop
args_clean[cleanidx] = NULL;
execvp(args_clean[0], args_clean); // execute in parent
fprintf(stderr, "error in child execi \n"); // error
exit(0);
Also, see my answer here for something similar with pipes: fd leak, custom Shell
And, for a full blown shell, see my answer: Implementing input/output redirection in a Linux shell using C and look at the embedded pastebin link
I want to run execlp() from C file and write the result to some output file.
I use the line:
buff = "./cgi-bin/smth";
execlp(buff, buff, "> /cgi-bin/tmp", NULL);
where smth is a compiled c script.
But smth prints to stdout, and no file appears.
What happens, and how to put script result to an output file?
You have to handle it yourself with dup2 if using execlp. You can look at how I handle file out with execvp in comparison. I pass a flag for out redirection and then I handle it:
if (structpipeline->option[0] == 1) { /* output redirection */
int length = structpipeline[i].size;
char *filename = structpipeline->data[length - 1];
for (int k = length - 2; k < length; k++)
structpipeline->data[k] = '\0';
fd[1] = open(filename, O_WRONLY | O_CREAT, 0666);
dup2(fd[1], STDOUT_FILENO);
close(fd[1]);
} /* TODO: input redirection */
execvp(structpipeline[i].data[0], structpipeline[i].data);
See also this question
Redirecting exec output to a buffer or file
I'm trying to write a simple code which execute a program from subfolders from a input file and print thr result into a output file.
My problem is that when i execute the program it keeps failing on me. since the execvp command is trying to look for an exe named "a.out" on the wrong location. in (desktop rather than searching the correct path address).
here's the code. please help me out :)
pid_t runner;
char enter[] = "/home/demo/Desktop/OS/Ex1/Ex12/code/input.txt"; // input file
char path[] = "/home/demo/Desktop/OS/Ex1/Ex12/Ex1/ronen/"; correct path
char *r [] = {"./a.out", NULL};
int savedFD = dup(0);
int sever2Fd=dup(1);
int fdin = open(enter,O_RDONLY);
int fdout = open ("output.txt", O_CREAT | O_RDWR, 0466);
dup2(fdin, 0);
dup2(fdout, 1);
if ((runner = fork()) < 0) {perror("could not make fork");}
else if (runner == 0) {
if (execvp(r[0],r) < 0 ) {printf("Failed!\n");}
} else if (runner != 0) {
waitpid(runner,0,0);
dup2(savedFD, 0);
dup2(sever2Fd, 1);
printf("done\n");
}
close(fdin);close(fdout);
The answer was simple.
"chdir(wanted path)"
int dirchange = chdir(argv[1]);
I currently have a rudimentary implementation of Bash written in C. However, I'm getting issues when I try to redirect the standard output twice. Here is the relevant code:
Reading in each command:
for ( ; ; ) {
printf ("(%d)$ ", nCmd); // Prompt for command
fflush (stdout);
if ((line = getLine (stdin)) == NULL) // Read line
break; // Break on end of file
list = lex (line);
free (line);
if (list == NULL) {
continue;
} else if (getenv ("DUMP_LIST")) { // Dump token list only if
dumpList (list); // environment variable set
printf ("\n");
}
cmd = parse (list); // Parsed command?
freeList (list);
if (cmd == NULL) {
continue;
} else if (getenv ("DUMP_TREE")) { // Dump command tree only if
dumpTree (cmd, 0); // environment variable set
printf ("\n");
}
process (cmd); // Execute command
freeCMD (cmd); // Free associated storage
nCmd++; // Adjust prompt
}
The part of the shell we're my code is messing up:
if (cmdList->type==SIMPLE)
{
pid_t fork_result;
fork_result = fork();
if (fork_result < 0) {
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == 0) {
if (cmdList->fromType==RED_IN)
{
int fe = open(cmdList->fromFile, O_RDONLY, 0);
dup2(fe, 0);
close(fe);
}
if ((cmdList->toType==RED_OUT) || (cmdList->fromType==RED_APP))
{
int fd = open(cmdList->toFile, O_CREAT | O_WRONLY, 0666);
dup2(fd, 1);
close(fd);
}
execvp(cmdList->argv[0],cmdList->argv);
exit(EXIT_FAILURE);
}
else {
int status;
wait(&status);
}
}
This last snippet of code works exactly how I intend it to when I'm reading in just one simple command. However, the issue arises when I use the for loop to try to redirect stout twice. For example, I try to run:
cat Tests/star.wars > +Bash.tmp
cat +Bash.tmp
cat Tests/stk.txt > +Bash.tmp
cat +Bash.tmp
The first command writes, say, "ABC" to Bash.tmp. However, when I run the second command, I expect it to return "DE". However, I'm getting "DEC" as the output. What is wrong?
O_WRONLY is "write-only" permissions. O_TRUNC is what truncates the file on open.
– Etan Reisner
I've about got my practice implementation of a Unix shell done, except I'm having an issue with implementing cat when its output is to a file; IE: cat foo.txt > bar.txt - outputting foo's contents to bar.
Let's start from the main function & then I'll define the submethods:
int main(int argc, char **argv)
{
printf("[MYSHELL] $ ");
while (TRUE) {
user_input = getchar();
switch (user_input) {
case EOF:
exit(-1);
case '\n':
printf("[MYSHELL] $ ");
break;
default:
// parse input into cmd_argv - store # commands in cmd_argc
handle_user_input();
//determine input and execute foreground/background process
execute_command();
}
background = 0;
}
printf("\n[MYSHELL] $ ");
return 0;
}
handle_user_input just populates the cmd_argv array to execute the user_input, and removes the > and sets an output flag if the user wishes to output to a file. This is the meat of that method:
while (buffer_pointer != NULL) {
cmd_argv[cmd_argc] = buffer_pointer;
buffer_pointer = strtok(NULL, " ");
if(strcmp(cmd_argv[cmd_argc], ">") == 0){
printf("\nThere was a '>' in %s # index: %d for buffer_pointer: %s \n", *cmd_argv,cmd_argc,buffer_pointer);
cmd_argv[cmd_argc] = strtok(NULL, " ");
output = 1;
}
cmd_argc++;
if(output){
filename = buffer_pointer;
printf("The return of handling input for filename %s = %s + %s \n", buffer_pointer, cmd_argv[0], cmd_argv[1]);
return;
}
}
execute_command is then called, interpreting the now populated cmd_argv. Just to give you an idea of the big picture. Obviously, none of these cases match and the create_process method is called:
int execute_command()
{
if (strcmp("pwd", cmd_argv[0]) == 0){
printf("%s\n",getenv("PATH"));
return 1;
}
else if(strcmp("cd", cmd_argv[0]) == 0){
change_directory();
return 1;
}
else if (strcmp("jobs", cmd_argv[0]) == 0){
display_job_list();
return 1;
}
else if (strcmp("kill", cmd_argv[0]) == 0){
kill_job();
}
else if (strcmp("EOT", cmd_argv[0]) == 0){
exit(1);
}
else if (strcmp("exit", cmd_argv[0]) == 0){
exit(-1);
}
else{
create_process();
return;
}
}
Pretty straight forward, right?
create_process is where I'm having issues.
void create_process()
{
status = 0;
int pid = fork();
background = 0;
if (pid == 0) {
// child process
if(output){
printf("Output set in create process to %d\n",output);
output = 0;
int output_fd = open(filename, O_RDONLY);
printf("Output desc = %d\n",output_fd);
if (output_fd > -1) {
dup2(output_fd, STDOUT_FILENO);
close(output_fd);
} else {
perror("open");
}
}
printf("Executing command, but STDOUT writing to COMMAND PROMPT instead of FILE - as I get the 'open' error above \n");
execvp(*cmd_argv,cmd_argv);
// If an error occurs, print error and exit
fprintf (stderr, "unknown command: %s\n", cmd_argv[0]);
exit(0);
} else {
// parent process, waiting on child process
waitpid(pid, &status, 0);
if (status != 0)
fprintf (stderr, "error: %s exited with status code %d\n", cmd_argv[0], status);
}
return;
}
My printed output_fd = -1, and I manage to get the perror("open") inside the else stating: open: No such file or directory. It then prints that it's "writing to COMMAND PROMPT instead of FILE", as I display to the console. Then executes execvp which handles cat foo.txt, but prints it to the console instead of the file.
I realize it shouldn't at this point, as having output_fd = -1 isnt desirable and should be returning another value; but I cant figure out how to use file descriptors correctly in order to open a new/existing file with cat foo.txt > bar.txt and write to it, as WELL AS GET BACK to the command line's stdin.
I have managed to output to the file, but then lose getting back the correct stdin. Could someone please direct me here? I feel like I'm going in circles over something silly I'm doing wrong or looking over.
Any help is greatly GREATLY appreciated.
Why do you use O_RDONLY if you want to write to the file? My guess is that you should use something like:
int output_fd = open(filename, O_WRONLY|O_CREAT, 0666);
(The 0666 is to set up the access rights when creating).
And obviously, if you can't open a redicted file, you shouldn't launch the command.
First, obvious thing I notice is that you've opened the file O_RDONLY. Not going to work so well for output!
Second, basic process for redirecting the output is:
open file for writing
dup stdout so you can keep a copy if needed. same with stderr if redirecting.
fcntl your duplicate to CLOEXEC (alternatively, use dup3)
dup2 file to stdout
exec the command
and finally, are you really passing around command names as global variables? I think this will come back to haunt you once you try and implement cat foo | ( cat bar; echo hi; cat ) > baz or somesuch.