Home > Enterprise >  Passing string with a space between words through various function layers
Passing string with a space between words through various function layers

Time:01-26

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.

CodePudding user response:

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

CodePudding user response:

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.

  •  Tags:  
  • Related