Having an Issue Passing Variables to Subshell

Having an issue passing variables to subshell

You are using ssh to connect to a remote host and run a script on that host. ssh does not export the local environment to the remote session but instead performs a login on the remote host which sets the environment according to the remote user's configuration on the remote host.

I suggest you pass all needed values via the command you want to execute. This could be done like this:

ssh -t $i '
sudo pam_tally2 --user='"$NEWUSER2"' --reset
echo -e "'"$PASSWORD"'\n'"$PASSWORD"'" | sudo passwd '"$NEWUSER2"'
sudo chage -d 0 '"$NEWUSER2"

Watch closely how this uses quotes. At each occasion where you used a variable, I terminate the single-quoted string (using '), then add a double-quoted use of the variable (e. g. "$PASSWORD") and then start the single-quoted string again (using ' again). This way, the shell executing the ssh command will expand the variables already, so you have no need to pass them into the remote shell.

But be aware that special characters in the password (like " or ' or or maybe a bunch of other characters) can mean trouble using this simple mechanism. To be safe against this as well, you would need to use the %q format specifier of the printf command to quote your values before passing them:

ssh -t "$i" "$(printf '
sudo pam_tally2 --user=%q --reset
{ echo %q; echo %q; } | sudo passwd %q
sudo chage -d 0 %q' \
"$NEWUSER2" "$PASSWORD" "$PASSWORD" "$NEWUSER2" "$NEWUSER2")"

Set a parent shell's variable from a subshell

The whole point of a subshell is that it doesn't affect the calling session. In bash a subshell is a child process, other shells differ but even then a variable setting in a subshell does not affect the caller. By definition.

Do you need a subshell? If you just need a group then use braces:

a=3
{ a=4;}
echo $a

gives 4 (be careful of the spaces in that one). Alternatively, write the variable value to stdout and capture it in the caller:

a=3
a=$(a=4;echo $a)
echo $a

avoid using back-ticks ``, they are deprecated and can be difficult to read.

Pass environment variables to subshell CMD

This batch file demonstrates on execution the problem and offers a solution.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "SOURCE_PATH=C:\Source"
set "PHPUNIT_PATH="%%SOURCE_PATH%%\cgi-bin\tests\__init\tools\phpunit.phar""
echo %SOURCE_PATH%\cgi-bin\php.exe %PHPUNIT_PATH%
echo/
echo Reference to environment variable SOURCE_PATH in environment
echo variable PHPUNIT_PATH is not expanded on running php.exe.
echo/
echo Solution:
echo/
echo Explicitly set environment variable PHPUNIT_PATH once more with
echo its own value with using additionally the command CALL to expand
echo all variable references inside the variable value.
echo/
call set "PHPUNIT_PATH=%PHPUNIT_PATH%"
echo %SOURCE_PATH%\cgi-bin\php.exe %PHPUNIT_PATH%
echo/
endlocal
pause

Don't run SETLOCAL more than once for defining the command line environment with specifying the parameters EnableExtensions and EnableDelayedExpansion. Both can be specified on running SETLOCAL only once. See this answer for a detailed explanation what the commands SETLOCAL and ENDLOCAL do which should make it clear why it is not advisable to run SETLOCAL more often than really needed.

And enable delayed expansion only when really needed as exclamation marks in text to process or directory/file names or parameter strings are handled not anymore as literal character with delayed expansion enabled.

Pass all variables from one shell script to another?

You have basically two options:

  1. Make the variable an environment variable (export TESTVARIABLE) before executing the 2nd script.
  2. Source the 2nd script, i.e. . test2.sh and it will run in the same shell. This would let you share more complex variables like arrays easily, but also means that the other script could modify variables in the source shell.

UPDATE:

To use export to set an environment variable, you can either use an existing variable:

A=10
# ...
export A

This ought to work in both bash and sh. bash also allows it to be combined like so:

export A=10

This also works in my sh (which happens to be bash, you can use echo $SHELL to check). But I don't believe that that's guaranteed to work in all sh, so best to play it safe and separate them.

Any variable you export in this way will be visible in scripts you execute, for example:

a.sh:

#!/bin/sh

MESSAGE="hello"
export MESSAGE
./b.sh

b.sh:

#!/bin/sh

echo "The message is: $MESSAGE"

Then:

$ ./a.sh
The message is: hello

The fact that these are both shell scripts is also just incidental. Environment variables can be passed to any process you execute, for example if we used python instead it might look like:

a.sh:

#!/bin/sh

MESSAGE="hello"
export MESSAGE
./b.py

b.py:

#!/usr/bin/python

import os

print 'The message is:', os.environ['MESSAGE']

Sourcing:

Instead we could source like this:

a.sh:

#!/bin/sh

MESSAGE="hello"

. ./b.sh

b.sh:

#!/bin/sh

echo "The message is: $MESSAGE"

Then:

$ ./a.sh
The message is: hello

This more or less "imports" the contents of b.sh directly and executes it in the same shell. Notice that we didn't have to export the variable to access it. This implicitly shares all the variables you have, as well as allows the other script to add/delete/modify variables in the shell. Of course, in this model both your scripts should be the same language (sh or bash). To give an example how we could pass messages back and forth:

a.sh:

#!/bin/sh

MESSAGE="hello"

. ./b.sh

echo "[A] The message is: $MESSAGE"

b.sh:

#!/bin/sh

echo "[B] The message is: $MESSAGE"

MESSAGE="goodbye"

Then:

$ ./a.sh
[B] The message is: hello
[A] The message is: goodbye

This works equally well in bash. It also makes it easy to share more complex data which you could not express as an environment variable (at least without some heavy lifting on your part), like arrays or associative arrays.

Bash: export not passing variables correctly to parent

To access variable's in b.sh use source instead :

source b.sh var

It should give what you wanted.

While-loop subshell dilemma in Bash

The problem is that the while loop is part of a pipeline. In a bash pipeline, every element of the pipeline is executed in its own subshell [ref]. So after the while loop terminates, the while loop subshell's copy of var is discarded, and the original var of the parent (whose value is unchanged) is echoed.

One way to fix this is by using Process Substitution as shown below:

var=0
while read i;
do
# perform computations on $i
((var++))
done < <(find . -type f -name "*.bin" -maxdepth 1)

Take a look at BashFAQ/024 for other workarounds.

Notice that I have also replaced ls with find because it is not good practice to parse ls.

How to substitute shell variables in complex text files

Looking, it turns out on my system there is an envsubst command which is part of the gettext-base package.

So, this makes it easy:

envsubst < "source.txt" > "destination.txt"

Note if you want to use the same file for both, you'll have to use something like moreutil's sponge, as suggested by Johnny Utahh: envsubst < "source.txt" | sponge "source.txt". (Because the shell redirect will otherwise empty the file before its read.)

Defining a variable with or without export

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.



Related Topics



Leave a reply



Submit