Passing arguments to /bin/bash via a bash script - bash

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?

Related

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

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

Bash pass argument to --init-file script

I'm running a shell script using bash --init-file script.sh that runs some commands, then leaves an interactive session open. How can I pass arguments to this init file from the process that runs the initial bash command? bash --init-file 'script.sh arg' doesn't work.
Interestingly, if the script contains echo "$# $*", passing an argument as I did above causes it to print nothing, while not passing an argument prints '0'.
Create a file with the content:
#!/bin/bash
script.sh arg
Pass that file to bash: bash --init-file thatfile
I'd like the arg to come from the command that runs bash with the
Create a file from the command line and pass it:
arg="$1"
cat >thatfile <<EOF
$(declare -p arg)
script.sh \"\$arg\"
EOF
bash --init-file thatfile
You might be interested in researching what is a process substitution in bash.

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.

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 "$#"

How to specify zeroeth argument

I'm writing a bash script that starts the tcsh interpreter as a login shell and has it execute my_command. The tcsh man page says that there are two ways to start a login shell. The first is to use /bin/tcsh -l with no other arguments. Not an option, because I need the shell to execute my_command. The second is to specify a dash (-) as the zeroeth argument.
Now the bash exec command with the -l option does exactly this, and in fact the following works perfectly:
#!/bin/bash
exec -l /bin/tcsh -c my_command
Except... I can't use exec because I need the script to come back and do some other things afterwards! So how can I specify - as the zeroeth argument to /bin/tcsh without using exec?
You can enclose the exec command into a sub-shell of your script.
#!/bin/bash
(exec -l /bin/tcsh -c my_command)
# ... whatever else you need to do after the command is done
You can write a wrapper (w.sh) script that contains:
#!/bin/bash
exec -l /bin/tcsh -c my_command
and execute w.sh in your main script.

Resources