0

I'm writing a script that takes files (filename...) as arguments, and redirects both the stdout and stderr to a file of name filename.output. In the files are commands, and I'd only like to bash once (in case a command within a file should not be run multiple times i.e. mkdir or rmdir). I have found that, when iterating through the arguments, this command works:

bash $a &> "$(basename $a).output"

However, I'd also like to use the error status of this command in case .output could not be created for whatever reason:

if ! bash $a &> "$(basename $a).output"
    then
        echo >&2 "failed to create $(basename $a).output"
fi

When I do this, my if statement always evaluates to true, which I think is because either stdout or stderr need to fail in redirecting, as opposed to both of them. Very few of the commands I am testing with produce both stderr and stdout (i.e. a command like date produces stdout, but no stderr, thus causing a non-zero return value as stderr fails to redirect).

Am I correct in my analysis of why my code is not functioning properly, and if so, is there a way to check for the failure of stdout redirection and stderr redirection individually, so I accurately display when .output isn't created?

EDIT: I have found that the issue actually lies in the bash "$a" component, as any time $a is a file with a command that produces an error, the overall if statement evaluates to true. I suppose now my question would be, are there any instances in which redirecting stdout and stderr would result in an error that I should look out for?

9
  • Always double quote your variables when you use them, eg bash "$a" &> "$(basename "$a").output" which can be better written as bash "$a" &> "${a##*/}.output"
    – roaima
    Apr 11, 2022 at 21:38
  • Meant to say that my if statement always evaluates to true when the command is non-zero, and it seems the command almost aways returns a non-zero number. I'll edit the main post to reflect this.
    – woz
    Apr 11, 2022 at 21:40
  • Curious: if you can't create the .output file why do you think you'd be able to create a corresponding .error file in the same place?
    – roaima
    Apr 11, 2022 at 21:42
  • I actually can't imagine a case where .output could not be created, it is really just to practice defensive programming.
    – woz
    Apr 11, 2022 at 21:51
  • It's not defensive if the likelihood of falling to create output is matched by the likelihood of failing to create error. Writing to a different directory or using the system logging subsystem (logger 'hello, world') would seem more prudent
    – roaima
    Apr 11, 2022 at 21:53

1 Answer 1

2

If you do

if ! bash $a &> "$(basename $a).output"; then

the main branch of the if-statement runs if there is an error in the redirection, or if the script runs but returns a non-zero exit status. The &> file operator is the same as > file 2>&1, and it redirects both stdout and stderr, but since there's only one file involved, you'd be hard-pressed to find a way to have one of the redirections succeed, but the other to fail. The redirection can fail, for all reasons creating a file can, e.g. permission issues, a non-existing path, disk full.

Let's make a test script: this will just print something, and if it was given an argument, it'll exit with an error.

$ cat test.sh
#!/bin/bash
echo test script
if [ "$#" != 0 ]; then
    echo exiting with error
    exit 1
fi
$ chmod +x test.sh

Here, the redirection fails (since ./non-existing-dir/ indeed didn't exist):

$ if ! ./test.sh > ./non-existing-dir/test.output; then 
    echo "it failed (for some reason)"; fi
bash: ./non-existing-dir/test.output: No such file or directory
it failed (for some reason)

Here, the redirection succeeds, and output is collected to the file, but the script itself returns a failing status:

$ rm -f test.output
$ if ! ./test.sh 1 > ./test.output; then
    echo "it failed (for some reason)"; fi
it failed (for some reason)

$ cat test.output 
test script
exiting with error

You can't miss an error in a redirection; one will cause the command to exit with a non-zero status. But the exact value is not defined and depends on the shell, so we can't use that to tell apart the redirection failing and the script itself failing. The POSIX Shell Command Language definitions says in 2.8.2 Exit Status for Commands:

If a command fails during word expansion or redirection, its exit status shall be between 1 and 125 inclusive.

Bash's manual says a similar thing in 3.7.1 Simple Command Expansion.


Now, if you do want to check an error in the redirection specifically, you can do it, you just have to open the redirection separately before running the program. E.g. we can use a script like this to run our program:

$ cat redir.sh 
#!/bin/sh

outfile="${1?please define output file}"
cmd="${2?please define command to run}"
shift 2

if ! exec 9> "$outfile"; then
    echo "error: cannot create redirection to '$outfile'"
    exit 1
fi

if ! "$cmd" "$@" >&9 2>&9; then
    echo "error: script '$cmd' failed"
    exit 1
fi
exec 9>&-         # close the output fd

Now, the failing redirection is detected as such:

$ bash redir.sh ./non-existing-dir/test.output ./test.sh
redir.sh: line 8: ./non-existing-dir/test.output: No such file or directory
error: cannot create redirection to './non-existing-dir/test.output'

and so is the script failing:

$ bash redir.sh ./test.output ./test.sh 1
error: script './test.sh' failed

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .