Variable substitution in script file and setting a new variable - bash

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

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\""

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

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

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}'

Why does bash -c "false; echo $?" print 0?

I'm building a script that tries to run some commands on a server (over SSH), and writes on the screen whether they were successful.
I noticed a strange behaviour for $?, namely not being 0 when the previous command failed.
Initially, I had:
ssh <user>#<server> <<EOF
false
if [ $? -ne 0 ]; then
echo "It failed"
else
echo "It worked"
fi
EOF
If I copy and paste the script inside <<EOF and EOF, it prints It failed. If I run it with the ssh part, it prints It worked. To simplify, I then tried:
ssh <user>#<server> <<EOF
false
echo $?
EOF
Same thing happened. If I copy-paste or type the commands inside, it prints 1, but if I run all of it (including the ssh), it prints 0.
The same error happens if I directly use bash this way
bash <<EOF
false
echo $?
EOF
or
bash -c "false; echo $?"
Why does this happen? How can I check if the previous command failed in this context?
This is due to variable expansion. When you write bash -c "false; echo $?" the variable is expanded before the commands are ran. So your command is exactly like bash -c "false; echo 0;" if your previous command was successful.
To have the right result try bash -c 'false; echo $?'. This prevents variable expansion, it will be expanded when interpreted.
For the here document version do:
bash << 'EOF'
false
echo $?
'EOF'
In this case you need to quote the delimiter of the here document. But beware that the syntax you must use is the syntax for the shell you use to type the command. In the example, I was in tcsh , and it requires to use the exact same opening and closing delimiter. Under bash, the closing delimiter must be the opening one after quote removal.

Inline bash script variables

Admittedly, I'm a bash neophyte. I always want to reach for Python for my shell scripting purposes. However, I'm trying to push myself to learn some bash. I'm curious why the following code doesn't work.
sh -c "F=\"123\"; echo $F"
It doesn't work because variable expansion in the double-quoted string happens before the command is called. That is, if I type:
echo "$HOME"
The shell transforms this into:
echo "/home/lars"
Before actually calling the echo command. Similarly, if you type:
sh -c "F=\"123\"; echo $F"
This gets transformed into:
sh -c "F=\"123\"; echo"
Before calling a the sh command. You can use single quotes to inhibit variable expansion, for example:
sh -c 'F="123"; echo $F'
You can also escape the $ with a backslash:
sh -c "F=\"123\"; echo \$F"
Not an answer to the core question, but if anyone is looking to do this inline in a (subjectively) more elegant way than bash -c:
( export MY_FLAG="Hello"; echo "$MY_FLAG" )
The syntax is nicer, no escape chars etc.

Resources