2

I'm trying to increment a variable value in a while loop, but I want to increment it twice. By "twice" I mean increment variable value first time, then do some operation and then increment already incremented value once more, and this is all inside a while loop. My code looks like this:

    i=1
    setglobal="SET GLOBAL "
    while [ $i -le $# ]
    do
         assign=$setglobal$i=$($i+1)
         START=`date +%s`
         mysql $database -u $user -se "$assign;select
         here goes my database query, not important"
         END=`date +%s`
         echo $END - $START | bc>>output.txt
         i=$(($i+1))
         mysqld restart
    done

And I have a list of arguments sent to my shell: innodb_change_buffer_max_size 16 key_buffer_size 1431655770, as 1st, 2nd, 3rd and 4th arguments respectively. So I want the while loop to do:

    SET GLOBAL innodb_change_buffer_max_size = 16

after

    assign=$setglobal$i=$($i+1)

and

    SET GLOBAL key_buffer_size = 1431655770

after

    i=$(($i+1))
    assign=$setglobal$i=$($i+1)

As a result, in my output.txt I should have got the running time of each query, however I get only four zeros. So I guess my loop is either not doing this part "SET GLOBAL key_buffer_size = 512" correctly, or it is not doing the right incrementation. Could anyone tell me what might be wrong with my code?

0

2 Answers 2

3

glenn jackman's helpful answer offers bash solutions.

However, given that you've generically tagged your question shell and that your code seems to use only POSIX-compliant features (except for errors), I assume that you're looking for a POSIX-compliant solution:

#!/bin/sh

# Make sure that parameters were passed and that they were passed in pairs.
if [ $# -eq 0 ] || [ $(( $# % 2 )) -ne 0 ]; then
  echo "ERROR: Please pass parameters in pairs, and pass at least one pair." >&2
  exit 2
fi

while [ $# -gt 0 ]; do # Continue while arguments remain.

  # Store the (next) pair of parameters in variables.
  name=$1
  val=$2
  shift 2 # Remove the (next) 2 positional parameters from the start of the array.

  # Synthesize the mysql SET statement.
  assign="SET GLOBAL $name=$val"

  # Get start timestanp (resolution in seconds).
  start=$(date +%s)

  # Invoke the mysql command.
  mysql $database -u $user -se "$assign;select
  here goes my database query, not important"

  # Get end timestamp.
  end=$(date +%s)

  # Calculate the execution time span and append it to the output file.
  echo $(( end - start )) >>output.txt

  # Restart mysql.
  mysqld restart

done

As for what you've tried:

  • $($i+1) cannot work as a reference to the $i-th positional parameter:

    • Since $(...) is a command substitution (the modern equivalent of `...`), the expanded result of $i+1 would actually be interpreted as a command to execute, which is not the intent.
  • Using POSIX features only, there is no direct way of referencing a positional parameter by index.

    • By contrast, in bash you could use ${@:i:1}, but that's a nonstandard extension).
  • Therefore, the simplest approach is to use shift to consume positional parameters (remove them from the start of the array of parameters) after saving their values in variables, and then checking whether any are left with [ $# -gt 0 ]. This also obviates the need for auxiliary index variable $i.

  • While echo $END - $START | bc works, there's no need to involve external utility bc, because simple arithmetic expansion $(( END - START )) will do.

  • It's best to avoid variable names such as $START and $END, because all-uppercase shell-variable names can conflict with environment variables and special shell variables.

Sign up to request clarification or add additional context in comments.

Comments

1

I believe that mysql can read commands from stdin, more readable than a single double quoted string.

while (( $# > 1 )); do
    var=$1
    val=$2
    shift 2
    start=$SECONDS

    mysql $database -u $user -s <<ENDSQL
SET GLOBAL $var=$val;
select ...
ENDSQL

    echo $(( $SECONDS - $start )) >> output.txt
    mysqld restart
done

If you have bash 4, I'd process the arguments like this:

declare -A vars
while (( $# > 1 )); do
    vars[$1]=$2
    shift 2
done

foreach var "${!vars[@]}"; do
    start=$SECONDS

    mysql $database -u $user -s <<ENDSQL
SET GLOBAL $var=${vars[$var]};
# ... rest is the same
done

Assuming you can set multiple sql global in one session:

sql_commands=()
while (( $# > 1 )); do
    sql_commands+=( "SET GLOBAL $1=$2;" )
    shift 2
done
sql_commands+=( "select ... from ... where ...;" )

start=$SECONDS

printf "%s\n" "${sql_commands[@]}" |  mysql $database -u $user -s

echo $(( $SECONDS - $start )) >> output.txt
mysqld restart

Comments

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.