1

Problem

In some bash scripts, I don't want to set -e. So I write variable declarations like

var=$(false) || { echo 'var failed!' 1>&2 ; exit 1 ; }

which will print var failed! .

But using declare, the || is never taken.

declare var=$(false) || { echo 'var failed!' 1>&2 ; exit 1 ; }

That will not print var failed!.

Imperfect Solution

So I've come to using

declare var=$(false)
[ -z "${var}" ] || { echo 'var failed!' 1>&2 ; exit 1 ; }

Does anyone know how to turn the Imperfect Solution two lines into a neat one line ? In other words, is there a bash idiom to make the declare var failure neater?

More Thoughts

This seems like an unfortunate mistake in the design of bash declare.

JamesThomasMoon
  • 6,169
  • 7
  • 37
  • 63
  • 2
    Why do you need to invoke `declare` and perform the command substitution+assignment in the same statement? You can declare your vars separately, and then later run the command substitution+assignment with your one-line error check that you presented at the top of your question. – bgoldst Jun 06 '15 at 03:53
  • What about: `declare var=$($(false) || { echo 'var failed!' 1>&2 ; })` ? – Nir Alfasi Jun 06 '15 at 04:38
  • @bgoldst I'm sorry, I should have made this more challenging by using `-r` in `declare -r`. – JamesThomasMoon Jun 07 '15 at 02:53
  • 1
    @JamesThomasMoon1979 easy enough: `var=$(thing) || stuff; declare -r var`. – kojiro Jun 07 '15 at 22:13
  • I agree with @bgoldst because trying to do it all in one line is unnecessary and violates the single responsibility principle. If the `declare` statement serves a purpose at all, it is to set an attribute (or scope) to the name, which is a completely independent responsibility from the assignment. I recommend doing it in two lines. – kojiro Jun 07 '15 at 22:16

1 Answers1

2

Firstly, the issue of two lines vs. one line can be solved with a little thing called Mr. semicolon (also note the && vs. ||; pretty sure you meant the former):

declare var=$(false); [ -z "${var}" ] && { echo 'var failed!' 1>&2 ; exit 1 ; }

But I think you're looking for a better way of detecting the error. The problem is that declare always returns an error code based on whether it succeeded in parsing its options and carrying out the assignment. The error you're trying to detect is inside a command substitution, so it's outside the scope of declare's return code design. Thus, I don't think there's any possible solution for your problem using declare with a command substitution on the RHS. (Actually there are messy things you could do like redirecting error infomation to a flat file from inside the command substitution and reading it back in from your main code, but just no.)

Instead, I'd suggest declaring all your variables in advance of assigning them from command substitutions. In the initial declaration you can assign a default value, if you want. This is how I normally do this kind of thing:

declare -i rc=-1;
declare s='';
declare -i i=-1;
declare -a a=();

s=$(give me a string); rc=$?; if [[ $rc -ne 0 ]]; then echo "s [$rc]." >&2; exit 1; fi;
i=$(give me a number); rc=$?; if [[ $rc -ne 0 ]]; then echo "i [$rc]." >&2; exit 1; fi;
a=($(gimme an array)); rc=$?; if [[ $rc -ne 0 ]]; then echo "a [$rc]." >&2; exit 1; fi;

Edit: Ok, I thought of something that comes close to what you want, but if properly done, it would need to be two statements, and it's ugly, although elegant in a way. And it would only work if the value you want to assign has no spaces or glob (pathname expansion) characters, which makes it quite limited.

The solution involves declaring the variable as an array, and having the command substitution print two words, the first of which being the actual value you want to assign, and the second being the return code of the command substitution. You can then check index 1 afterward (in addition to $?, which can still be used to check the success of the actual declare call, although that shouldn't ever fail), and if success, use index 0, which elegantly can be accessed directly as a normal non-array variable can:

declare -a y=($(echo value-for-y; false; echo $?;)); [[ $? -ne 0 || ${y[1]} -ne 0 ]] && { echo 'error!'; exit 1; }; ## fails, exits
## error!
declare -a y=($(echo value-for-y; true; echo $?;)); [[ $? -ne 0 || ${y[1]} -ne 0 ]] && { echo 'error!'; exit 1; }; ## succeeds
echo $y;
## value-for-y

I don't think you can do better than this. I still recommend my original solution: declare separately from command substitution+assignment.

bgoldst
  • 34,190
  • 6
  • 38
  • 64
  • Yes, I mean "a better way of *detecting* the error". I should have added to my original question, what about with `-r` in `declare -r`? – JamesThomasMoon Jun 07 '15 at 02:55
  • `declare -r` just sets the `readonly` attribute on the variable(s) being declared; it can't be used to detect errors inside command substitutions. – bgoldst Jun 07 '15 at 07:39
  • Yes, I'm just adding a challenge to your suggested answer of declaring in advance. Perhaps adding this challenge after-the-question is a little unfair? Either way, it would be great if the declaration *and* assignment *and* error check could be done in a simple, single statement. e.g. something nearer to `var=$(something) || echo 'ERROR!'`. I think our idea of just using `[ -z "${var}" ] && echo 'ERROR!'` is as good as it gets. But I was hoping for something more succinct *and* could handle an empty `"${var}"` string value. – JamesThomasMoon Jun 07 '15 at 15:46