1

I seem to be having issues with some shell scripting (ZSH in particular), where the shell fails to understand exports with spaces in them. The problem only happens when `runcommand` or $(runcommand) is used.

Test case:

# Works fine:
export test1="a b c"

# Does not work:
$(echo 'export test2="a b c"')

The error is something along the lines of export:4: not valid in this context: c". Adding 1-2 \-escapes before the spaces and/or the quotes might change the error message to export:4: not valid in this context: b\.

Might anyone have insight into what the problem is? Thank you.

(The reason I am doing this is a hack to allow python to set shell variables by dynamically generating code which gets run in .myshellrc; if anyone knows of a more elegant way to do this, that would be quite welcome as a comment. Thank you.) edit: To clarify, I am looking for a way to make the above work, or an alternative way to allow an external script to dictate what to export.

(*sigh*, I hope this isn't a version-specific issue in 4.3.12... I think this may have worked in the past.)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145

3 Answers3

3

The issue here is that work splitting is done to the output before it is run as a command. What you end up with is export being called with 3 arguments - test1="a, b, and c".

Using eval as other answers have mentioned is one way to get around it.

An altenative solution would be to use process substitution. This is especially useful if your script is generating multiple lines of code.

Example:

zsh% source =(echo export test1=\"a b c\")
zsh% echo $test1                          
a b c

p.s. You might want to use the <(...) syntax instead of =(...) which will allow it to also work in bash.

Shawn Chin
  • 84,080
  • 19
  • 162
  • 191
  • Ah, fascinating, thank you. Sadly I can only accept one answer; which method do you think is cleaner in general? Do they have any relative advantages? – ninjagecko Apr 20 '12 at 12:49
  • 1
    In this context, they both work just as well. I'm biased towards process substitution as they also come in handy in many situations (e.g. http://stackoverflow.com/questions/7651946/bash-coproc-and-leftover-coproc-output/7653550#7653550), however to many it is an unfamiliar concept and may require additional comments if you want to make it accessible to other readers. – Shawn Chin Apr 20 '12 at 13:02
  • Ah I think I see. In the case of `eval "$(somescript)"`, we are doing `eval "multipleLinesOfShellCodeToEmbedWhichWereRegurgitatedBySomescript"`; in the other case of `source =(somescript)`, we are still running somescript, but instead of dumping the code into the file, we are creating a temporary anonymous file, and sourcing the file (which may or may not have similar, or exactly the same, semantics). – ninjagecko Apr 20 '12 at 13:36
  • Yup, that sounds about right (but usually a named pipe is used instead of an actual temp file). `source` is used so that the contained commands are run within the same bash process and should therefore give you the same result as `eval`. – Shawn Chin Apr 20 '12 at 14:11
  • 1
    **Correction:** there are subtle differences when using `source`, for example when calling `exit` which terminates the processing of the script but not the parent shell. One can also provide additional args to `source` to be used as positional arguments for the sourced script. Consider `source <(echo "echo \$2") hello world`. – Shawn Chin Apr 20 '12 at 14:20
2

Take a look at this. You can try:

eval 'export test2="a b c"'
kev
  • 155,172
  • 47
  • 273
  • 272
  • Unfortunately this does not seem to use another command. Is it possible to adapt this solution to use the output of another script which generates multiple lines of code, rather than `'export test2="a b c"'`? Sorry, I mentioned my goal in the question; I will attempt to clarify it in an edit. – ninjagecko Apr 20 '12 at 12:15
  • Ah, the simple extension eval '$(...)' does indeed seem to work, thank you. – ninjagecko Apr 20 '12 at 12:21
2

The usual way to do this is to use eval as kev has said and pass it the full output of the program in question. The program should then produce valid shell code itself:

eval "$(your_script.py)"

And your_script.py would print something like export var1="a b c" (yes, using parenthesis in the output is OK).

An example of a program using this technique is rbenv. The user has to put eval "$(rbenv init -)" in his shell init file, and rbenv init actually outputs quite a lot of code:

export PATH="/opt/rbenv/shims:${PATH}"
source "/opt/rbenv/libexec/../completions/rbenv.zsh"
rbenv rehash 2>/dev/null
rbenv() {
  local command="$1"
  if [ "$#" -gt 0 ]; then
    shift
  fi

  case "$command" in
  shell)
    eval `rbenv "sh-$command" "$@"`;;
  *)
    command rbenv "$command" "$@";;
  esac
}
Moritz Bunkus
  • 11,592
  • 3
  • 37
  • 49
  • Accepting with the caveat that people should also check out ShawnChin's excellent answer http://stackoverflow.com/a/10246161/711085 of `source =(scriptname) [arg0 arg1 ...]` – ninjagecko Apr 21 '12 at 09:40