9

Bash doesn't have a goto operator. I have a simple task, and I can't figure out how to solve it without goto or adding a new proc, and that is undesireable.

I have two conditions and a block of code that should work this way:

[ condition 1 ] if true run some commands and check [ condidion 2 ]; if false exec a block of code.

[ condition 2 ] if true don't exec the same block of code; if false do execute the same block.

Is it solvable, or I have to define some proc and use exit there? I used a flag var, but I don't like it.

With goto available it would look this way:

[ cond 1 ] || goto label 1
  command
  ...
  command
  [ cond 2 ] && goto label 2

:label 1

block of code

:label 2

Something like that.

Pilot6
  • 90,100
  • 91
  • 213
  • 324
  • 1
    Old Unix's Thompson shell actually used to have goto statement in the past. That's besides the point, however. How would you express what you want in pseudocode with goto ? Perhaps functions would be appropriate here. – Sergiy Kolodyazhnyy Jul 22 '18 at 22:06
  • Hold on, I will write it with goto. – Pilot6 Jul 22 '18 at 22:10
  • I've rewritten my answer a bit. Let me know if anything doesn't make sense or needs extra clarification. – Sergiy Kolodyazhnyy Jul 22 '18 at 23:05
  • 1
    I first learned of GOTO in old BASIC classes a few decades ago. Now even modern BASIC languages frown against it's use. Recently I've seen it in grub code which is pretty ugly IMO. With good program design I really don't see a need for GOTO's. In Assembly language it's very prevalent, "Branch" instruction either conditionally on test or unconditionally (GOTO): https://en.wikipedia.org/wiki/Branch_(computer_science) – WinEunuuchs2Unix Jul 23 '18 at 00:15
  • 1
    By "new proc" I assume you mean start a new script? – muru Jul 23 '18 at 00:56
  • Damn! I missed this question! You should have pinged me in chat! ;-) – Fabby Aug 29 '18 at 22:49

3 Answers3

14

The typical way to work to use branching in shell scripts would be via functions declared before main block of code. However, I think the underlying issue here is the logical one, and not the goto. Obviously label 1 is repeated, so this can live as function. But also, condition 2 could be turned into a function that also calls label 1 for the sake of readability:

#!/bin/bash

label1(){
    echo "label 1 function"
}

check_cond2(){
    if ! [ condition2 ]; then
        label1 arg1 arg2
    fi
}

if [ condition1  ]; then

    command
    ...
    command
    check_cond2
else 
    label1
fi

othercommand2
othercommand3

What I've noticed is that in both conditions you have if false exec a block of code and if false exec a block of code, so one idea would be to start at checking whether those conditions both false. However, making something like if ! [ cond1 ] || ! [ cond2 ] would change branching logic. You can still see the pseudocode version of that by seeing this posts edit history.

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
  • The problem is that I am afraid upstream won't accept new procs in that script. One function will be enough. – Pilot6 Jul 23 '18 at 07:45
  • 2
    @Pilot6 I think the upstream may be misunderstanding what a process is and isn't. Functions in bash run in current shell, no new process: https://unix.stackexchange.com/a/305360/85039 But any non-builtin command is a process, regardless of whether it runs in function or not – Sergiy Kolodyazhnyy Jul 23 '18 at 19:22
12

When I moved from Windows to Linux on my desktop, I had a lot of pre-existing .BAT and .CMD files to convert and I wasn't going to rewrite the logic for them, so I found a way to do a goto in bash that works because the goto function runs sed on itself to strip out any parts of the script that shouldn’t run, and then evals it all.

The below source is slightly modified from the original to make it more robust:

#!/bin/bash

BAT / CMD goto function

function goto { label=$1 cmd=$(sed -n "/^:[[:blank:]][[:blank:]]*${label}/{:a;n;p;ba};" $0 | grep -v ':$') eval "$cmd" exit }

apt update

Just for the heck of it: how to create a variable where to jump to:

start=${1:-"start"} goto "$start"

: start goto_msg="Starting..." echo $goto_msg

Just jump to the label:

goto "continue"

: skipped goto_msg="This is skipped!" echo "$goto_msg"

: continue goto_msg="Ended..." echo "$goto_msg"

following doesn't jump to apt update whereas original does

goto update

and I do not feel guilty at all as Linus Torvalds famously said:

From: Linus Torvalds
Subject: Re: any chance of 2.6.0-test*?
Date: Sun, 12 Jan 2003 11:38:35 -0800 (PST)

I think goto's are fine, and they are often more readable than large amounts of indentation. That's especially true if the code flow isn't actually naturally indented (in this case it is, so I don't think using goto is in any way clearer than not, but in general goto's can be quite good for readability).

Of course, in stupid languages like Pascal, where labels cannot be descriptive, goto's can be bad. But that's not the fault of the goto, that's the braindamage of the language designer.

Original source for the code (modified to make it less error prone)
The source for the quote

Fabby
  • 34,259
11

You should put your block_of_code into a function and use some if/else:

my_function() {
    block of code
    ...
}

if [[ condition 1 ]] ; then
    command
    ...
    command

    if [[ ! condition 2 ]] ; then
        # label 1
        my_function
    fi

else
    # label 1
    my_function
fi

# label 2    
Byte Commander
  • 107,489