views:

16679

answers:

7

I'm trying to write a shell script that, when run, will set some environment variables that will stay set in the caller's shell.

setenv FOO foo

in csh/tcsh, or

export FOO=foo

in sh/bash only set it during the script's execution.

I already know that

source myscript

will run the commands of the script rather than launching a new shell, and that can result in setting the "caller's" environment.

But here's the rub:

I want this script to be callable from either bash or csh. In other words, I want users of either shell to be able to run my script and have their shell's environment changed. So 'source' won't work for me, since a user running csh can't source a bash script, and a user running bash can't source a csh script.

Is there any reasonable solution that doesn't involve having to write and maintain TWO versions on the script?

+7  A: 

You're not going to be able to modify the caller's shell because it's in a different process context. When child processes inherit your shell's variables, they're inheriting copies themselves.

One thing you can do is to write a script that emits the correct commands for tcsh or sh based how it's invoked. If you're script is "setit" then do:

ln -s setit setit-sh

and

ln -s setit setit-csh

Now either directly or in an alias, you do this from sh

eval `setit-sh`

or this from csh

eval `setit-csh`

setit uses $0 to determine its output style.

This is reminescent of how people use to get the TERM environment variable set.

The advantage here is that setit is just written in whichever shell you like as in:

#!/bin/bash
arg0=$0
arg0=${arg0##*/}
for nv in \
   NAME1=VALUE1 \
   NAME2=VALUE2
do
   if [ x$arg0 = xsetit-sh ]; then
      echo 'export '$nv' ;'
   elif [ x$arg0 = xsetit-csh ]; then
      echo 'setenv '${nv%%=*}' '${nv##*=}' ;'
   fi
done

with the symbolic links given above, and the eval of the backquoted expression, this has the desired result.

To simplify invocation for csh, tcsh, or similar shells:

alias dosetit 'eval `setit-csh`'

or for sh, bash, and the like:

alias dosetit='eval `setit-sh`'

One nice thing about this is that you only have to maintain the list in one place. In theory you could even stick the list in a file and put cat nvpairfilename between "in" and "do".

This is pretty much how login shell terminal settings used to be done: a script would output statments to be executed in the login shell. An alias would generally be used to make invocation simple, as in "test vt100". As mentioned in another answer, there is also similar functionality in the INN UseNet news server.

Thomas Kammeyer
I think this might be on the right track. But I don't quite know what should be in 'setit' that will allow it to run correctly from either shell. Can you spell out a little more what you had in mind?
Larry Gritz
Basically, it would check $0 and move into the appropriate part of the script based on what name it was called with.
phresus
I think what Thomas is saying, you write the `setit` script in one language, but it then outputs a language specific set of instructions that must be `eval'd` by the calling process.
sirlancelot
Aha, I see what you are doing now. Ugh, that's clever but awkward. Thanks for clarifying.
Larry Gritz
+10  A: 

Your shell process has a copy of the parent's environment and no access the parent process's environment whatsoever. When your shell process terminates any changes you've made to its environment are lost. Sourcing a script file is the most commonly used method for configuring a shell environment, you may just want to bite the bullet and maintain one for each of the two flavors of shell.

converter42
A: 

Other than writings conditionals depending on what $SHELL/$TERM is set to, no. What's wrong with using Perl? It's pretty ubiquitous (I can't think of a single UNIX variant that doesn't have it), and it'll spare you the trouble.

phresus
How does Perl solve the problem? The Perl program still can't set the environment variables of the calling shell, can it?
Larry Gritz
No. It can, however, set it through Local::Env, then call your shell script with system() or backticks.
phresus
I'm pretty sure that system() or backticks would be making a new child shell, not calling to the shell that launched the Perl script.
Larry Gritz
A: 

Just to amplify what Thomas Kammeyer just said, the Usenet news server INN has a program (I think it's called innconfvar or something like that) that reads the config file, and depending on the first argument, spits out either csh, sh, or perl versions of the configuration. Very nifty.

Paul Tomblin
A: 

This works - it isn't what I'd use, but it 'works':

#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL -i

If the script is called "teredo", then:

% env | grep SHELL
SHELL=/bin/csh
% env | grep TEREDO
% teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
% exit # Leaves the shell that was run by 'teredo'
% # Oh, drat!
% env | grep TEREDO
% exec teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
% # exit # will log you out (or, at least, exits the shell)

The same mechanism works for Bash or Korn shell. You may find that the prompt after the exit commands appears in funny places.

Jonathan Leffler
Um, this is kind of drastic: you're replacing the login shell. If you're going to do this... you should check into how this impacts session and process group and other things. For example: what do you think happens to managed child processes?
Thomas Kammeyer
Undoubtedly - that's why I said I would not use it. If you exec twice, you've not lost session or process group information; that is based on PID and PID doesn't change. In a profile or login file, it gets you through a common language environment setting script. But, as I said, I would not use it.
Jonathan Leffler
+2  A: 

You should use modules, see http://modules.sourceforge.net/

Davide
We use modulefiles extensively here, and csh/bourne-ish support is one reason. We have legacy csh scripts, bash scripts and python scripts, and they all get environment variable settings from the same modulefiles, rather than having an env.csh, env.sh, env.py set of scripts with the extra maintenance that entails. Additionally, modulefiles allow your environment to reflect version dependencies: if you need need to change to version 3 from version 4 of a tool, instead of resetting all your env vars manually, you can just module swap and everything changes over.
mataap
A: 

You can invoke another one Bash with the different bash_profile. Also, you can create special bash_profile for using in multi-bashprofile environment.

Remember that you can use functions inside of bashprofile, and that functions will be avialable globally. for example, "function user { export USER_NAME $1 }" can set variable in runtime, for example: user olegchir && env | grep olegchir

Oleg Chiruhin
None of this will affect the calling shell.
Ignacio Vazquez-Abrams