yorua007
yorua007

Reputation: 833

bash string quotation

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

Answers (3)

rob mayoff
rob mayoff

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".

EDIT

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

Gordon Davisson
Gordon Davisson

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

kev
kev

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 words

Upvotes: 1

Related Questions