views:

63

answers:

4

I have two shell scripts that I'd like to invoke from a C program. I would like shell variables set in the first script to be visible in the second. Here's what it would look like:

a.sh:

var=blah
<save vars>

b.sh:

<restore vars>
echo $var

The best I've come up with so far is a variant on "set > /tmp/vars" to save the variables and "eval $(cat /tmp/vars)" to restore them. The "eval" chokes when it tries to restore a read-only variable, so I need to grep those out. A list of these variables is available via "declare -r". But there are some vars which don't show up in this list, yet still can't be set in eval, e.g. BASH_ARGC. So I need to grep those out, too.

At this point, my solution feels very brittle and error-prone, and I'm not sure how portable it is. Is there a better way to do this?

A: 

If it's possible for a.sh to call b.sh, it will carry over if they're exported. Or having a parent set all the values necessary and then call both. That's the most secure and sure method I can think of.

Not sure if it's accepted dogma, but:

bash -c 'export foo=bar; env > xxxx'
env `cat xxxx` otherscript.sh

The otherscript will have the env printed to xxxx ...

Update:

Also note:

man execle

On how to set environment variables for another system call from within C, if you need to do that. And:

man getenv

and http://www.crasseux.com/books/ctutorial/Environment-variables.html

eruciform
I need to do some work in C between the execution of a.sh and b.sh, so it's not possible for a to call b. Note that I'm interested in shell variables, not environment variables.
danvk
ah, sorry about that!
eruciform
+1  A: 

If you can use a common prefix on your variable names, here is one way to do it:

# save the variables
yourprefix_width=1200
yourprefix_height=2150
yourprefix_length=1975
yourprefix_material=gravel
yourprefix_customer_array=("Acme Plumbing" "123 Main" "Anytown")
declare -p $(echo ${!yourprefix@}) > varfile

# load the variables
while read -r line
do
    if [[ $line == declare\ * ]]
    then
        eval "$line"
    fi
done < varfile

Of course, your prefix will be shorter. You could do further validation upon loading the variables to make sure that the variable names conform to your naming scheme.

The advantage of using declare is that it is more secure than just using eval by itself.

If you need to, you can filter out variables that are marked as readonly or select variables that are marked for export.

Other commands of interest (some may vary by Bash version):

  • export - without arguments, lists all exported variables using a declare format
  • declare -px - same as the previous command
  • declare -pr - lists readonly variables
Dennis Williamson
The two scripts are user-provided, so requiring a common prefix would be a bit awkward.On the other hand, I could just check for variables which have been modified by each script. Going to throw this idea in an answer so I can get formatting...
danvk
A: 

One way to avoid setting problematic variables is by storing only those which have changed during the execution of each script. For example,

a.sh:

set > /tmp/pre
foo=bar
set > /tmp/post
grep -v -F -f/tmp/pre /tmp/post > /tmp/vars

b.sh:

eval $(cat /tmp/vars)
echo $foo

/tmp/vars contains this:

PIPESTATUS=([0]="0")
_=
foo=bar

Evidently evaling the first two lines has no adverse effect.

danvk
`set` will include function definitions, `declare -p` won't (`declare -f` will). Be aware of the [security risks](http://mywiki.wooledge.org/BashFAQ/048) of `eval` with unvalidated or unprotected (by `declare` for example) input.
Dennis Williamson
A: 

An alternative to saving and restoring shell state would be to make the C program and the shell program work in parallel: the C program starts the shell program, which runs a.sh, then notifies the C program (perhaps passing some information it's learned from executing a.sh), and when the C program is ready for more it tells the shell program to run b.sh. The shell program would look like this:

. a.sh
echo "information gleaned from a"
arguments_for_b=$(read -r)
. b.sh

And the general structure of the C program would be:

  • set up two pairs of pipes, one for C->shell and one for shell->C
  • fork, exec the shell wrapper
  • read information gleaned from a on the shell->C pipe
  • more processing
  • write arguments for b on the C->shell pipe
  • wait for child process to end
Gilles