Score:11

Why is `$(git branch)` outputting all files' names inside folder (besides git branches)?

dj flag

I wrote a command to delete all git branches without needing to write them all mannually, which is currently like this:

git branch -d $(git branch)

It does what I want, but also tries to delete branches named as files in current directory, so the output is something like this:

error: branch 'angular.json' not found.
error: branch 'assembly.xml' not found.
error: branch 'ci-settings.xml' not found.
error: branch 'coverage' not found.
error: branch 'cypress.json' not found.
error: branch 'Dockerfile.angular' not found.
error: branch 'Dockerfile.spring' not found.
error: branch 'e2e' not found.
error: branch 'examples' not found.
error: branch 'mvnw' not found.
error: branch 'mvnw.cmd' not found.
error: branch 'nginx' not found.
error: branch 'node' not found.
error: branch 'node_modules' not found.
error: branch 'package.json' not found.
error: branch 'package-lock.json' not found.
error: branch 'pom.xml' not found.
error: branch 'README.md' not found.
error: branch 'scripts' not found.
error: branch 'serve-proxy.conf.json' not found.
error: branch 'src' not found.
error: branch 'target' not found.
error: branch 'to-do.txt' not found.
error: branch 'tsconfig.json' not found.
error: branch 'web.config' not found.
error: Cannot delete branch 'dev' checked out at 'D:/Documentos/oreons/Rise/archivekeeper-ui'
Deleted branch test (was 729628a).

If I echo $(git branch), the output is like this:

$ echo $(git branch)
angular.json assembly.xml ci-settings.xml coverage cypress.json Dockerfile.angular Dockerfile.spring e2e examples mvnw mvnw.cmd nginx node node_modules package.json package-lock.json pom.xml README.md scripts serve-proxy.conf.json src target to-do.txt tsconfig.json web.config dev

Which explains the command's output. But why does $(git branch) outputs all these file names besides the git branches?

hr flag
IIRC `git branch` marks the current branch with an asterisk - the unquoted expansion `$(git branch)` will then be subject to filename generation (globbing), turning the `*` into a list of non-hidden filenames in the current directory
Score:19
jp flag
Dan

As @steeldriver mentions in the above comment, git branch has an asterisk (*) in its output to mark the currently checked-out branch.

$ git branch
* main
  test

When running it as $(git branch), that asterisk in the output is expanded to all non-hidden files and directories in the directory you're currently in.

To remove the asterisk, you can format the output by passing the --format option by only showing the short format of the refnames.

$ git branch --format='%(refname:short)'
main
test

So the command you want to use would be the following.

git branch -d $(git branch --format='%(refname:short)')
MEMark avatar
cn flag
Mind blown! Good explanation.
in flag
And the moral is - always examine the output of a command _first_ before using it as input to something else, especially something destructive
Score:7
nl flag

Don't use git branch in scripts. Instead, use the low-level commands such as git for-each-ref:

git for-each-ref --shell --format='%(refname:short)' refs/heads | \
while read branch; do git branch -d "$branch"; done

or

git for-each-ref --shell --format='%(refname:short)' refs/heads | \
xargs git branch -d

This avoids the problem of the command from the question, because it correctly quotes parameter expansions. Not quoting a parameter expansion (or command substitution) will perform pathname expansion on the expanded value.

Since git branch is a user-facing command, it marks the current branch with a *. And as you might know, * as a globbing character/wildcard, which resolves to any 0 or more characters. When executing echo *, your shell first expands and replaces * with all (non-hidden) files in your current directory, then passes this list of files as parameters to echo.

Conclusion: always quote "dollar-expressions" (not a real term, I am talking about parameter expansions and command substitutions) to prevent field splitting and pathname expansion – unless you have very good reasons to not do so (and know what you are doing)

ng flag
Downvoted as this does not answer the OP's question of *why* they're getting the behavior they're seeing.
hr flag
This is a more robust solution than the currently accepted one, I think
jp flag
Dan
@jwodder I disagree with the downvote. `git branch` internally uses `git for-each-ref`, and `for-each-ref` is better and safer to use in scripts. My answer directly answers OP's question, but this answer is more correct as a "permanent" solution.
Gabriel Bergoc avatar
dj flag
Nice! I didn't know I could prevent this expansion with surrounding the expression with double-quotes.
Score:1
eg flag

I use this in a shell function for essentially the same purpose, though it leaves main, master, and the current branch (marked with the asterisk as discussed in the older answer and comment). You may find it useful.

git branch |
    grep -v '^\*' |
    grep -oE '[^ ]+' |
    grep -vE '^(master|main)$' |
    while read branch_name; do
        git branch -D $branch_name
    done
ng flag
Downvoted as this does not answer the OP's question of *why* they're getting the behavior they're seeing.
Matthew Read avatar
eg flag
That was already answered in perfect detail. I'm not going to repeat that uselessly, I'm going to help OP further.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.