Search code examples
shellenvironment-variableszsh

Zsh export ignoring quotes and backslashes


I have a shell script (let's call it produce.sh) which outputs data in the following form, but does not save it to a file:

FOO=value
BAR=value2
ZAP=value3

I'd like to use these values as environment variables in a shell script. I'm currently doing this using the following shell code:

export $(./produce.sh)

This works great, except when the values to the right of the = contain spaces. For instance:

FOO=split value

I've tried two different approaches in produce.sh:

  1. Wrapping the values in quotes (FOO="split value")
  2. Escaping whitespace with backslashes (FOO=split\ value)

Both of these do not work: if I inspect the environment variables, FOO contains "split in the first example and split\ in the second.

How can I get export to handle this correctly?


Solution

  • The f parameter expansion flag in zsh will split an input on newlines, so this should handle input values with whitespace:

    export ${(f)"$(./produce.sh)"}
    

    What's happening

    The output from produce.sh:

    • key-value pairs.
    • each kv pair is on its own line.
    • key is separated from the value by =.
    • syntax is shell-like, but not exactly shell syntax, so,
    • white space and some other characters special to the shell are allowed in the value.

    The parts of the substitution:

    • produce.sh : generates the key-value output, for example: N=V1\nP=V 2\n.
    • $(...) : command substitution. It is replaced with the output, minus trailing newlines: N=V1\nP=V 2.
    • "..." : the quotes ensure that the prior result is treated as a single term for the next step.
    • ${(f)...} : expands that single term into multiple scalar values, splitting on newlines due to the (f) flag. The result is effectively 'N=V1' 'P=V 2'.
    • export : assigns and exports each parameter, acting like export 'N=V1' 'P=V 2'.

    Another option

    The substitution below adds some other cryptic zsh-isms to create an associative array. This avoids adding arbitrary variables to the consuming shell's environment:

    % ./s2.sh
    A=A
    B=
    C=C C
    % typeset -A vals=("${(@s:=:)${(f)"$(./s2.sh)"}}")
    % print ${vals[A]}
    A
    % print ${vals[C]}
    C C
    

    A minor tradeoff - this will not work if the value contains an equals, e.g. D=D=D.