SmxCde
SmxCde

Reputation: 5403

Run set of commands and return error code if any failed

In a nodejs project I have a shortcut yarn lint that runs couple of linters in such way:

lint_1 && lint_2 && lint_3

If any of these find an error it return an error code, as a result yarn lint itself returns error code, as a result - build fails.

It works somewhat fine, catches all the errors though there is a small issue: If a linter fails with error code - rest of the linters wont be executed.

What I would like - execute all of them (so they all print all errors) and only then fail.

I know that I can create a bash script (that I will run in yarn lint), run each of the linters one by one collecting return codes and then check if any of codes is non-zero - exit 1 and it will fail the yarn lint. But I am wondering is there more elegant way to do it?

Upvotes: 1

Views: 2106

Answers (4)

Ivan
Ivan

Reputation: 7277

I'd suggest to to update accepted answer like this:

#!/bin/bash
result=0
trap '((result+=e))' ERR
e=1; lint_1
e=2; lint_2
e=4; lint_3
exit "$result"

This will allow to easily understand which one(or many) failed.

Upvotes: 0

Kache
Kache

Reputation: 16677

I think this clean, clear, and succinct:

declare -i result=0  # declare $result is an integer

lint_1; result+=$?
lint_2; result+=$?

exit "$result"

Upvotes: 0

KamilCuk
KamilCuk

Reputation: 140990

What I would like - execute all of them (so they all print all errors) and only then fail

Basically we have a list of exit codes to catch. If any of them is nonzero, we need to set a variable to have nonzero value. Expanding that to a list, would look like this:

result=0
if ! lint_1; then result=1; fi
if ! lint_2; then result=1; fi
if ! lint_3; then result=1; fi
exit "$result"

As a programmer, I see that we have a pattern here. So we can go with an array, but bash does not have 2d arrays. It would be a workaround with eval to get around quoted parameters. It is doable. You have to use eval, to double evaulate the array "pointer"/name, but works. Note that eval is evil.

cmds_1=(lint_1 "arg with spaces you pass to lint_1")
cmds_2=(lint_2)
cmds_3=(lint_3)

result=0
# compgen results list of variables starting with `cmds_`
# so naming is important
for i in $(compgen -v cmds_); do
    # at first, `$i` is only expanded
    # then the array is expanded `"${cmds_?[@]}"`
    if ! eval "\"\${$i[@]}\""; then
        result=1
    fi
done
exit "$result"

We can also go with xargs. From manual EXIT STATUS is 123 if __any__ invocation of the command exited with status 1-125. If you know that your programs will exit between 1-125 exit status you can (usually xargs handles different exit statuses correctly anyway (returns 123), but let's stay conforming):

xargs -l1 -- bash -c '"$@"' -- <<EOF
lint_1 "arg with spaces you pass to lint_1"
lint_2
lint_3
EOF
result=$?          # or just exit "$?"
exit "$result"

which looks strangely clean. On a side note, by passing just -P <number of jobs> to xargs you can execute all the command in parallel. You can accommodate for the 1-125 error range, by handling the error inside the bash script, ie.

xargs -l1 -- bash -c '"$@" || exit 1' -- <<EOF
lint_1 "arg with spaces you pass to lint_1"
lint_2
lint_3
EOF
result=$?
exit "$result"

And I have another idea. After each command we can output the return status on a dedicated file descriptor. Then from all return statuses filter zeros and check if there are any other statuses on the stream. If they are, we should exit with nonzero status. This feels like a work-done-around and is basically the same as the first code snipped, but the if ! ....; then result=1; fi is simplified to ; echo $? >&10.

tmp=$(mktemp)
(
    lint_1 "arg with spaces you pass to lint_1"; echo $? >&10
    lint_2; echo $? >&10
    lint_3; echo $? >&10
) 10> >(
    [ -z "$(grep -v 0)" ]
    echo $? > "$tmp"
)
result="$(cat "$tmp"; rm "$tmp")"
exit "$result"

From the options presented, I would go with the other answer ;) or with the xargs second snipped.

Upvotes: 3

that other guy
that other guy

Reputation: 123470

You could trap on ERR and set a flag. This would run each of the linters and exit with failure if any one of them fails:

#!/bin/bash
result=0
trap 'result=1' ERR
lint_1
lint_2
lint_3
exit "$result"         

Upvotes: 8

Related Questions