[comp.unix.questions] Funny csh output?

davisd@cpsvax.cps.msu.edu (Dug) (02/28/90)

Check this out....

% jobs
[1]  + Stopped              cat
% jobs | wc
       0       0       0

Why can't "jobs" get piped into "wc" (word count)?  I can redirect
the output of "jobs" to a file, so why can't I do it to program?

Just wondering if someone out-there knew.

-Dug

gwyn@smoke.BRL.MIL (Doug Gwyn) (02/28/90)

In article <6662@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) writes:
>% jobs | wc
>       0       0       0

There are no suspended jobs in the shell that's running the pipeline
(it's a subprocess of the one that printed the prompt).

davisd@cpsvax.cps.msu.edu (Dug) (03/01/90)

In article <12251@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
#In article <6662@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) writes:
#>% jobs | wc
#>       0       0       0
#
#There are no suspended jobs in the shell that's running the pipeline
#(it's a subprocess of the one that printed the prompt).

But it that's true then why can I redirect it to a file and have my
stopped jobs show up there?  (i.e.  jobs > test  will result in "test"
having the correct output of jobs)

-Dug

dce@smsc.sony.com (David Elliott) (03/01/90)

In article <6669@cps3xx.UUCP> davisd@cpsvax.UUCP (Dug) writes:
>In article <12251@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
>#In article <6662@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) writes:
>#>% jobs | wc
>#>       0       0       0
>#
>#There are no suspended jobs in the shell that's running the pipeline
>#(it's a subprocess of the one that printed the prompt).
>
>But it that's true then why can I redirect it to a file and have my
>stopped jobs show up there?  (i.e.  jobs > test  will result in "test"
>having the correct output of jobs)

Mainly because it's an easier problem to solve.

With redirection of a builtin, all you have to do is to save the
current file descriptor state, run the builtin, and then put it back.
When you work with pipes, you have to fork new subshells to run each
piece.

If you take a look at the csh source (sh.h, specifically), you'll find
that csh starts out by saving the standard file descriptors so that it
can handle redirection for builtins easily.

It is interesting to note that some builtins do work with pipes.
History, for example, does just what you'd expect with a pipe
(in most cases -- on the machine I'm using right now, history | more
doesn't work correctly).

If you do need to send the output of jobs through a pipe, your
best bet is to just use a temp file.  In my .cshrc, I have the
following alias which allows me to use the output of "jobs -l"
to send me to the working directory for any given job:

	alias jd 'jobs -l >! $home/.jobdir ; eval `jobdir < $home/.jobdir`'

The jobdir command is a simple shell script:


#!/bin/sh
#
# jobdir - print a command to cd to the directory where the named job
#	   is executing.  The argument can be +, -, or a 1- or 2-digit
#	   number.  No argument is the same as '+'.
#

PATH=/bin:/usr/bin:/usr/ucb

#
# Global variables
#

Myname=`basename "$0"`

main()
{

	case "$1" in
	""|+)
		dir=`getplus`
		;;
	-)
		dir=`getminus`
		;;
	[0-9]|[0-9][0-9])
		dir=`getnum "$1"`
		;;
	*)
		echo "echo bad directory"
		exit
		;;
	esac

	case "$dir" in
	"")
		echo "cd ."
		;;
	*)
		echo "cd $dir"
		;;
	esac
	exit 0
}

getplus()
{
	sed -n 's/^[^ ]*  +.*(wd: \(.*\))$/\1/p'
}

getminus()
{
	sed -n 's/^[^ ]*  -.*(wd: \(.*\))$/\1/p'
}

getnum()
{
	sed -n 's/^\['"$1"'\].*(wd: \(.*\))$/\1/p'
}

main ${1+"$@"}
exit 0
-- 
David Elliott
dce@smsc.sony.com | ...!{uunet,mips}!sonyusa!dce
(408)944-4073
"...it becomes natural, like a third sense." -- Homer Simpson

gwyn@smoke.BRL.MIL (Doug Gwyn) (03/02/90)

In article <6669@cps3xx.UUCP> davisd@cpsvax.UUCP (Dug) writes:
-In article <12251@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) writes:
-#In article <6662@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) writes:
-#>% jobs | wc
-#>       0       0       0
-#There are no suspended jobs in the shell that's running the pipeline
-#(it's a subprocess of the one that printed the prompt).
-But it that's true then why can I redirect it to a file and have my
-stopped jobs show up there?  (i.e.  jobs > test  will result in "test"
-having the correct output of jobs)

Because the shell doesn't find it necessary to set up a subshell in
that case; it runs the command with I/O redirected then jumps back to
the main command-processing loop.  The ways that shells decide when to
use subprocesses and when to short-cut them are rather complicated..

chris@mimsy.umd.edu (Chris Torek) (03/07/90)

>>In article <6662@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) asks
why `jobs | wc' produces `0 0 0' while `jobs' by itself produces at
least one line of output.

>In article <12251@smoke.BRL.MIL> gwyn@brl.arpa (Doug Gwyn) provides
a terse but correct answer:
>>There are no suspended jobs in the shell that's running the pipeline
>>(it's a subprocess of the one that printed the prompt).

In article <6669@cps3xx.UUCP> davisd@cpsvax.cps.msu.edu (Dug) asks further:
>But it that's true then why can I redirect it to a file and have my
>stopped jobs show up there?  (i.e.  jobs > test  will result in "test"
>having the correct output of jobs)

To understand all of this you need to know several things: first, what
makes something a `job'; second, how the shell manages jobs; and third,
how the shell manages pipes and file redirection.

A `job' is a collection of processes (a `pipeline'), all of which must
be direct descendents of the process that controls them.  For instance,
given

	% sleep 1000 | cat | tr -d X &

the C shell is the immediate parent of three processes, one a `sleep 1000',
one a `cat', and one a `tr -d X'.  These three are arranged in a pipeline
via a great deal of shell magic.

The shell manages the jobs via signals and the `wait3' (now `wait4') system
call, and (initially) through `process groups'.  Only the immediate parent
of the job can do this, because only the immediate parent of a process
can wait for that process (wait3()/wait4()).  The shell creates a sub-
process with the `fork' system call: after the shell forks, the child is
no longer the immediate parent of any jobs that the parent created.  (The
parent, however, *is* the immediate parent of the child, and thus the
child process is added to the list the parent maintains.)  Since the child
is not the parent of the other processes, it must forget about them.

Now, to create the pipeline `a | b', the shell does the following:

	step 0: call pipe() to create a pipe.

	step 1: call fork() to create a subprocess.

	  1.1:	in the child, close stdout, make the write end of the pipe
		stdout, close the read end of the pipe (not needed), and
		then exec process `a'.  child part of shell is now gone
		(parent can now continue if it used vfork()).
		[various details about setpgrp() left out here.]

	  1.2:	in the parent, close the write end of the pipe (no longer
		needed).  record the child process ID as part of the new
		job.

	step 2: call fork() to create a subprocess.

	  2.1:	in the child, close stdin, make the read end of the pipe
		stdin, and then exec process `b'.
		[more details about setpgrp() left out here.]

	  2.2:	in the parent, close the read end of the pipe.  record the
		child process ID as part of the new job.

Now suppose that `b' is anything, but `a' is the `jobs' command.  In this
case, step 1.1 does not do the exec() to run part a, but rather simply
prints out its list of jobs.  But wait!  This code is run by a child
process, and a child process is not the direct parent of any jobs the
shell is running, so it has already forgotten about them.  Hence, it
lists no jobs.

Well, what if we run `jobs > foo' instead of `jobs | cat'?  This time the
shell only has to run one command, so instead of forking, it squirrels
away its current stdout, opens the output file `foo', does the task at
hand (here printing jobs, not fork-and-exec'ing some other program), and
then moves stdout back.  (In actuality, the shell cheats and never moves
the file descriptors at all, except once when it first starts up.)  This
time the jobs-printing is done by the original shell; it does not forget
about any jobs.

Now, the shell could be more clever, and not forget its jobs even when
it forks, and then `jobs | cat' would do something useful.  But the shell
is just not that clever.

Incidentally, the C shell gets very confused when you pipe into a built-in:

	% cat | jobs
	[1]  + Running              cat |
	% Reset tty pgrp from 2710 to 1281

	% jobs
	[1]    Hangup               cat |
	% cat | echo
	% Reset tty pgrp from 2711 to 1281
	j
	[1]    Hangup               cat |
	% 

It does not matter what you pipe into or out of, as long as the last thing
in the pipeline is a built-in.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@cs.umd.edu	Path:	uunet!mimsy!chris