1

I found this handy bash function for selecting and checking out a previous branch a while back. It worked like a charm. The key bits are:

  BRANCHES=(
    $(
      git reflog |
        egrep -io "moving from ([^[:space:]]+)" |
        awk '{ print $3 }' |        # extract 3rd column
        awk ' !x[$0]++' |           # Removes duplicates.  See http://stackoverflow.com/questions/11532157
        egrep -v '^[a-f0-9]{40}$' | # remove hash results
        while read line; do         # verify existence
          ([[ $CHECK_EXISTENCE = '0' ]] || git rev-parse --verify "$line" &>/dev/null) && echo "$line"
        done |
        head -n "$NUM"
    )
  )

if [[ $INTERACTIVE = '1' ]]; then PS3="Choose a branch: "

select d in "${BRANCHES[@]}"; do
  test -n "$d" && break
  echo ">>> Invalid Selection"
done

git checkout "$d"

else printf '%s\n' "${BRANCHES[@]}" fi

Our branch names are all of the form bug/... or task/....

For some reason, today it started failing. When I debugged it with set -x, I discovered that echo ${BRANCHES[@]}, which select appears to evaluate unquoted, is removing the /.

To simplify/verify the issue, I ran this:

foo=( "a/b" "c/d" )
echo $foo
# prints a b
echo ${foo[@]}
# prints a b c d
echo "${foo[@]}"
# prints a/b c/d

This issue is wreaking havoc all over my aliases/functions, as you might expect.

Strangely, while the function in the script prints:

1) master
task
2) 16658-...
task
3) 16525-...
Choose a branch:

Selecting from foo prints as expected:

select f in "${foo[@]}"; do
  echo $f
  break
done
# prompts:
# 1) a/b
# 2) c/d

so I may be wrong about the root cause, but I doubt it. Selecting 1 in the prompt above echos a b.

In fact...

foo=a/b
echo $foo
# prints a b
echo "$foo"
# prints a/b

foo="a/b" echo $foo

prints a/b

If it matters, I'm using git-bash for Windows rather than unix bash, but I can't imagine that's the issue.

Any ideas?

1 Answers1

2

When you echo something unquoted, it is subjected to what is known as split+glob. This means that what you echo will be split on any characters in the variable IFS and then treated as a glob and, if it matches anything, that glob will be expanded. The part you care about here is the split. What you describe cannot happen with the default value of IFS (which is a space, a tab and a newline). If you are seeing this behavior, then you (or something in your shell session) has set IFS to something else, most likely a /. To illustrate:

$ foo=( "a/b" "c/d" )
$ echo ${foo[@]}
a/b c/d

Now, change IFS

$ IFS='/' $ echo ${foo[@]} a b c d

Of course, properly quoting fixes it

$ echo "${foo[@]}" a/b c/d

And so does un-setting IFS

$ unset IFS $ echo ${foo[@]} a/b c/d

This is just one of the reasons why you should always quote your variables. For more on that see Security implications of forgetting to quote a variable in bash/POSIX shells and Why does my shell script choke on whitespace or other special characters?.

Note that this explains why your examples are failing, not why your original script is failing. The original has everything properly quoted as far as I can tell.

terdon
  • 242,166