I am trying to learn how to use the pipe() command in C, and trying to create a test program to duplicate the functionality of ls | grep ".c", if I were to enter this into a linux terminal. If I enter this into the terminal, I only get test.c as a result.
My code is as follows:
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "fcntl.h"
int main(int argc, char** argv)
{
int pipefd[2];
int childpid,childpid2;
char* cmd[3]={"ls",NULL,NULL};
char* cmd2[3]={"grep",".c",NULL};
pipe(pipefd);
if(childpid=fork()){
//parent
}else{
//child
//write
close(pipefd[0]);
dup2(pipefd[1],STDOUT_FILENO);
execvp("ls", cmd);
}
if(childpid2=fork()){
}
else{
close(pipefd[1]);
dup2(pipefd[0],STDIN_FILENO);
execvp("grep",cmd2);
}
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
This code returns me the following results ($ is the terminal prompt):
$a.out
$test.c
(blank line)
The program doesn't complete, but hangs until I quit it. What problems do I have? How can I mimic the terminal? I'm new to C, and using a premade template of a program, so forgive me if there are glaring mistakes.
Try this:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char** argv)
{
int pipefd[2];
int childpid,childpid2;
char* cmd[3]={"ls",NULL,NULL};
char* cmd2[3]={"grep",".c",NULL};
pipe(pipefd);
if(childpid=fork()){
//parent
close(pipefd[1]);
dup2(pipefd[0],STDIN_FILENO);
execvp("grep",cmd2);
}else{
//child
//write
close(pipefd[0]);
dup2(pipefd[1],STDOUT_FILENO);
execvp("ls", cmd);
}
return 0;
}
Actually the program exits right away — in fact, the parent process exits before the children run, which is why there's a shell prompt before "test.c".
You can improve things a bit by adding this in your parent:
wait(childpid);
wait(childpid2);
which will make the parent exit after both children.
Your program quits immediately, while your processes run in the background. They overwrite the prompt, and makes you think the program is still running even though the shell is waiting for your input (press enter or type a command blindly and see.
You only see test.c because that's the only matching file in your directory (also note that you're checking for "filenames containing c anywhere except the first character", not "ending in .c" which would be grep '\.c$').
The simple fix is to add:
wait(NULL); wait(NULL);
right before your return 0.
This question is a bit old, but here's an older answer that was never provided. Use libpipeline. libpipeline is a pipeline manipulation library. The use case is one of the man page maintainers who had to frequently use a command like the following (and work around associated OS bugs):
zsoelim < input-file | tbl | nroff -mandoc -Tutf8
Here's the libpipeline way:
pipeline *p;
int status;
p = pipeline_new ();
pipeline_want_infile (p, "input-file");
pipeline_command_args (p, "zsoelim", NULL);
pipeline_command_args (p, "tbl", NULL);
pipeline_command_args (p, "nroff", "-mandoc", "-Tutf8", NULL);
status = pipeline_run (p);
The libpipeline has more examples on its homepage. The library is also included in many distros, including Arch, Debian, Fedora, Linux from Scratch and Ubuntu.
Related
Can you give me some ideas on the following code? The code runs but it doesn't exit. The other string, s="ls -1" works well. Running the shell snippet via sh(1) works just fine also.
#include <unistd.h>
#include <string.h>
int
main(int argc, char *argv[])
{
int fd[2];
char *s = "ls -1 \"/usr/bin\" | while IFS= read -r fp\ndo\ncat <<- EOF\n\t$fp\nEOF\ndone;";
//char *s = "ls -1";
pipe(fd);
switch(fork()) {
case 0:
close(fd[1]);
dup2(fd[0], 0);
execl("/bin/sh", "sh", NULL);
close(fd[0]);
break;
default:
close(fd[0]);
write(fd[1], s, strlen(s) + 1);
close(fd[1]);
break;
}
return 0;
}
When I was converting my comments into an answer, I tested the proposed change, and it didn't fix the problem. One fix that ought to work is to add ; exit at the end of the string, even though that's tantamount to cheating. However, testing that also shows it doesn't finish; it is as if the ls isn't terminating.
I went to another terminal to see whether the processes for ls or pipe97 (my name for your code) were still around; they weren't.
Try typing ps to your 'hung' process.
You should get a normal output and your prompt.
Because the parent doesn't wait for the child to exit, the prompt is lost somewhere in the output from ls (which is quite long and produced quite slowly). Redirect the output to /dev/null and you'll see your prompt.
The best fix is probably to add a wait() loop in the parent process, so it doesn't exit until after the child does.
#include <sys/wait.h>
…
int corpse;
int status;
while ((corpse = wait(&status)) > 0)
;
While debugging, you can print corpse and status so you can see what's going on.
I observe that this comment (without its preamble) remains valid:
…the close(fd[0]); needs to be before the execl() — remember, if it is successful, execl() never returns (it only returns on failure). You should arguably have an error report and exit(1); or similar after the execl(); at the moment, you report success even on failure.
By itself, the Rule of Thumb comment is valid, but it is not actually applicable to this code — unclosed pipe descriptors aren't causing the trouble, even though one pipe descriptor that should have been closed was not closed.
#WilliamPursell Thanks, you're right. I wasn't focusing on that on this example.
#user3629249 I'm aware I'm not doing proper error checking, thank you!
#mosvy It was OpenBSD, indeed it ran on a Mac.
This version works:
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int
main(int argc, char *argv[])
{
int fd[2];
char *s = "ls -1 '/usr/bin' | while IFS= read -r fp\ndo\ncat <<- EOF\n\t$fp\nEOF\ndone;";
//char *s = "ls -1";
pipe(fd);
switch(fork()) {
case 0:
close(fd[1]);
dup2(fd[0], 0);
execl("/bin/sh", "sh", NULL);
close(fd[0]);
break;
default:
close(fd[0]);
write(fd[1], s, strlen(s));
close(fd[1]);
wait(NULL);
break;
}
return 0;
}
It was the wait(2) that did it. I remember I did wait(2) at some point, however I think I screwed the close(1) order so it was blocking.
Anyway, problem fixed, thanks!
A regular C program, an implementation of some shell program, which uses standard input to get commands and executes them, in which main() is declared as: int main(int argc, char *argv[]), works normally.
It's a small shell program that prints a prompt and waits for user input in a loop, and executes the command entered. However I want to make the commandline parameters invisible when invoked, as follows:
rename the main() function to old_main().
create a new main() function that:
Copies the argv[] string array to a new array, and then
Erases the original argv[] structure.
Forks itself and in the child calls old_main with the saved commandline parameters, and exits the parent process.
This works as 'ps -ef' now shows the command run without the commandline arguments displayed.
However, the program now behaves different, and does not wait for user-input, but keeps printing the prompt. Even Ctrl-C does not work anymore.
If, however, I call the program as:
$ echo quit | ./newprog
The command 'quit' is executed (causing the program to exit), so the program still works when the stdin is redirected, but does not work in interactive mode.
Below the new program that forks itself and in the child calls the old main with the saved commandline parameters.
/* newprog.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_NARGS 40
extern int old_main(int nargc, char *nargv[]);
int main(int argc, char *argv[])
{
int i; pid_t pid;
int nargc=argc;
char *nargv[];
nargv[0]=argv[0];
for (i = 1; i < argc; i++)
{
nargv[i]=strdup(argv[i]);
memset(argv[i],0,strlen(argv[i]));
}
nargv[i]=NULL;
pid=fork();
if (pid == 0) return old_main(nargc, nargv); /* child calls old_main() */
else if (pid == -1) printf("error: fork()\n");
exit(0); /* exit parent */
}
I am trying to make my own shell in C. It uses one pipe and the input (for now) is static. I execute commands using execvp.
Everything is fine except when I run the command ls |grep ".c" I get no results. Can anyone show me where is the problem and find a solution.
The shell so far:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int p[2];
int pid;
int r;
main()
{
char *ls[] = {"ls", NULL};
char *grep[] = {"grep", "\".c\"", NULL};
pipe(p);
pid = fork();
if (pid != 0) {
// Parent: Output is to child via pipe[1]
// Change stdout to pipe[1]
dup2(p[1], 1);
close(p[0]);
r = execvp("ls", ls);
} else {
// Child: Input is from pipe[0] and output is via stdout.
dup2(p[0], 0);
close(p[1]);
r = execvp("grep", grep);
close(p[0]);
}
return r;
}
Remove the quotes in the argument to grep. i.e., use
char *grep[] = {"grep", ".c", NULL};
If you are calling execvp, the usual shell expansion of arguments (i.e., globbing, removal of quotes, etc) does not happen, so effectively what you are doing is the same as
ls | grep '".c"'
In a normal shell.
Also be aware that nothing that comes after the call to execvp will execute, execvp replaces the current process, it will never return.
You have multiple problems:
One problem is that you have far too few calls to close(). When you use dup2() to replicate a file descriptor from a pipe to standard input or standard output, you should close both file descriptors returned by pipe().
A second problem is that the shell removes double quotes around arguments but you've added them around your. You are looking for files whose name contains ".c" (where the double quotes are part of the file name being searched for). Use:
char *grep[] = { "grep", "\\.c$", NULL };
This looks for a dot and a c at the end of the line.
You should report failures after execvp(). If any of the exec*() functions returns, it failed. It can happen when the user mistypes a command name, for example. It is crucial that you report the error and that the child process then exits. If you don't do that, you can end up in a normal iterative shell (rather than this one-shot, non-iterative, non-interactive shell) with multiple shell processes all trying to read from the terminal at the same time, which leads to chaos and confusion.
I'm having problems understanding the right use of the pipe in UNIX Systems.
I have a main process which create a child process. The child process must run a different program from the father, he has to make some operation and then the child must communicate to the father the results.
However in the child process I have to print on the terminal the partial results of these operations.
I'm trying with a test program to do so, but I'm a bit stuck right now. This is the main test program
TEST.C
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int mypipe[2];
pid_t pid;
char readbuffer[6];
pipe(mypipe);
if((pid = fork()) == 0){
close(mypipe[0]);
dup2(mypipe[1], STDOUT_FILENO);
execlp("./proc", "./proc", NULL);
} else {
wait(-1);
close(mypipe[1]);
read(mypipe[0], readbuffer, sizeof(readbuffer));
printf("%s", readbuffer);
}
}
And the c file of the ./proc program is this:
PROC.C
#include <stdio.h>
int main(int argc, char* argv[]){
printf("check\n");
return 0;
}
With this solution, the proc program can't print anything on the terminal. How do I make the proc program to print on the terminal AND on the pipe so the main program can read from there???
Thank you!
Even when you redirect stdout to the parent program, you can still use stderr. Just call fprintf(stderr, ...) instead of printf(...) when you want to print to the console.
If you want your "proc" program to print in terminal for log/debug infos, u can use fprintf and stderr:
fprintf(stderr, "What you need to print\n");
You can also see where your program is writing, use strace
After a long time of researching for my problem, I have really no idea how I can solve it.
My question is that I need the C source code for something like this:
ls &
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
int new_pid;
int status;
new_pid = fork();
if (new_pid == 0) {
execlp("ls", "ls", "-l", NULL);
} else {
waitpid(new_pid, &status, 0);
}
return 0;
}
If I code it like this, 'ls -l' will be executed, but it wasnt forked to the background.
It is just an example 'ls -l', it could also be 'xournal &;', 'libreoffice &' or something like this.
My main problem is that I have no idea how to code '&' in C.
Can anyone please provide me a tip or even a solution for this?
When you execute another program, you need to call these functions:
new_pid = fork();
exec(...); //in the child only
If you want to wait until that new process ends (i.e. not specifying the & ) you call
waitpid(new_pid, ...);
That way, your shell blocks until the process you launced is finished.
If you don't want to wait, but just continue operating your shell (i.e. specifying the &) you simply do not call waitpid().
Use system("ls &").
It'd help if you explained why you wanted to run something in the background.