/bin/sh source from stdin (from other program) not file

14

5

Kind of a tricky one to name this...

Basically I have a program which when run prints on STDOUT a set of shell variables:

$ ./settings
SETTING_ONE="this is setting one"
SETTING_TWO="This is the second setting"
ANOTHER_SETTING="This is another setting".

I want to run this from within a shell script as if the STDOUT were being evaluated with source.

I'd like to do something like ...

source `./settings`

... but of course that doesn't work.

I know I could do:

./settings >/tmp/file
source /tmp/file

but I really don't want to do that.

Any clues?

Majenko

Posted 2011-04-18T20:11:07.073

Reputation: 29 007

Answers

15

You can use eval:

eval "$(./settings)"

eval "`./settings`"

Keith

Posted 2011-04-18T20:11:07.073

Reputation: 7 263

$( ) doesn't work in heirloom shell (which predates POSIX). – Rufflewind – 2015-08-14T23:31:09.443

Sorry, should have mentioned, it's /bin/sh not bash. $() doesn't work. I have updated the question. – Majenko – 2011-04-18T20:19:53.293

@Matt: Well, sh is bash on most systems. Unless of course you meant recent Ubuntu versions, where it has been replaced with dash. – Hello71 – 2011-04-18T20:24:09.863

@Matt: In that case, backticks should work. But you should add the exact version of sh too - it could be a symlink to dash, ash, busybox... I have not seen a copy of "the real 'sh'" live. – user1686 – 2011-04-18T20:29:45.863

1

@Matt: So you've got an ... interesting system there. Especially since almost all "sh" variations support $( ) -- starting with Almquist's shell in 4.3BSD -- and it's POSIX too. (Note: not arguing, just curious.)

– user1686 – 2011-04-18T20:31:35.970

$() exists, it just doesn't work like that in this circumstance. FreeBSD 8.2's /bin/sh – Majenko – 2011-04-18T20:32:56.873

Well, it didn't when I tried it just now - now it does. Strange - must be having a 'moment'... – Majenko – 2011-04-18T20:34:16.563

15

On systems where /dev/fd is available, bash supports process substitution:

source <(./settings)

Here, <( ) will expand to an automatically assigned path under /dev/fd/... from which the output of ./settings can be read.

user1686

Posted 2011-04-18T20:11:07.073

Reputation: 283 655

1In the bash manual, this is called "process substitution". – glenn jackman – 2011-04-18T20:46:14.873

6

declare `./settings`

Or of course...

export `./settings`

Test it of course...

export `echo -e "asdf=test\nqwerty=dvorak"` ; echo $asdf $qwerty

Handling whitespace:

eval export `./settings`

Hello71

Posted 2011-04-18T20:11:07.073

Reputation: 7 636

Aha, the export trick works! Thanks – Majenko – 2011-04-18T20:28:07.097

Now - how can I handle spaces in a value? – Majenko – 2011-04-18T20:30:14.457

@Matt: I can't use backticks in comments, so please see edited answer. – user1686 – 2011-04-18T20:34:46.513

@grawity: Yes you can... a backtick --> \ <--and here's another one --> ` <--` – Hello71 – 2011-04-19T02:24:54.567

2

source /dev/stdin < ./settings

I think /dev/stdin is a Linux only thing though.

LawrenceC

Posted 2011-04-18T20:11:07.073

Reputation: 63 487

1I like this /dev/stdin trick, but what your answer does is in fact equivalent to a plain source ./settings without executing it. One could use a here-document to overcome this: source /dev/stdin <<EOF \n $(./settings) \n EOF. – tlwhitec – 2019-05-27T09:45:27.700

@tlwhitec, or herestring (if available): source /dev/stdn <<<"$(./settings)". But process substitution (if available) is even shorter: source <(./settings). – Sasha – 2019-08-11T08:02:32.907

that tries to source the content of settings. Even with './settings' it fails with './settings': Ambiguous (' = backtick) – Majenko – 2011-04-18T20:18:30.423

/dev/stdin works on BSD and Cygwin, too. – user1686 – 2011-04-18T20:20:29.333

1Using |, however, is not going to work (at least not exactly), because both sides of the pipe are separate subprocesses, so sourced commands would not affect the current shell. – user1686 – 2011-04-18T20:22:55.870

1edited to reflect that. – LawrenceC – 2011-04-18T20:46:51.430

0

Wanted to provide another perspective here, as the other answers create files and don't directly pull from stdin. I have some builds that need to send some prepared environment information to multiple scripts. What I'm doing is preparing a bunch of Bash compatible variable assignments in a string:

Var1="Foo"
Var2="Bar"
Var3="Baz"

When I'm preparing to execute the script, I base64 encode the above multiline string and pipe it into my shell script:

echo base64EncodedParameters | build.sh

In build.sh I read from stdin, base64 decode and eval the result.

params=""
while read line; do params="${params}${line}"; done
eval `echo $params | base64 -D`

echo "Hello ${Var1} ${Var2}"

Jay Proulx

Posted 2011-04-18T20:11:07.073

Reputation: 1