[net.lang.c] pipes and the sort command

zarth@drutx.UUCP (CovartDL) (12/21/84)

	I am interested in using the popen() command to form a pipe
	between my C program and the UNIX sort command. I would appreciate
	any information on the best way to do this(examples would help).
	I should point out that the input to the sort command would be
	comming from an array and the output should be stored in an array.

			Thanks in advance,

				Zarth Arn
				(alias Dave Covart)
				drutx!zarth


   M E R R Y   C H R I S T M A S   A N D   A   H A P P Y   N E W   Y E A R !!!!

smh@mit-eddie.UUCP (Steven M. Haflich) (12/26/84)

>> 	I am interested in using the popen() command to form a pipe
>> 	between my C program and the UNIX sort command. I would appreciate
>> 	any information on the best way to do this(examples would help).
>> 	I should point out that the input to the sort command would be
>> 	comming from an array and the output should be stored in an array.
>
>It is very hard to manage both ends of a piped filter from a program.
>
>Have you considered using the qsort() library routine to sort your data?

It is very hard to connect both ends of a piped filter from a single
program in the general case, but sort(I) is a special case in that it is
guaranteed not to produce output until it reads the EOF on its input.
Something like the following (untested) ought to work.  It would be
nicer if it error checked the pipe, fork, wait, and fdopen calls:

	int	pipe_to[2];
	int	pipe_fr[2];
	FILE	sortfile;
	...
	pipe(pipe_to);
	pipe(pipe_from);
	if (fork()==0) {
		close(pipe_to[1]);
		close(pipe_from[0]);
		close(0);
		close(1);
		dup(pipe_to[0]); close(pipe_to[0]);
		dup(pipe_fr[1]); close(pipe_fr[1]);
		execl("/bin/sort","sort" /* optional comma-separated
					    sort args */ , 0);
		perror("Can't exec /bin/sort");
		exit(-1);
	}
	close(pipe_to[0]); close(pipe_fr[1]);
	sortfile = fdopen(pipe_to[1],"w");
	/* Code to write data to the sort program goes here, e.g.:
	for (i=0; i<arraydim; i++)
		fprintf(sortfile,"%d\n",array[i]);
	*/
	fclose(sortfile);
	sortfile = fdopen(pipe_from[0],"r");
	/* Code to read data back from the sort goes here, e.g.:
	   Note that this code doesn't make use of the fact that sort
	   necessarily returns the same number of items as written.
	for (i=0; fscanf(sortfile,"%d\n",&array[i]) == 1; i++);
	*/
	fclose(sortfile);
	wait(0);

Hope this helps.  It isn't nearly so confusing as it looks, provided
I typed it extempore without errors.

gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (12/27/84)

> 	I am interested in using the popen() command to form a pipe
> 	between my C program and the UNIX sort command. I would appreciate
> 	any information on the best way to do this(examples would help).
> 	I should point out that the input to the sort command would be
> 	comming from an array and the output should be stored in an array.

It is very hard to manage both ends of a piped filter from a program.

Have you considered using the qsort() library routine to sort your data?

jack@vu44.UUCP (Jack Jansen) (12/27/84)

> 	I am interested in using the popen() command to form a pipe
> 	between my C program and the UNIX sort command. I would appreciate
> 	any information on the best way to do this(examples would help).
> 	I should point out that the input to the sort command would be
> 	comming from an array and the output should be stored in an array.
It is very hard to get *both* ends of a pipe into your hands.
The simplest solution is to use a tempfile, which makes your
code something like

    char command[BUFSIZ], *tnm;
    FILE *tfp;
    tnm = tempname();	/* Get temp filename */
    if((tfp = fopen(tnm,"w"))==NULL) <error handling>;
    write_array_to_file(data,tfp);
    fclose(tfp);
    sprintf(command,"sort %s",tnm);
    if( (tfp=popen(command,"r")) == NULL ) <error handling>;
    read_array_from_file(data,tfp);
    if(pclose(tfp) != 0 ) <error handling>; 

In this case, read_array_from_file() and write_array_to_file()
read/write an array to a file (in ASCII), probably with a printf or
scanf loop.

If you don't want to use a tempfile, you have to do it yourself.
This means setting up two pipes, forking, re-directing all file-
descriptors, etc etc etc.
-- 
	Jack Jansen, {seismo|philabs|decvax}!mcvax!vu44!jack
	or				       ...!vu44!htsa!jack
If *this* is my opinion, I wasn't sober at the time.

geoff@desint.UUCP (Geoff Kuenning) (12/29/84)

***                                ******                                  ***
*** If you follow this article up, PLEASE remove net.sources from the list ***
***                                ******                                  ***

In article <6770@brl-tgr.ARPA> gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) writes:

>It is very hard to manage both ends of a piped filter from a program.
>
>Have you considered using the qsort() library routine to sort your data?

Doug's second point seems hard to argue.  Nevertheless, there are applications
besides sorting that require being able to pipe an array through a program and
back into that same array.  Vi, provides this feature, for example.  Sort is
easier than most, because it buffers its output and thus cannot hang a pipe.

The following example is extracted from a program I wrote that provides
this feature.  I have removed a lot of clutter, which has the unfortunate
side effect that I may have broken it in the process (I have collapsed some
subroutines whose division have nothing to do with the problem at hand, and
I may have blown the argument substitution).

Don't forget to remove my .signature!

-----------------------------------cut here-------------------------------
#include <signal.h>

/*
 * pipethru will pipe the "n" bytes in "ibuf" through "command".  The result
 * is placed in "obuf," but is limited to "lim" bytes.  The return value is
 * the number of bytes read, or -1 if an error occurs.
 *
 * "ibuf" and "obuf" may overlap in any fashion without difficulty.
 */
int pipethru (command, ibuf, n, obuf, lim)
    char *	command;
    char *	ibuf;
    int		n;
    char *	obuf;
    int		lim;
    {
    int		fd;			/* Loop counter for closing files */
    int		inpipes[2];		/* Descriptors for the input pipe */
    int		outpipes[2];		/* Descriptors for the output pipe */
    int		pipe_pid;		/* Pid of the process we pipe thru */
    char *	shellname;		/* Shell to use to run "command" */

    if (pipe (inpipes) < 0)		/* Set up the pipes we will use */
	return -1;
    if (pipe (outpipes) < 0)
	{
	close (inpipes[0]);
	close (inpipes[1]);
	return -1;
	}
    /* (You may want to change the following code to retry if the error
     * is EAGAIN.  Also, bsd systems should use vfork here (but *not* in
     * handlepipe, below */
    if ((pipe_pid = fork ()) < 0)	/* Make a kid - quit if can't */
	{
	close (inpipes[0]);
	close (inpipes[1]);
	close (outpipes[0]);
	close (outpipes[1]);
	return -1;
	}
    else if (pipe_pid > 0)		/* Parent */
	{
	close (outpipes[0]);		/* Close unused ends of pipes */
	close (inpipes[1]);
	return
	  handlepipe (pipe_pid, inpipes[0], outpipes[1], ibuf, n, obuf, lim);
	}
    else				/* Child */
	{
	close (0);			/* Set up stdin as read end of the */
	dup (outpipes[0]);		/* ..output pipe */
	close (outpipes[0]);
	close (outpipes[1]);		/* Close unused end of pipe */

	close (1);			/* Set up stdout as write end of the */
	dup (inpipes[1]);		/* ..input pipe */
	close (inpipes[0]);
	close (inpipes[1]);

	for (fd = 3;  fd < 20;  )	/* Close all others except stderr */
		close (fd++);
/*
	At this point, standard input is the data coming from the buffer,
	and standard output is the data that will go back.  All we need to
	do is exec the command in question.
*/
	if ((shellname = getenv ("SHELL")) == NULL)
	    shellname = "/bin/sh";
	execl (shellname, shellname, "-c", command, (char *) NULL);
	exit (127);
	}
    }

/*
 * handlepipe is called once we have the command running at the end of a pair
 * of pipes.  Handlepipe will do the real work, writing "buf" to the output
 * pipe and reading the result into the input pipe.  To avoid hanging due to
 * full pipes, we fork into two processes.  The child will write, and the
 * parent will read.
 */
int handlepipe (pipe_pid, inpipe, outpipe, ibuf, n, obuf, lim)
    int		pipe_pid;		/* Pid of the process we pipe thru */
    int		inpipe;			/* Pipe we read from */
    int		outpipe;		/* Pipe we write to */
    char *	ibuf;
    int		n;
    char *	obuf;
    int		lim;
    {
    int		exit_pid;		/* Pid of last child to exit */
    int		exit_stat;		/* Return status of child */
    int		(*saveint) ();		/* Places to save some signals */
    int		(*savequit) ();
    int		write_pid;		/* Pid of child who is writing */

    saveint = signal (SIGINT, SIG_IGN); /* Ignore signals the command sees */
    savequit = signal (SIGQUIT, SIG_IGN);

    /* Don't use vfork here! */
    if ((write_pid = fork ()) < 0)	/* Make a kid to write */
	{				/* Failure! */
	close (outpipe);		/* Clean up pipes */
	close (inpipe);			/* ..this also kills the command */
	signal (SIGINT, saveint);	/* Put the signals back */
	signal (SIGQUIT, savequit);

	while ((exit_pid = wait ((int *) NULL)) != pipe_pid  &&  exit_pid > 0)
	    ;
	return -1;
	}
    else if (write_pid == 0)		/* Child */
	{
	close (inpipe);			/* Close unused pipe */
	exit_stat = (write (outpipe, buf, n) == n) ? 0 : 1;
	close (outpipe);
	exit (exit_stat);
	}
    else if (write_pid > 0)		/* Parent? */
	{
	close (outpipe);		/* Close unused pipe */
	n = read (inpipe, buf, lim);	/* Read back the command output */
	close (inpipe);
	signal (SIGINT, saveint);	/* Put the signals back */
	signal (SIGQUIT, savequit);
	/*
	 * The only thing left now is to wait for both children to exit.
	 */
	while ((pipe_pid > 0  ||  write_pid > 0)
	  &&  (exit_pid = wait (&exit_stat)) > 0)
	    {
	    if (exit_pid == pipe_pid)
		pipe_pid = exit_stat ? -1 : 0;
	    else if (exit_pid == write_pid)
		write_pid = exit_stat ? -1 : 0;
	    }
	if (write_pid < 0		/* This means you can't write pipe */
	  ||  pipe_pid < 0)		/* This means command failed */
	    return -1;
	else
	    return n;			/* All is well, return no. read */
	}
    }
-----------------------------cut again-------------------------------
-- 

	Geoff Kuenning
	...!ihnp4!trwrb!desint!geoff