问题
I need to create a string from a full POSIX path (starting at the root), so that it could be pasted directly into a Unix shell like bash
, e.g. in Terminal.app
, without the need for quotes around the path.
(I do not actually pass the string to a shell, but instead need it for passing it to another program. That program expects the path in just the form that you get when you drag a file into Terminal.app
.)
For that, I need to escape at least any spaces in the string, by prepending them with a backslash. And some more characters as well.
For example, this path:
/directory/-as"<>' *+
Would be escaped as follows:
/directory/-as\"\<\>\'\ \*+
What's a safe algorithm to perform that conversion? I could escape every character, but that would be overkill.
There seems to be no framework function for doing this, so I'll need to do the replacing with string operations.
To be conservative (for the most popular shells), while also avoiding clearly unnecessary escapings, what set of characters should be escaped?
回答1:
Better to put the whole thing in single quotes, rather than adding backslashes to individual characters; then the only character you need to escape is a single-quote present inside the string.
The Python standard library's implementation, provided as an example which can be easily reimplemented in any other language having only basic primitives, reads as follows:
def quote(s):
"""Return a shell-escaped version of the string *s*."""
if not s:
return "''"
if _find_unsafe(s) is None:
return s
# use single quotes, and put single quotes into double quotes
# the string $'b is then quoted as '$'"'"'b'
return "'" + s.replace("'", "'\"'\"'") + "'"
That is to say, the general algorithm is as follows:
- An empty string becomes
''
(a pair of literal single-quotes). - A string which is known to be safe (though it's safest to not try to implement a codepath for this at all, particularly as shells often implement their own syntax extensions in undefined space) can be emitted bare/unquoted.
- Otherwise, prepend a
'
, emit your input string with all'
s replaced with the literal string'"'"'
, and then append a final'
.
That's it. You don't need to escape backslashes (they're literal inside single quotes), newlines (likewise), or anything else.
回答2:
For the record, Terminal.app
escapes the following non-control ASCII chars when dropping a file name into its window:
Space
!"#$%&'()*,:;<=>?[]`{|}~
And these are not escaped:
Control codes (00-1F and 7F)
Alphanumerical
+-.@^_
And here's the code that would perform the replacement:
NSString* shellPathFromPOSIXPath (NSString *path)
{
static NSRegularExpression *regex = nil;
if (!regex) {
NSString *pattern =
@"([ !\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\,\\:\\;\\<\\=\\>\\?\\[\\]\\`\\{\\|\\}\\~])";
regex =
[NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
}
NSString *result =
[regex stringByReplacingMatchesInString:path
options:0
range:NSMakeRange(0, path.length)
withTemplate:@"\\\\$1"];
return result;
}
来源:https://stackoverflow.com/questions/55381517/create-a-shell-escaped-posix-path-in-macos