4

I have a lot of code with lines like the following:

local -r foo="$(bar)"

Just recently I discovered shellcheck and that warns about those assignments, because local hides the actual error code when running scripts using set -e.

https://www.shellcheck.net/wiki/SC2312

I'm fixing this using the following, though, that bloats code a lot.

local    foo
         foo="$(bar)"
local -r foo="${foo:?No foo calculated.}"

Is there anything I'm missing to have a more condensed syntax, like without caring about the warning?

1
  • set -e is not recommended in general. Unless you're using that, I think you can just ignore that shellcheck warning. Commented 2 hours ago

2 Answers 2

3

If you're going to use local to declare each variable separately I'd be inclined to coalesce the first two lines (much like the POSIX variant of export PATH; PATH=/bin/…):

local foo; foo="$(bar)"

I'm not so sure that the third line's error case will ever get executed; if bar failed, then the assignment foo="$(bar)" will exit, and if it succeeds then it will never need to be executed. On this basis it seems better to tag the variable with readonly:

local foo; foo="$(bar)"; readonly foo

Unfortunately this isn't much different to your original code. I've put mine on a single line; yours is on three.

5
  • It needs to be executed if one wants to have the variable read-only, which is what I started with wanting to have. Additionally, when an additional line is needed already, the way it is in my example it would protect against a succeeding command unexpectedly e.g. not creating any output for some reason. Commented 20 hours ago
  • @ThorstenSchöning ah yes of course. I was thinking more about the :? unset/null variable warning Commented 18 hours ago
  • readonly is a good hint, though, because my original approach didn't care too much for additional empty checks as well. Though, that's not really what I hoped to find, especially when called commands in $(...) become longer. :-) Something more aligned with local -r [...] would be great, some option telling bash to not cover the error code by local or something. Most likely nobody is interested in if local itself succeeds. Commented 8 hours ago
  • Why local foo; foo="$(bar)" instead of local foo="$(bar)", @ChrisDavies? With or without the -r. Commented 1 hour ago
  • @terdon the crux of the question is that $( … ) failures are not caught by set -e when you're doing local x=$( … ). For example bash -ec 'f() { local x=$(false); }; f; echo "done"' should arguably fail when false is invoked but doesn't Commented 1 hour ago
1

If you don't mind subverting bash syntax you can create a function to perform the assignment and set the readonly attribute. Here's a worked example.

First create a function called assign that assigns a variable to the output from a command and sets it to be readonly. An assignment error will send a TERM signal to the program itself, causing it to abort.

assign() {
    local _n="$1" _ss
    shift

    local _errTrap="$(trap -p ERR)" _setE=$([ -o errexit ] && echo 'set -e')
    trap '
        echo "TRAP/ERR: PID=$$ bad assignment to variable: ${_n:-<unknown>}" >&2
        kill "$$"
    ' ERR
    set +e

    eval "$_n"'=$("$@")'
    _ss=$?

    readonly "${_n}"

    if [ -n "$_errTrap" ]; then eval "$_errTrap"; else trap - ERR; fi
    if [ -n "$_setE" ]; then set -e; fi

    return "$_ss"
}

Now let's use it:

echo 'Assign x=$(date)'
assign x date
echo "x=$x"

And:

echo 'Assign y=$(false)'
assign y false
echo "y=$y"

And finally:

echo 'Fail to assign to readonly x'
x=123

You can use this approach in a function by localising a variable and then assigning it:

f() {
    local d; assign d date
    echo "f(): d=$d" >&2
    …
}

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.