1

Sorry I cannot give a clear title for what's happening but here is the simplified problem code.

#!/bin/bash

# get the absolute path of .conf directory
get_conf_dir() {
    local path=$(some_command) || { echo "please install some_command first."; exit 100; }
    echo "$path"
}

# process the configuration
read_conf() {
    local conf_path="$(get_conf_dir)/foo.conf"
    [ -r "$conf_path" ] || { echo "conf file not found"; exit 200; }
    # more code ...
}

read_conf

So basically here what I am trying to do is, reading a simple configuration file in bash script, and I have some trouble in error handling.

The some_command is a command which comes from a 3rd party library (i.e. greadlink from coreutils), required for obtain the path.

When running the code above, I expect it outputs "command not found" because that's where the FIRST error occurs, but actually it always prints "conf file not found".

I am very confused about such behavior, and I think BASH probably intent to handle thing like this but I don't know why. And most importantly, how to fix it?

Any idea would be greatly appreciated.

0

1 Answer 1

2

Do you see your please install some_command first message anywhere? Is it in $conf_path from the local conf_path="$(get_conf_dir)/foo.conf" line? Do you have a $conf_path value of please install some_command first/foo.conf? Which then fails the -r test?

No, you don't. (But feel free to echo the value of $conf_path in that exit 200 block to confirm this fact.) (Also Error messages should, in general, get sent to standard error and not standard output anyway. So they should be echo "..." 2>&1. That way they don't be caught by the normal command substitution at all.)

The reason you don't is because that exit 100 block is never happening.

You can see this with set -x at the top of your script also. Go try it.

See what I mean?

The reason it isn't happening is that the failure return of some_command is being swallowed by the local path=$(some_command) assignment statement.

Try running this command:

f() { local a=$(false); echo "Returned: $?"; }; f

Do you expect to see Returned: 1? You might but you won't see that.

What you will see is Returned: 0.

Now try either of these versions:

f() { a=$(false); echo "Returned: $?"; }; f
f() { local a; a=$(false); echo "Returned: $?"; }; f

Get the output you expected in the first place?

Right. local and export and declare and typeset are statements on their own. They have their own return values. They ignore (and replace) the return value of the commands that execute in their contexts.

The solution to your problem is to split the local path and path=$(some_command) statements.

http://www.shellcheck.net/ catches this (and many other common errors). You should make it your friend.

In addition to the above (if you've managed to follow along this far) even with the changes mentioned so far your exit 100 won't exit the main script since it will only exit the sub-shell spawned by the command substitution in the assignment.

If you want that exit 100 to exit your script then you either need to notice and re-exit with it (check for get_conf_dir failure after the conf_path assignment and exit with the previous exit code) or drop the get_conf_dir function itself and just do that inline in read_conf.

Sign up to request clarification or add additional context in comments.

4 Comments

Perhaps worth being more explicit about the problem of the message please install some_command first being sent to stdout instead of stderr. As well as the fact that the exit in get_conf_dir only exits from the subshell in $(get_conf_dir).
@rici Absolutely right. I lost the forest for the trees there a bit. Updating.
@Lin I solved some of your problem. And got you to the point where you can correctly handle your exit 100 failure since it actually happens. But, as rici pointed out you can't get exit 100 there to exit the script on its own. It is in a sub-shell. See the last paragraph in the updated answer.
Thank you @Etan Reisner & @rici, very interesting and informative. I use shellcheck very often but I didn't pay enough attention on the detailed information but now I would. And yes, I never notice that $(get_conf_dir) runs in a sub-shell. THANKS for point that out.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.