Unquoted variables and command substitutions like `$i` or `$(git …)` apply the [split+glob operator](https://unix.stackexchange.com/questions/78914/quoted-vs-unquoted-string-expansion/78924#78924) to the string result. That is:

 1. Build a string containing the value of the variable or the output of the command (minus final newlines in the latter case).
 2. Split the string into separate fields according to the value of [`IFS`](http://en.wikipedia.org/wiki/Internal_field_separator).
 3. Interpret each field as a wildcard pattern and replace it by the list of matching file names; if a pattern doesn't match any file, leave it intact.

The output of `git branch | grep -v master` (step 1) contains `* master` which is split (step 2) into two fields `*` and `master`; `*` is replaced (step 3) by the list of file names in the current directory.

You can run `set -f` to temporarily disable globbing. Another approach is to avoid command substitution and instead [parse the input with `read`](https://unix.stackexchange.com/a/150939). Neither do the right thing though — you'll have spurious branch names, because the git output contains more than what you need (the `*` flag to indicate the current branch, but also things like `  remotes/somewhere/foo -> bar` for remote-tracking branches). I think the following is safe if inelegant:


    for i in $(git branch | sed -e 's/^..//' -e 's/ .*//'); do
       echo $i
    done

I think the robust way is to use [`git-for-each-ref`](https://www.kernel.org/pub/software/scm/git/docs/git-for-each-ref.html).

    for i in $(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes); do
      echo $i
    done