I'm trying to write a simple shell, it all works except the redirect with '>'. I was advised to parse my input string from the user using a delimiter of ">". The output string then has two elements, output[0] will be the entire command that user enters including the arguments. output[1] will be the filename.
My issue is that it creates the file specified by output[1], however it writes nothing to it! Even if I put a printf to stdout, it writes to the terminal instead.
else if(*delim == '>'){
int f = open(output_str[1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
int stdout_sv = dup(1); // Saving the current stdout descriptor for restoring later.
if(errno != 0){
perror("");
}
dup2(f,1);
execvp(output_str[0], output_str);
printf("Sample Line to file.");
dup2(stdout_sv, 1); // Restoring stdout
close(f);
}
This results in the following output:
[04/04 15:55]# ls > test.txt
output_str[0] = ls
output_str[1] = test.txt
Sample Line to file.[04/04 15:55]#
I'm really confused as to why the stdout isn't being redirected towards the output file(test.txt). I mean the output file is correct.
Note: I realise there is a space before test.txt, I have tried parsing with " >" which removes the space in the output_str[1], the file still ends up empty.
Related
I'm making a minishell and I have to create in C a command as "<<" in bash.
So I have to enter some input in the terminal and redirect it to the standard input but I'm having problems doing this.
Example of what should happen:
Minishell > cat << deli
> jlaf
> faljs
> deli
jlaf
faljs
What is happening:
Minishell > cat << deli
> jlaf
> faljs
> deli
jlaf
faljs
Is the same but it gets stuck after faljs.
The code for "<<" is the following:
buffer = readline("> ");
while (strncmp(buffer, delimiter, ft_strlen(buffer)) != 0)
{
str = ft_strjoin(str, ft_strjoin("\n", buffer);
free(buffer);
buffer = readline("> ");
}
write(0, str+1, ft_strlen(str));
readline() is the c function which reads the input of every line till it reaches the delimiter in the loop, ft_strjoin simply joins two strings and returns a new one. At the end I try to write all the lines read to the stdin which I think is the problem. Later I execute a process in execve, for example cat if I have put "cat << delimiter" to the stdout and supposedly with the previously written information in the stdin. But something is wrong and it shows the output but I get stuck in the terminal.
¿What could be the problem in this code?
Transferring a comment into a semblance of an answer.
Normally (traditionally), you'd write the here document to a file (probably an anonymous file) and then duplicate the file descriptor of the file to the child's standard input (and close it in the parent). Being anonymous, the file data will be released when the last process with a file descriptor for the file exits.
An "anonymous" file is a file which you create with a name (and for which you get a file descriptor) and then immediately remove. The standard C tmpfile() creates such a file for you but returns a file stream (FILE *). You probably want the equivalent that gives you a file descriptor. The POSIX functions mkstemp() and
unlink() would give you that functionality.
So I am using the open function in C to open a target file, and if it does not exist, I will create it. I am redirecting stdout from the terminal into this file. When I run my program, it creates a new file with the name that I type into the shell, but appends a "?" to the end. Can someone help me figure out how to remove that?
My code is below
// Take output from ls as input into the next argument.
command = strtok(NULL, " "); // Holds the value for the destination file.
printf("%s\n", command);
int targetFD = open(command, O_WRONLY | O_CREAT | O_TRUNC, 0700);
if (targetFD == -1)
{
perror("open()");
exit(1);
}
printf("The file descriptor for targetFD is %d\n", targetFD);
int result = dup2(targetFD, 1);
if (result == -1)
{
perror("dup2");
exit(2);
}
ls(); // instead of printing ls to the terminal, it gets written to the file.
Here is an image of a sample execution. Notice how junk.txt file already exists...I want my program to redirect "ls" into that file or create a new one of the file does not exist.
sample program execution
I bet the ? is not a literal ? character, but the way that your version of ls displays a filename containing a non-printable character, e.g. if you use GNU ls and have the -q option enabled by default.
I also note that in your sample output, there is an extra blank line between junk.txt and The file descriptor for targetFD. You only printed one newline after command, so I suspect that the string command itself ends with a \n. That would fit if it was parsed from a string that ended with \n (e.g. a line read with fgets). So you actually created a file named junk.txt\n, and ls prints the newline character as ?.
Perhaps you wanted to use " \n" as the delimiter string for strtok, so that a newline will also be treated as a delimiter and not be included in the token string.
Im programming a shell. This is a small piece of it that handles redirects. I only included the snippet that is causing the error. This portion of the program is supposed to take a command/argument line and execute the line accordingly. The wc < junk > junk2 is an example of a command line input and for some reason when i execute the line its producing that error junk: >: open: No such file or directory. The rest of the program functions fine but its breaking here.
: wc < junk > junk2
junk: >: open: No such file or directory
1 4 31 junk2
1 4 31 total
I briefly had it working as intended but for some reason I must have lost a piece of the puzzle because it's broken once again.
Ive looked at my array values and they are as expected. For some clarification I'm using a copy of the arguments array so the original is unchanged by setting the argument copy value to NULL. Im setting the argument copy value to null so the loop doesn't repeatedly use a redirection.
If you have any advise or see the problem that I apparently can't it would be appreciated.
while (arguments[i] != NULL)
{
if ((strcmp(argumentsCopy[i], "<") == 0))
{
if ((access(argumentsCopy[i + 1], R_OK) == -1))
{
printf("Cannot open %s for input\n", argumentsCopy[i + 1]);
fflush(stdout);
redirect = 1;
}
else
{
int fd = open(argumentsCopy[i + 1], O_RDONLY);
dup2(fd, STDIN_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1;
execvp(command, &arguments[i + 1]);
}
}
if ((strcmp(argumentsCopy[i], ">") == 0))
{
int fd = open(argumentsCopy[ i + 1], O_RDWR);
dup2(fd, STDOUT_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1; // avoid repeat use of redirection symbol in arguments array
execvp(command, &arguments[i + 1]);
}
i++;
}
if (redirect == 0)
{
execvp(command, execArgs); //execArgs is entire command w/ arguments
}
exit 0; //default if error occurred.
First, note that you're not correctly using the execvp function. Once a call to execvp (or any call in the exec family) succeeds, the execution of the current program terminates and is replaced by the program you are execing. Thus, control flow should never proceed past an execvp call unless that call failed. Therefore, you cannot call execvp until you are sure both the stdin and stdout file descriptors have been properly redirected (in the case that you have both input and output redirection). From your code sample, it appears you call execvp once either one is detected, which means your shell can only redirect input or output, not both.
However, that is not the source of the error you're receiving. Consider the input wc < junk > junk2. Based on the information you provided, the command and arguments array will be populated as follows:
command = "wc"
arguments[0] = ">"
arguments[1] = "junk"
arguments[2] = "<"
arguments[3] = "junk2"
arguments[4] = NULL
When i = 0, the first if-statement will be taken, opening the file descriptor and performing execvp with the parameters command and &arguments[i+1]. These correspond to the file to be executed and the argv array passed to that function's main method respectively. In this case, the command is wc and the args are { "junk", "<", "junk2", NULL } (as this is the value of the null terminated array beginning at &arguments[i+1]). On Unix systems, it is conventional for the first argument (argv[0]) to be the name of the current program. Thus the actual command line arguments begin at argv[1] (see Program Arguments for documentation). From wc's perspective, the command line arguments it should process are { "<", "junk2", NULL } after it processes what it believes to be its program name, argv[0], or junk.
wc takes as arguments the list of files it will process. In this case, it believes that list to be < and junk2, as "<" is an operator recognized by bash and other shells, not the executing program. Thus, wc (which believes its name is junk because of argv[0]) attempts to open < as an input file and fails, printing the message:
junk: >: open: No such file or directory
Im writing a program using C wherein in executing system commands using POPEN.
Write the output of every command to a file.
I want to be able to write to the same file and number the output of the commands.
Here's the output that Im aiming to do.
Assuming the commands that I want to run is..
1) ls
2) bash -version
So the output that I want looks something like this..
1) ls
*file.txt file2.txt file3.txt file4.txt
2) bash -version
GNU bash, version 4.2.45(1)-release (i686-pc-linux-gnu)
This is the code that I made:
FILE * out = NULL;
char * com = malloc(2000);
char * execCom = malloc(30000);
char * comR = malloc(2000);
strcpy(execCom, commands);
FILE * fp = NULL;
//remove("conf.txt")
out=fopen("com.txt","w");
dup2(fileno(out),1);
//run command and redirect the output to the file specified
if (execCom!=NULL)
{
fp=popen(com1,"w");
fwrite(com2, 1,strlen(com2),fp);
fwrite(com3, 1, strlen(com3),fp);
// get the first token
com = strtok(execCom, "\n");
// walk through other tokens
while( com != NULL )
{
strcpy(comR,"\0");
strcat(comR,com);
strcat(comR,"\n");
printf("%s",comR);
//execute command
fwrite(comR, 1, strlen(comR),fp);
com = strtok(NULL, "\n");
}
pclose(fp);
fclose(out);
}
I was able to redirect the output of stdout to a file and I used dup2 to achieve this. Then knowing that dup2 redirects stdout to file, I used printf, print the string that I want to insert in the file (I was expecting that the string in the printf will be redirected to the same file and insert it in the place that I want but it didnt.)
Here's what I get..
Yes, the output was redirected to the file as well as the string in the printf. But, as you can see below, it is not formatted the way I want it to be...
*file.txt file2.txt file3.txt file4.txt
GNU bash, version 4.2.45(1)-release (i686-pc-linux-gnu)
1) ls
2) bash -version
How will I be able to do that? Thank you!
EDIT: I've managed to narrow down the problem, but it still doesn't make much sense to me. My code becomes 8 lines:
int savedOut = dup(1);
printf("Changing the outstream to process.txt!")
if ( freopen("process.txt", "w"m stdout) == NULL)
printf("FREOPEN() FAILURE")
printf("Print to File\n");
dup2(savedOut, 1);
printf("Done with print to file");
return 1;
This code prints all to the terminal. Removing the two lines with "savedOut" prints all to process.txt. I understand the latter result, but I don't understand the former.
END EDIT
I'm having a lot of difficulty working with freopen(). Take this snippet of code:
int savedIn = dup(0);
int savedOut = dup(1);
while(1)
{
readFile[0] = '\0';
writeFile[0] = '\0';
dup2(savedIn, 0);
dup2(savedOut, 1);
if(getInputFlag == 1)
{
printf("myshell$ ");
gotInputFlag = getUserInput(arguments, command, readFile, writeFile, catOrApp, bkgdFlag);
}
else
{
gotInputFlag = getUserInput(arguments, command, readFile, writeFile, catOrApp, bkgdFlag);
}
if(gotInputFlag == 1)
{
history[historySize] = (char*)malloc(sizeof(char) * 1000);
if (writeFile[0] != '\0' && *catOrApp == 0)
{
printf("Changing the outstream!\n");
freopen(writeFile, "w", stdout);
}
printf("Print to File\n");
dup2(savedIn, 0);
dup2(savedOut, 1);
printf("Done with print to file!\n");
...
This program will execute and print "Changing the Outstream!" just as expected. "Print to File!" is never printed, neither to the terminal nor to the writeFile. "Done with print to file!\n" does not print either. Later on in the code, the code calls an execv() on a Hello World program, which prints to terminal as expected. However, upon program termination suddenly all the printf statements print to terminal, even the "Print to File" statements.
Save for a single fgets(), getUserInput() does not work with streams.
I can't for the life of me understand why this is happening. Does anyone have any idea?
This is what I believe is happening is all associated with stream buffering. You are starting with stdout pointing at your terminal. Because it is a character device, the stream is line-buffered. You print Changing the outstream to process.txt! without a new-line so the text stays in the buffer. You now reopen stdout to a file; the stream switches to fully-buffered. You then print Print to File\n which remains in the buffer despite the new-line.
Now you use dup2 to change stdout back to the terminal. However, this works on the fd that underlies the stream. The stream library code is unaware of this change and leaves the stream fully buffered. You print once more and exit, which flushes the stream to the fd (now your terminal).
Add fflush calls after each printf and I'll bet you see the behavior you expect.