0

I am trying to write a code that runs a script on a list of files determined based on users input. for some reason the following code doesn't work? is there any way to do evalute the query_cmd and iterate over the files it outputs.

if [[ $# -gt 0 && "$1" == "--diff" ]]; then
  query_cmd="git diff --name-only '*.cc' '*.h'"
else
  query_cmd='find . \( -name "*.cc" -o -name "*.h" \)'
fi

while IFS='' read -r line; do
  absolute_filepath=$(realpath "$line")
  if [[ $absolute_filepath =~ $ignore_list ]]; then
     continue
  fi
  cpp_filepaths+=("$absolute_filepath")
done < <($query_cmd)
8
  • $(..) already evaluates it. Try echo "$query_cmd" Commented Mar 22, 2021 at 23:36
  • @thatotherguy sorry, I had a typo in the question. Commented Mar 22, 2021 at 23:37
  • 1
    Storing complex commands in variables doesn't work. Either use an array instead, or just put the entire if block inside the < <(...) expression. See "Why does shell ignore quoting characters in arguments passed to it through variables?" and BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail! Commented Mar 22, 2021 at 23:43
  • 1
    @MrR, no, it won't work in the OP's case for the reason I described. Having the literal text '*.cc' in the $query_cmd string means the command needs quotes to be honored when it's parsed. When they're not parsed, they're instead treated as literal characters. Commented Mar 23, 2021 at 0:00
  • 1
    git expects a command line of the form ["git", "diff", "--name-only", "*.cc", "*.h"] (JSON-escaped), which is what a shell will do when it parses and executes git diff --name-only '*.cc' '*.h' as code. Running $query_cmd will instead run ["git", "diff", "--name-only", "'*.cc'", "'*.h'"], adding literal single quotes that in a normally-parsed command would be removed by the shell. Commented Mar 23, 2021 at 0:04

1 Answer 1

2

Generally if you have code you want to run later, you would put it in a function and not in a string:

if [[ $# -gt 0 && "$1" == "--diff" ]]; then
  query_cmd() {
    git diff --name-only '*.cc' '*.h'
  }
else
  query_cmd() {
    find . \( -name "*.cc" -o -name "*.h" \)
  }
fi

while IFS='' read -r line; do
  ...
done < <(query_cmd)

But if you enjoy the additional escaping challenges, you can use strings and evaluate them with eval:

if [[ $# -gt 0 && "$1" == "--diff" ]]; then
  query_cmd="git diff --name-only '*.cc' '*.h'"
else
  query_cmd='find . \( -name "*.cc" -o -name "*.h" \)'
fi

while IFS='' read -r line; do
  ...
done < <(eval "$query_cmd")
Sign up to request clarification or add additional context in comments.

8 Comments

I'd consider showcasing use of arrays to store command lines long before demonstrating eval. (Be happy to edit towards that end myself, if it'd be welcome).
I don't know... I included the (arguably) best solution for the problem OP should have asked, and for the literal question they did ask. Arrays seem to be the worst of both worlds in this specific case
And can do the function method with the if in the function too rather than having 2 variants ..
@thatotherguy, re: arrays-as-worst-of-both-worlds, how so? query_cmd=( git diff --name-only '*.cc' '*.h' ) and then < <("${query_cmd[@]}")... what's unclean? You don't have the extra escaping challenges, and you can modify your commands use variables in their arguments without introducing security bugs.
...similarly, in the other side of the if, query_cmd=( find . '(' -name '*.cc' -o -name '*.h' ')' ) -- same escaping that one would use running that find command on a command line.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.