Search code examples
regexzshglobanonymous-functionrawstring

How to pass a raw string to a variable in Zsh?


I need to fetch the value associated with the "version" key from a JSON file with jq or with grep whether the former is not installed. I'm using Zsh 5.7.1.

The JSON file looks like that;

{
    "version": "1.7.0+01e7dgc6",
    "date": "2020-04-06",
}

Thereby, the expected result is:

1.7.0+01e7dgc6

Here is my script with a conditional test who feed a regular expression which in turn is passed to an anonymous function's positional argument:

#!/usr/bin/env zsh
# Fetch silently JSON latest numeroted version with `jq` if installed; fallback to `grep` otherwise.
set -x
if dpkg -s 'jq' | grep -qF 'install ok installed'; then
    print "using jq"
    API="jq -r '.master.version'"
else
    print "falling back to grep"
    API="grep -Po '(?<="version": "\)\[\^"]*'"
fi

function {
    local JSON='path/to/URL/index.json'
    curl -fsSL "$JSON" | $@
} ${API}

The above script returns the following error (in debug mode):

+(anon):2> curl -fsSL path/to/the/URL/index.json
+(anon):2> 'jq -r '\''.master.version'\'
(anon):2: command not found: 'jq -r '.master.version'
curl: (23) Failed writing body (0 != 9320)

You can see the command is not found because the regex has been interpreted by espace sequence characters (i.e. 'jq -r '\''.master.version'\'). That is why I need raw strings to parse correctly the regex into the positional argument.

The same error rises for sure when the test fails:

falling back to grep
+dpkg.zsh:9> API='grep -Po '\''(?<=version: )[^]*'\' 
...
+(anon):2> 'grep -Po '\''(?<=version: )[^]*'\'
(anon):2: command not found: grep -Po '(?<=version: )[^]*'
curl: (23) Failed writing body (0 != 9320)

How can I correctly parse the regex by NOT escaping characters like the single quote (i.e. ') as there is no such thing as a raw string mechanism within Z Shell?

Is it possible with parameter expansion? I tried those in vain:

  • P flag (i.e. espace sequence replacement)
  • g:c (process escape sequence with concatenation option)
  • e flag (performs single word shell expansion)

or with globbing qualifiers? or perhaps with sub-string modifiers?

EDIT: You made my day. I'm grateful to both user1934428 and chepner for your kindness.


Solution

  • zsh doesn't perform word-splitting on parameter expansions by default (and relying on it would be error-prone anyway), so you are passing the single argument jq -r .master.version to your anonymous function, not 3 arguments jq, -r, and .master.version. Use an array instead.

    #!/usr/bin/env zsh
    # Fetch silently JSON latest numeroted version with `jq` if installed; fallback to `grep` otherwise.
        if dpkg -s 'jq' | grep -qF 'install ok installed'; then
            print "using jq"
            API=(jq -r '.master.version')
        else
            print "falling back to grep"
            API=(grep -Po '(?<="version": "\)\[\^"]*')
        fi
    
    function {
        local JSON='unified_resource_identifier/index.json'
        curl -fsSL "$JSON" | "$@"
    } $API  # or "${API[@]}"
    

    Further, it's simpler to use whence to see if jq is available at all, rather than checking if it was installed with dpkg specifically.

    if whence jq > /dev/null; then
      API=(jq -r '.master.version')
    else
      API=(grep -Po '(?<="version": "\)\[\^"]*')
    fi