I am invoking an executable from inside a shell script. The scenario which I am coding is different, but I have written a dummy executable and a script to show what I am facing
Script :
#!/bin/sh -h
MYARGS=""
for arg in "$@"; do
shift
case $arg in
*)
pattern=" "
if [[ $arg =~ $pattern ]]; then
MYARGS="$MYARGS \"$arg\""
else
MYARGS="$MYARGS $arg"
fi
;;
esac
done
echo $MYARGS
./a.out $MYARGS
Program:
#include "iostream"
int main (int argc, char *argv[])
{
for (int i = 0; i < argc; i++)
{
std::cout << argv[i] << std::endl;
}
}
Now, I don't want to use $@ as I have to filter some arguments to be passed to the main program. But, when I try the above piece with space separated string as argument I get following output:
Command : ./a.out "a b"
Output :
./a.out
a b
Command: a.sh "a b"
Output :
"a b"
./a.out
"a
b"
The problem is that in second case arguments are automatically split and 'a' and 'b' come as different arguments even though I am preserving the quotes ""
What can be the solution to this?
Once a quote gets into a bash variable, it is just a character. Don't confuse what you type with what the variable contains. In this regard, bash is no different from C.
const char* v = "a b";
printf("%s\n", v); // => a b
const char* w = "\"a b\"";
printf("%s\n", w); // => "a b"
Bash is just the same:
$ v="a b"
$ echo "$v"
a b
$ w="\"a b\""
$ echo "$w"
"a b"
In the above, I put quotes around my variable expansions, which you should always do. (Well, almost always. But do it unless you have a really good reason, and can explain your reason.)
The reason I quote the expansion is to prevent the contents of the string from being word split after the expansion. Word-splitting splits the string into separate words at whitespace (unless $IFS has been set to something different). That's it. It doesn't look through the string for special characters, because there aren't any. Because once a quote is in a string, it is just another character. (And the same goes for all other special characters, like $
and \
.
You can (and people may recommend to you) use complicated constructs using eval
or eval-like operations (like bash -c
). Those will almost always end in grief. The simplest, best, and recommended solution is to use an array:
myargs=()
for arg in "$@"; do
shift
case "$arg" in
*) myargs+=("$arg") ;;
esac
done
echo "${myargs[@]}"
./a.out "${myargs[@]}"
Note that every quote in the above snippet is essential (except for the ones in case "$arg"
) and they all have the same purpose: to avoid word-splitting when the variable (or the array elements) is expanded. (The exception for the case
word is because the shell does not word-split expansions in that context. But you don't need to remember that; just use the quotes as shown.)