Using a variable with an apostrophe in it when I am already in quoting hell

74 views Asked by At

I am running...

xargs -0 somescript -C << EOF
drush ev 'somethingelse("$STRING",[]);'
EOF

somescript is a script I need to run and I do not control. It expects a command as an argument after -C much like su -c does.

This works if $STRING does not contain an apostrophe but if it does then it falls apart as it can be expected. I tried

xargs -0 somescript -C << "EOF1"
xargs -0 drush ev << 'EOF2'
somethingelse("$STRING",[]);
EOF2
EOF1

but this gives me sh: syntax error: unexpected "(". I do not quite understand why.

Ps.: Please do not recommend running something else: the problem here is running this script. This is not an X-Y problem, I have a screw I need to screw in. I can't use a rivet. I am asking where to find the appropriate screwdriver. Thanks!

2

There are 2 answers

3
Charles Duffy On

Using A Python Helper

The use of sh instead of bash complicates this a bit -- bash can escape arbitrary strings into content that bash itself can evaluate, but it doesn't have an equivalent feature that targets baseline POSIX sh; hence the use of a Python helper below to provide a solution that works on operating systems where sh is provided by ash, dash, or another non-bash shell.

# Define a helper using the python shlex module to shell-quote arbitrary strings
quote() {
  python3 -c '
import sys, shlex
print(" ".join(shlex.quote(s) for s in sys.argv[1:]))
' "$@"
}

# Store your exact/literal command in a string
# note you could also run drush_arg='somethingelse("$STRING",[]);'
drush_arg=$(cat <<'EOF'
somethingelse("$STRING",[]);
EOF
)

# Pass that literal command through the helper to quote it appropriately
drush_cmd=$(quote drush ev "$drush_arg")

# Use the result of that quoting operation
somescript -C "$drush_cmd"

You could even do this further: somescript_cmd=$(quote somescript -C "$drush_cmd") will create a somescript_cmd variable that can be eval'd to invoke somescript.


Bash Only (5.0+, sh compatibility not guaranteed)

If you're curious how to do this without needing Python: if the program you're starting invokes bash instead of sh, or you don't need to support operating systems where sh is provided by a non-bash shell, one could instead use:

drush_arg=$(cat <<'EOF'
somethingelse("$STRING",[]);
EOF
)

drush_cmd_arr=( drush ev "$drush_arg" )
drush_cmd=${drush_cmd_arr[*]@Q} # requires bash 5.0+

somescript -C "$drush_cmd"

Bash Only (3.x-compatible, sh compatibility not guaranteed)

If we avoid using 5.0+-only features, this would instead look like:

drush_arg=$(cat <<'EOF'
somethingelse("$STRING",[]);
EOF
)

drush_cmd_arr=( drush ev "$drush_arg" )
printf -v drush_cmd '%q ' "${drush_cmd_arr[@]}"

somescript -C "$drush_cmd"
3
jhnc On

Your sample code is:

xargs -0 somescript -C << EOF
drush ev 'somethingelse("$STRING",[]);'
EOF

From the tags, this appears to be a bash command-line that is attempting to get drush to execute a PHP command.

You wish to pass to PHP a string to use as an argument to the somethingelse function.

Instead of attempting to do any kind of string interpolation, you can pass the value in the environment and tell PHP to access the value from there:

MyVar=$STRING xargs -0 somescript -C <<'EOD'
    drush ev 'somethingelse(getenv("MyVar"),[]);'
EOD

Note that textual string interpolation across semantic domains is best avoided. It is notoriously hard to get right, since the receiving code may not always interpret the result as intended, opening it up to the injection class of bugs (including many tainted input weaknesses such as SQL injection; Shellshock; AI prompt injection attacks; etc.)