I have a list of strings and I want to pass those strings as arguments in a single Bash command line call. For simple alphanumeric strings it suffices to just pass them verbatim:
> script.pl foo bar baz yes no
foo
bar
baz
yes
no
I understand that if an argument contains spaces or backslashes or double-quotes, I need to backslash-escape the double-quotes and backslashes, and then double-quote the argument.
> script.pl foo bar baz "\"yes\"\\\"no\""
foo
bar
baz
"yes"\"no"
But when an argument contains an exclamation mark, this happens:
> script.pl !foo
-bash: !foo: event not found
Double quoting doesn't work:
> script.pl "!foo"
-bash: !foo: event not found
Nor does backslash-escaping (notice how the literal backslash is present in the output):
> script.pl "\!foo"
\!foo
I don't know much about Bash yet but I know that there are other special characters which do similar things. What is the general procedure for safely escaping an arbitrary string for use as a command line argument in Bash? Let's assume the string can be of arbitrary length and contain arbitrary combinations of special characters. I would like an escape()
subroutine that I can use as below (Perl example):
$cmd = join " ", map { escape($_); } @args;
Here are some more example strings which should be safely escaped by this function (I know some of these look Windows-like, that's deliberate):
yes
no
Hello, world [string with a comma and space in it]
C:\Program Files\ [path with backslashes and a space in it]
" [i.e. a double-quote]
\ [backslash]
\\ [two backslashes]
\\\ [three backslashes]
\\\\ [four backslashes]
\\\\\ [five backslashes]
"\ [double-quote, backslash]
"\T [double-quote, backslash, T]
"\\T [double-quote, backslash, backslash, T]
!1
!A
"!\/'" [double-quote, exclamation, backslash, forward slash, apostrophe, double quote]
"Jeff's!" [double-quote, J, e, f, f, apostrophe, s, exclamation, double quote]
$PATH
%PATH%
&
<>|&^
*@$$A$@#?-_
EDIT:
Would this do the trick? Escape every unusual character with a backslash, and omit single or double quotes. (Example is in Perl but any language can do this)
sub escape {
$_[0] =~ s/([^a-zA-Z0-9_])/\\$1/g;
return $_[0];
}
To escape a string for use as a command line argument in Bash, simply put a backslash in front of every non-alphanumeric character. Do not wrap the string in single quotes or double quotes.
A non-quoted backslash, \, is used as an escape character in Bash. It preserves the literal value of the next character that follows, with the exception of newline.
Escape characters: Bash escape character is defined by non-quoted backslash (\). It preserves the literal value of the character followed by this symbol. Normally, $ symbol is used in bash to represent any defined variable.
If you want to securely quote anything for Bash, you can use its built-in printf %q
formatting:
cat strings.txt
:
yes no Hello, world C:\Program Files\ " \ \\ \\\ \\\\ \\\\\ "\ "\T "\\T !1 !A "!\/'" "Jeff's!" $PATH %PATH% & <>|&^ *@$$A$@#?-_
cat quote.sh
:
#!/bin/bash while IFS= read -r string do printf '%q\n' "$string" done < strings.txt
./quote.sh
:
yes no Hello\,\ world C:\\Program\ Files\\ \" \\ \\\\ \\\\\\ \\\\\\\\ \\\\\\\\\\ \"\\ \"\\T \"\\\\T \!1 \!A \"\!\\/\'\" \"Jeff\'s\!\" \$PATH %PATH% \& \<\>\|\&\^ \*@\$\$A\$@#\?-_
These strings can be copied verbatim to for example echo
to output the original strings in strings.txt.
What is the general procedure for safely escaping an arbitrary string for use as a command line argument in Bash?
Replace every occurrence of '
with '\''
, then put '
at the beginning and end.
Every character except for a single quote can be used verbatim in a single-quote-delimited string. There's no way to put a single quote inside a single-quote-delimited string, but that's easy enough to work around: end the string ('
), then add a single quote by using a backslash to escape it (\'
), then begin a new string ('
).
As far as I know, this will always work, with no exceptions.
You can use single quotes to escape strings for Bash. Note however this does not expand variables within quotes as double quotes do. In your example, the following should work:
script.pl '!foo'
From Perl, this depends on the function you are using to spawn the external process. For example, if you use the system
function, you can pass arguments as parameters so there"s no need to escape them. Of course you"d still need to escape quotes for Perl:
system("/usr/bin/rm", "-fr", "/tmp/CGI_test", "/var/tmp/CGI");
sub text_to_shell_lit(_) {
return $_[0] if $_[0] =~ /^[a-zA-Z0-9_\-]+\z/;
my $s = $_[0];
$s =~ s/'/'\\''/g;
return "'$s'";
}
See this earlier post for an example.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With