3

I have bash script similar to:

{
  # Try block
  command 1
  command 2
  command 3
} || {
  # Catch errors
  while true; do
  {
    # Try block inside
    # Change parameters for command 1, command 2
    command 1
    command 2
    command 3
    break
  } || {
      # Change parameters and try again
      continue
  }
done
}

More or less this code works fine, but...

For some reason try sections works not as expected for me. I thought that it fails if some of my commands return not 0 code, but it's not true.

For some specific cases my command 2 returns 1 code in the first try block, but it doesn't fail and goes into catch section, command 3 executes from this try block and that's it.

Here is my question:

How to handle errors in bash? Just to play with return codes?

UPDATE:

Original code looks very similar to: The main idea is that all 3 commands should be executed one-by-one and all of them are somehow related to folder_name.

folder_name=my_folder
suffix=0
{
  cd /home/test/
  mkdir $folder_name
  ls -la $folder_name
} || {
    while true; do
      suffix=$(expr $suffix + 1)
      {
        folder_name=$folder_name_$suffix
        cd /home/test/
        mkdir $folder_name
        ls -la $folder_name
        break
      } || {
          continue
      }
    done
}
4
  • I would recommend implementing this as a script instead of writing a lengthy and less readable one liner. Hope you can understand the reason why. Commented Jul 30, 2017 at 10:13
  • @Anubis, I'm trying to wrap this into the script, but it looks awful and works not as I expect..That's why I published this question. Commented Jul 30, 2017 at 10:14
  • If you can show a simplified but working code, may be I'll be able to help. Commented Jul 30, 2017 at 10:15
  • @Anubism, I've updated original question. Commented Jul 30, 2017 at 10:24

2 Answers 2

1

I see two problems here.

When grouping commands using curly braces, there must be a semicolon at the end (or a newline before the closing brace). Refer to docs here

On the other hand, you are not considering return codes anyway. You normally check the return code with something like [[ $? -eq 0 ]]. But in here, you can simply chain the commands with &&. e.g.

{
  cd /home/test/ && 
  mkdir $folder_name &&
  ls -la $folder_name;
}

However, following will do what you need.

cd /home/test/
prefix=my_folder                                                                                                                                      
suffix=0

folder_name="$prefix"
while true; do
  if [[ -e $folder_name ]]; then
    folder_name="${prefix}_$((++suffix))"
    continue
  fi  
  mkdir "$folder_name"
  if [[ $? -ne 0 ]]; then
    echo "mkdir failed"
    exit 1
  fi  
  break
done
Sign up to request clarification or add additional context in comments.

4 Comments

Well, I've replaced my code into { command 1 && command 2; echo $?} || { echo "failed"} and it returns non-zero return code. It can't print failed as expected. What is wrong here?
That's wrong. Braced group's return code is the return code of the last command. In your case echo $? executes successfully and returns 0, which means the group execution was successful. Just remove it and it'll work, { comamnd 1 && command 2; } || {echo 'failed'; }. Don't forget the semicolon (or a newline).
@Anubis, hmm..interesting. So, it's not possible to echo some success message there?
Yes you can, { comamnd 1 && command 2 && echo 'success'; } || {echo 'failed'; }. Bash proceeds to the next command in an && chain only if the current command returned with 0. If any failed, the chain will break right there with the failed error code.
1

As you probably already know, you can use set -e (== set -o errexit) to simplify error handling by making the script abort on an untested failure.

Unfortunately with it, non-exec'd subshells behave differently than exec-ed shell children, because a non-exec'd subshell can see whether it's tested whereas an exec'd shell child cannot.

Consequently, this:

#!/bin/sh -e

echo "EXEC'D SHELL CHILD"
if sh -e <<'EOF'
true(){ echo true; return 0; }
false(){ echo false; return 1; }
true
false 
true
true
EOF
then
    echo SUCCESS
else
    echo FAILED: $?
fi

echo ===========
echo "NON-EXEC'D SUBSHELL"
if (
true(){ echo true; return 0; }
false(){ echo false; return 1; }
true
false 
true
true
)
then
    echo SUCCESS
else
    echo FAILED: $?
fi

Outputs:

EXEC'D SHELL CHILD
true
false
FAILED: 1
===========
NON-EXEC'D SUBSHELL
true
false
true
true
SUCCESS

(Feel free to use /bin/bash instead of /bin/sh -- it behaves exactly the same)

So you can use set -e to somewhat simplify error handling, but you still need && or || return 1 in a tested subshell (or block or function), or you need to use an exec'd shell child instead. In your case, you can do:

 sh -ec' command 1
  command 2
  command 3' || ...

or

 {  comman 1 && command 2 && command 3; } || ... 

3 Comments

Currently, I'm using bash -x in my script with set set -o nounset set -o errexit set -o pipefail, that's why I'm not sure how it works together with -e.
set -o errexit == set -e. They're two ways of turning on the same feature.
@smart Personally, I'm quite convinced that, although I use it myself, the errexit feature is misdesigned. I think the above difference between exec'd shells and subshells/blocks/functions shouldn't exist, but the semantics of set -e are already set in stone/POSIX.

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.