Execute command containing quotes from shell variable [duplicate] - bash

This question already has answers here:
Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate]
(3 answers)
Closed 6 years ago.
I'm currently having problems to execute a command from a shell variable.
In general the following works as expected:
COMMAND="echo A"
echo $COMMAND
$COMMAND
produces:
echo A
A
But if I do
COMMAND="su aUser -s /bin/bash -c 'echo A'"
echo $COMMAND
$COMMAND
I get
su aUser -s /bin/bash -c 'echo A'
Password:
A': -c: line 0: unexpected EOF while looking for matching `''
A': -c: line 1: syntax error: unexpected end of file
If I enter the line
su aUser -s /bin/bash -c 'echo A'
directly it works as expected.
It seems my assumption that $COMMAND is equal to entering the content as command directly is wrong.
Questions
1) Does anyone know how I can run the command from a variable?
2) What exactly is the difference between
COMMAND="command"
$COMMAND
and
command
?

Arrays are useful to keep your parameters whole:
command=(su aUser -s /bin/bash -c 'echo A')
and invoke it exactly like this:
"${command[#]}"

You need eval.
$ eval $VARIABLE

Related

call a bash function as root but gives 'unexpected end of file'

Why does the last example throw an error but the others work? Bash is invoked in any case.
#!/bin/bash
function hello {
echo "Hello! user=$USER, uid=$UID, home=$HOME";
}
# Test that it works.
hello
# ok
bash -c "$(declare -f hello); hello"
# ok
sudo su $USER bash -c "$(declare -f hello); hello"
# error: bash: -c: line 1: syntax error: unexpected end of file
sudo -i -u $USER bash -c "$(declare -f hello); hello"
It fail because of the -i or --login switch:
It seems like when debugging with -x
$ set -x
$ sudo -i -u $USER bash -c "$(declare -f hello); hello"
++ declare -f hello
+ sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME"
}; hello'
Now if doing it manually it cause the same error:
sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME"
}; hello'
Now lets just do a simple tiny change that make it work:
sudo -i -u lea bash -c 'hello ()
{
echo "Hello! user=$USER, uid=$UID, home=$HOME";}; hello'
The reason is that sudo -i runs everything like an interactive shell. And when doing so, every newline character from the declare -f hello is internally turned into space. The curly-brace code block need a semi-colon before the closing curly-brace when on the same line, which declare -f funcname does not provide since it expands the function source with closing curly brace at a new line.
Now lets make this behaviour very straightforward:
$ sudo bash -c 'echo hello
echo world'
hello
world
It executes both echo statements because they are separated by a newline.
but:
$ sudo -i bash -c 'echo hello
echo world'
hello echo world
It executes the first echo statement that prints everything as arguments because the newline has been replaced by a space.
It is the same code in all examples, so it should be ok.
Yes, "$(declare -f hello); hello" is always the same string. But it is processed differently by sudo su and sudo -i as found out by Lea Gris .
sudo -i quotes its arguments before passing them to bash. This quoting process seems to be undocumented and very poor. To see what was actually executed, you can print the argument passed to bash -c inside your ~/.bash_profile/:
Content of ~/.bash_profile
cat <<EOF
# is executed as
$BASH_EXECUTION_STRING
# resulting in output
EOF
Some examples of sudo -i's terrible and inconsistent quoting
Linebreaks are replaced by line continuations
$ sudo -u $USER -i echo '1
2'
# is executed as
echo 1\
2
# resulting in output
12
Quotes are escaped as literals
$ sudo -u $USER -i echo \'single\' \"double\"
# is executed as
echo \'single\' \"double\"
# resulting in output
'single' "double"
But $ is not quoted
$ sudo -u $USER -i echo \$var
# is executed as
echo $var
# resulting in output
Some side notes:
There might be a misunderstanding in your usage of su.
sudo su $USER bash -c "some command"
does not execute bash -c "echo 1; echo 2". The -c ... is interpreted by su and passed as -c ... to $USER's default shell. Afterwards, the remaining arguments are passed to that shell as well. The executed command is
defaultShellOfUSER -c "some command" bash
You probably wanted to write
sudo su -s bash -c "some command" "$USER"
Interactive shells behave differently
su just executes the command specified by -c. But sudo -i starts a login shell, in your case that login shell seems to be bash (this is not necessarily the case, see section above).
An interactive bash session behaves different from bash -c "..." or bash script.sh. An interactive bash sources files like .profile, .bash_profile, and enables history expansion, aliases, and so on. For a full list see the section Interactive Shell Behavior in bash's manual.

Executing 'bash -c' in 'docker exec' command

Context: I'm trying to write a shortcut for my daily use of the docker exec command. For some reasons, I'm experimenting the problem that my output is sometimes broken when I'm using a bash console inside a container (history messed up, lines overwrite each other as I'm writing, ...)
I read here that you could overcome this problem by adding some command before starting the bash console.
Here is a relevant excerpt of what my script does
#!/bin/bash
containerHash=$1
commandToRun='bash -c "stty cols $COLUMNS rows $LINES && bash -l"'
finalCommand="winpty docker exec -it $containerHash $commandToRun"
echo $finalCommand
$finalCommand
Here is the output I get:
winpty docker exec -it 0b63a bash -c "stty cols $COLUMNS rows $LINES && bash -l"
cols: -c: line 0: unexpected EOF while looking for matching `"'
cols: -c: line 1: syntax error: unexpected end of file
I read here that this had to do with parsing and expansion. However, I can't use a function or an eval command (or at least I didn't succeed in making it work).
If I execute the first output line directly in my terminal, it works without trouble.
How can I overcome this problem?
It's not Docker related, but Bash (In other words, the docker's part of the command works well, it's just bash grumbling on the container like it would grumble on your host):
Minimal reproducible error
cmd='bash -c "echo hello"'
$cmd
hello": -c: line 0: unexpected EOF while looking for matching `"'
hello": -c: line 1: syntax error: unexpected end of file
Fix
cmd='bash -c "echo hello"'
eval $cmd
hello
Answer
foo='docker exec -it XXX bash -c "echo hello"'
eval $foo
This will let you execute your command echo hello on your container, now if you want to add dynamic variables to this command (like echo $string) you just have to get rid of single quotes for double ones, to make this works you will have to escape inner double quotes:
foo="docker exec -it $container bash -c \"echo $variable\""
A complete example
FOO="Hello"
container=$1
bar=$2
cmd="bash -c \"echo $FOO, $bar\""
final_cmd="docker exec -it $container $cmd"
echo "running command: \"$final_cmd\""
eval $final_cmd
Let's take time to dig in,
$FOO is a static variable, in our case it works exactly like a regular variable, just to show you.
$bar is a dynamic variable which takes second command line argument as value
Because $cmd and $final_cmd uses only double quotes, variables are interpreted
Because we use eval $final_cmd command is well interpreted, bash is happy.
Finally, a usage example:
bash /tmp/dockerize.sh 5b02ab015730 world
Gives
running command: "docker exec -it 5b02ab015730 bash -c "echo Hello, world""
Hello, world

export variable within `/bin/bash -c` [duplicate]

This question already has answers here:
I can't seem to use the Bash "-c" option with arguments after the "-c" option string
(4 answers)
Closed 5 years ago.
I'm trying to export variable within the /bin/bash -c command.
This results in empty output:
/bin/bash -c "export FOO=foo; echo $FOO"
What would be the proper way to do that?
Since you double-quoted the command,
the $FOO got evaluated in your current shell,
not by the /bin/bash -c.
That is, what actually got executed was this:
/bin/bash -c 'export FOO=foo; echo '
Enclose in single-quotes:
/bin/bash -c 'export FOO=foo; echo $FOO'
An equivalent shorter form:
FOO=foo /bin/bash -c 'echo $FOO'

Bash judgement gets the unexpected result [duplicate]

This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 5 years ago.
This is the code of the my shell script:
#! /bin/bash
if ["$SHELL" = "/bin/bash"];then
echo "this is bash"
elif ["$SHELL" = "aa"];then
echo "this is aa"
else
echo "this is not /bin/bash, but $SHELL"
fi
why I execute the test_bash_03 script file gets the else result? shouldn't it be:this is bash ?
aircraftdeMacBook-Pro:bash_demo ldl$ ./test_bash_03
./test_bash_03: line 3: [/bin/bash: No such file or directory
./test_bash_03: line 5: [/bin/bash: No such file or directory
this is not /bin/bash, but /bin/bash
And I echo the $SHELL I also get the /bin/bash
aircraftdeMacBook-Pro:bash_demo ldl$ echo $SHELL
/bin/bash
You are missing a space after [ and before ].
The bash tries to execute a command named [/bin/bash instead of [ (which is test), then doesn't find that and has an exit code of 1 (false). So you end up in the else case.

bash for loop work in command line, but failed in script

When a run a for statement in debian bash command line, it works fine.
But when I run it in a sh script or run it with bash command, it's keeping report "error near unexpected token `do'"
Where is the difference?
[leon#www] ~/tmp $ for i in {1..10}; do echo $i; done
1
2
3
4
5
6
7
8
9
10
[leon#www] ~/tmp $ bash for i in {1..10}; do echo $i; done
-bash: syntax error near unexpected token `do'
BTW, all works fine in centos enviorment.
Use the -c option so that bash reads the commands from the string you pass in. Also, use single quotes around the command.
bash -c 'for i in {1..10}; do echo $i; done'
your bash command line ends with the first ;
so it gets executed separately as:
bash for i in {1..10};
do echo $i;
done
and man bash says command argument should be a file to load: bash [options] [file]
You can wrap all your script inside inverted commas or in a file. Because here, you're doing bash for i in {1..10} then do echo $i and so on. You should use -c option if you don't put it in a file.

Resources