3

I'd like to assign the contents of variables of another bash script to variables in the calling script.

Specifically, I source this file: https://projects.archlinux.org/svntogit/packages.git/plain/trunk/PKGBUILD?h=packages/firefox (and other alike files).

The file contains variables named depends, makedepends, etc.

So in my script I have multiple statements like these:

depends="$(source "/path/to/file" ; printf '%s' "${depends[@]}")"
makedepends="$(source "/path/to/file" ; printf '%s' "${makedepends[@]}")"
...

So basically, each statement starts it's own subshell which sources the file and prints the contents of just ONE variable to a variable in the parent shell.

Is there another way which involves to start just a SINGLE subshell, source the file and get the contents of specified variables of the file assigned to specified variables in the calling shell without polluting the environment of the calling shell?

------------------------------------------------------------------------

Since Mark Mann pointed out the dangers of source-ing foreign scripts, I ended up with another solution. Instead of using source to get variables of another script, one can use a multi line grep with a perl regex to grep all needed variables from the file (varName=(...),varName2="...",varname3='...',varName4=...) and eval the result:

$ grepvars='(license)|(depends)|(makedepends)|(url)|(pkgdesc)|(pkgver)'
$
$ eval $(grep -Pzo "^(${grepvars})=\([^\)\(\`]*\)|^(${grepvars})=\"[^\"\(\`]*\"|^(${grepvars})='\''[^'\'']*'\''|^(${grepvars})=[^\s;\(\`]*" /tmp/above_mentioned_file)
$ echo $url
https://www.mozilla.org/firefox/
$
$ echo ${depends[@]}
gtk3 gtk2 mozilla-common libxt startup-notification mime-types dbus-glib alsa-lib ffmpeg2.8 desktop-file-utils hicolor-icon-theme libvpx icu libevent nss hunspell sqlite ttf-font
  • 1
    You could source it in a subshell that does all the commands that require those variables. Is there a big problem with having those variables in the calling shell though? Will the mess up future steps or something? – Eric Renouf Feb 29 '16 at 23:08
  • This is a general question to improve my bash code - I treat it like this: I assume, each file I source directly in the parent shell will possibly overwrite variables in the parent shell. Also, basically all follow up code in the specific script depends on the information retrieved from the file. – MCH Feb 29 '16 at 23:11
  • That's true that they will overwrite existing variables, but I don't think your proposed approach is going to make for better scripting. If you have that much data to manage, perhaps it's time to move to a more traditional programming language instead of a shell – Eric Renouf Feb 29 '16 at 23:12
  • You may be right that this is better to be handled by python, perl, etc. :) – MCH Feb 29 '16 at 23:16
  • @MCH: No, the grep solution doesn't mitigate the problem. Imagine a line that reads url=http://example.com; rm -rf $HOME. If you want to be safe we should probably parse the file directly. – unpythonic Mar 1 '16 at 19:17
8

Use eval.

If you have your source (in /tmp/other.sh):

a=1
b=2
c=3

And you want only a portion, you can use eval to get just those items (here in /tmp/main.sh):

eval $(source /tmp/other.sh;
       echo a="$a";
       echo b="$b";)

echo a is $a "(expect 1)"
echo b is $b "(expect 2)"
echo c is $c "(expect nothing)"

And running it:

$ bash /tmp/main.sh
a is 1 (expect 1)
b is 2 (expect 2)
c is (expect nothing)

WARNING: Performing an eval or source on an untrusted script is very dangerous. You're executing a shell script, and that script can perform anything you could do yourself. WARNING

  • 3
    This only works if the values don't contain any shell special character. You can make this robust, but it requires some effort. – Gilles Feb 29 '16 at 23:29
  • Well now I'm interested! How can you make this robust --- why isn't this robust? – MCH Feb 29 '16 at 23:31
  • Are you saying, I need to escape all shell special chars before eval? – MCH Feb 29 '16 at 23:38
  • 1
    As soon as you're executing source against a foreign script, you're in very dangerous territory. Escaping the actual values matters little if the script has a rm -rf $HOME. Updating answer to reflect this. – unpythonic Feb 29 '16 at 23:45
  • You're obviously right - tbh I didn't even think of rm -rf $HOME :) So eval cat script in subshell ?!? :) – MCH Feb 29 '16 at 23:51
3

Without using eval you can still do this via process substitution if you're using bash or similar:

source <(source "/path/to/file" ; printf %s\\n "depends=${depends[*]}" "makedepends=${makedepends[*]}")

This starts a subshell and sources the file, just like your initial example, but then instead of directly printing the value and using command substitution, it prints the value formatted as an assignment and sources the output using process substitution.

This exactly answers the following part of your question:

Is there another way which involves to start just a SINGLE subshell, source the file and get the contents of specified variables of the file assigned to specified variables in the calling shell without polluting the environment of the calling shell?

But of course the dangers of sourcing foreign scripts still apply. Don't source an untrusted script, ever.

If you wrote the script yourself and you are absolutely sure it has no side effects (and produces no output) then you could use the above for your own personal computer. It's not something that should ever be in a production script. Very very big security hole.

  • Running source "/path/to/file" will still execute any commands found in that file. This is the root of the insecurity. I've amended my comments above. – unpythonic Mar 1 '16 at 21:22
  • @MarkMann, absolutely correct. I will edit my answer to emphasize that point. I just wanted to provide the alternate answer; I think source with process substitution makes more sense than eval with command substitution. Either one involves sourcing a foreign script, though. – Wildcard Mar 1 '16 at 21:29
  • Also your solution works if there is a variable with multiple words. You just need to escape " character like this: printf %s\\n "var1=\"$var1\"" – Vadim Kotov Nov 16 '17 at 15:12
1

source <(cat "/path/to/file" | grep "depends\|makedepends")

The /path/to/file is not sourced at all (vs. the approach of Wildcard’s) . See discussion about insecurity above. Also if it is, for example .env file of docker-compose or similar, it could be used in a such a way to draw variables by other scripts without executing unquoted variables, those which can cause execution errors. For example - traefik frontend rule like, WEB_FRONTEND_RULE=Host:my.host.name;PathPrefix:/my-path-prefix which is valid in docker-compose .env is not valid to be sourced by bash. And WEB_FRONTEND_RULE='Host:my.host.name;PathPrefix:/my-path-prefix' is valid to be sourced by bash, but is not valid in docker-compose .env.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.