Why doesn't nohup sh -c "..." store variable? - bash

Why doesn't "nohup sh -c" stores variable?
$ nohup sh -c "RU=ru" &
[1] 17042
[1]+ Done nohup sh -c "RU=ru"
$ echo ${RU}
$ RU=ru
$ echo ${RU}
ru
How do I make it such that it store the variable value so that I can use in a loop later?
For now, it's not recording the value when I use RU=ru instead my bash loop, i.e.
nohup sh -c "RU=ru; for file in *.${RU}-en.${RU}; do some_command ${RU}.txt; done" &
It doesn't work within the sh -c "..." too, cat nohup.out outputs nothing for the echo:
$ nohup sh -c "FR=fr; echo ${FR}" &
[1] 17999
[1]+ Done nohup sh -c "FR=fr; echo ${FR}"
$ cat nohup.out

Why doesn't "nohup sh -c" stores variable?
Environment variables only live in a process, and potentially children of a process. When you run sh -c you are creating a new child process. Any environment variables in that child process cannot "move up" to the parent process. That's simply how shells and environment variables work.
It doesn't work within the sh -c "..." too, cat nohup.out outputs
nothing for the echo"
The reason for this is that you are using double quotes. When you use double quotes, the shell does variable expansion before running the command. If you switch to single quotes, the variable won't be expanded until the shell command runs:
nohup sh -c 'FR=fr; echo ${FR}'

Related

Using /bin/bash -l -c with concatenated commands does not pass environment variables

I'm trying to execute a series of bash commands using /bin/bash -l -c as follows:
/bin/bash -l -c "cmd1 && cmd2 && cmd3..."
What I notice is that if cmd1 happens to export an environment variable, it is not seen by cmd2 and so on. If I run the same concatenated commands without the /bin/bash -l -c option, it just runs fine.
For example, this does not print the value of MYVAR:
$/bin/bash -l -c "export MYVAR="myvar" && echo $MYVAR"
whereas, the following works as expected
$export MYVAR="myvar" && echo $MYVAR
myvar
Can't find why using /bin/bash -l -c won't work here? Or are there any suggestion how to make this work with using /bin/bash -l -c?
variables are exported for child processes, parent process environment can't be changed by child process so consecutive commands (forking sub processes can't see variables exported from preceeding command), some commands are particular becuase don't fork new process but are run inside current shell process they are builtin commands only builtin commands can change current process environment export is a builtin, for more information about builtins man bash /^shell builtin.
Otherwise expansion is done during command parsing:
/bin/bash -l -c "export MYVAR="myvar" && echo $MYVAR"
is one command (note the nested quotes are not doing what you expect because are closing and opening quoted string, myvar is not quoted and may be split). So $MYVAR is expanded to current value before assigned to myvar.
export MYVAR="myvar" && echo $MYVAR
are two commands and $MYVAR because && is bash syntax (not literal like "&&"), is expanded after being assigned.
so that $ have a literal meaning (not expanded) use single quotes or escape with backslash.
/bin/bash -l -c 'export MYVAR="myvar" && echo "$MYVAR"'
or
/bin/bash -l -c "export MYVAR=\"myvar\" && echo \"\$MYVAR\""

Variable substitution in script file and setting a new variable

I'm attempting to run a command in a shell script and set a variable with the resulting process id. Stripped down to the relevant parts, I have:
#!/bin/bash
USER=myAppUser
PATH_TO_APP=/opt/folder/subfolder
PID=`su - $USER -c 'nohup $PATH_TO_APP/myapp --option > /dev/null 2>&1 & echo $!'`;
echo $PID
I understand that I need to use double quotes around the nohup command for variable substitution, but if I do, PID is not being set. If I use double quotes and hardcode the PATH_TO_APP it will execute and set PID. I'm guessing it's a problem with the combination of the back tick and single/double quotes.. but I'm not sure what the solution is.
Put the command in double quotes so that $PATH_TO_APP will be substituted, and then escape the $ in $! so it will be included literally in the argument to su, and then processed by the subshell.
PID=$(su - $USER -c "nohup $PATH_TO_APP/myapp --option > /dev/null 2>&1 & echo \$!")
echo $PID

How to save PID to variable in bash -c?

I'm having trouble with saving the PID into a variable when I'm using bash -c. For example:
bash -c "PID=$$; echo $PID"
In this case the output is empty. How to save the child PID (the PID of the command inside the double quotes) now in the variable PID
just use simple quotes or you expression is evaluated inside your current command line (too soon) and not in the child bash command, and PID isn't defined yet at this moment and you're actually passing
bash -c "PID=4353; echo"
(where 4353 is the pid of the current bash process)
Someone noted that it's not clear if you want to pass parent pid or child pid
to pass parent pid, fix it like this (only the part within double quotes is evaluated before calling bash child process):
bash -c "PID=$$; "'echo $PID'
to pass child pid, fix it like this (nothing is evaluated in the current shell, same trick used for awk scripts):
bash -c 'PID=$$; echo $PID'
set -x is useful for debugging and will show the actual command you end up running prefixed with +:
$ set -x
$ bash -c "PID=$$; echo $PID"
+ bash -c 'PID=1900; echo '
And indeed, in that command you would expect empty output. This happens because $$ and $PID are substituted before bash is called.
To avoid this, you can single quote the string or escape the "$"s:
$ set -x
$ bash -c 'PID=$$; echo $PID'
+ bash -c 'PID=$$; echo $PID'
1925
$ bash -c "PID=\$\$; echo \$PID"
+ bash -c 'PID=$$; echo $PID'
1929

Echo variable using sudo bash -c 'echo $myVariable' - bash script

I want to echo a string into the /etc/hosts file. The string is stored in a variable called $myString.
When I run the following code the echo is empty:
finalString="Hello\nWorld"
sudo bash -c 'echo -e "$finalString"'
What am I doing wrong?
You're not exporting the variable into the environment so that it can be picked up by subprocesses.
You haven't told sudo to preserve the environment.
\
finalString="Hello\nWorld"
export finalString
sudo -E bash -c 'echo -e "$finalString"'
Alternatively, you can have the current shell substitute instead:
finalString="Hello\nWorld"
sudo bash -c 'echo -e "'"$finalString"'"'
You can do this:
bash -c "echo -e '$finalString'"
i.e using double quote to pass argument to the subshell, thus the variable ($finalString) is expanded (by the current shell) as expected.
Though I would recommend not using the -e flag with echo. Instead you can just do:
finalString="Hello
World"

Subshells and PIDs: Why do $$ and \$\$ sometimes match under nested sh -c?

I know this is an artificially complicated example, but why are both PIDs the same in the first line, while (as expected, to me at least) the two other lines yield different PIDs?
$ sh -c 'sh -c "echo $$ \$\$"'
4500 4500
$ sh -c 'sh -c "echo $$ \$\$"; true'
4596 5060
$ sh -c 'true; sh -c "echo $$ \$\$"'
4728 2868
Thanks!
For me in bash 4.1.5, the output of first line is:
sh -c 'sh -c "echo $$ \$\$"'
4063 4064
as expected - values are different.
Also tested on ash, sh, and zsh.
It must be some tricky optimization.
Update:
in bash 3.2 there is and "ONESHOT" feature:
see comment in the shell.c:1243:
#if defined (ONESHOT)
/* Run one command, given as the argument to the -c option. Tell
parse_and_execute not to fork for a simple command. */

Resources