Alex Brodov
Alex Brodov

Reputation: 3515

Bash - variables are not evaluated inside other variables

I've written a short script which needs to find some text using regex.

I'm incrementing a counter inside a while loop, and this counter is part of another command. Unfortunately this command is always running with the initial counter.

Here a snippet from my code:

COUNTER=1
LAST_COMMIT=`git log remotes/origin/devel --pretty=oneline --pretty=format:%s | head -${COUNTER}`
JIRA_ID=`echo $LAST_COMMIT | grep -o -P '[A-Z]{2,}-\d+' | xargs`

while [[ ! -z "$JIRA_ID" && $COUNTER -lt "5" ]]; do
        echo "This is the current  counter:  $COUNTER"
        echo "This is the last commit $LAST_COMMIT"
        COUNTER=$[COUNTER+1]
done
echo "this is the counter outside the loop $COUNTER"

Upvotes: 0

Views: 83

Answers (2)

Charles Duffy
Charles Duffy

Reputation: 295403

The best-practices way to encapsulate code (as per BashFAQ #50) is with a function:

get_last_commit() {
  git log remotes/origin/devel --pretty=oneline --pretty=format:%s \
    | sed -n "$(( $1 + 1)) p"
}

Then:

while (( counter < 5 )); do
  last_commit=$(get_last_commit "$counter")
  IFS=$'\n' read -r -d '' -a jira_id \
    < <(grep -o -P '[A-Z]{2,}-\d+' <<<"$last_commit") ||:
  [[ $jira_id ]] || break

  echo "This is the current  counter:  $counter"
  echo "This is the last commit $last_commit"
  echo "Found ${#jira_id[@]} jira IDs"
  printf '  %s\n' "${jira_id[@]}"

  (( counter++ ))
done

Other notes:

  • Use of read -a, here, reads the JIRA IDs into an array; you can then ask for the array's length (with ${#jira_id[@]}), expand a specific entry from the array (with ${jira_id[0]} to get the first ID, [1] for the second, etc); expand them all into an argument list (with "${jira_id[@]}"), etc.
  • Non-system-defined shell variables should have at least one lower-case character in their names. See the fourth paragraph of the POSIX spec on environment variables at http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html, keeping in mind that environment variables and shell variables share a single namespace. Following this practice prevents you from overwriting system variables by mistake.
  • $(( ... )) is the POSIX-standard way to enter a math context; (( )), without a leading $, is a bash extension.
  • While code inside of [[ ]] and (( )) does not require double quotes to prevent glob expansion or string-splitting, double quotes should be used around expansions in (almost) all other cases.
  • sed '2 p' gets line 2 more efficiently than head -2 | tail -n 1.

However, even that is much less efficient than just calling git log only one single time and iterating over its results.

while IFS= read -r -u 3 last_commit; do
  IFS=$'\n' read -r -d '' -a jira_id \
    < <(grep -o -P '[A-Z]{2,}-\d+' <<<"$last_commit") ||:
  [[ $jira_id ]] || continue
  echo "Found ${#jira_id[@]} jira IDs"
  printf '  %s\n' "${jira_id[@]}"
done 3< <(git log remotes/origin/devel --pretty=oneline --pretty=format:%s)

Upvotes: 2

that other guy
that other guy

Reputation: 123470

Bash is a procedural language, meaning it contains a series of steps to be carried out in order.

LAST_COMMIT=`...`is a step that sets the variable LAST_COMMIT to the value of the command. You have made this step only execute once, which is why you see the same value over and over.

If you want it to execute again for new values of $COUNTER, you can place the statement inside the loop:

while
    LAST_COMMIT=`git log remotes/origin/devel --pretty=oneline --pretty=format:%s | head -${COUNTER} | tail -n 1`
    JIRA_ID=`echo $LAST_COMMIT | grep -o -P '[A-Z]{2,}-\d+' | xargs`
    [[ ! -z "$JIRA_ID" && $COUNTER -lt "5" ]]
do

    echo "This is the current  counter:  $COUNTER"
    echo "This is the last commit $LAST_COMMIT"
    COUNTER=$[COUNTER+1]
done
echo "this is the counter outside the loop $COUNTER"

Since the steps in the loop are executed multiple times, the LAST_COMMIT=`...` step is also executed multiple times, each time with a new value for $COUNTER.

Upvotes: 0

Related Questions