0

I have two functions in Bash. One is a generic run function, that accepts an input and evaluates it, while printing the command, and testing the exit code. This is used in a large script to ensure each command executes successfully before continuing.

The second one is a complex function, that is doing some Git history parsing. The problematic line is the only one shown.

I am calling this function from a for-loop, that iterates over a list of terms to search. The issue is that spaces are not being handled correctly, when between other words. I have tried running my script though shell-checking websites, and all of the suggestions seem to break my code.

function run() {
        echo "> ${1}"
        eval "${1}"
        # Test exit code of the eval, and exit if non-zero        
}

function searchCommitContents() {
        run 'result=$(git log -S'"${1}"' --format=format:%H)'
        # Do something with result, which is a list of matching SHA1 hashes for the commits
        echo "${result}"
}

# Main

declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')

for i in "${searchContents[@]}"
do
    searchCommitContents "${i}"
done

Here is the output I get:

> result=$(git log -Sfoo --format=format:%H)
<results>

> result=$(git log -Sbar --format=format:%H)
<results>

> result=$(git log -S foo  --format=format:%H)
<results>

> result=$(git log -Sfoo bar  --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

I tried to add additional single and double-quotes to various areas of the code, such that the 'foo bar' string would not resolve to two different words. I also tried adding an escape to the dollar sign, like so: -s'"\${1}"' based on other questions on this site.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Alan
  • 506
  • 7
  • 24
  • 1
    You are reinventing `set -x` and digging yourself deep into awful unmaintainable code. – tripleee Jan 25 '22 at 19:58
  • `run 'result=$(git log -s'"$(printf %q "$1")"'; --format=format:%H)'` – Fravadona Jan 25 '22 at 19:58
  • @tripleee Thanks for the feedback, I am not too versed in bash, could you explain how I could use `set -x` instead of my run function? I would love to use the best-practices instead of my own. – Alan Jan 25 '22 at 21:55

2 Answers2

2

Why are you printing result=$(? It's an internal variable, it can be anything, there is no need for it in logs.

Print the command that you are executing, not the variable name.

run() {
   echo "+ $*" >&2
   "$@"
}
searchCommitContents() {
   local result
   result=$(run git log -s"${1}" --format=format:%H)
   : do stuff to "${result}"
   echo "$result"
}

issue with an input that has a space in the middle.

If you want quoted string, use printf "%q" or ${...@Q} for newer Bash, but I don't really enjoy both quoting methods and just use $*. I really like /bin/printf from GNU coreutils, but it's a separate process... while ${..@Q} is the fastest, it's (still) not enough portable for me (I have some old Bash around).

# compare
$ set -- a 'b  c' d
$ echo "+ $*" >&2
+ a b  c d
$ echo "+$(printf " %q" "$@")" >&2
+ a b\ \ c d
$ echo "+" "${@@Q}" >&2
+ 'a' 'b  c' 'd'
$ echo "+$(/bin/printf " %q" "$@")" >&2
+ a 'b  c' d
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • I wasn't sure how to extract the result of the `run` function. I tried your version instead, and things seem to be much happier--although I am getting another issue with an input that has a space in the middle. I will investigate, but will otherwise accept this answer if the issue is on my end (which it probably is). – Alan Jan 25 '22 at 21:49
  • Update: the issue was on my end, and your solution worked well for me. Thanks for the help! – Alan Jan 25 '22 at 22:00
  • updated with some quotation styles. Or could be you did not quote input arguments. Either way - check your script with shellcheck – KamilCuk Jan 25 '22 at 22:35
  • I mentioned this vaguely in the question--I did use Shellcheck, and its suggestions broke things further, although I was unable to put the entire file in it, so it may not have had the full picture of what was wrong. – Alan Jan 25 '22 at 23:31
-1

See these lines:

> result=$(git log -Sfoo bar  --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.

Specifically this: -Sfoo bar. It should be -S"foo bar" or -S "foo bar". Because to pass an argument with spaces, we need to quote the argument. But, each time the argument pass through a command/function layer, one layer of quote ('', "") is extracted. So, we need to nest the quote.

So in this line:

declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')

change 'foo bar' to '"foo bar"' or "'foo bar'" or "\"foo bar\"".

This is a case of 2 layers nested quotes. The more the layer, the trickier it gets. Here's an example of 4 layers quotes I once did.

M Imam Pratama
  • 998
  • 11
  • 26