Why getting blank output when running "/bin/sh" with "-c" option [duplicate] - bash

This question already has answers here:
How to use positional parameters with "bash -c" command?
(2 answers)
Closed 14 days ago.
I am running /bin/sh with -c option as below but getting a blank output:
[root#dockerhost dproj]# /bin/sh -c echo helloworld
[root#dockerhost dproj]#
Why the above command is not printing helloworld?
I have tried to read the man page but not able to understand anything.
From man sh:
-c Read commands from the command_string operand.
Set the value of special parameter 0
(see Section 2.5.2, Special Parameters) from the
value of the command_name operand and the positional
parameters ($1, $2, and so on) in sequence from the
remaining argument operands.
No commands shall be read from the standard input.

/bin/sh -c echo helloworld runs the command echo, which prints a blank line. You meant to type:
/bin/sh -c "echo helloworld"
which runs the command echo helloworld.
The -c option to sh causes the first non-option argument to be treated as a string of commands to run in a new shell. The remaining arguments are used to fill in the that shell's numbered arguments, starting with $0 (the "name" under which the shell process is running.) These arguments are not automatically parsed to any utility executed by that shell.
So if you invoke sh with /bin/sh -c echo helloworld:
the input passed to the shell interpreter sh is simply echo
helloworld becomes $0 in the sh session.
Normally, $0 in a shell invocation should be the name of the shell; that's what it will be set to by default:
bash$ /bin/sh
$ echo $0
/bin/sh
$ exit
bash$ echo $0
bash
Since the command given to the shell by -c is interpreted as though it were input to the shell itself, you can use $n in the command in order to refer to the extra arguments to the shell interpreter. If you want to do that, you need to remember to single-quote the -c option argument so that it's contents are not interpreted by the outer shell. Perhaps studying the following will help:
bash$ /bin/sh -c 'echo $0' fakename # Correctly single-quoted
fakename
bash$ /bin/sh -c "echo $0" fakename # Here, $0 is expanded by the outer shell
bash
bash$ /bin/sh -c 'echo "$#"' sh arg1 arg2 arg3 # $0 is not part of $#
arg1 arg2 arg3

Related

Empty Positional Argument when using /bin/bash -c <script>

I'm trying to launch a script with /bin/bash -c with positional arguments but can't figure out the following issue:
Suppose I have test.sh as follows:
#!/bin/bash
echo $0
echo $1
> ./test.sh a
./test.sh
a
> /bin/bash -c ./test.sh a
./test.sh
Why does the second one return an empty position argument for $1? Based on the man page:
-c 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, the first argument is assigned to $0 and any remaining arguments are assigned to the positional
parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.
It seems like "a" should be assigned to $0 at least, which is not what I saw. /bin/bash -c 'echo $0' a works as expected. Thanks!
The string after -c acts like a miniature script, and the arguments after that are passed to it as $0, $1, $2, etc. For example:
$ bash -c 'echo "\$0=$0, \$1=$1, \$2=$2"' zero one two
$0=zero, $1=one, $2=two
(Note: it's important that the mini-script is in single-quotes; without them the references to $0 would be expanded by your interactive shell before they even get passed to the bash -c command.)
In your case, the mini-script runs another script (./test.sh), but doesn't pass on the arguments. If you wanted to pass them on, you'd do something like this:
$ bash -c './test.sh "$1" "$2"' zero one two
./test.sh
one
If the script had bothered to print its $2 here, it would've gotten "two". It doesn't help to pass on $0, because for a real script that's automatically set to the actual command used to run the script.
bash [long-opt] [-abefhkmnptuvxdBCDHP] [-o option] [-O shopt_option]
-c string [argument ...]
-c supposed to be followed by a string, so you may quote ./test.sh a like:
$ /bin/bash -c "./test.sh a"
./test.sh
a
The -c option does not collect all following arguments of the bash command, but just uses the first non-option argument, which in your case is the one immediately following it. I don't see why you want to use -c here. I would write your command as
/bin/bash test.sh a
Since in this case, no PATH search is involved, you can also omit the ./ part. In fact, test.sh doesn't even need to be executable here.

What are "double sh" calls?

Can you explain what are (as I call it) "double sh calls", how they work and the advantages of such constructs ?
EDIT1 : Maybe an example can help you understand what I'm talking about.
$ set -- --no-resume-playback https://www.facebook.com/TBN/videos/1580372468665943/
$ sh -c 'echo $*'
$
$ sh -c 'echo $*' sh "$*"
--no-resume-playback https://www.facebook.com/TBN/videos/1580372468665943/
I couln't find any example of this in the man :
$ man bash | col -b | egrep "sh -c [\"']"
$
N.B.: If the title of my question is ambiguous, please help me change it because I don't know how to call such shell calls.
man bash describes -c and its arguments:
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.
In this case, the command_string is echo $*, and the positional arguments are set in this way:
$0 = sh
$1 = "$*" = '--no-resume-playback https://www.facebook.com/TBN/videos/1580372468665943/'

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

Pass all args to a command called in a new shell using bash -c

I've simplified my example to the following:
file1.sh:
#!/bin/bash
bash -c "./file2.sh $#"
file2.sh:
#!/bin/bash
echo "first $1"
echo "second $2"
I expect that if I call ./file1.sh a b to get:
first a
second b
but instead I get:
first a
second
In other words, my later arguments after the first one are not getting passed through to the command that I'm executing inside a new bash shell. I've tried many variations of removing and moving around the quotation marks in the file1.sh file, but haven't got this to work.
Why is this happening, and how do I get the behavior I want?
(UPDATE - I realize it seems pointless that I'm calling bash -c in this example, my actual file1.sh is a proxy script for a command that gets called locally to run in a docker container so it's actually docker exec -i mycontainer bash -c '')
Change file1.sh to this with different quoting:
#!/bin/bash
bash -c './file2.sh "$#"' - "$#"
- "$#" is passing hyphen to populate $0 and $# is being passed in to populate all other positional parameters in bash -c command line.
You can also make it:
bash -c './file2.sh "$#"' "$0" "$#"
However there is no real need to use bash -c here and you can just use:
./file2.sh "$#"

Passing arguments to /bin/bash via a bash script

I am writing a bash script that takes a number of command line arguments (possibly including spaces) and passes all of them to a program (/bin/some_program) via a login shell. The login shell that is called from the bash script will depend on the user's login shell. Let's suppose the user uses /bin/bash as their login shell in this example... but it might be /bin/tcsh or anything else.
If I know how many arguments will be passed to some_program, I can put the following lines in my bash script:
#!/bin/bash
# ... (some lines where we determine that the user's login shell is bash) ...
/bin/bash --login -c "/bin/some_program \"$1\" \"$2\""
and then call the above script as follows:
my_script "this is too" cool
With the above example I can confirm that some_program receives two arguments, "this is too" and "cool".
My question is... what if I don't know how many arguments will be passed? I'd like to pass all the arguments that were sent to my_script along to some_program. The problem is I can't figure out how to do this. Here are some things that don't work:
/bin/bash --login -c "/bin/some_program $#" # --> 3 arguments: "this","is","too"
/bin/bash --login -c /bin/some_program "$#" # --> passes no arguments
Quoting the bash manual for -c:
If the -c option is present, then commands are read from string. If there are arguments after the string, they are assigned to the positional parameters, starting with $0.
Works for me:
$ cat x.sh
#!/bin/bash
/bin/bash --login -c 'echo 1:$1 2:$2 3:$3' echo "$#"
$ ./x.sh "foo bar" "baz" "argh blargh quargh"
1:foo bar 2:baz 3:argh blargh quargh
I don't know how you arrived at the "passes no arguments" conclusion, maybe you missed the $0 bit?
Avoid embedding variables into other scripts, pass them on as arguments instead. In this case:
bash --login -c 'some_program "$#"' some_program "$#"
The first argument after -c '...' is taken as $0, so I just put in some_program there.
On a side note, it's an odd requirement to require a login shell. Doesn't the user log in?

Resources