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.

3
  • 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 "? Commented Jan 25, 2020 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. Commented Jan 25, 2020 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? Commented Jan 25, 2020 at 19:49

4 Answers 4

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[@]}"
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks you, but this solution does not fully solve the problem. Added details to original question to reflect.
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?
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.
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.
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.

1 Comment

Tested working with both var="-f '/path/to/file/with spaces' " and var='-f "/path/to/file/with spaces" ' . Thanks you.
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"

1 Comment

Tested working, thanks you. Equivalent solution here: stackoverflow.com/a/37381300/1461017 , but using xargs -n1 (I don't know why).
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.

1 Comment

Tested working, tshiono. Some expanding about why declare -a solves the problem would be fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.