violet313
violet313

Reputation: 1932

Bash-Scripting stderr and stdout

I am new to bash-scripting & trying to understand how things work. It's all a bit strange..
I have two scripts. First this one:

#!/usr/bin/bash
#name: stderrtest0.sh
echo "on ${1}: this is an error" >&2
echo "on ${1}: this is an info" >&1
echo "on ${1}: this is just text"


And this second script calls the first:

#!/usr/bin/bash
#name: stderrtest1.sh
echo "invoking: stderrtest0.sh test1 >&2 ~output:"
./stderrtest0.sh test1 >&2

echo "invoking: stderrtest0.sh test2 >&1 ~output:"
./stderrtest0.sh test2 >&1

echo "invoking: stderrtest0.sh test3 2>&1 ~output:"
./stderrtest0.sh test3 2>&1

echo "invoking: stderrtest0.sh test4 1>&2 ~output:"
./stderrtest0.sh test4 1>&2

echo "invoking: stderrtest0.sh test5 ~output:"
./stderrtest0.sh test5


Here are my tests (debian squeeze) with the output:

METATEST1) invoke stderrtest1.sh

$ ./stderrtest1.sh
invoking: stderrtest0.sh test1 >&2 ~output:
on test1: this is an error
on test1: this is an info
on test1: this is just text
invoking: stderrtest0.sh test2 >&1 ~output:
on test2: this is an error
on test2: this is an info
on test2: this is just text
invoking: stderrtest0.sh test3 2>&1 ~output:
on test3: this is an error
on test3: this is an info
on test3: this is just text
invoking: stderrtest0.sh test4 1>&2 ~output:
on test4: this is an error
on test4: this is an info
on test4: this is just text
invoking: stderrtest0.sh test5 ~output:
on test5: this is an error
on test5: this is an info
on test5: this is just text

This is as I expect. Since by default stderr & stdout get sent to the terminal.


METATEST2) invoke stderrtest1.sh & redirect output to out

$ ./stderrtest1.sh >out
on test1: this is an error
on test1: this is an info
on test1: this is just text
on test2: this is an error
on test4: this is an error
on test4: this is an info
on test4: this is just text
on test5: this is an error

$ cat out
invoking: stderrtest0.sh test1 >&2 ~output:
invoking: stderrtest0.sh test2 >&1 ~output:
on test2: this is an info
on test2: this is just text
invoking: stderrtest0.sh test3 2>&1 ~output:
on test3: this is an error
on test3: this is an info
on test3: this is just text
invoking: stderrtest0.sh test4 1>&2 ~output:
invoking: stderrtest0.sh test5 ~output:
on test5: this is an info
on test5: this is just text

So here:

This is not quite as I expect. I somehow thought everything might end up in out


METATEST3) invoke stderrtest1.sh & redirect stdout to inf.out

$ ./stderrtest1.sh 1>inf.out
on test1: this is an error
on test1: this is an info
on test1: this is just text
on test2: this is an error
on test4: this is an error
on test4: this is an info
on test4: this is just text
on test5: this is an error

$ cat inf.out
invoking: stderrtest0.sh test1 >&2 ~output:
invoking: stderrtest0.sh test2 >&1 ~output:
on test2: this is an info
on test2: this is just text
invoking: stderrtest0.sh test3 2>&1 ~output:
on test3: this is an error
on test3: this is an info
on test3: this is just text
invoking: stderrtest0.sh test4 1>&2 ~output:
invoking: stderrtest0.sh test5 ~output:
on test5: this is an info
on test5: this is just text

Results are identical to METATEST2:

Ok. Now I understand METATEST2. Redirecting without specification defaults to stdout.


METATEST4) invoke stderrtest1.sh & redirect stderr to err.out

$ ./stderrtest1.sh 2>err.out
invoking: stderrtest0.sh test1 >&2 ~output:
invoking: stderrtest0.sh test2 >&1 ~output:
on test2: this is an info
on test2: this is just text
invoking: stderrtest0.sh test3 2>&1 ~output:
on test3: this is an error
on test3: this is an info
on test3: this is just text
invoking: stderrtest0.sh test4 1>&2 ~output:
invoking: stderrtest0.sh test5 ~output:
on test5: this is an info
on test5: this is just text

$ cat err.out
on test1: this is an error
on test1: this is an info
on test1: this is just text
on test2: this is an error
on test4: this is an error
on test4: this is an info
on test4: this is just text
on test5: this is an error

And here I get confused. Because in METATEST3 test1:
all output from stderrtest0.sh gets redirected to stderr & so goes to the terminal but not to inf.out

And yet here in METATEST4 test2:
all output from stderrtest1.sh is being redirected to stdout ~BUT the stderr emitted by stderrtest0.sh somehow escapes ?


So this seems to imply:


Is this the case?

Upvotes: 1

Views: 1141

Answers (2)

violet313
violet313

Reputation: 1932

Ok! Thank you Ignacio !

I think I was getting hung up on the s/h notation & the meaning of redirection. Understanding this in terms of copying a file descriptor makes it clearer to me. Because I'm a bit thick, I'm going to labour the point & maybe you can correct me if I'm still not getting it...


Firstly:
echo "foo" >&2
This does not redirect any/all of echo's output to stderr; it is a s/h equivalent to echo "foo" 1>&2. It sets the stdout FD of the echo process to be the same as it's FD for stderr.


So then for:
{ echo "foo" 2>&1 >&2 ; } > /dev/null
Here, the stderr FD for the echo process is set to the stdout FD; then the stdout FD is set to the stderr FD ~accomplishing nothing since the new assignment is the same as the current value. And nothing gets to the terminal because the function's stdout FD is set to /dev/null


And then for:
{ echo "foo" >&2 ; } > /dev/null 2>&1
Here the stdout FD of the echo process is set to the stderr FD. The function's stdout FD is set to /dev/null and the function's stderr FD is set to the value of the stdout FD ~which is of course /dev/null. &So nothing gets to the terminal.


Or to put it another way:

$ { echo "phew"; echo "bah" >&2; }
phew
bah
$ { echo "phew"; echo "bah" >&2; } 2>/dev/null
phew
$ { echo "phew"; echo "bah" >&2; } 1>/dev/null
bah
$ { echo "phew"; echo "bah" >&2; } 1>/dev/null 2>&1
$


I am a numpty.

&BTW I like how you use { a function } instead of having to write out whole silly scripts. Much neater ;)

Upvotes: 0

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 799430

Normally the shell opens two separate file descriptors for stdout (1) and stderr (2). To redirect the output from a program sent to one to the other, it suffices to copy the FD from the other.

$ { echo "foo" >&2 ; } > /dev/null
foo
$ { echo "foo" 2>&1 >&2 ; } > /dev/null
$ { echo "foo" >&2 ; } > /dev/null 2>&1
$

Upvotes: 2

Related Questions