Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game. |
Notices |
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
Are you new to LinuxQuestions.org? Visit the following links:
Site Howto |
Site FAQ |
Sitemap |
Register Now
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
|
|
03-31-2016, 10:43 AM
|
#1
|
Member
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33
Rep:
|
Wrote Shell in C - piping output from first command to second command, sending to output file issue
Problem Description (code snippets follow):
My question is related to redirection I/O when you are piping command to command and sending the second command's output to a file. This is a shell program written in C on Suse Linux that reads in a command line and pipes the first commands output to be the input of the second command. The second command is supposed to send its output to an output file. This shell works if you pipe a commands output to the input of the second command, and outputs the result to stdout, but if I try to send the second command's output to a file, it does not. However, if I try sending just one command's output (without piping from one command to another), I can send its output to a file with no problems.
Hope my code snippet and descriptions are acceptable and please let me know if they are not.
Code:
Shell start:
Initialization of shared memory for process data sharing:
shm_fd = shm_open((const char *) STATE_FILE, (O_CREAT | O_EXCL | O_RDWR), (S_IREAD | S_IWRITE));
ftruncate(shm_fd, sizeof(SHARED_VAR));
conf = mmap(0, sizeof(SHARED_VAR), (PROT_READ | PROT_WRITE), MAP_SHARED, shm_fd, 0));
(init of variables, buffers follows)
(init of terminal next):
status = isatty(fileno(stdin));
if terminal (status == 0):
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
tcgetattr(fileno(input),&initial_settings);
new_settings = initial_settings;
new_settings.c_lflag |= ICANON;
new_settings.c_lflag |= ECHO;
new_settings.c_lflag |= ISIG;
tcsetattr(fileno(input), TCSANOW, &new_settings);
(init. of signals next):
ignore.sa_handler = SIG_IGN;
sigemptyset(&ignore.sa_mask);
ignore.sa_flags = 0;
sigaction(SIGINT, &ignore, NULL);
childsigint.sa_sigaction = &sig_int;
sigemptyset(&childsigint.sa_mask);
childsigint.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &childsigint, NULL);
childsigchld.sa_sigaction = &sig_child;
sigemptyset(&childsigchld.sa_mask);
childsigchld.sa_flags = SA_SIGINFO;
sigaction(SIGCHLD, &childsigchld, NULL);
next - an infinite loop reading the command line from a Linux console:
while (fgets(buf, MAXLINE_NETBOOK, input) != NULL)
then - parse the command line just read into buf:
command to check for semi-colon (more than one command in line read in):
saveBufPtr = strchr((const char *)buf, ';');
Similar check to see if there is a pipe in command line read in:
saveBufPtrPipeToCmda = strchr((const char *)buf, '|');
And to check for an input file (for a command's arguments):
saveBufPtrIn = strchr((const char *)buf, '<');
if there is an input file, open it:
input_filea = fopen(inFileBufa, "r");
and save the name of the output file in a string:
input_file_chPtra = &buf[countCmdChrs];
Check if there is an output file:
saveBufPtrOuta = strchr((const char *)buf, '>');
if there is an output file, open it:
output_filea = fopen(outFileBufa, "w");
and save name of output file in a string:
output_file_chPtra = &buf[countCmdChrs];
if there is a pipe, parse, separate the 2 commands into buffers (saveBufa,saveBufb):
strncpy(saveBufa, saveBufPtrPipeToCmdb, cmdLena);
strncpy(saveBufb, saveBufForLater, cmdLenb);
if there is a pipe between commands, or if there is an output file command:
signal(SIGPIPE, sig_pipe);
pipe(fdpipe1); (pipe from first command to second command)
pipe(fdpipe2); (outfile case)
if there is an input file with command arguments, open it and get them:
fgets(inputbufa, MAXLINE, input_filea); (and add a null char at end)
strcpy(inFileArgsReada, inputbufa);
Put commands and their args into shared buffers to be given to exec function later:
(using strtok(buffer, space-char), parsing command and its args with space char):
If there is a second command - similarly - place command and its args into shared mem
Now deal with forking children processes for each command to be run:
while-loop for each command (only looking at 1-2 commands right now):
conf->sigChildDone = FALSE;
if ((pid = fork()) < 0)
exit(EXIT_FAILURE);
else if (pid == 0) /* child */
{
i = conf->cmdNumber;
**redirection of IO happens here:**
if first command is running, and there is a pipe to send output to next command:
dup2(fdpipe1[1], fileno(stdout));
close(fdpipe1[0]);
if second command is running, and there is a pipe to get output from first command as this command's input:
dup2(fdpipe1[0], fileno(stdin));
close (fdpipe1[1]);
if there is an output file:
dup2(fdpipe2[1], fileno(stdout));
Set up signal for parent to send child process so it can exec command:
sigemptyset(&newmaskexec);
sigaddset(&newmaskexec, SIG_TELL_CHILD_EXEC);
sigprocmask(SIG_BLOCK, &newmaskexec, &oldmaskexec);
actionexec.sa_flags = SA_SIGINFO;
actionexec.sa_sigaction = catchit_FromParentToChild;
sigaction(SIG_TELL_CHILD_EXEC, &actionexec, NULL);
sigsuspend(&oldmaskexec);
}
else /* parent */
{
svalfork.sival_int = 1;
svalexec.sival_int = 2;
sleep(1);
if (no output file)
fflush(stdout);
sigqueue(pid, SIG_TELL_CHILD_EXEC, svalexec);
sigwaitinfo(&newmaskexec, NULL);
else (there is an output file - read pipe from child that ran, send to file)
***** where error occurs when there is piping and an outfile, so tried 2 ways***
*** tried this way first, which works for one command, but not 2 with piping***
fflush(stdout);
sigqueue(pid, SIG_TELL_CHILD_EXEC, svalexec);
sigwaitinfo(&newmaskexec, NULL);
n = read(fdpipe2[0], line, MAXBUF);
fputs(line, output_file);
**** other way I tried for case when 2 commands pipe and there is an outfile***
fflush(stdout);
sigqueue(pid, SIG_TELL_CHILD_EXEC, svalexec);
sigwaitinfo(&newmaskexec, NULL);
close(fdpipe2[1]);
FD_ZERO (&rfds);
FD_ZERO (&wfds);
FD_ZERO (&efds);
FD_SET (fdpipe2[0], &rfds);
const int m = fdpipe2[0] + 1;
BOOL doneMonitoring = FALSE;
BOOL checkForSigChild = FALSE;
while (!doneMonitoring)
{
int r = select (m, &rfds, &wfds, &efds, 0);
if (FD_ISSET (fdpipe2[0], &rfds))
{
ioctl (fdpipe2[0], FIONREAD, &size);
ssize_t r = splice (src, NULL, dst, NULL, size, 0);
}
else
{
checkForSigChild = TRUE;
}
if ((checkForSigChild == TRUE) && (conf->sigChildDone == TRUE))
{
doneMonitoring = TRUE;
}
else if ((checkForSigChild == TRUE) && (conf->sigChildDone == FALSE))
{
checkForSigChild = FALSE;
}
}
do
{ /* parent */
pid = waitpid(pid, &status, 0));
(followed by checking the status of the waitpid())
}
while (!WIFEXITED(status) && !WIFSIGNALED(status));
tcflush(fileno(output), TCOFLUSH);
fflush(output_filea);
} //end parent's part
conf->cmdNumber++;
(while loop continues - forks next command in command line if there is one)
until done with all commands in command line
signal handler that runs once parent gives this signal to child so it can exec command:
void catchit_FromParentToChild(int signo, siginfo_t *info, void *extra)
{
char *env[] = { NULL };
if (there are no outfiles)
(if first command):
execvp(conf->cmdArga[0],(char* const*) conf->cmdArga );
(if second command):
execvp(conf->cmdArgb[0],(char* const*) conf->cmdArgb );
else (case of piping and an outfile -- ***errors here!!***)
(***tried execvp - did not work - execve seemed to get me further along ***)
(if first command):
execve(conf->cmdArga[0],(char* const*) conf->cmdArga, env );
(if second command):
execve(conf->cmdArgb[0],(char* const*) conf->cmdArgb, env );
}
sig_child handler for when child finishes after it has execed command:
static void sig_child(int signo, siginfo_t *child_sigaction, void *context)
{
for all commands that complete running:
(check child_sigaction->si_code to see if child exitted normally or there was error)
if (command that completed was 2nd command)
{
(this shared memory variable is checked in the case when I monitor
for the second child being done running, so stop splicing output to out file)
conf->sigChildDone = TRUE;
}
return; /* control goes back to parent */
}
handler that stops the shell from running:
static void sig_int(int signo, siginfo_t *child_sigaction, void *context)
{
tcflush(child_sigaction->si_fd, TCOFLUSH);
kill(child_sigaction->si_pid, SIGTERM);
}
Test cases that work:
ls -l | grep characterPatternFromFileInDir
ps > psOutputFile
Test case that is causing me trouble:
ls < DashLargFromInputFile | grep characterPatternFromFileInDir > outputFile
|
|
|
03-31-2016, 12:19 PM
|
#2
|
Senior Member
Registered: Nov 2005
Distribution: Debian, Arch
Posts: 3,808
|
Hmm, you've mixed real C code and pseudo code description, which I find a bit hard to follow. Putting the psuedo code into comments would help a bit.
But first, just a high level description of what you're trying to do, because you shouldn't need shared memory, signals, or splice(2) to implement a shell (though some signal handling would be needed for job control). The shell should hook up the pipes between the child processes and file handles and then just wait for them to finish.
|
|
|
03-31-2016, 06:27 PM
|
#3
|
Member
Registered: Jun 2011
Location: North Easton,MA
Distribution: currently Ubuntu Linux on my netbook while job searching, but also use Cygwin GNU tools on MSXP nb
Posts: 33
Original Poster
Rep:
|
OK, I'll put the pseudo code into comments. I am new to posting here. I did not want to post too much code.
I guess I implemented this more difficulty than it required?
The way you describe it sounds so simple.
Maybe I should start again with the simple version you just described, and see if it works for me.
If that works, then I guess I can think of what I did as a good learning experience about signals, handlers, shared memory, etc.
I added those things when my simpler version was not working, but perhaps there was another error then and I thought these things
would help when they were not necessary.
Thank you for responding!
|
|
|
All times are GMT -5. The time now is 02:40 PM.
|
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.
|
Latest Threads
LQ News
|
|