How to overwrite environment variables in mac using Shell Script - bash

I have a set of environment variables that need to be set on the basis of the arguments specified in the shell script.
But the problem is that those variables are already defined in the bash profile
FOR EXAMPLE:
bash_profile has a variable called "KARAN":
export KARAN=/config/1
Now on running the shell script, this is what it should do:
export KARAN=/config/2 (Changed the bash profile's KARAN value to 2)

Your question is not clear. If your script needs to set the env var to a specific value just do so using export VAR=val. What I think you're asking is how to have a script modify the environment of the current shell. And that is impossible without the cooperation of both shells. That is because environment vars are inherited by child processes. But a child process cannot directly modify the environment of its parent process (or some other random process for that matter). To do so the two processes must coordinate the exchange of data. This is typically done by using the source command if the child process is a shell script. Or by having the child process write a series of export statements to stdout and having the parent shell capture and execute those statements. For example, let's say I have a script named set_env that looks like this
#!/bin/sh
echo export KARAN=/config_2
echo export VAR2=val2
The current shell would then do
eval $(set_env)
Note, however, eval is dangerous. I prefer to do this which is slightly safer:
set_env | source /dev/stdin
That, however, only works in shells like ksh and zsh. Due to how bash handles pipelines the source is actually executed in a child shell and therefore the vars won't be set in the current shell.

You can create a new Profile with all the new definitions. and then call the line below on top of your shell script. Similarly, you can create as many profiles as you want and use it.
source bash_profile_new

Related

Access local shell variables in vim

In vim I can access my bash environment variables such as $PWD and $PATH. I would like to know how to access my temporary shell variables in vim too.
For example, suppose I was in my terminal and define a variable foo="bar". Then I enter vim and try to access this variable with the following command :!echo $foo, but it does not recognize this variable. From my understanding, vim starts a new shell each time a bash command is invoked and then closes it immediately after. Is there a way to use the same shell in vim that my local variable foo was defined in?
No, you can't interact with the parent shell from a subprocess it spawned (without that shell's active participation, which isn't reasonably/practically available in the scenario at hand) -- but you can export your variables to make them accessible to new shells started in child processes.
Running
set -a
...will make any variable defined going forward be automatically exported to the environment, even without an explicit export command.
Since (unlike the C system() function) vim's system() honors the SHELL environment variable, if SHELL=/bin/bash (or :set shell=/bin/bash has been run in vim), you can also invoke exported functions from vim. That is, if you define the function and export it as follows:
foo() { echo "bar"; }
export -f foo
...then you can invoke it with !foo from inside vim.
Even then, however, this is running in a new, transient shell instance, not the original parent process.
Explanation
Environment variables and shell variables are two entirely different concepts, but as we manipulate them in a similar way in bash, it's easy to get confused.
Whenever a process is created (by fork), it may include an environment, given by its parent at fork-time. The child process may then access and modify its content. How this is done as a user depends on the program :
In vim, you can access an environment variable like this : :echo $foo
In bash, you can access it like this : $ echo "$foo"
In most programming languages, you can access it with a syntax coherent with the rest of the language, such as ENV['foo'] in ruby
On the other hand, a program may allocate memory for any internal use, but notably, it will quite often define and use variables. Once again, this depends on the program :
In vim, you would use the :let command to assign an internal variable
In bash, you would assign a variable with $ foo='bar', and then read it with $ echo "$foo"
In most programming languages, you have a variation of the foo='bar' syntax, sometimes with type declarations, etc
As you can see, bash uses the same syntax to read an environment variable and one of its own private variables, which can lead to some confusion.
When you execute vim from your bash shell, the environment is copied over from the parent process (bash) to the child (vim), but the private memory of bash (including the variables you may have defined) are not.
Thus, accessing them from the child process would require some inter-process communication mechanism, between parent and child. While technically doable, this option is not implemented in bash nor vim.
Solution
In order for your variable to be accessible from vim (or any forked process, for that matter), you need it to be present in the environment of your vim process.
Several options to do that :
$ export foo='bar' : This will mark your variable for export to the environment of subsequently executed commands. That's what you want in most cases.
$ foo='bar' vim : This adds your variable to the environment of this vim command. Very useful for troubleshooting, or for one-liners.
$ set -a : As you can see in bash manpage, this marks every subsequent definitions for export to the environment of subsequent commands. It's essentially equivalent to prepending every subsequent definition by export.
To go further
The question uses the :!echo $foo syntax to display the value of foo, which is yet another usecase. The ! here is actually an escape sequence that allows you to execute a shell command from vim.
However, vim cannot execute anything in the parent shell (the one you executed the vim command in), so it creates a new bash shell in a child process, executes echo in it, and displays the result.
In the current case, the result is mostly the same, but it could easily be misleading in other situations, so it's important to understand what is happening here.
There is another vim syntax, using expand, that allows one to lookup variables : :echo expand("$foo")
It however works entirely differently.
If no internal variable named foo exists, vim will invoke a shell to look it up (similarly to what ! would do).
This options is way slower than an environment lookup, and not recommended for most usecases.
If you want to use a value from your shell on the :substitute command, there's actually a way to do it.
I don't know if it solves your need but here we go.
Let's say we want to substitute Mydir by your PWD:
:s/Mydir/\=expand($PWD)/g

Why can you set environment variables in Bash functions but not in the script itself

Why does this work:
# a.sh
setEnv() {
export TEST_A='Set'
}
when this doesn't:
# b.sh
export TEST_B='Set'
Ex:
> source a.sh
> setEnv
> env | grep TEST_A
TEST_A=Set
> b.sh
> env | grep TEST_B
I understand why running the script doesn't work and what to do to make it work (source b.sh etc), but I'm curious to why the function works.
This is on OS X if that matters.
You need to understand the difference between sourcing and executing a script.
Sourcing runs the script from the parent-shell in which the script is invoked; all the environment variables are retained until the parent-shell is terminated (the terminal is closed, or the variables are reset or unset), whereas
Execute forks a new shell from the parent shell and those variables including your export variables are retained only in the sub-shell's environment and destroyed at the end of script termination.
i.e. the sub-shell ( imagine it being an environment) created in the first case to hold the variables are not allocated in scope of a separate child environment but are just added in the parents' ( e.g. imagine an extra memory cell, maintained by the parent ) environment which is held until you have the session open. But executing a script is, imagine a simple analogy, calling a function whose variables are in stored in stack which loose scope at the end of function call. Likewise, the forked shell's environment looses scope at the end of its termination.
So it comes down to this, even if you have a function to export your variable, if you don't source it to the current shell and just plainly execute it, the variable is not retained; i.e.
# a.sh
setEnv() {
export TEST_A='Set'
}
and if you run it in the shell as
bash script.sh # unlike/NOT source script.sh
env | grep TEST_A
# empty
Executing a function does not, in and of itself, start a new process like b.sh does.
From the man page (emphasis on the last sentence):
FUNCTIONS
A shell function, defined as described above under SHELL GRAMMAR,
stores a series of commands for later execution. When the name of a
shell function is used as a simple command name, the list of commands
associated with that function name is executed. **Functions are executed
in the context of the current shell; no new process is created to
interpret them (contrast this with the execution of a shell script).**
I understand why running the script doesn't work and what to do to make it work (source b.sh etc)
So you already understand the fact that executing b.sh directly -- in a child process, whose changes to the environment fundamentally won't be visible to the current process (shell) -- will not define TEST_B in the current (shell) process, so we can take this scenario out of the picture.
I'm curious why the function works.
When you source a script, you execute it in the context of the current shell - loosely speaking, it is as if you had typed the contents of the script directly at the prompt: any changes to the environment, including shell-specific elements such as shell variables, aliases, functions, become visible to the current shell.
Therefore, after executing source a.sh, function setEnv is now available in the current shell, and invoking it executes export TEST_A='Set', which defines environment variable TEST_A in the current shell (and subsequently created child processes would see it).
Perhaps your misconception is around what chepner's helpful answer addresses: in POSIX-like shells, functions run in the current shell - in contrast with scripts (when run without source), for which a child process is created.
This is on OS X if that matters.
Not in this case, because only functionality built into bash itself is used.

In bash, when creating Variables. When to export and when not to export?

From what I've read, is it correct to assume that the only thing export does is make the variable visible to child processes?
What would be a scenario where you would want to make a variable only visible to the scope it was initialized in, and what would be a scenario in which you would want a variable available to all child scopes?
In general, you only need to export a variable that another process will look for in its environment. How do you know which variables those are? You have to read their documentation.
Whether or not a variable is marked for export makes no difference to the current shell.
Let's construct a demonstration.
$ printf 'echo "foo=$foo"\n' > script
$ bash script
foo=
$ foo=3
$ bash script
foo=
$ export foo
bash script
foo=3
The first time and second time you run script, foo is undefined in its environment because its parent process (the current shell) did not export foo. The third time it is called, the parent adds foo to the script's initial environment because foo was exported.
In response to your comment, the term "environment" has a very precise meaning here. All processes, not just shells, receive an array of strings from its parent on startup, referred to as its environment. There are no particular semantics associated with these strings; it's up to the receiving program how to interpret them.
The shell, for example, ignores strings in its environment that do not have the form name=value, where name is a valid shell identifier. For each such string, the shell defines a shell variable with the given name and value, and marks its export attribute. This is what we mean by an environment variable. You can also "promote" a regular shell variable at any time to an environment variable using the export command, but this doesn't affect the meaning of the variable in the current process.
When any process creates a new process, a copy of its environment is given to the new process. The shell additionally creates name=value strings to pass on from each of its environment variables.

*export* all variables from key=value file to shell

If I want to inherit environment variables to child processes, i do something like:
export MYVAR=tork
Assume I have a a file site.conf containing assignments of values (that can contain spaces) to variables:
EMAIL="dev#example.com"
FULLNAME="Master Yedi"
FOO=bar
Now I would like to process this file whenever I open a new shell (e.g. with some code in ~/.bashrc or ~/.profile), so that any processes started from within that newly opened shell will inherit the assignments via environmental variables.
The obvious solution would be to prefix each line in site.conf with an export and just source the file. However I cannot do this since the file is also read (directly) by some other applications, so the format is fixed.
I tried something like
cat site.conf | while read assignment
do
export "${assignment}"
done
But this doesn't work, for various reasons (the most important being that export is executed in a subshell, so the variable will never be exported to the children of the calling shell).
Is there a way to programmatically export unknown variables in bash?
Run set -a before sourcing the file. This marks all new and modified variables that follow for export automatically.
set -a
source site.conf
set +a # Require export again, if desired.
The problem you observed is that the pipe executes the export in a subshell. You can avoid that simply by using input redirection instead of a pipe.
while read assignment; do
export "$assignment"
done < site.conf
This won't work, however, if (unlikely though it is) you have multiple assignments on one line, such as
EMAIL="dev#example.com" FULLNAME="Master Yedi"

Why are bash script variables not saving?

I have a simple bash script:
#!/bin/bash
JAVA_HOME=/usr
EC2_HOME=~/ec2-api
echo $EC2_HOME
export PATH=$PATH:$EC2_HOME/bin
I run the script like so
$ ./ec2
/Users/user/ec2-api
The script runs and produces the correct output.
However, when I now try to access the EC2_HOME variable, I get nothing out:
$ echo $EC2_HOME
I get a blank string back. What am I doing wrong?
Do either of the following instead:
source ec2
or
. ec2
(note the . notation is just a shortcut for source)
Explanation:
This is because ./ec2 actually spawns a subshell from your current shell to execute the script, and subshells cannot affect the environment of the parent shell from which it spawned.
Thus, EC2_HOME does get set to /Users/user/ec2-api correctly in the subshell (and similarly the PATH environment variable is updated and exported correctly in the subshell as well), but those changes won't propagate back to your parent shell.
Using source runs the script directly in the current shell without spawning a subshell, so the changes made will persist.
(A note on export: export is used to tell new shells spawned from the current shell to use the variables exported from the current shell. So for any variables you would only use in the current shell, they need not be exported.)
A shell script can never modify the environment of their parent.
To fix your problem, you can use the dot (.) command:
$ . ./ec2
and that should work. In cshell, it would be
% source ./ec2
To learn more about shells and scripts, my best resource is by far Unix power tools.

Resources