How to access shell variable value from Node.js? - node.js

Let's say there is a variable key1 and its value is 123
key1=123
so when I run the command in linux environment echo $key1, I get output as 123.
Now I have the following gulp task.
const child_process = require('child_process');
....
gulp.task('printKeyValue', function() {
var value1 = child_process.execSync('echo $key1');
console.log(value1.toString().trim());
});
Here, I'm trying to access value of linux variable from nodejs by using Child Process
But when I run the following gulp task, I don't get the desired output.
npm run gulp -- printKeyValue
Instead I get output as $key1 and not 123.
See below screenshot
Other commands like ls & pwd in gulp task gives the desired output.
Can some one please help on this or suggest an alternate way?

You are not exporting the variable. When you just do
key1=123
the variable is not propagated to subprocesses. It will be available in your current bash process, so you can see it when you type echo $key1, but it will not get inherited by the node process. As man bash says:
When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following. Unless otherwise noted, the values are inherited from the shell.
[...]
shell variables and functions marked for export, along with variables exported for the command, passed in the environment
You need to either define the variable as exported
export key1=123
or mark an existing variable for export
key1=123
export key1
or launch your node with the modified environment, either via the bash innate capability to do so
key1=123 node code.js
or using /usr/bin/env utility:
env key1=123 node code.js
Once the variable is properly passed to the node process, it will be available both in process.env.key1 and as $key1 in a child process.
EDIT: I just noticed, you actually gave the command you're running; it does not matter, the same logic goes for every executable, whether node or npm or anything else.

Related

How can I change actual shell cd with nodejs

I am creating a command line program to act as a shortcut for my environment. Commands like $ cd $enterprise/products/<product> are so common, that I like to compile into: $ enterprise product.
My problem is: I cannot change shell directory directly, like running $ cd $enterprise/products/<product> with process.chdir.
Using console.log(process.cwd()) shows that the directory has changed, but not on shell, only on nodejs internal process(I think it's running on a own shell).
Typing $ pwd in shell shows that I still are on the same folder.
I was searching for a solution, like a shell script that interprets the output of a nodejs file, then source the output.
Thanks.
This is actually bit trickier than it sounds.
You can't just change the working directory of the shell that is running the process, without making assumptions about which shell it is or the OS (I personally use applescript to spawn new terminal tabs).
What we can do, however, is spawn a new shell!
let child_process = require('child_process');
child_process.spawn(
// With this variable we know we use the same shell as the one that started this process
process.env.SHELL,
{
// Change the cwd
cwd: `${process.cwd()}/products/${product_id}`,
// This makes this process "take over" the terminal
stdio: 'inherit',
// If you want, you can also add more environment variables here, but you can also remove this line
env: { ...process.env, extra_environment: 'some value' },
},
);
When you run this, it seems like you cd into a directory, but actually you are still running inside nodejs!
You can't do this; every child process has its own working directory inherited from the parent. In this case, your cd gets its working directory from its parent (your shell). A child process can't change the directory – or any other state – of the parent process.

Whats the underlieing concept of environment variables?

Jolly good evening. I've used Environment Envirables before in Node.js applications, but I feel I havent understood the underlieing concept yet.
Its (in this case) not Node who gives me the ability to set environment variables, right? Is it Linux? Does this concept persist through the whole OS? Do environment Variables have a scope? Can I use them everywhere? Is the pattern always the same? Are they written into the run application, or do some Applications (like node) the ability to actively access them from the inside?
Woulld love to grasp the basic concept.
Environment variables is a functionality provided by the operating system (e.g. Linux).
You can set then in terminal or shell scripts using:
name=value
Or in Node using:
process.env.name = value;
You can access them in shell using:
echo $name
Or in Node using:
console.log(process.env.name);
The scope of the environment variables is the process when they are defined and the sub-processes that it executes.
For example write a Node program called envtest.js:
console.log('Node program:', process.env.test);
process.env.test = 'new value';
console.log('Node program:', process.env.test);
And a shell script called envtest1.sh:
test=value
echo "Shell script: $test"
node envtest.js
echo "Shell script: $test"
Running sh envtest1.sh wil print:
Shell script: value
Node program: undefined
Node program: new value
Shell script: value
As yu can see the Node program doesn't get the value because it wasn't exported. It can set the value and use the new value but it will not be changed in the shell script.
Now, write a different shell script:
test=value
export test
echo "Shell script: $test"
node envtest.js
echo "Shell script: $test"
This time running sh envtest2.sh will print:
Shell script: value
Node program: value
Node program: new value
Shell script: value
It means that the Node program got the value because it was exported this time, it can still change it and use the new value but it works on its own copy, it is not changed in the original shell script that called this Node program.
Instead of:
test=value
export test
You can write:
export test=value
as a shorthand.
A more complicated example, write envtest3.sh:
export test=value
echo "Shell script: $test"
node envtest.js
echo "Shell script: $test"
test=value2 node envtest.js
echo "Shell script: $test"
This time it will print:
Shell script: value
Node program: value
Node program: new value
Shell script: value
Node program: value2
Node program: new value
Shell script: value
Which shows that running test=value2 node envtest.js sets the value of test variable to value2 but only to this invocation of the Node program - the value in the rest of the shell script is still value as it was before.
Those are the 3 kinds of scope of environemnt variables - normally a variable in a shell script is not exported and the programs that you run can't see it. When it is exported then the programs that you run can see it and can modify it but they work on their own copy and it isn't changed in the shell script.
When you run name=value command then the environment variable will be set just for that command but the old value will remain in the rest of the script.
Those are the basics of environment variables and how you can use them in Node.

incrementing an environmental variable

I need to increment an environmental variable by these steps:
envar=1
export envar
sh script_incrementation
echo $envar
where script_incrementation contains something like this:
#! /bin/sh
envar=$[envar+1] #I've tried also other methods of incrementation
export envar
Whatever I do, after exiting the script the variable remains with its initial value 1.
THanks for your time.
A shell script executes in its own shell, so you cannot affect the outer shell unless you source it. See this question for details of that discussion.
Consider the following script, which I will call Foo.sh.
#!/bin/bash
export HELLO=$(($HELLO+1))
Suppose in the outer shell, I define an environmental variable:
export HELLO=1
If I run the script like this, it run inside its own shell and will not affect the parent.
./Foo.sh
However, if I source it, it will just execute the commands in the current shell, and will achieve the desired affect.
. Foo.sh
echo $HELLO # prints 2
Your script can not change the environment of the calling process (shell), it merely inherits it.
So, if you export foo=bar, and then invoke sh (a new process) with your script, the script will see the value of $foo (which is "bar"), and it will be able to change its own copy of it – but that is not going to affect the environment of the parent process (where you exported the variable).
You can simply source your script in the original shell, i.e. run
source increment_script.sh
or
. increment_script.sh
and that will then change the value of the variable.
This is because sourceing a script avoids spawning a new shell (process).
Another trick is to have your script output the changed environment, and then eval that output, for example:
counter=$[counter+1]
echo "counter=$counter"
and then run that as
eval `increment_script.sh`

How to make declare in a Linux shell script?

I want to put below declare in a shell script: proxy_set
declare -x https_proxy="https://192.168.220.4:8080/"
And then I execute it like below.
$ ./proxy_set
But "export" shows nothing happened.
And in another way if I execute it like this:
$ source proxy_set
Then "export" shows it works!
My question is how can I make it work without additional "source" cmd?
Thanks!
You can't. Setting variables in the environment only affects the environment of that shell and any future children it spawns; there's no way to affect the parent shell. When you run it without the source (or .), a brand new shell is started up, then the variable is set in that shell's environment, and then that shell exits, taking its environment with it.
The source reads the commands and executes them within the current shell as if you had typed them.
So if you want to set environment variables in a script, you have to source it. Alternatively, you can have a command generate shell commands as output instead of running them, and then the parent can evaluate the output of the command. Things like ssh-agent use this approach.
Try just adding:
export https_proxy="https://192.168.220.4:8080/"
Then execute your script normally.

Defining a variable with or without export

What is export for?
What is the difference between:
export name=value
and
name=value
export makes the variable available to sub-processes.
That is,
export name=value
means that the variable name is available to any process you run from that shell process. If you want a process to make use of this variable, use export, and run the process from that shell.
name=value
means the variable scope is restricted to the shell, and is not available to any other process. You would use this for (say) loop variables, temporary variables etc.
It's important to note that exporting a variable doesn't make it available to parent processes. That is, specifying and exporting a variable in a spawned process doesn't make it available in the process that launched it.
To illustrate what the other answers are saying:
$ foo="Hello, World"
$ echo $foo
Hello, World
$ bar="Goodbye"
$ export foo
$ bash
bash-3.2$ echo $foo
Hello, World
bash-3.2$ echo $bar
bash-3.2$
It has been said that it's not necessary to export in bash when spawning subshells, while others said the exact opposite. It is important to note the difference between subshells (those that are created by (), ``, $() or loops) and subprocesses (processes that are invoked by name, for example a literal bash appearing in your script).
Subshells will have access to all variables from the parent, regardless of their exported state.
Subprocesses will only see the exported variables.
What is common in these two constructs is that neither can pass variables back to the parent shell.
$ noexport=noexport; export export=export; (echo subshell: $noexport $export; subshell=subshell); bash -c 'echo subprocess: $noexport $export; subprocess=subprocess'; echo parent: $subshell $subprocess
subshell: noexport export
subprocess: export
parent:
There is one more source of confusion: some think that 'forked' subprocesses are the ones that don't see non-exported variables. Usually fork()s are immediately followed by exec()s, and that's why it would seem that the fork() is the thing to look for, while in fact it's the exec(). You can run commands without fork()ing first with the exec command, and processes started by this method will also have no access to unexported variables:
$ noexport=noexport; export export=export; exec bash -c 'echo execd process: $noexport $export; execd=execd'; echo parent: $execd
execd process: export
Note that we don't see the parent: line this time, because we have replaced the parent shell with the exec command, so there's nothing left to execute that command.
This answer is wrong but retained for historical purposes. See 2nd edit below.
Others have answered that export makes the variable available to subshells, and that is correct but merely a side effect. When you export a variable, it puts that variable in the environment of the current shell (ie the shell calls putenv(3) or setenv(3)).
The environment of a process is inherited across exec, making the variable visible in subshells.
Edit (with 5 year's perspective):
This is a silly answer. The purpose of 'export' is to make variables "be in the environment of subsequently executed commands", whether those commands be subshells or subprocesses. A naive implementation would be to simply put the variable in the environment of the shell, but this would make it impossible to implement export -p.
2nd Edit (with another 5 years in passing).
This answer is just bizarre. Perhaps I had some reason at one point to claim that bash puts the exported variable into its own environment, but those reasons were not given here and are now lost to history. See Exporting a function local variable to the environment.
export NAME=value for settings and variables that have meaning to a subprocess.
NAME=value for temporary or loop variables private to the current shell process.
In more detail, export marks the variable name in the environment that copies to a subprocesses and their subprocesses upon creation. No name or value is ever copied back from the subprocess.
A common error is to place a space around the equal sign:
$ export FOO = "bar"
bash: export: `=': not a valid identifier
Only the exported variable (B) is seen by the subprocess:
$ A="Alice"; export B="Bob"; echo "echo A is \$A. B is \$B" | bash
A is . B is Bob
Changes in the subprocess do not change the main shell:
$ export B="Bob"; echo 'B="Banana"' | bash; echo $B
Bob
Variables marked for export have values copied when the subprocess is created:
$ export B="Bob"; echo '(sleep 30; echo "Subprocess 1 has B=$B")' | bash &
[1] 3306
$ B="Banana"; echo '(sleep 30; echo "Subprocess 2 has B=$B")' | bash
Subprocess 1 has B=Bob
Subprocess 2 has B=Banana
[1]+ Done echo '(sleep 30; echo "Subprocess 1 has B=$B")' | bash
Only exported variables become part of the environment (man environ):
$ ALICE="Alice"; export BOB="Bob"; env | grep "ALICE\|BOB"
BOB=Bob
So, now it should be as clear as is the summer's sun! Thanks to Brain Agnew, alexp, and William Prusell.
It should be noted that you can export a variable and later change the value. The variable's changed value will be available to child processes. Once export has been set for a variable you must do export -n <var> to remove the property.
$ K=1
$ export K
$ K=2
$ bash -c 'echo ${K-unset}'
2
$ export -n K
$ bash -c 'echo ${K-unset}'
unset
export will make the variable available to all shells forked from the current shell.
As you might already know, UNIX allows processes to have a set of environment variables, which are key/value pairs, both key and value being strings.
Operating system is responsible for keeping these pairs for each process separately.
Program can access its environment variables through this UNIX API:
char *getenv(const char *name);
int setenv(const char *name, const char *value, int override);
int unsetenv(const char *name);
Processes also inherit environment variables from parent processes. Operating system is responsible for creating a copy of all "envars" at the moment the child process is created.
Bash, among other shells, is capable of setting its environment variables on user request. This is what export exists for.
export is a Bash command to set environment variable for Bash. All variables set with this command would be inherited by all processes that this Bash would create.
More on Environment in Bash
Another kind of variable in Bash is internal variable. Since Bash is not just interactive shell, it is in fact a script interpreter, as any other interpreter (e.g. Python) it is capable of keeping its own set of variables. It should be mentioned that Bash (unlike Python) supports only string variables.
Notation for defining Bash variables is name=value. These variables stay inside Bash and have nothing to do with environment variables kept by operating system.
More on Shell Parameters (including variables)
Also worth noting that, according to Bash reference manual:
The environment for any simple command or function may be augmented
temporarily by prefixing it with parameter assignments, as described
in Shell Parameters. These assignment statements affect only the
environment seen by that command.
To sum things up:
export is used to set environment variable in operating system. This variable will be available to all child processes created by current Bash process ever after.
Bash variable notation (name=value) is used to set local variables available only to current process of bash
Bash variable notation prefixing another command creates environment variable only for scope of that command.
The accepted answer implies this, but I'd like to make explicit the connection to shell builtins:
As mentioned already, export will make a variable available to both the shell and children. If export is not used, the variable will only be available in the shell, and only shell builtins can access it.
That is,
tango=3
env | grep tango # prints nothing, since env is a child process
set | grep tango # prints tango=3 - "type set" shows `set` is a shell builtin
Two of the creators of UNIX, Brian Kernighan and Rob Pike, explain this in their book "The UNIX Programming Environment". Google for the title and you'll easily find a pdf version.
They address shell variables in section 3.6, and focus on the use of the export command at the end of that section:
When you want to make the value of a variable accessible in sub-shells, the shell's export command should be used. (You might think about why there is no way to export the value of a variable from a sub-shell to its parent).
Here's yet another example:
VARTEST="value of VARTEST"
#export VARTEST="value of VARTEST"
sudo env | grep -i vartest
sudo echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"
sudo bash -c 'echo ${SUDO_USER} ${SUDO_UID}:${SUDO_GID} "${VARTEST}"'
Only by using export VARTEST the value of VARTEST is available in sudo bash -c '...'!
For further examples see:
http://mywiki.wooledge.org/SubShell
bash-hackers.org/wiki/doku.php/scripting/processtree
Just to show the difference between an exported variable being in the environment (env) and a non-exported variable not being in the environment:
If I do this:
$ MYNAME=Fred
$ export OURNAME=Jim
then only $OURNAME appears in the env. The variable $MYNAME is not in the env.
$ env | grep NAME
OURNAME=Jim
but the variable $MYNAME does exist in the shell
$ echo $MYNAME
Fred
By default, variables created within a script are only available to the current shell; child processes (sub-shells) will not have access to values that have been set or modified. Allowing child processes to see the values, requires use of the export command.
As yet another corollary to the existing answers here, let's rephrase the problem statement.
The answer to "should I export" is identical to the answer to the question "Does your subsequent code run a command which implicitly accesses this variable?"
For a properly documented standard utility, the answer to this can be found in the ENVIRONMENT section of the utility's man page. So, for example, the git manual page mentions that GIT_PAGER controls which utility is used to browse multi-page output from git. Hence,
# XXX FIXME: buggy
branch="main"
GIT_PAGER="less"
git log -n 25 --oneline "$branch"
git log "$branch"
will not work correctly, because you did not export GIT_PAGER. (Of course, if your system already declared the variable as exported somewhere else, the bug is not reproducible.)
We are explicitly referring to the variable $branch, and the git program code doesn't refer to a system variable branch anywhere (as also suggested by the fact that its name is written in lower case; but many beginners erroneously use upper case for their private variables, too! See Correct Bash and shell script variable capitalization for a discussion) so there is no reason to export branch.
The correct code would look like
branch="main"
export GIT_PAGER="less"
git log -n 25 --oneline "$branch"
git log -p "$branch"
(or equivalently, you can explicitly prefix each invocation of git with the temporary assignment
branch="main"
GIT_PAGER="less" git log -n 25 --oneline "$branch"
GIT_PAGER="less" git log -p "$branch"
In case it's not obvious, the shell script syntax
var=value command arguments
temporarily sets var to value for the duration of the execution of
command arguments
and exports it to the command subprocess, and then afterwards, reverts it back to the previous value, which could be undefined, or defined with a different - possibly empty - value, and unexported if that's what it was before.)
For internal, ad-hoc or otherwise poorly documented tools, you simply have to know whether they silently inspect their environment. This is rarely important in practice, outside of a few specific use cases, such as passing a password or authentication token or other secret information to a process running in some sort of container or isolated environment.
If you really need to know, and have access to the source code, look for code which uses the getenv system call (or on Windows, with my condolences, variations like getenv_s, w_getenv, etc). For some scripting languages (such as Perl or Ruby), look for ENV. For Python, look for os.environ (but notice also that e.g. from os import environ as foo means that foo is now an alias for os.environ). In Node, look for process.env. For C and related languages, look for envp (but this is just a convention for what to call the optional third argument to main, after argc and argv; the language lets you call them anything you like). For shell scripts (as briefly mentioned above), perhaps look for variables with uppercase or occasionally mixed-case names, or usage of the utility env. Many informal scripts have undocumented but discoverable assignments usually near the beginning of the script; in particular, look for the ?= default assignment parameter expansion.
For a brief demo, here is a shell script which invokes a Python script which looks for $NICKNAME, and falls back to a default value if it's unset.
#!/bin/sh
NICKNAME="Handsome Guy"
demo () {
python3 <<\____
from os import environ as env
print("Hello, %s" % env.get("NICKNAME", "Anonymous Coward"))
____
}
demo
# prints "Hello, Anonymous Coward"
# Fix: forgot export
export NICKNAME
demo
# prints "Hello, Handsome Guy"
As another tangential remark, let me reiterate that you only ever need to export a variable once. Many beginners cargo-cult code like
# XXX FIXME: redundant exports
export PATH="$HOME/bin:$PATH"
export PATH="/opt/acme/bin:$PATH"
but typically, your operating system has already declared PATH as exported, so this is better written
PATH="$HOME/bin:$PATH"
PATH="/opt/acme/bin:$PATH"
or perhaps refactored to something like
for p in "$HOME/bin" "/opt/acme/bin"
do
case :$PATH: in
*:"$p":*) ;;
*) PATH="$p:$PATH";;
esac
done
# Avoid polluting the variable namespace of your interactive shell
unset p
which avoids adding duplicate entries to your PATH.
Although not explicitly mentioned in the discussion, it is NOT necessary to use export when spawning a subshell from inside bash since all the variables are copied into the child process.

Resources