Can /bin/bash -c export variable to parent shell - bash

I'm trying to run arbitrary bash commands in the shell, but I can only access the shell buy running /bin/bash -c
Is there anyway of being able to run something like:
/bin/bash -c "export FOO=bar"
and then see FOO set in the original shell?

No.
This isn't shell-specific -- no UNIX process can change a parent process's environment variables without that parent process honoring an interface (for instance, reading new variables/values from stdout), or using unreliable and unsupportable hackery (like attaching to the parent process with a debugger and calling setenv() directly).
Consider ssh-agent as an example:
$ ssh-agent
SSH_AUTH_SOCK=/var/folders/t2/t58p1nwx1g38tkhykqfhvmm80000gn/T//ssh-0HSNi1V5h9wf/agent.17313; export SSH_AUTH_SOCK;
SSH_AGENT_PID=17314; export SSH_AGENT_PID;
echo Agent pid 17314;
...thus, documented for use with a pattern akin to:
$ eval "$(ssh-agent)"
In this case, that interface is eval-able shell code; however, as this is trivially used to execute arbitrary commands, supporting this interface is a security risk.
Inasmuch as your goal is to use the result of shell commands to modify the environment of a program that isn't a shell language at all, and thus doesn't support eval or source, this gives you the ability to use a safer stream format, such as a NUL-delimited stream. For instance, if your shell program writes key=val\0 pairs, with literal NUL characters for \0, you can do something akin to the following in Python:
for env_val in s.split('\0'):
if not env_val.contains('='): continue
k, v = env_val.split('=', 1)
environ[k] = v
...ported to your language of choice. To write in this format from shell:
printf '%s=%s\0' "$key" "$val"
...will suffice.

Related

How to create and set a system-wide environmental variable in Ubuntu through makefile?

I'm trying to create a system-wide environmental variable TEST_ENV_ONE.
I want to use it right after executing makefile without logout and after rebooting. So I'm trying to repeat manual moves like export variable and write it ti /etc/environment
I wrote a makefile like this, but it doesn't work:
var_value := some_string
TEST_ENV_ONE := $(var_value)
vars:
$(shell export TEST_ENV_ONE=$(var_value))
grep 'TEST_ENV_ONE=' /etc/environment || "TEST_ENV_ONE=\"$(var_value)\"" | sudo tee -a /etc/environment > /dev/null
What you want to do is basically impossible on a POSIX system as you've stated it. The environment of a process is inherited from its parent (the process that started it) and once a process is running, its environment cannot ever be changed externally. That includes by its children, or by modifying some other file.
You can, by modifying /etc/environment, change the environment for new logins but this will not change the environment of any existing shell or its child.
That being said, your makefile also has a number of problems:
$(shell export TEST_ENV_ONE=$(var_value))
This is doubly-not right. First, it's an anti-pattern to use the make $(shell ...) function inside a recipe script. Recipes are already shell scripts so it's useless (and can lead to unexpected behavior) to use $(shell ...) with them.
Second, this is a no-op: what this does is start a shell, tell the shell to set an environment variable and export it, then the shell exits. When the shell exits, all the changes to its environment are lost (obviously, because it exited!) So this does nothing.
Next:
grep 'TEST_ENV_ONE=' /etc/environment || "TEST_ENV_ONE=\"$(var_value)\"" | sudo tee -a /etc/environment > /dev/null
This does nothing because the statement "TEST_ENV_ONE=\"$(var_value)\"" sets an environment variable but generates no output, so there's no input to the sudo tee command and nothing happens. I expect you forgot an echo command here:
grep 'TEST_ENV_ONE=' /etc/environment || echo TEST_ENV_ONE=\"$(var_value)\" | sudo tee -a /etc/environment > /dev/null
However as I mention above, modifying /etc/environment will only take effect for new logins to the system, it won't modify any existing login or shell.

Problem handling enviroment variable when launching terminal from bash script

The following script gets called with an enviroment variable setted.
I need to launch a terminal and inside that terminal read that variable from another script ( script.sh ).
xfce4-terminal -x sh -c \
"export VAR='${VAR}'
/home/usr/scripts/script.sh"
It works but not when VAR has single quotes in it.
I also feel like there is a better way to pass enviroment variable to the terminal but I don't know how.
I really appreciate any kind of help and I'm sorry for my english.
One of the intended features of the environment is that you can add to it, but you never remove things from it. Add VAR to the current environment, and it will be inherited by xfce4-terminal and any process started by that terminal.
export VAR
xfce4-terminal -x sh -c /home/usr/scripts/script.sh
If you don't want it in the current environment, only in the new terminal's, then use a precommend assignment.
VAR="$VAR" xfce4-terminal -x sh -c /home/usr/scripts/script.sh
This avoids any fragile dynamic script construction like you are contending with.
Since xfce4-terminal appears to not fork a new process itself, I would pass the desired value as an argument to sh.
xfce4-terminal -x sh -c 'VAR="$1" /home/usr/scripts/script.sh' _ "$VAR"
The argument to -c is still a fixed string rather than one generated by interpolating the value of $VAR.

How to run a time-limited background command and read its output (without timeout command)

I'm looking at https://stackoverflow.com/a/10225050/1737158
And in same Q there is an answer with timeout command but it's not in all OSes, so I want to avoid it.
What I try to do is:
demo="$(top)" &
TASK_PID=$!
sleep 3
echo "TASK_PID: $TASK_PID"
echo "demo: $demo"
And I expect to have nothing in $demo variable while top command never ends.
Now I get an empty result. Which is "acceptable" but when i re-use the same thing with the command which should return value, I still get an empty result, which is not ok. E.g.:
demo="$(uptime)" &
TASK_PID=$!
sleep 3
echo "TASK_PID: $TASK_PID"
echo "demo: $demo"
This should return uptime result but it doesn't. I also tried to kill the process by TASK_PID but I always get. If a command fails, I expect to have stderr captures somehow. It can be in different variable but it has to be captured and not leaked out.
What happens when you execute var=$(cmd) &
Let's start by noting that the simple command in bash has the form:
[variable assignments] [command] [redirections]
for example
$ demo=$(echo 313) declare -p demo
declare -x demo="313"
According to the manual:
[..] the text after the = in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.
Also, after the [command] above is expanded, the first word is taken to be the name of the command, but:
If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.
So, as expected, when demo=$(cmd) is run, the result of $(..) command substitution is assigned to the demo variable in the current shell.
Another point to note is related to the background operator &. It operates on the so called lists, which are sequences of one or more pipelines. Also:
If a command is terminated by the control operator &, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background.
Finally, when you say:
$ demo=$(top) &
# ^^^^^^^^^^^ simple command, consisting ONLY of variable assignment
that simple command is executed in a subshell (call it s1), inside which $(top) is executed in another subshell (call it s2), the result of this command substitution is assigned to variable demo inside the shell s1. Since no commands are given, after variable assignment, s1 terminates, but the parent shell never receives the variables set in child (s1).
Communicating with a background process
If you're looking for a reliable way to communicate with the process run asynchronously, you might consider coprocesses in bash, or named pipes (FIFO) in other POSIX environments.
Coprocess setup is simpler, since coproc will setup pipes for you, but note you might not reliably read them if process is terminated before writing any output.
#!/bin/bash
coproc top -b -n3
cat <&${COPROC[0]}
FIFO setup would look something like this:
#!/bin/bash
# fifo setup/clean-up
tmp=$(mktemp -td)
mkfifo "$tmp/out"
trap 'rm -rf "$tmp"' EXIT
# bg job, terminates after 3s
top -b >"$tmp/out" -n3 &
# read the output
cat "$tmp/out"
but note, if a FIFO is opened in blocking mode, the writer won't be able to write to it until someone opens it for reading (and starts reading).
Killing after timeout
How you'll kill the background process depends on what setup you've used, but for a simple coproc case above:
#!/bin/bash
coproc top -b
sleep 3
kill -INT "$COPROC_PID"
cat <&${COPROC[0]}

bash which OR operator to use - pipe v double pipe

When I'm looking at bash script code, I sometimes see | and sometimes see ||, but I don't know which is preferable.
I'm trying to do something like ..
set -e;
ret=0 && { which ansible || ret=$?; }
if [[ ${ret} -ne 0 ]]; then
# install ansible here
fi
Please advise which OR operator is preferred in this scenario.
| isn't an OR operator at all. You could use ||, though:
which ansible || {
true # put your code to install ansible here
}
This is equivalent to an if:
if ! which ansible; then
true # put your code to install ansible here
fi
By the way -- consider making a habit of using type (a shell builtin) rather than which (an external command). type is both faster and has a better understanding of shell behavior: If you have an ansible command that's provided by, say, a shell function invoking the real command, which won't know that it's there, but type will correctly detect it as available.
There is a big difference between using a single pipe (pipe output from one command to be used as input for the next command) and a process control OR (double pipe).
cat /etc/issue | less
This runs the cat command on the /etc/issue file, and instead of immediately sending the output to stdout it is piped to be the input for the less command. Yes, this isn't a great example, since you could instead simply do less /etc/issue - but at least you can see how it works
touch /etc/testing || echo Did not work
For this one, the touch command is run, or attempted to run. If it has a non-zero exit status, then the double pipe OR kicks in, and tries to execute the echo command. If the touch command worked, then whatever the other choice is (our echo command in this case) is never attempted...

How do retrieve output from a bashscript that you run from a tcl script <- (modulefile script)

In my home dir, I have sub directories (CentOS, Ubuntu, etc) all for specific nodes I have access to.
Each OS will hold their own copy of programs, one of which is Python:
$HOME/{CentOS, Ubuntu, ...}/{python2,python3}
I am using environment modules so that when I ssh into a different computer (COMP), Python aliases will be set for that specific (COMP). For example:
COMP1 is CentOS
when I ssh into COMP1, "python3" should point to $HOME/Centos/python3/bin/python3
COMP2 is Ubuntu
when I ssh into COMP2 "python2" should point to $HOME/Ubuntu/python2/bin/python2
I can retrieve the OS name in bash using lsb_release -si, but I am working with modulefiles which are written in tcl, and haven't found something like lsb_release. Can I have a bash script that outputs lsb_release -si when called from a tcl script?
I tried doing this but no luck:
BASH SCRIPT:
#!/bin/bash
OS=$(lsb_release -si)
echo $OS
MODULEFILE SCRIPT:
#%Modulefile1.0
set OS [catch {exec bash /path/to/bash_file} output]
puts $OS
This doesn't do much.
Option A: export the variable in bash and access the environment variable in tcl.
#!/bin/bash
OS=$(lsb_release -si)
export OS
somescript.tcl
#!/usr/bin/tclsh
puts $::env(OS)
Option B: Use the platform package that comes with tcl.
#!/usr/bin/tclsh
package require platform
puts [platform::identify] ; # detailed OS-CPU
puts [platform::generic] ; # more generic OS-CPU
References: env platform
Your code mostly doesn't look obviously wrong.
But following the [catch {exec ...} output] the value that you are looking for should be in the output variable; the OS variable will have a code indicating effectively whether the bash script produced any output to stderr. Since you're definitely not interested in that debugging output which might be produced for reasons not under your easy control, you can probably do this:
catch {exec bash /path/to/bash_file 2>/dev/null} output
puts $output
Also make sure your bash script has an explicit exit at the end. Might as well ensure that it stops correctly. That's the default behaviour, but it's better to be explicit here as this is a (small) program.

Resources