bash -c variable does not get assigned - linux

I am trying to execute the following command:
$ bash -c "var='test' && echo $var"
and only an empty line is being printed.
If I execute the same command without bash -c
$ var='test' && echo $var
test
the value assigned to $var is being printed. Could someone explain why I can't assign variables in the first example?

Double quotes expand variables, so your command is expanded to
bash -c "var='test' && echo"
if $var is empty when you run it. You can verify the behaviour with
var=hey
bash -c "var='test' && echo $var"
Switch the quotes:
bash -c 'var="test" && echo $var'

Related

Bash discards command line arguments when passing to another bash shell

I have a big script (call it test) that, after stripping out the unrelated parts, comes down to just this using which I can explain my question:
#!/bin/bash
bash -c "$#"
This doesn't work as expected. E.g. ./test echo hi executes the only the echo and the argument disappears!
Testing with various inputs I can see only $1 is passed to bash -c ... and rest are discarded.
But if I use a variable like:
#!/bin/bash
cmd="$#"
bash -c "$cmd"
it works as expected for all inputs.
Questions:
1) I would like to understand why the double quotes don't "pass" the entire command line arguments to bash -c .... What am I missing here (that it works perfectly fine when using an intermediate variable)?
2) Why does bash discard the rest of the arguments (except $1) without any error messages?
For example:
bash -c "ls" -l -a hi hello blah
simply runs echo and hi hello blah doesn't result in any errors at all?
(If possible, please refer to the bash grammar where this behaviour is documented).
1) I would like to understand why the double quotes don't "pass" the entire command line arguments to bash -c .... What am I missing here (that it works perfectly fine when using an intermediate variable)?
From info bash #:
#
($#) Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands
to a separate word. That is, "$#" is equivalent to "$1" "$2" ....
Thus, bash -c "$#" is equivalent to bash -c "$1" "$2" .... In the case of ./test echo hi invocation, the expression is expanded to
bash -c "echo" "hi"
2) Why does bash discard the rest of the arguments (except $1) without any error messages?
Bash actually doesn't discard anything. From man bash:
If the -c option is present, then commands are read from the first non-option argument command_string. If there are arguments after the command_string, they are assigned to the positional parameters, starting with $0.
Thus, for the command bash -c "echo" "hi", Bash passes "hi" as $0 for the "echo" script.
bash -c "ls" -l -a hi hello blah
simply runs echo and hi hello blah doesn't result in any errors at all?
According to the rules mentioned above, Bash executes "ls" script and passes the following positional parameters to this script:
$0: "-l"
$1: "-a"
$2: "hi"
$3: "hello"
$4: "blah"
Thus, the command actually executes ls, and the positional parameters are unused in the script. You can use them by referencing to the positional parameters, e.g.:
$ set -x
$ bash -c "ls \$0 \$1 \$3" -l -a hi hello blah
+ bash -c 'ls $0 $1 $3' -l -a hi hello blah
ls: cannot access hello: No such file or directory
You should be using $* instead of $# to pass command line as string. "$#" expands to multiple quoted arguments and "$*" combines multiple arguments into a single argument.
#!/bin/bash
bash -c "$*"
Problem is with your $# it executes:
bash -c echo hi
But with $* it executes:
bash -c 'echo hi'
When you use:
cmd="$#"
and use: bash -c "$cmd" it does the same thing for you.
Read: What is the difference between “$#” and “$*” in Bash?

linux env command does not work as expected?

These 2 lines work as expected:
$ env NEW=hello bash -c "env | grep hello"
NEW=hello
$ env NEW=hello bash -c "echo $PATH"
/bin:/usr/bin
But I don't know why the following does not work (as expected).
$ env NEW=hello bash -c "echo $NEW"
Any suggestion?
$ env NEW=hello bash -c "echo $NEW"
You're using double-quotes on the argument to bash here, so the $NEW in the argument is expanded by your current shell, not by the bash command you're executing. Since $NEW isn't set in your current shell, the command is expanded to bash -c "echo ".
Use single-quotes on the argument to solve this:
$ env NEW=hello bash -c 'echo $NEW'
hello

Linux bash script: share variable among terminal windows

If I do this:
#!/bin/bash
gnome-terminal --window-with-profile=KGDB -x bash -c 'VAR1=$(tty);
echo $VAR1; bash'
echo $VAR1
How can I get the last line from this script to work? I.e., be able to access the value of $VAR1 (stored on the new terminal window) from the original one? Currently, while the first echo is working, the last one only outputs an empty line.
The short version is that you can't share the variable. There's no shared channel for that.
You can write it to a file/pipe/etc. and then read from it though.
Something like the following should do what you want:
#!/bin/bash
if _file=$(mktemp -q); then
gnome-terminal --window-with-profile=KGDB -x bash -c 'VAR1=$(tty); echo "$VAR1"; declare -p VAR1 > '\'"$_file"\''; bash'
cat "$_file"
. "$_file"
echo "$VAR1"
fi

How can I get the command that was executed at the command line?

If I call a script this way:
myScript.sh -a something -b anotherSomething
Within my script is there a way to get the command that called the script?
In my script on the first line I'm trying to use:
lastCommand=!!
echo $lastCommand
But the result is always null.
If I do echo !! the only thing that prints to the console is !!, but from the command line if I do echo !! I get the last command printed.
I've also tried:
echo $BASH_COMMAND
but I'm getting null here as well. Is it because the script is called in a subshell and thus there is no previous command stored in memory for the subshell?
The full command which called the script would be "$0" "$#", that is, the command itself followed by all the arguments quoted. This may not be the exact command which was run, but if the script is idempotent it can be run to get the same result:
$ cat myScript.sh
#!/usr/bin/env bash
printf '%q ' "$0" "$#"
printf '\n'
$ ./myScript.sh -a "foo bar" -b bar
./myScript.sh -a foo\ bar -b bar
Here's my script myScript.sh
#!/bin/bash
temp=`mktemp`
ps --pid $BASHPID -f > $temp
lastCommand=`tail -n 1 $temp | xargs | cut -d ' ' -f 8-`
rm $temp
echo $lastCommand
or
#!/bin/sh
last=`cat /proc/$$/cmdline | xargs -0`
echo $last

bash init script - ambiguous redirect

I am trying to execute a hadoop job from an init script and output to a log file. The commands are as follows
log_file="/home/hadoop/log_`date +%Y%m%d-%H:%M:%S`.log"
echo $log_file
su - hadoop -c 'java -jar /home/hadoop/testing.jar > $log_file'
But I keep getting -bash: $log_file: ambiguous redirect.
What is wrong with my statement?
The problem is that you are using single quotes around your command, so the variable $log_file is not being expanded.
edit
As clarified by William Pursell's comment, $log_file is being expanded but by the wrong shell. See this example (I am using set -x to show the commands being evaluated):
$ log_file="/dir/file.log"
$ set -x
$ su - -c 'set -x; echo $log_file'
+ su - -c 'set -x; echo $log_file' # no expansion of $log_file yet
+ echo # expansion here, no variable $log_file
$ su - -c "set -x; echo $log_file"
+ su - -c 'set -x; echo /dir/file.log' # expansion here, $log_file exists
+ echo /dir/file.log
/dir/file.log
solution
Try using double quotes instead, so $log_file is expanded at the correct point:
su - hadoop -c "java -jar /home/hadoop/testing.jar > $log_file"

Resources