LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
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


Reply
  Search this Thread
Old 03-31-2016, 09:43 AM   #1
KarenWest
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: Reputation: Disabled
Question 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
 
Old 03-31-2016, 11:19 AM   #2
ntubski
Senior Member
 
Registered: Nov 2005
Distribution: Debian, Arch
Posts: 3,780

Rep: Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081Reputation: 2081
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.
 
Old 03-31-2016, 05:27 PM   #3
KarenWest
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: Reputation: Disabled
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!
 
  


Reply

Tags
dup2, exec, fork, pipes, signal



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] sending a command output to a variable hnasr2001 Linux - General 1 12-06-2015 01:30 PM
[SOLVED] output of a tar command not sending to my E-Mail amario26 Linux - Newbie 3 05-05-2011 06:09 AM
Redirect output of a command to another file inside shell script mukul_d Linux - General 4 08-27-2010 02:23 PM
Piping Output of a Command Like Find cipher7836 Linux - Newbie 13 08-16-2009 03:41 AM
piping output of find to another command or loop steven19782007 Linux - Newbie 6 06-25-2009 05:29 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 02:47 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration