I'm trying to create a macro on my shell.

The operation I'm trying to automate is this one:

Find all the files containing TEXT_TO_SEARCH, and open them with VSCODE

I can do this with a one-liner

$ code `git grep TEXT_TO_SEARCH | cut -d: -f1 | sort -u`

so my first step was adding the following function to ~/.zshrc

cgrep() {
  code `git grep "$1" | cut -d: -f1 | sort -u`

and then


This works.

Now I'm trying to add an extra feature:

Before opening each file with VSCode, echo to the console "Opening FILE_NAME"

First I've tryied this

cgrep() {
  grep_results=`git grep "$1" | cut -d: -f1 | sort -u`
  for file in $grep_results; do
    echo "Opening $file"
    code $file

Note that grep_results is a "vertical list" i.e.

$ echo $grep_results


This way the for loop considers the first file as the whole grep_results and it opens FILE3.py (not src/path3/FILE3.py).

I've also tryied this (with the help of GPT)

cgrep() {
  grep_results=`git grep "$1" | cut -d: -f1 | sort -u`
  echo "$grep_results" | while read -r file; do
    echo "Opening $file"
    code "$file"

This way I can open just the first grepped file, and I get a message I don't want from VSCode and I don't actually understand


Opening src/path1/FILE1.py
Run with 'code -' to read from stdin (e.g. 'ps aux | grep code | code -').

1 Answer 1


git grep TEXT_TO_SEARCH | cut -d: -f1 | sort -u

You can simplify this to

git grep -l TEXT_TO_SEARCH

You don't need the sorting step since the list won't include the same file name multiple times, and git grep (unlike grep -r) produces a sorted list in practice.

grep_results=`git grep …`

You're putting a list of file names in a string. Use an array instead. The input is a newline-separated list, so tell zsh that (by default it would treat spaces, tabs and nuls as separators as well, which would break with file names containing spaces or tabs), using the f parameter expansion flag.

grep_results=( ${(f)"$(git grep -l -- TEXT_TO_SEARCH)"} )

You can make that code work with newlines in file names (which may or may not matter in your case). Tell git grep to use null bytes to separate list elements, and zsh to split the output at null bytes.

grep_results=( ${(0)"$(git grep -lz -- TEXT_TO_SEARCH)"} )

Now that you have the file names in an array, just iterate over the array.

for file in $grep_results; do
  print -r Opening $file
  code -- $file

I get a message I don't want from VSCode and I don't actually understand

At a guess, code emits this message when it notices that its standard input is a pipe but it sees a file name on its command line. If that's the case, you could get rid of it by running code </dev/null "$file", so that code's standard input is not a pipe.

  • Yup, your guess in the last paragraph is correct. Dec 12, 2023 at 16:28
  • My fault 😅 Thank you @Gilles
    – chc
    Dec 12, 2023 at 22:02
  • Just one tiny correction I had to make for file in "${grep_results[@]}";. Again, thank you very much
    – chc
    Dec 12, 2023 at 22:18
  • @chc for file in $grep_results is equivalent and simpler. It doesn't work in bash, only in zsh, but my whole answer is about zsh, not bash. Dec 13, 2023 at 10:21
  • Strictly speaking $grep_results is not the same as "${grep_result[@]}" in that it discards empty elements (which here is probably a good thing). Empty elements cannot occur here as ${(f)"$(...)"} or ${(0)"$(...)"} would have discarded empty records already. Dec 14, 2023 at 18:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .