0

My Bash script sends a set of (multiple) parameters to a C program.

This is my C program:

$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main( int argc, char **argv )
{

    printf("Parameter 1 is: %s \n", argv[1]);
    printf("Parameter 2 is: %s \n", argv[2]);
    return 0;
}

Once compiled (as MyCProgram), it behaves OK:

MyCProgram -f "/path/to/file/with spaces"
Parameter 1 is: -f
Parameter 2 is: /path/to/file/with spaces

But, when trying to send to it parameters via shell variable:

$ var='-f "/path/to/file/with spaces" '
$ MyCProgram $var
Parameter 1 is: -f
Parameter 2 is: "/path/to/file/with
$ MyCProgram "$var"
Parameter 1 is: -f "/path/to/file/with spaces"
Parameter 2 is: (null)
$ MyCProgram '$var'
Parameter 1 is: $var
Parameter 2 is: (null)
$ MyCProgram "$(echo $var)"
Parameter 1 is: -f "/path/to/file/with spaces"
Parameter 2 is: (null)
$ MyCProgram "$(echo "$var")"
Parameter 1 is: -f "/path/to/file/with spaces"
Parameter 2 is: (null)
$ MyCProgram "$(echo '$var')"
Parameter 1 is: $var
Parameter 2 is: (null)
$ MyCProgram '$(echo "$var")'
Parameter 1 is: $(echo "$var")
Parameter 2 is: (null)
$ var="-f '/path/to/file/with spaces' "
$ MyCProgram $var
Parameter 1 is: -f
Parameter 2 is: '/path/to/file/with

How can I obtain the proper behavior, this is, same as when run without shell variables?

Notes:

Upon Craig Estey answer, this works fine:

$ var=(-f "/file with spaces")
$ MyCProgram "${var[@]}"

This method works, but only if I manually (explicitly) assign the value to var. Assuming var is already assigned (i.e: via input read or file read), I would say the problem here is transferring it to a bash array variable. So:

$ var='-f "/file with spaces"'
$ var2=( $var )
$ MyCProgram "${var2[@]}"
Parameter 1 is: -f
Parameter 2 is: "/file

The problem is still there.

Sopalajo de Arrierez
  • 3,543
  • 4
  • 34
  • 52
  • `Assuming var is already assigned (i.e: via input read or file read)` - then you should fix the way you do "input read" or "file read" so that it properly handles inputs with spaces. This looks like XY problem - you are trying to solve something somewhere else that it needs to be solved. You are asking how to parse strings with `"` enclosed elements in them? Write your own parser, that will correctly read and escape them the way you want. What should happen with unprintable characters in filenames? Or what if a filename contains a `"`? – KamilCuk Jan 25 '20 at 18:47
  • Have you looked at the special variable $@ and how your script is interacting with the user? When I want to pass command line arguments from the shell's command line to a subcommand within the script, then I use "$@" so that all arguments given to the script are quoted when give to the subcommand. – Jeff Holt Jan 25 '20 at 19:15
  • Quotes are shell syntax, but what's stored in variables (and files) is *data*, and generally is not subject to being treated as shell syntax (exceptions: spaces as delimiters and wildcard expansion -- if you don't quote variables). What you're asking for is more shell-syntax-like parsing of data. This is rather dangerous unless you're very clear about what parsing rules you want applied, and also tends to be complicated and easy to get wrong. Which brings up an important question: what's your actual goal here? Why are you trying to mix shell syntax with your data? Is there a better way? – Gordon Davisson Jan 25 '20 at 19:49

4 Answers4

3

You probably want to use a bash array variable

Do the following to set the variable:

var=(-f "/file with spaces")

Then, invoke your program:

MyCProgram "${var[@]}"
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • Thanks you, but this solution does not fully solve the problem. Added details to original question to reflect. – Sopalajo de Arrierez Jan 25 '20 at 17:54
  • If you read the variable from a file, and you pass it to your program (e.g. `myprogram "$var"`), the first argument will be the _entire_ line and the program will only receive a _single_ argument. This can be solved _if_ you want `myprogram` to _parse_ the line and do the argument split internally (i.e. it handles quoted strings). The input file line itself would need to be of the form: `-f "/file with spaces"` or `-f '/file with spaces'`. Is that what you would like to do? – Craig Estey Jan 25 '20 at 18:15
  • On the other hand, it may be easier to have `myprogram` read the file and parse all the lines. Depends what you're thinking of. – Craig Estey Jan 25 '20 at 18:23
  • I was rather thinking about some other solution, like the `xargs` way. Anyway, your conversion to array variable put me on the good way, and I have solved it (as referred on other comments) with `IFS=$'\n' varArray=( $(xargs -n1 <<<"$var") )`, then `MyCProgram "${varArray[@]}"` . Thanks you. – Sopalajo de Arrierez Jan 26 '20 at 14:18
3

var='-f "/path/to/file/with spaces" '

In this situation, the contents of var are shell syntax.

To process shell syntax, we can therefore use the eval function:

eval "MyProgram $var"

This will construct the argument

MyProgram -f "/path/to/file/with spaces" 

and then evaluate it as a little shell script.

Needless to say, there are security implications. We are trusting the inputs that went into the construction of script contained in var. For that reason, eval should be shunned as much as possible.

When we introduce eval, we have to think about: who controls the inputs that go into the string being passed to eval? Could someone malicious sneak in something to get eval to run arbitrary code?

If the construction of var is under your control, and either has no untrusted inputs, or those inputs are dealt with properly, then this may be an appropriate solution.

Kaz
  • 55,781
  • 9
  • 100
  • 149
3

Without specifying delimiter in xargs, it parses " and ' quotes and backslashes, so assuming var content is sane, you could do:

<<<"$var" xargs MyProgram

You could write your own tokenizer for var content - ex. split it on the first space and then remove the " quotes.

# split var on space
IFS=' ' read -r option file <<<"$var"
# remove quotes from file - ie. remove last and first character
file=${file%\"}
file=${file#\"}
# run your program
MyProgram "$option" "$file"
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

Would you please try the following:

var='-f "/path/to/file/with spaces" '
declare -a var2="( $var )"
./MyCProgram "${var2[@]}"

Output:

Parameter 1 is: -f 
Parameter 2 is: /path/to/file/with spaces 

Note that declare may have a potential risk as eval and you will need to take a control so that an arbitrary string is not fed to declare.

tshiono
  • 21,248
  • 2
  • 14
  • 22