![为什么命令中的星号会导致for循环扩展?](https://linux22.com/image/54297/%E4%B8%BA%E4%BB%80%E4%B9%88%E5%91%BD%E4%BB%A4%E4%B8%AD%E7%9A%84%E6%98%9F%E5%8F%B7%E4%BC%9A%E5%AF%BC%E8%87%B4for%E5%BE%AA%E7%8E%AF%E6%89%A9%E5%B1%95%EF%BC%9F.png)
我想写一个可以运行的脚本
git diff --name-status master..<BRANCH>
但是当我运行这个时:
for i in $(git branch | grep -v master); do
echo $i;
done
我得到回显一个目录,因为git branch
回显星号(我在当前目录中有一个目录)
* <SELECTED BRANCH>
为什么*
会扩展以及如何防止扩展?
更新:我可以通过使用以下方法来防止这种情况:
for i in $(git branch | tr -d '*' | grep -v master);
done;
但为什么会出现这种情况呢?为什么我需要删除星号?
答案1
不带引号的变量和命令替换,例如$i
or$(git …)
应用分割+全局运算符到字符串结果。那是:
- 构建一个包含变量值或命令输出的字符串(在后一种情况下减去最后的换行符)。
- 根据值将字符串拆分为单独的字段
IFS
。 - 将每个字段解释为通配符模式,并将其替换为匹配文件名列表;如果模式与任何文件都不匹配,则保持原样。
git branch | grep -v master
(步骤 1)的输出包含* master
被分割(步骤 2)为两个字段*
和master
;*
被当前目录中的文件名列表替换(步骤 3)。
您可以运行set -f
暂时禁用通配符。另一种方法是避免命令替换,而是解析输入read
。但两者都没有做正确的事情——你会得到虚假的分支名称,因为 git 输出包含的内容超出了你的需要(*
指示当前分支的标志,还有remotes/somewhere/foo -> bar
远程跟踪分支之类的东西)。我认为以下内容如果不优雅的话是安全的:
for i in $(git branch | sed -e 's/^..//' -e 's/ .*//'); do
echo $i
done
我认为最可靠的方法是使用git-for-each-ref
。
for i in $(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes); do
echo $i
done
答案2
像这样的文本流应该使用循环来读取while
,而不是for
,这可能是导致问题的原因(尽管我无法重现它)。一个简单的方法可以做到这一点:
git branch | while IFS= read -r line
do
echo "${line:2}"
done
比较:
$ cd -- "$(mktemp -d)"
$ git init
Initialized empty Git repository in /tmp/tmp.MJFmu7q7EH/.git/
$ touch README
$ git commit --allow-empty -m "Initial commit"
[master (root-commit) da46b03] Initial commit
$ git branch foo
$ git branch
foo
* master
$ for i in $(git branch)
> do
> echo $i
> done
foo
*
master
$ git branch | while IFS= read -r line
> do
> echo "${line:2}"
> done
foo
master