Bash shell adding extra single quotes

AIX 6.1
bash shell

#!/bin/bash -x
STATEMENT="cvs commit -m \"This is\" ../PBP/EIR.ENTRY"
echo $STATEMENT
exit 0

This is the output
+ STATEMENT='cvs commit -m "This is" ../PBP/EIR.ENTRY'
+ echo cvs commit -m '"This' 'is"' ../PBP/EIR.ENTRY
cvs commit -m "This is" ../PBP/EIR.ENTRY
+ exit 0

Problem: Notice the single quotes between "This" and "is" ... This causes the called program "cvs" to think those are separate parameters which they are not.

If I change the shell to this

#!/bin/bash -x
STATEMENT="cvs commit -m \"This is\" ../PBP/EIR.ENTRY"
echo $STATEMENT
$STATEMENT
exit 0

Then this is the ouput

+ STATEMENT='cvs commit -m "This is" ../PBP/EIR.ENTRY'
+ echo cvs commit -m '"This' 'is"' ../PBP/EIR.ENTRY
cvs commit -m "This is" ../PBP/EIR.ENTRY
+ cvs commit -m '"This' 'is"' ../PBP/EIR.ENTRY
cvs commit: nothing known about `is"'
cvs [commit aborted]: correct above errors first!
+ exit 0

Please do not pass this off as a "cvs" problem. It is not. If I change the script to this

#!/bin/bash -x
STATEMENT="cvs commit -m \"Thisis\" ../PBP/EIR.ENTRY"
echo $STATEMENT
$STATEMENT
exit 0

Then this is the output
+ STATEMENT='cvs commit -m "Thisis" ../PBP/EIR.ENTRY'
+ echo cvs commit -m '"Thisis"' ../PBP/EIR.ENTRY
cvs commit -m "Thisis" ../PBP/EIR.ENTRY
+ cvs commit -m '"Thisis"' ../PBP/EIR.ENTRY
+ exit 0

How do I get bash to ignore that space?

Did you try to enclose your $STATEMENT into double quote ?

echo "$STATEMENT"

What are you trying to achieve ?

If you remove the -x option of your bash command, doesn't your script behave the way you expect ?

-x does not make a difference. I have done both -x and +x and no'x' with the same results.

What I need to do is actually execute $STATEMENT

bash does not parse quotes inside quotes or other forms of that sort of doublethink. To make it do so, you use eval.

The reason it doesn't parse quotes inside quotes and the like is the same reason eval tends to be avoided -- parsing syntax inside syntax leaves you open to someone injecting a `rm -Rf ~/` into your input and having your code execute that.

I might try something like this:

set -- cvs commit -m "This is" ../PBP/EIR.ENTRY

"$@"

...to preserve tokens (not quotes -- the whole point is you don't want quotes) as demanded.

NOTE:
cvs = Concurrent Versions System
commit = commit to the cvs repository
-m = What follows in the quotes is the Log message for this revision
"This is" = the Log message for this revision
./q.sh = the file

This works:

#!/bin/bash -x
set -- cvs commit -m "This is the comment" ./q.sh 
"$@"
exit 0

What I want to be able to do is dynamically create that whole string in the script and execute it.

This fails

#!/bin/bash -x
STATEMENT="set -- cvs commit -m \"This is the comment\" ./q.sh "
"$STATEMENT"
exit 0

This fails

#!/bin/bash -x
STATEMENT="set -- cvs commit -m \"This is the comment\" ./q.sh "
$STATEMENT
exit 0

This is so frustrating because it seems so simple. I am feeling like a not so bright person right now. :slight_smile:

(Referring to your last post) Why are you using set -- in the assignment to $STATEMENT?

Try:

STATEMENT="cvs commit -m \"This is the comment\" ./q.sh "
eval "$STATEMENT"

or, in the spirit of the examples:

STATEMENT="set -- cvs commit -m \"This is the comment\" ./q.sh "
eval "$STATEMENT"
"$@"

I suggested set -- as a replacement for putting the command in quotes. It's not a way to put the command in quotes.

What you want is not easy or straightforward for the reasons I already explained. Quotes are not usually a part of strings, quotes denote strings. If you want to give that command to a shell, run it in a shell.

A bourne shell will not evaluate quotes there without an eval, scrutinizer.

If you really wish to kludge it into running like that instead of just running it in the first place, you can feed it into a shell. This is not efficient and probably not the greatest idea.

echo "$COMMAND" | sh

This may shed some light:

$ STATEMENT="echo \"hello          there\""
+ STATEMENT='echo "hello          there"'
$ $STATEMENT
+ echo '"hello' 'there"'
"hello there"
$ eval "$STATEMENT"
+ eval 'echo "hello          there"'
++ echo 'hello          there'
hello          there

--
@corona, You are right of course. I jumped in after the last post, because of the the set statement. I had not really read the problem at hand... I'll correct post #7...

This works, but I thought eval was a bad thing? It opens the code up for an Injection attack, right?

Thank you very much for your help!!!!!

Indeed eval is definitely a security risk, you would for example need to make sure that the variable content comes from a controlled source and not from user input or input files or any other source that you do not control or insufficiently control otherwise someone might gain access to the rights of the user executing the script.

So you would need to think if it isn't better to use a different setup that does not require the use of eval. So for example, just do not use the intermediate step of a command variable like $STATEMENT ( I personally never use those ), but execute the command directly without eval. If you do not like that, an alternative could be to create a function.

Yes. So the correct way to prevent this is to not do what you're doing -- not put quotes inside quotes, and have to parse the quotes inside quotes... Store the string in such a way you don't need eval, echo | sh, or other such trickery to parse it. Every alternative we've shown you, you've turned around and tried to store it in a string again, causing the same problems you had before. It's not the way in which you're storing it inside a string that's doing it... It's the fact that you're doing it at all. That doesn't work, no matter how you cut it, without needing to use walking security holes like eval.

Which brings me to the real question. Why do you need to store it in a string like this?

CVS has the ability to store commit messages for each version of a file. These messages tend to be very descriptive and are entered by the user explaining what they did to the file.
So, how would you suggest I get this information from the user as an input and pass it to cvs as a string?

read MESSAGE

cvs commit -m "${MESSAGE}" ./q.s

Or if you want the message to be optional:

read MESSAGE
[ -z "$MESSAGE" ] && cvs commit ./q.s
[ -z "$MESSAGE" ] || cvs commit -m "${MESSAGE}" ./q.s

I have tried to get this to the bare essentials. What I want to do is pass a string from a.sh to b.sh and echo that to the screen.

#!/bin/bash -x
#a.sh
echo $1
./b.sh $1

#!/bin/bash -x
#b.sh
echo $1

$a.sh "test multi"
+ echo test multi
test multi
+ ./b.sh test multi
+ echo test
test

Why can't I get this to work???????? I have gone back through this thread and the only one I can get to work is the "eval" which is too susceptible to attack. Sorry, but I am just getting frustrated with myself.

If you don't put strings in quotes, the shell splits on them on spaces. The quotes are not considered part of the string -- they define whether splitting happens or not inside it.

Substitution happens inside double quotes, but not single ones. So use double quotes here.

"$1"

Note that -x might not show the quotes anyway... This is because -- I repeat -- the quotes are not part of the string. They're delimiters. They define where one string stops and ends, but aren't included in the data itself.

I also note that if you'd used my code as given, it would have worked; it included quotes where necessary...

I removed the -x

#!/bin/bash
#a.sh
echo $1
./b.sh "$1"
#!/bin/bash
#b.sh
echo $1
STMT="cvs commit -m "$1" ./q.sh"
$STMT

How does that differ from your code as given?

This is my output trying to run either script.

$a.sh "This is"
This is
This is
cvs commit: nothing known about `is'
cvs [commit aborted]: correct above errors first!
$b.sh "This is"
This is
cvs commit: nothing known about `is'
cvs [commit aborted]: correct above errors first!
$

When I use this code:

#!/bin/bash
#b.sh
echo $1
STMT="cvs commit -m "$1" ./q.sh"
echo $STMT

the output is

cvs commit -m This is ./q.sh

That statement will not execute. This will

cvs commit -m "This is" ./q.sh

This is the cvs syntax

cvs [cvs-options] commit [command-options] [filename]

so if I don't have the double quotes this command

cvs commit -m This is ./q.s

h
will attempt to commit filename "is" with message "This". So I do need the double quotes, right? They ARE part of the string I am passing to the cvs statement.

---------- Post updated at 03:27 PM ---------- Previous update was at 03:25 PM ----------

I do not mean to sound testy or petty, I am just very frustrated with myself right now. Please keep trying for me and my sanity sake. :slight_smile: Thanks

Try this (using Corona688's suggestion in #14):

#!/bin/bash -x
echo "$1"
cvs commit -m "$1" ./q.s
./test.sh "This is"

Tried something new

#!/bin/bash -x
#b.sh
echo $1
STMT="cvs commit -m "\"$1\"" ./q.sh"
echo $STMT
STMT2="cvs commit -m "$1" ./q.sh"
echo $STMT2
STMT3="cvs commit -m \"$1\" ./q.sh"
echo $STMT3
STMT4="cvs commit -m \""$1"\" ./q.sh"
echo $STMT4

L$b.sh "First Second Third"
+ echo First Second Third
First Second Third
+ STMT='cvs commit -m "First Second Third" ./q.sh'
+ echo cvs commit -m '"First' Second 'Third"' ./q.sh
cvs commit -m "First Second Third" ./q.sh
+ STMT2='cvs commit -m First Second Third ./q.sh'
+ echo cvs commit -m First Second Third ./q.sh
cvs commit -m First Second Third ./q.sh
+ STMT3='cvs commit -m "First Second Third" ./q.sh'
+ echo cvs commit -m '"First' Second 'Third"' ./q.sh
cvs commit -m "First Second Third" ./q.sh
+ STMT4='cvs commit -m "First Second Third" ./q.sh'
+ echo cvs commit -m '"First' Second 'Third"' ./q.sh
cvs commit -m "First Second Third" ./q.sh

Still not right......