1

I'm trying to run a shell command (currently either sh or bash) which connects to a database. Because the database is still 'warming up', the command fails.

So I was trying to do a loop (let's say ... 100 tries) and each time the command fails, wait 1 second and retry.

If there's an error, this is the start of the string that is dumped to stdout: Sqlcmd: Error: <snipped>

Here's what I've been trying:


for i in $(seq 1 100)
do
    X='/opt/mssql-tools/bin/sqlcmd -E -S localhost -Q "<some sql statement> "'
    if [[ $X == Sqlcmd: Error:* ]]
        echo "."
    then
        break
    fi
done

It's not working as I figure out the string comparison stuff with shell/bash ... but was more making sure if I was on the right track etc.

halfer
  • 19,824
  • 17
  • 99
  • 186
Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • 1
    Use `$()` to get the output into a bash variable. See my edit . – Matthieu Jun 28 '19 at 06:50
  • I've never done any shell scripting like this before @WernerHenze - so I never knew about status code results for commands, file descriptors, `2>&1` and `$?` idioms, `stdout` and `stderr` are two different things, etc. Lots of shell/bash shell 101 learning, through this. I learnt enough clues from below to get me to the answer. – Pure.Krome Jun 28 '19 at 07:40

2 Answers2

2

You could try something like:

while true ; do
    if Sqlcmd xxx xxx xxx ; then break ; fi
    # or:
    Sqlcmd xx xxx xxx && break
    sleep 1
done

You can also add a counter:

for ((n=100;n>0;n--)) ; do
    Sqlcmd xxx xxx xxx
    if [[ $? == 0 ]] ; then
        break
    fi
    sleep 1
done
[[ $n == 0 ]] && echo Timeout && exit 1

I'm showing two different ways of testing the return value here, but the first one is preferred (if cmd ; then ... ; fi).

$? is the return value from the last command, which is 0 when it completed successfully. If it returns 0 even in case of error (which can happen for malformed programs), you can test the output with grep:

Sqlcmd xxx xxx 2>&1 | grep <error pattern> > /dev/null
if [[ $? != 0 ]] ; then break ; fi

Here we test $? != 0 because grep will return 0 when the error pattern has been found.

If you want to get the output result into a variable, run the command with X=$(Sqlcmd xxx xxx). Then you can use bash string comparison:

X=$(Sqlcmd xxx xxx)
if [[ "$X" =~ .*error.* ]] ; then
    <handle error here>
fi

Note bash can match regexp, which makes it really handy at checking error types.

You can also use a switch/case construct:

case "$X" in
    *Error:*) echo " Error detected " ;;
    *) break ;;
esac

(Note the double ;;)

Matthieu
  • 2,736
  • 4
  • 57
  • 87
  • what does `$? == 0` mean plz? – Pure.Krome Jun 28 '19 at 06:33
  • 1
    `$?` stores the exit status of the last executed command; `0` meaning success and `not 0` error (usually, several numbers are used to encode different errors). – danrodlor Jun 28 '19 at 06:39
  • 1
    @Pure.Krome it's the return value of the last run command. See my edit. – Matthieu Jun 28 '19 at 06:39
  • 1
    AH! thank you :) never knew you could do this (commands have return codes). /me tries ... hmm ... still returns a string??? /me googles for `2>&1` .... Oh! Ok - this is all new to me. So it's redirect `stderror` to `stdout`. kewl. and there's the status code! gotcha! ok .. i think i've got this now... – Pure.Krome Jun 28 '19 at 06:57
  • @Pure.Krome now you can print the full output of `man bash` and read a page every evening ;) – Matthieu Jun 28 '19 at 07:02
  • To go easier on your cpu, add a `sleep 1` to the end of your `while` loop – zwbetz Jun 28 '19 at 15:04
  • @zwbetz, true. Though sometimes the program itself has an internal timeout too... – Matthieu Jun 28 '19 at 16:00
1

I ended up learning all the clues from @matthieu's post. This is what I ended up doing:

for i in $(seq 1 30)
do
    /opt/mssql-tools/bin/sqlcmd -U sa -P <snip> -S localhost -Q "USE Master" 2>&1

    if [[ $? != 0 ]]
    then
        # Failed
        echo "."
        sleep 1s
    else
        # worked!
        break
    fi
done

breakdown for those learning (like me)

  • execute a sql query using the sqlcmd command. Any errors via stderr (that's the 2 in 2>&1) will be redirected to the console stdout (that's the $1). REF: 2>&1 shell idiom.
  • the result status code is sent to $? (REF: what is bash dollar questionmark ?)
  • if it failed(any value that is NOT a zero), then sleep 1 sec and we'll try. Only re-try 30 times, though.
  • if we worked (value is zero), then stop trying and go on....

So there we have it! shell/bash shell 101 stuff. good luck!

Pure.Krome
  • 84,693
  • 113
  • 396
  • 647
  • Great! Although there's no real point in merging `2>&1` if you're not either removing the whole output (with `> /dev/null`) or piping to another command (`| grep ...`), or getting it into a variable. – Matthieu Jun 28 '19 at 07:53
  • And you could use a `for((i...))`, as well as `echo -n` to show the `.` on the same line instead of on a new line each :) – Matthieu Jun 28 '19 at 07:55