why `sh -c "export a=1&&echo $a"` returns nothing? [duplicate] - linux

This question already has answers here:
Difference between single and double quotes in Bash
(7 answers)
Closed 2 years ago.
A)
first I run export a=1&&echo $a in linux terminal and I get 1.
[root#ip-172-31-16-92 ec2-user]# export a=1&&echo $a
1
B)
But when I run sh -c "export a=2&&echo $a" and I still get 1 instead of 2.
[root#ip-172-31-16-92 ec2-user]# sh -c "export a=2&&echo $a"
1
What happend? If sh -c "export a=2&&echo $a" itself is a child process of the terminal,it should have its own environment, first it should make a copy of his father environment,so a=1,but when it execute export a=2 ,environment a should be set to 2,then echo $a should be 2.But it returns 1,what happend?

Try this one and and you'll see it works.
sh -c 'export a=2&&echo $a'
When enclosed in double-quotes, $a is expanded to its current value by the shell before invoking sh.

Related

Linux variable value not working when running from script [duplicate]

This question already has answers here:
Difference between single and double quotes in Bash
(7 answers)
Closed 2 years ago.
This is MyScript.sh
I want to execute a script as a text inside my script, I have tried to do it like so:
bash -c "
#!/bin/bash
STR=6
echo $STR
"
Prints empty line.
I tried replacing bash -c with sh -c or eval, all options acts the same, why is that and how can it be solved?
Using single quotes to avoid interpretation in the current shell:
$ cat myscript.sh
bash -c 'STR=6; echo $STR'
./myscript.sh
6

remote ssh command: first echo output is lost

I'm trying to run several commands on a remote box via ssh 1-liner call by specifying them as semicolon-separated string passed to "bash -c". It works for some cases, but does not for others. Check this out:
# Note: the "echo 1" output is lost:
bash-3.2$ ssh sandbox bash -c "echo 1; echo 2; echo 3"
2
3
# Note: first echo is ignored again
bash-3.2$ ssh sandbox bash -c "echo 0; echo 1; echo 2; echo 3"
1
2
3
# But when we run other commands (for example "date") then nothing is lost
bash-3.2$ ssh sandbox bash -c "date; date;"
Wed Nov 7 20:27:55 UTC 3018
Wed Nov 7 20:27:55 UTC 3018
What am I missing?
Remote OS: Ubuntu 16.04.5 LTS
Remote ssh: OpenSSH_7.2p2 Ubuntu-4ubuntu2.4, OpenSSL 1.0.2g 1 Mar 2016
Local OS: macOS High Sierra Versoin 10.13.3
Local ssh: OpenSSH_7.6p1, LibreSSL 2.6.2
Update:
The above example is heavily simplified picture of what I'm trying to do.
The practical application is actually to generate few files on remote box by echo'ing into remote filesystem:
#!/bin/bash
A=a
B=b
C=c
ssh -i ~/.ssh/${REMOTE_FQDN}.pem ${REMOTE_FQDN} sudo bash -c \
"echo $A > /tmp/_a; echo $B > /tmp/_b; echo $C > /tmp/_c;"
After I run the above script and go to remote box to check results I see the following:
root#sandbox:/tmp# for i in `find ./ -name '_*'|sort`; do echo "----- ${i} ----"; cat $i; done
----- ./_a ----
----- ./_b ----
b
----- ./_c ----
c
As you can see the 1st "echo" command generated blank file!
To be clear, there's 3 shells at work here - the one that interprets ssh, your local shell that is; the one that ssh will be automatically running for you, and the bash you're invoking explicitly.
The reason the 1 is "disappearing" is that the shell that interprets the ssh command "eats" the quotes around the -c arguments, and then the shell on the other side of ssh splits the arguments at whitespace. So it ends up looking like bash -c echo 1 ; echo 2; echo 3. In turn, -c just gets echo, which echos an empty line; 1 becomes the value of that shell's $1, which isn't used. Then the inner bash returns, and the direct ssh shell runs the echo 2; echo 3 normally.
Consider this:
$ ssh xxx bash -c "'echo 1'; echo 2; echo 3"
1
2
3
where echo 1 is protected within the ssh arguments, so the 2nd level ssh shell is passed bash -c 'echo 1'; echo 2; echo 3. The innermost 3rd level shell echos 1, and then the 2nd level ssh shell echos 2 and 3.
Here is yet another interesting permutation:
$ ssh xxx bash -c "'echo 1; echo 2; echo 3'"
1
2
3
here, the inner shell gets all the echos as they're kept grouped within the first shell by " and within the second shell by '.
In general, shell scripts to pass arguments to shell scripts that run shell scripts can be pretty difficult to build. I'd recommend you change your technique a bit to save yourself a lot of effort. Instead of passing the shell commands as command line parameters to the ssh argument, instead provide it through the standard input to the shell. Consider using a pipeline like this, which avoids recursive shell interpretation:
$ echo "echo 1; echo 2; echo 3" | ssh -T xxx
1
2
3
( Here, the -T is just to supress ssh complaining of lack of pseudoterminal).
All the arguments to ssh are combined into a single whitespace-separate string passed to sh -c on the remote end. This means that
ssh sandbox bash -c "echo 1; echo 2; echo 3"
results in the execution of
sh -c 'bash -c echo 1; echo 2; echo 3'
Note the loss of quotes; ssh got the three arguments bash, -c, and echo 1; echo 2; echo 3 after quote removal. On the remote end, bash -c echo 1 just executes echo, with $0 in the shell set to 1.
The command
ssh sandbox bash -c "date; date;"
is treated the same way, but now the first command contains no whitespace. The result on the remote end is
sh -c 'bash -c date; date;'
which means first a new instance of bash runs the date command, followed by the date command being executed directly by sh.
In general, it's a bad idea to use ssh's implicit concatenation. Always pass the command you want executed as a properly escaped single argument:
ssh sandbox 'bash -c "echo 1; echo 2; echo 3"'

Why is my shell (sh) variable in command mode not exported?

If I run a shell command like this my exported variable is not visible.
sh -c "export x=100; echo x is $x"
I would expect that it outputs "x is 100" but it just say "x is ". If I run this is in an interactive mode it works like expected.
My shell version is: GNU bash, version 3.2.51(1)-release (x86_64-suse-linux-gnu)
The $x get interpreted by the current shell, not the sh shell that's you're starting.
Escape it with a backslash:
sh -c "export x=100; echo x is \$x"
Or use single quotes to prevent the shell from interpreting variables:
sh -c 'export x=100; echo x is $x'

Linux bash know the script path which includes library script [duplicate]

This question already has answers here:
How do I know the script file name in a Bash script?
(25 answers)
Closed 7 years ago.
I have a library script named A and a script B, C which includes A with
. ../../../A
The problem is how A can know which time I run ./B.sh or ./C.sh, example:
if(run ./B.sh)
echo "B (file path) is calling"
else
echo "C (file path) is calling"
You can use $0 to determine the command that was executed:
A.sh:
echo $0
B.sh:
. ./A.sh
When run:
$ sh B.sh
B.sh
$ sh A.sh
A.sh
It will only give the command that was executed, not the arguments:
$ sh B.sh one two three
B.sh

linux - shellscript - "$#" [duplicate]

This question already has answers here:
What does the $# construct mean in bash? [duplicate]
(3 answers)
Closed 7 years ago.
I have a shellscript with the following lines:
set -o nounset
set -o errexit
set -o xtrace
if [ "$#" -ne 0 ]
then
echo 'A message'
exit 1
fi
Can somebody explain the commands, in particular the setter and the "$#" portion?
$# is the number of arguments passed to the script.
So this
if [ "$#" -ne 0 ]
check ensures that if no arguments are passed then the script exits,
which implies that the script expects one or more arguments.
In a simple script called my_script, have this:
#!/bin/bash
echo $#
and run with:
$ ./my_script # prints 0
$ ./my_script a b cde # prints 3
$ ./my_script 1 2 3 4 # prints 4
The set built-in options:
set -o unset (equivalent to set -u): Treats unset variable as error.
set -o errexit (equivalent to set -e): Exits immediately on error.
set -o xtrace (equivalent to set -x): Displays the expanded command. Typically used to to debug shell scripts.
Consider a simple script called opt to demonstrate this:
#!/bin/bash
set -e
set -u
set -x
cmd="ps $$"
${cmd}
echo $var # 'var' is unset. So it's an "error". Since we have
# 'set -o e', the script exits.
echo "won't print this
"
outputs something like:
+ cmd='ps 2885'
+ ps 2885
PID TTY STAT TIME COMMAND
2885 pts/1 S+ 0:00 /bin/bash ./s
./s: line 9: var: unbound variable
The first two lines in the output (starting with +) are due to set -x.
The next two are the result of running the ${cmd}.
The next line is the error, happened as the result of set -u.
You can read more about the set built-in options here.
In Bash, $# keeps the number of command line arguments. In your case, the conditional part will fire only when there are some.
I believe very similar question was answered here, second or third answer matching your problem.

Resources