views:

442

answers:

7

I write shell scripts to do various things in OSX and Linux, but I always have the same problem. The script runs but any changes it makes to the environment (except the disks, of course) do not "stick." When the script terminates the changes revert.

How can I make my changes stick?

Edit: A lot of the answers have been great, but they haven't helped the situation I'm facing tonight. I'm trying to run this to get my TAP interface working

sudo bash # we go superuser
exec 4<>/dev/tap0  # opens device, creates interface tap0
ifconfig tap0 10.10.10.1 10.10.10.255
ifconfig tap0 up

which works wonderfully if I paste it in, but not if I source it or sh it. BTW, I cannot close the terminal window afterwards as then I lose the tap0 interface.

Last Edit: Thanks everybody, the answer was to use source as you all suggested but to remove the sudo command from the script itself.

+2  A: 

Do you mean the 'environment' as in environment variables? You can't, not the way you mean. That 'environment' is a process context thing; it's not global to your system, it's a part of the specific execution environment of the script. It can be handed off to processes the script starts, but that's it. If you want to set up reliable settings for environment variables, you do it in system shared initialization files like /etc/profile and /etc/bash.bashrc (actual names OS and install dependent). Different init scripts are run under different circumstances (login shells vs. spawned shells, things like that), but the idea is that you pre-initialize shells with whatever environment context you want to always have around.

chaos
thanks. I've done an edit to the question which shows the thing I'm trying to run. I don't think it can be preinitialized (nor is it serious server software).
Yar
+2  A: 

If you are setting environment variables in your script, they will only stick during the run of your script.

If you need environment variables for your entire shell session, you need to set them in your .profile, .bashrc, .tcshrc, or the .rc file for whatever shell you're using. See "man <your shell>" (e.g. man bash) for examples on how to do this.

Andy White
Thanks. I edited the question to show what I'm trying to do. I don't think I can put it in a shell script.
Yar
+3  A: 

That behavior is by design - at least in bash, which is what I use, but I think most shells work the same way. In general, there's no way for a process to export things like environment variables to its parent process. What you can do, if you want to run a series of commands to change the environment in the current shell process, is write the commands in a file and source it. In bash you'd type

$ source commands.sh

or

$ . commands.sh
David Zaslavsky
Excellent. I didn't know this, and in most cases it would help, but not tonight: see the question (revised) above. Thanks!
Yar
Thanks, got it now, the sudo inside the script was causing problems with using source.
Yar
+3  A: 

Changes to the environment in a child process do not affect the parent process. Luckily you can run a shell script in the current process by saying

. /path/to/script.sh

or

source /path/to/script.sh

Be careful though, if you run exit in the script it will exit the current shell.

Chas. Owens
Right. Or if I run sudo it will also exit THAT sudo command from within the shell. Thanks.
Yar
+1  A: 

That's by design.

You can write your desired "sticky" environment variables out to a file and read it in on the next run. For example:

#!/bin/sh

# load from ~/.my_env
[ -r ~/.my_env ] && . ~/.my_env

# replace or add new entries to ~/.my_env
for i in "$@"; do
    [ "${i/=/}" = "$i" ] && continue
    touch ~/.my_env
    sed -i -e "\|^export ${i%%=*}=|d" ~/.my_env
    echo "export $i" >> ~/.my_env
    eval export "$i"
done
ephemient
Excuse my ignorance, but who would read it in on the next run? Are we talking about writing a .my_env file? Will the shell pick that up on launch?
Yar
No, only this shell script which explicitly sources ~/.my_env. It wasn't clear (to me) from the question that you wanted anything else.
ephemient
+1  A: 

Your real question appears to be only about the tap interface. You should change the question to reflect that.

Read the documentation about tap from Linux's Documentation/networking/tuntap.txt. Here is a short quote:

"In order to use the driver a program has to open /dev/net/tun and issue a corresponding ioctl() to register a network device with the kernel. A network device will appear as tunXX or tapXX, depending on the options chosen. When the program closes the file descriptor, the network device and all corresponding routes will disappear."

Pretty straightforward I would say. Nothing mysterious about it. Your script opens the tap interface and when the script exits it closes the file descriptor.

Zan Lynx
Thanks for your helpful answer. The real question IS about shell scripting, not about the tap interface: how would I get a file descriptor to be persistent beyond the shell script? Or is it impossible (which is fine too).
Yar
+1  A: 

When you sudo, you must understand that you are running another program, another shell in this case. It is a misunderstanding that pasting those commands on the terminal would be the same as putting them on a script. What happens is that after sudo, the terminal is connected to another shell session, and the second and further commands are sent to this other shell session. If you put all commands on a script, all four commands are executed on the same shell session, giving undesirable results.

In this specific case, I suggest you to put the three last commands on another script (e.g. setup-tap.sh), then call that script as

sudo bash setup-tap.sh

Sourcing, as suggested, is an alternative to persist changes of an invocation of a script in the current shell, but it wouldn't work in this case. It is one level above, it may look for you as a script, since the terminal is the same, but what happens is that after the sudo, you aren't in the same shell anymore.

It is like to make a script like this:

vi file.txt
iThe quick brown fox jumps over the lazy dog.<ESC>:wq

Of course, the second line (vi commands) won't be typed in vi, it will still be in the original script.

Another option would be to use here-documents, and pass the privileged commands to sudo bash from standard input:

sudo bash <<-EOF
  exec 4<>/dev/tap0
  ifconfig tap0 10.10.10.1 10.10.10.255
  ifconfig tap0 up
EOF
Juliano
Thanks. I did not know about here-documents, and while they don't work in this case (at least in my tests: the tap interface gets closed when the here-document is done running), they were a good find for me.
Yar
You are welcome. About the tap interface, that is intended behavior. A tap interface usually is attached to a daemon which will control it. It is unusual to have a shell script for this.
Juliano