コマンドのアスタリスクによってforループが拡張されるのはなぜですか?

コマンドのアスタリスクによってforループが拡張されるのはなぜですか?

実行できるスクリプトを作成したいと思います。

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または$(git …)適用)分割+グローバル演算子文字列の結果に。それは:

  1. 変数値またはコマンド出力を含む文字列を設定します(後者の場合は最後の改行を除く)。
  2. 値に応じて文字列を別のフィールドに分割IFS
  3. 各フィールドをワイルドカードパターンとして解釈し、それを一致するファイル名のリストに置き換えます。パターンが一致するファイルがない場合は、そのままにしておきます。

git branch | grep -v master出力内容(ステップ1)は* master2つのフィールドに分割(ステップ2)され*(ステップ3)、現在のディレクトリのファイル名のリストmasterに置き換えられます。*

ワイルドカードを一時的に無効にするために実行できます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

関連情報