Is there a "goto" statement in bash ? I know It is considered bad practice, but I need specifically "goto".

Solution 1

If you are using it to skip part of a large script for debugging (see Karl Nicoll's comment), then if false could be a good option (not sure if "false" is always available, for me it is in /bin/false):

# ... Code I want to run here ...

if false; then

# ... Code I want to skip here ...

fi

# ... I want to resume here ...

The difficulty comes in when it's time to rip out your debugging code. The "if false" construct is pretty straightforward and memorable, but how do you find the matching fi? If your editor allows you to block indent, you could indent the skipped block (then you'll want to put it back when you're done). Or a comment on the fi line, but it would have to be something you'll remember, which I suspect will be very programmer-dependent.

Solution 2

No, there is not; see §3.2.4 "Compound Commands" in the Bash Reference Manual for information about the control structures that do exist. In particular, note the mention of break and continue, which aren't as flexible as goto, but are more flexible in Bash than in some languages, and may help you achieve what you want. (Whatever it is that you want . . .)

Solution 3

It indeed may be useful for some debug or demonstration needs.

I found that Bob Copeland solution http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:

#!/bin/bash
# include this boilerplate
function jumpto
{
    label=$1
    cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
    eval "$cmd"
    exit
}

start=${1:-"start"}

jumpto $start

start:
# your script goes here...
x=100
jumpto foo

mid:
x=101
echo "This is not printed!"

foo:
x=${x:-10}
echo x is $x

results in:

$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101

Solution 4

You can use case in bash to simulate a goto:

#!/bin/bash

case bar in
  foo)
    echo foo
    ;&

  bar)
    echo bar
    ;&

  *)
    echo star
    ;;
esac

produces:

bar
star

Solution 5

If you're testing/debugging a bash script, and simply want to skip forwards past one or more sections of code, here is a very simple way to do it that is also very easy to find and remove later (unlike most of the methods described above).

#!/bin/bash

echo "Run this"

cat >/dev/null <<GOTO_1

echo "Don't run this"

GOTO_1

echo "Also run this"

cat >/dev/null <<GOTO_2

echo "Don't run this either"

GOTO_2

echo "Yet more code I want to run"

To put your script back to normal, just delete any lines with GOTO.

We can also prettify this solution, by adding a goto command as an alias:

#!/bin/bash

shopt -s expand_aliases
alias goto="cat >/dev/null <<"

goto GOTO_1

echo "Don't run this"

GOTO_1

echo "Run this"

goto GOTO_2

echo "Don't run this either"

GOTO_2

echo "All done"

Aliases don't usually work in bash scripts, so we need the shopt command to fix that.

If you want to be able to enable/disable your goto's, we need a little bit more:

#!/bin/bash

shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
  alias goto="cat >/dev/null <<"
else
  alias goto=":"
fi

goto '#GOTO_1'

echo "Don't run this"

#GOTO1

echo "Run this"

goto '#GOTO_2'

echo "Don't run this either"

#GOTO_2

echo "All done"

Then you can do export DEBUG=TRUE before running the script.

The labels are comments, so won't cause syntax errors if disable our goto's (by setting goto to the ':' no-op), but this means we need to quote them in our goto statements.

Whenever using any kind of goto solution, you need to be careful that the code you're jumping past doesn't set any variables that you rely on later - you may need to move those definitions to the top of your script, or just above one of your goto statements.

Solution 6

Although others have already clarified that there is no direct goto equivalent in bash (and provided the closest alternatives such as functions, loops, and break), I would like to illustrate how using a loop plus break can simulate a specific type of goto statement.

The situation where I find this the most useful is when I need to return to the beginning of a section of code if certain conditions are not met. In the example below, the while loop will run forever until ping stops dropping packets to a test IP.

#!/bin/bash

TestIP="8.8.8.8"

# Loop forever (until break is issued)
while true; do

    # Do a simple test for Internet connectivity
    PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")

    # Exit the loop if ping is no longer dropping packets
    if [ "$PacketLoss" == 0 ]; then
        echo "Connection restored"
        break
    else
        echo "No connectivity"
    fi
done

Solution 7

This solution had the following issues:

  • Indiscriminately removes all code lines ending in a :
  • Treats label: anywhere on a line as a label

Here's a fixed (shell-check clean and POSIX compatible) version:


#!/bin/sh

# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
goto() {
  label=$1
  cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
  eval "$cmd"
  exit
}

start=${1:-start}
goto "$start"  # GOTO start: by default

#start:#  Comments can occur after labels
echo start
goto end

  # skip: #  Whitespace is allowed
echo this is usually skipped

# end: #
echo end

Solution 8

There is one more ability to achieve a desired results: command trap. It can be used to clean-up purposes for example.

Solution 9

There is no goto in bash.

Here is some dirty workaround using trap which jumps only backwards:)

#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT

echo foo
goto trap 2> /dev/null
echo bar

Output:

$ ./test.sh 
foo
I am
here now.

This shouldn't be used in that way, but only for educational purposes. Here is why this works:

trap is using exception handling to achieve the change in code flow. In this case the trap is catching anything that causes the script to EXIT. The command goto doesn't exist, and hence throws an error, which would ordinarily exit the script. This error is being caught with trap, and the 2>/dev/null hides the error message that would ordinarily be displayed.

This implementation of goto is obviously not reliable, since any non-existent command (or any other error, for that manner), would execute the same trap command. In particular, you cannot choose which label to go-to.


Basically in real scenario you don't need any goto statements, they're redundant as random calls to different places only make your code difficult to understand.

If your code is invoked many times, then consider to use loop and changing its workflow to use continue and break.

If your code repeats it-self, consider writing the function and calling it as many times as you want.

If your code needs to jump into specific section based on the variable value, then consider using case statement.

If you can separate your long code into smaller pieces, consider moving it into separate files and call them from the parent script.

Solution 10

I found out a way to do this using functions.

Say, for example, you have 3 choices: A, B, and C. A and Bexecute a command, but C gives you more info and takes you to the original prompt again. This can be done using functions.

Note that since the line containg function demoFunction is just setting up the function, you need to call demoFunction after that script so the function will actually run.

You can easily adapt this by writing multiple other functions and calling them if you need to "GOTO" another place in your shell script.

function demoFunction {
        read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand

        case $runCommand in
            a|A) printf "\n\tpwd being executed...\n" && pwd;;
            b|B) printf "\n\tls being executed...\n" && ls;;
            c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
        esac
}

demoFunction

Solution 11

This is a small correction of the Judy Schmidt script put up by Hubbbitus.

Putting non-escaped labels in the script was problematic on the machine and caused it to crash. This was easy enough to resolve by adding # to escape the labels. Thanks to Alexej Magura and access_granted for their suggestions.

#!/bin/bash
# include this boilerplate
function goto {  
label=$1
cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}

start=${1:-"start"}

goto $start

#start#
echo "start"
goto bing

#boom#
echo boom
goto eof

#bang#
echo bang
goto boom

#bing#
echo bing
goto bang

#eof#
echo "the end mother-hugger..."

Solution 12

A simple searchable goto for the use of commenting out code blocks when debugging.

GOTO=false
if ${GOTO}; then
    echo "GOTO failed"
    ...
fi # End of GOTO
echo "GOTO done"

Result is-> GOTO done

Solution 13

My idea for creating something like "goto" is to use select with case and assign a variable, which I then check in an if statement. Not perfect, but may help in some cases

Example:

#!/usr/bin/env bash

select goto in Ubuntu Debian Quit ; do
    case $goto in
        Ubuntu) { CHOICE="Ubuntu" ; break ; } ;;
        Debian) { CHOICE="Debian" ; break ; } ;;
        Quit)   { echo "Bye" ; exit ; } ;;
        *)      { echo "Invalid selection, please try again..." ; } ;;
    esac
done

if [ "$CHOICE" == "Ubuntu" ]; then
    echo "I'm in Ubuntu"
fi

if [ "$CHOICE" == "Debian" ]; then
    echo "I'm in Debian"
fi

Solution 14

Why don't anyone just use functions directly ?
BTW functions are easier to deal with than making a new thing

My style :

#!/bin/bash

# Your functions
function1 ()
{
    commands
}

function2 ()
{
    commands
}
    :
    :

functionn ()
{
    commands
}

# Execute 1 to n in order
for i in {1..n}
    do
        function$i
    done

# with conditions
for i in {1..n}
    do
        [ condition$i ] && function$i
    done

# Random order
function1
functionn
function5
    :
    :
function3

Example for above style :

#!/bin/bash

# Your functions
function1 ()
{
    echo "Task 1"
}

function2 ()
{
    echo "Task 2"
}

function3 ()
{
    echo "Task 3"
}

function1
function3
function2

Output :

Task 1
Task 3
Task 2

Drawbacks :

  • Script in an organized way.
  • Less problems and not prone to errors.
  • You can make function inside a existing function.
  • Move back and forth without any problems.