Reputation: 833
I seem to get into a paradoxical situation. I have a long string stored in a variable $abstract
like this:
abstract='test1 and "test2"'
you see that there is a quotation mark in the string, and I want to send this string to a command as a single argument, I tried the following command:
command_name "$abstract"
after the variable substitution, it became: command_name test1 and "test2"
. And if i try:
command_name \"$abstract\"
it becomes: command_name "test1 and "test2""
. Both are not what I wants. Can anyone tell me how to achieve my goal?
Upvotes: 2
Views: 1808
Reputation: 385540
Your first try was correct:
command_name "$abstract"
That executes command_name
with a single argument, test1 and "test2"
. For example:
:; mkdir empty
:; cd empty
:; abstract='test1 and "test2"'
:; touch "$abstract"
:; ls -l
total 0
-rw-r--r-- 1 mayoff wheel 0 Mar 2 21:26 test1 and "test2"
You can see that touch
only created one file, and it is named test1 and "test2"
.
So, based on your comment, you actually want to interpolate a shell variable into a SQL statement to pass on the command line to sqlite3
.
First, you should be aware that using "
to quote a string in SQLite3 is dangerous. It is allowed as an exception to the normal rules, as described in the “SQLite Keywords” documentation, which also says “Future versions of SQLite might change to raise errors instead of accepting the malformed statements covered by the exceptions above.”
So, if we use single quotes as we're supposed to, you want to execute this:
sqlite3 test.db "insert into test values('$abstract')"
Of course, that works fine with the example value you gave for $abstract
.
Let's change to a more challenging version of $abstract
:
abstract="'test' and \"test2\""
To handle this, we need to quote the single-quotes before SQLite sees them. In a SQLite3 string, two single-quotes in a row represent one single-quote. That is, we want to run this SQLite3 command:
insert into test values('''test'' and "test2"')
Anyway, bash actually has a handy way of doing this. In bash, you can say ${variable//PATTERN/STRING}
, and bash will expand that to the value of $variable
, but it will replace every instance of PATTERN
with STRING
in the expansion. We can use that to replace each single-quote in $abstract
with two single-quotes. But the single-quote is magic to bash when it appears in PATTERN
, so we have to quote the single-quote there with a backslash. But we don't quote the single-quotes in the replacement STRING
. Wow, this gets confusing, doesn't it?
Anyway, the magic incantation you are looking for is this:
sqlite3 test.db "insert into test values('${abstract//\'/''}')"
We can test this using touch
:
:; mkdir empty
:; cd empty
:; touch sqlite3 test.db "insert into test values('${abstract//\'/''}')"
:; ls -l
total 0
-rw-r--r-- 1 mayoff wheel 0 Mar 3 15:01 insert into test values('''test'' and "test2"')
-rw-r--r-- 1 mayoff wheel 0 Mar 3 15:01 sqlite3
-rw-r--r-- 1 mayoff wheel 0 Mar 3 15:01 test.db
Or of course we can test it with SQLite:
:; rm -f test.db
:; sqlite3 test.db 'create table test (x)'
:; sqlite3 test.db "insert into test values('${abstract//\'/''}')"
:; sqlite3 test.db 'select * from test'
'test' and "test2"
Upvotes: 8
Reputation: 125748
If your comment on rob mayoff's answer is correct, the original description is wrong; you aren't trying to run the command with a single argument, you are trying to run it with three arguments: "test1", "and", and "test2" (with the quotes being not being part of the actual arguments). In that case, you need to use a different approach, because putting quotes into a variable doesn't do anything useful. Usually, the best way to do this sort of thing is to put the arguments you want into an array, and then use the "${arrayname[@]}"
idiom to pass it to the command as a series of arguments:
$ function printargs { printf "argument: '%s'\n" "$@"; }
$ abstract=(test1 and "test2")
$ printargs "${abstract[@]}"
argument: 'test1'
argument: 'and'
argument: 'test2'
$ args=(test.db "insert into test values(...,\"$abstract\")")
$ printargs "${args[@]}"
argument: 'test.db'
argument: 'insert into test values(...,"test1")'
Upvotes: 3
Reputation: 161624
The differences between "$abstract"
and $abstract
and \"$abstract\"
are:
"$abstract"
expands to test1 and "test2"
as a single word$abstract
expands to test1 and "test2"
, then split to (test1
, and
, "test2"
) as 3 words\"$abstract\"
is nothing but string concatenation of 3 items(\"
, $abstract
, \"
), after expand $abstract
, it turns into ("test1
, and
, "test""
) as 3 wordsUpvotes: 1