diff --git a/lix/libutil/strings.cc b/lix/libutil/strings.cc index 1788cc2f8..41db7a069 100644 --- a/lix/libutil/strings.cc +++ b/lix/libutil/strings.cc @@ -129,8 +129,41 @@ std::string shellEscape(const std::string_view s) std::string r; r.reserve(s.size() + 2); r += "'"; - for (auto & i : s) - if (i == '\'') r += "'\\''"; else r += i; + for (auto & i : s) { + if (i == '\'') { + // End the single quote, add a single backslash-escaped single quote, + // then start a single quote again. + // i.e., `I didn't know` becomes `'I didn'\''t know'`. + r += "'\\''"; + } else { + r += i; + } + } + + r += '\''; + return r; +} + +std::string bashEscape(const std::string_view s) +{ + std::string r; + r.reserve(s.size() + 2); + r += "'"; + for (auto & i : s) { + if (!std::isprint(i)) { + // Close the single quote, start an "ANSI-C Quote" ($'foo'), add `\xXX`, + // close the ANSI-C Quote, and finally start a normal single quote again. + r += fmt("'$'\\x%02x''", static_cast(static_cast(i))); + } else if (i == '\'') { + // End the single quote, add a single backslash-escaped single quote, + // then start a single quote again. + // i.e., `I didn't know` becomes `'I didn'\''t know'`. + r += "'\\''"; + } else { + r += i; + } + } + r += '\''; return r; } diff --git a/lix/libutil/strings.hh b/lix/libutil/strings.hh index 958bb7a92..bc3d9285b 100644 --- a/lix/libutil/strings.hh +++ b/lix/libutil/strings.hh @@ -191,6 +191,11 @@ std::string toLower(const std::string & s); */ std::string shellEscape(const std::string_view s); +/** + * Same as shellEscape, but also escapes nonprinting characters using $'ANSI C quotes'. + */ +std::string bashEscape(const std::string_view s); + /** * Base64 encoding/decoding. */