I need some help emulating the "|" command in unix. I need to be able to use the output from the first argument as the input of the second, something as simple as ls and more. I got this code so far but I'm just stuck at this point. Any and all help would be helpful.-Thanks.
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char ** words)
{
char** args;
char *cmd1[2] = { words[1], 0 };
char *cmd2[2] = { words[2], 0 };
int colon, arg1 ,i, pid, status;
int thepipe[2];
char ch;
args = (char **) malloc(argc*(sizeof(char*)));
colon = -1;
for (i=0;(i<argc); i=i+1){
if (strcmp(words[i],":") == 0) {
colon = i;
}
else {}
}
pipe(thepipe);
arg1 = colon;
arg1 = arg1 - 1;
for (i=0;(i<arg1); i=i+1){
args[i] = (char*) (malloc(strlen(words[i+1])+1));
strcpy(args[i], words[i+1]);
}
args[argc] = NULL;
pid = fork();
if (pid == 0) {
wait(&pid);
dup2(thepipe[1], 1);
close(thepipe[0]);
printf("in new\n");
execvp(*args, cmd1);
}
else {
close(thepipe[1]);
printf("in old\n");
while ((status=read(thepipe[0],&ch,1)) > 0){
execvp(*args, cmd2);
}
}
}
Assuming that argv[1] is a single word command (like ls) and argv[2] is a second single word command (like more), then:
Parent
Create a pipe.
Fork first child.
Fork second child.
Close both ends of the pipe.
Parent waits for both children to die, reports their exit status, and exits itself.
Child 1
Duplicates write end of pipe to standard output.
Close both ends of the pipe.
Uses execvp() to run the command in argv[1].
Exits, probably with an error message written to standard error (if the execvp() returns).
Child 2
Duplicates read end of pipe to standard input.
Close both ends of the pipe.
Uses execvp() to run the command in argv[2].
Exits, probably with an error message written to standard error (if the execvp() returns).
The only remaining trick is that you need to create a vector such as:
char cmd1[2] = { argv[1], 0 };
char cmd2[2] = { argv[2], 0 };
to pass as the second argument to execvp().
Note that this outline does not break the strings up. If you want to handle an invocation such as:
./execute "ls -la" "wc -wl"
then you will need to split each argument into separate words and create bigger arrays for cmd1 and cmd2. If you want to handle more than two commands, you need to think quite carefully about how you're going to manage the extra stages in the pipeline. The first and last commands are different from those in the middle (so 3 processes has three different mechanisms, but 4 or more substantially uses the same mechanism over and over for all except the first and last commands).
Related
I have a question which asks me to :
Write a program (in C under Linux!!!) that:
Receives via the command line a command with its options and/or parameters
Passes the command to a child process
The child executes the command and returns the results to the parent process
The father displays the results on the screen.
I managed to do this but the problem comes from the 2nd part of the question that I can't solve.
Part 2:
The program now receives as a parameter (via the command line) a shell script (shell file), i.e. a file containing a series of shell commands.
The file
Does not contain any programming
Is limited to the shell syntax seen in class (including pipes, redirects, command combinations, wildcards, ...)
Each of the commands in the file must be executed by a child, which sends the results back to the father via the pipe and the father will display the results on the screen (as for part 1).
Here is my code for part 1:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc,char * argv[]){
//Création du pipe
int descripteur[2];
if(pipe(descripteur) != 0){
return -1;
}
//Création du fils
pid_t pid_fils;
pid_fils = fork();
if(pid_fils ==-1){
return -1;
}
//Processus fils
if (pid_fils == 0){
close(descripteur[0]);
if(dup2(descripteur[1],1) == -1){
printf("Erreur\n");
return -1;
};
if(dup2(descripteur[1],2) == -1){
printf("Erreur\n");
return -1;
};
close(descripteur[1]);
if(execvp(argv[1],&argv[1]) == -1){
printf("Erreur\n");
return -1;
};
}
//Processus père
else{
char bufferHelper[256];
close(descripteur[1]);
int nbBit;
while((nbBit = read(descripteur[0], bufferHelper, sizeof(bufferHelper))) !=0){
write( 1, bufferHelper, nbBit);
}
}
return 0;
}
If I understand correctly, the input file contains one shell command per line without any programming structures combining multiple lines (i.e. for, while, if...).
So, you need to open the file and read it line by line with for example fgets(). For each line you can execute "sh -c command line" doing fork/exec of the following for each lines: av[0] = "sh", av[1]="-c", av[2] = cmdline, av[3] = NULL.
Or you can call system() which does the same internally.
Through another pipe, you can also pass the content of the file as input to the child process which executes a shell.
Here is a C program which operates finding specific properties like CPU bus info by consecutive calls of lshw (to access total hardware list with respective properties) and grep (to select just a relevant point among lshw results):
char *strCombine(char *str1, char *str2, int n)
{
int i = strlen(str2);
int j = 0;
if((str2 = (char *) realloc(str2, (i + n + 1))) == NULL)
perror(0);
while(j < n && str1[j])
{
str2[i] = str1[j];
i++;
j++;
}
str2[i] = 0;
return (str2);
}
int main()
{
pid_t parent;
char buf[1000] = {0};
char *str;
char *argv[6] = {"/usr/bin/lshw", "-C", "CPU", "|", "grep", "bus info"};
int fd[2];
int ret;
if(pipe(fd) == -1)
{
perror(NULL);
return -1;
}
parent = fork();
if(parent == 0)
{
close(fd[1]);
while((ret = read(fd[0], buf, 1000)))
str = strCombine(buf, str, ret);
close(fd[0]);
}
else
{
close(fd[0]);
execv(argv[0], argv);
close(fd[1]);
wait(0);
}
wait(0);
printf("%s", str);
return 0;
}
In this code grep is expected to follow lshw since both go executed by invoking execv. However, this pipeline doesn't work because lshw usage reference gets printed out in terminal (running on Ubuntu 18.04 LTS) instead of bus info needed originally. What makes this program failed to show just info that matters and what way must I try to set up pipeline?
The vertical bar is not a parameter you use to separate commands, as the execve(2) system call will load a program into the virtual space of one process only. You need to create two processes, one per command you want to execute, and communicate them so input from one goes to output from the other. I think also you'll be interested in the output of the last command, so you need to do two redirections (one from the first command to the second, and one from the output of the second command to a pipe descriptor), two forks, and two exec's in order to do this.
First the good news, you can do all this stuff with a simple call to popen(3) without the nitty gritties of making forks and execs while redirecting i/o from individual commands. Just use this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *cmd = "/usr/bin/lshw -C CPU | grep 'bus info'";
int n = 0;
char line[1000];
/* f will be associated to the output of the pipeline, so you can read from it.
* this is stated by the "r" of the second parameter */
FILE *f = popen(cmd, "r");
if (!f) {
perror(cmd);
exit(EXIT_FAILURE);
}
/* I read, line by line, and process the output,
* printing each line with some format string, but
* you are free here. */
while (fgets(line, sizeof line, f)) {
char *l = strtok(line, "\n");
if (!l) continue;
printf("line %d: [%s]\n", ++n, l);
}
/* once finished, you need to pclose(3) it. This
* makes program to wait(2) for child to finish and
* closing descriptor */
pclose(f);
}
If you need to mount such a pipeline you'll end having to
redirections from first command to second, from second to
parent process, and fork/exec both processes yourself.
In this approach, you handle a subshell to do the piping
and redirection work for you, and just you get a FILE * descriptor to read upon.
(if I find some time, I'll show you a full example of a chain of N commands with redirections to pipe them, but I cannot promise, as I have to write the code)
NOTE
fork() returns the pid of the child process to the parent, and 0 to the child process itself. I don't understand why you have a variable named parent where you store the value received from fork(). If it is nonzero (and non-negative) it represents the pid of a child process. You need two, as you need two processes. In the example I post, you create three processes (you ask a subshell to mount the pipeline for you, so you have a subshell you instruct to create two more processes, to execute your command) If you had to mount all this paraphernalia, you'd also to wait(2) for the children to finish (this is done in pclose(3) call)
I have a little program to spawn a process (only one) repeatedly, while overprinting its output in the same place. I use it as some kind of htop program when I try to see e.g. the output of ls -l (showing a file growing as it is being filled) or the output of df command. It starts the program, makes one fork, redirects the output of it to a pipe and gets the output of the command to count the number of lines output (to emit an escape sequence to put the cursor on top of the listing, and to emit a clear to the end of line after each output line, so shorter lines dont get blurred by longer ones. It shows you how to deal with forks and exec system calls, and you can use as example on how to do the things the brave way. But having popen(3) I think is the solution to your problem. If you want to have a look to my cont program, just find it here.
I've managed to make an abstract syntax tree for my minishell, the things is
when I tried to execute the piped commands I got stuck.
The first pipe execute and output the result to the stdout 1, while the second one grep filename either stuck or not executed at all.
I tried different approches and I got different result, yet none of theme works
I would appreciate any help.
This how my AST looks like.
ls -la | cat -e | grep filename
t_node *pipe_execution(t_node *node, t_list *blt, t_line *line, int std[2])
{
int pp[2];
if (node)
{
if (node->kind == NODE_PIPE)
{
if (node->and_or_command->left)
{
pipe(pp);
std[1] = pp[1];
pipe_execution(node->and_or_command->left, blt, line, std);
close(pp[1]);
}
if (node->and_or_command->right)
{
std[0] = pp[0];
std[1] = 1;
dprintf(2, "right std %d\n", std[1]);
pipe_execution(node->and_or_command->right, blt, line, std);
close(std[0]);
}
} else if (node->kind == NODE_SIMPLE_COMMAND)
{
dprintf(2, "====%s=== and stdin %d stdout %d\n", node->simple_command->head->name, std[0], std[1]);
execute_shell(blt, line->env, node, std);
}
}
return (node);
}
int execute_shell(t_list *blt, t_list *env, t_node *node, int std[2])
{
...
return (my_fork(path, env, cmds, std));
}
my implementation of fork process.
int my_fork(char *path, t_list *env, char **cmds, int std[2])
{
pid_t child;
char **env_tab;
int status;
status = 0;
env_tab = env_to_tab(env);
child = fork();
if (child > 0)
waitpid(child, &status, 0);
else if (child == 0)
{
dup2(std[0], 0);
dup2(std[1], 1);
execve(path, cmds, env_tab);
}
return (status);
}
I hope this code make some sense.
Pipes require concurrent execution
The problem, as far as I can tell from the code snippets you provided, is that my_fork() is blocking. So when you execute a process, your shell stops and wait for that process to finish, before starting the next one. If you do something simple, like:
/bin/echo Hello | cat
Then the pipe's internal buffer is big enough to store the whole input string Hello. Once the /bin/echo process finishes, you execute cat, which can then read the buffered data from the pipe. However, once it gets more complicated, or when the first process would send a lot more data to the pipe, its internal buffer will get full, and then it will block.
The solution is to defer calling waitpid() on the processes you fork until you have spawned all the processes that are part of the command line.
Create all required pipes before starting processes
Your function pipe_execution() assumes that there is only a single pipe; it starts the first process with filedescriptor 0 as its input, and it starts the second process with filedescriptor 1 as its output. However, if you have multiple pipes on a single command line, like in ls -la | cat -e | grep filename, then the output of the cat -e process need to go into the second pipe, not to standard output.
You need to create the second pipe before starting the right-hand command of the first pipe. It's probably simplest to just create all the pipes before starting any of the commands. You could do this by defining multiple phases:
Create pipes
Start commands
Wait for all commands to finish
You can traverse the abstract syntax tree you built multiple times, each time executing one of the phases.
I need to create a program that takes input from the user and executes it just like it does in the terminal. I am using the execvp() function for this purpose. The requirement of the program is to keep taking input from the user unless the quit call is encountered. The problem here is that the current program is replaced after the execvp() call. So, using a goto is not an option either. I found this Fork–exec article but it doesn't tell how to create an indefinite number of processes. Here is my code:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void main() {
char *args[4];
char inputCommand[100];
fgets (inputCommand, 100, stdin);
printf ("Splitting string \"%s\" into tokens:\n",inputCommand);
/* Perfrom string tokenization here */
execvp(args[0], args);
}
fork() can be called an indefinite number of times; as long as the return value indicates that you're the parent process, you can continue to operate as usual and call it again.
Thus, you can have a loop within which you fork, call an execvp() if you're the child process, and continue to the next iteration if you're the parent.
Execvp replaces the current process image with the command you run. So it cancels your C program. To produce the desired effect, you should fork before execvp. It would look something like this:
int status = 0;
pid_t pid = fork();
if(pid > 0) {
waitpid(pid, &status, 0);
// is parent
} else if(pid == 0) {
execvp(*args, args);
// is child
} else {
// fork didn't work
}
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.