Help! Unix (BSD) weirdness on redirect

So, here’s the quick demonstration of the problem:

Make some background processes…


# sleep 60 & ; sleep 60 & ; sleep 60 & ; sleep 60 &

Look at them…


# jobs
[1]    running    sleep 60
[2]    running    sleep 60
[3]  - running    sleep 60
[4]  + running    sleep 60

Good, right? Let’s count them up:


# jobs | wc -l
       0

Hmm. Where’d the go? Perhaps they’re not giong to STDOUT?


# jobs 2>&1 3>&1 4>&1 | wc -l
       0

…Nope. OK, so I usually think I’m pretty good at this but I’m stumped. Ideas?

This is a BSD-based disk array.

Oh, the shell is BASH. Not much choice on an appliance like this. When I use the alternate Bourne shell, there’s no difference.

It seems to work fine on darwin bash. I’ll note that ‘jobs’ is a shell builtin so depending on how your shell is set up (if it’s a weird embedded thing, for instance) it may not be launching it as a subprocess with its own stdout.

ETA: does echo $( jobs ); work?

Rather the opposite, I think.

To implement the pipe (" | wc"), the shell launches a subshell to run ‘jobs’. And that subshell jobs prints nothing because it has no jobs: the jobs belong to its parent.

As an aside, those semicolons are redundant. The “&” serves to delimit the commands. What you wrote says “run sleep in the background, then a null command interactively, then sleep in the background, then…”
ETA: And, yeah, I get the expected output (non-zero line count) in “GNU bash, version 3.2.25(1)-release (i686-redhat-linux-gnu)”

Nope. Doesn’t work. I tried back-quotes, no love there, either.

I could really kludge and “ps -e | grep -c <procname>” but I’m worried about false positives.


# jobs
[1]    running    sleep 60
[2]    running    sleep 60
[3]  - running    sleep 60
[4]  + running    sleep 60
# echo $( jobs );

# echo "`jobs`"

#

What if you redirect to a file first:



jobs > /tmp/foo ; cat /tmp/foo | wc -l


By the way, I get the behavior in the OP with tcsh version 6.14.00.

redirect to a file works. Kludgy but no false positives:


# jobs >out ; wc -l out
       3 out


If you want to avoid the temp file, you could use ps, selecting lines with matching parent PIDs to weed out false positives. Here’s a working command line under Linux. ps arguments and line format will likely vary on your BSD box, but it illustrates the idea:



# We have some potential false positives running.  Header line, although 
# actually eliminated by grep, was manually inserted for clarity's sake.

# Dump full process table, grep for sleep
[kyrie@ghost ~]$ ps -eF | grep sleep
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
kyrie      10817  1120  0 26971   516   4 14:08 pts/0    00:00:00 sleep 1000
kyrie      10855  1120  0 26971   516   4 14:17 pts/0    00:00:00 sleep 1000
kyrie      10882 10776  0 28159   944   5 14:20 pts/1    00:00:00 grep --color=auto sleep

# Start the jobs we actually want to detect.
[kyrie@ghost ~]$ sleep 60 & sleep 60 & sleep 60 &
[1] 10884
[2] 10885
[3] 10886

# Here are the jobs we actually want to detect.  We should see three.
[kyrie@ghost ~]$ ps -eF | grep -v awk | awk "\$3 == $$ && /sleep/ {print}" | wc -l
3


Not knowing your exact situation, a caveat: Being under job control, and having a given shell as your parent are not exactly the same thing. But you did previously mention that using ps might be an option if you could eliminate false positives, so I suspect that this, or something very similar might work for you.

Until you posted, I didn’t think I had access to the PPID. I was about to post that this appliance didn’t give me access to PPIDs. Odd little bugger.

Then I remembered, while typing it out, that this is BSD. “ps -al” not “ps -ef”.

I’ll play with the PPID column, should be a pretty easy awk statement.

Thanks for twitching that brain cell.

This is just what I need. Thanks all!


# cat testit

sleep 120 &
sleep 120 &
sleep 120 &
sleep 120 &
sleep 120 &

MYPID=$$
ps -al | awk '$3=="'$MYPID'" && $0~/sleep/ && $0!~/awk/ {sum++}END{print sum}'


…its funny to see different shell programming styles. I always insist on single quotes around awk statements, changes how we escape to external variables. I always back-quote to run a external command while friedo does a $( cmd ).

Useless Use Of Cat Award goes to…

Um… Not useless. Intentional. His original design would have the output be a single number. To preserve that behavior, one needs to recover a stream to pipe to ‘wc’. Doing "wc -l /tmp/foo’ produces a second field, namely the filename, which was not prescribed. You could do something like



wc -l /tmp/foo | cut -d ' ' -f 1


to restore the original behavior, but as long as you’re using a second process, ‘cat’ is a much better choice.

well, I can see doing it to avoid having to snip off the numeric with awk or something.

either:
jobs > /tmp/foo ; cat /tmp/foo | wc -l

…or…
jobs > /tmp/foo ; wc -l /tmp/foo | awk ‘{print $1}’

there’s probably some smart kernel reason to do one over the other.

EDIT: or what **Pasta **said.

Now, I’m curious. I usually use single quotes around awk statements, too, but I switched to the more cumbersome double-quotes because it’s the only way I know that still allows the shell to expand shell variables inside the statement. How do you do that inside of single quotes?

Edit to add: I think what Derleth was getting at is that “wc -l < /tmp/foo” would do as well as “cat /tmp/foo | wc -l”. No need for awk to snip.

And I missed the edit window for a second edit: Never mind answering my question. Having looked more closely (i.e. at all) at the code text in your post, I understand the style you employ.

The double quotes in “’$MYPID’” (double, single, external variable, single, double) are probably unecessary. It forces the string comparison rather than a numeric comparison.

Just habit.

…and I didn’t even think of the redirect into “wc” for the other option. Tighter still, I bet.

I suppose. The commonality of “UUOC” admonitions is a pet peeve of mine, as I feel they are often misguided and are a damagingly extensive form of premature optimization. But that’s a rant for another thread…

I’ve always used this construction:



export FOO="Hello"
echo world | awk '{print ENVIRON["FOO"],$1}'