]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
profile/systemd-osc-context: do not use sed for escaping
authorRasmus Villemoes <rv@rasmusvillemoes.dk>
Mon, 20 Jul 2026 12:49:06 +0000 (14:49 +0200)
committerRasmus Villemoes <rv@rasmusvillemoes.dk>
Tue, 21 Jul 2026 09:06:26 +0000 (11:06 +0200)
Bash is perfectly capable of performing the simple substitutions needed for
escaping according to the OSC 3008 spec, so there is no need for the fork+exec
and other overhead of calling sed.

In fact, when writing a test for ensuring that this is a drop-in replacement, I
found out that the current sed method is flawed: If $PWD contains newline
characters, they are passed through unchanged, because sed obviously is
line-oriented.

I do not know which bash version started supporting
${foo//pattern/replacement}, but I ran the below on all Debian images from docker
hub going back to Debian 6 (EOL 2016), carrying BASH_VERSION =
4.1.5(1)-release, and they all succeeded. Since the logic otherwise relies on
PROMPT_COMMAND being an array variable, which happened in 5.1, this should be
all good.

=== test.sh ===
#!/bin/bash

echo "BASH_VERSION = $BASH_VERSION"
ret=0

using_sed() {
    echo "$1" | sed -e 's/\\/\\x5c/g' -e 's/;/\\x3b/g' -e 's/[[:cntrl:]]/⍰/g'
}

pure_bash() {
    local str="$1"
    str="${str//\\/\\x5c}"
    str="${str//;/\\x3b}"
    str="${str//[[:cntrl:]]/⍰}"
    printf "%s" "${str}"
}

do_test() {
    local r0="$1"
    local r1="$(using_sed "$r0")"
    local r2="$(pure_bash "$r0")"
    local s0="$(printf '%s' "$r0" | od -A x -t x1z -w40 | head -n1)"
    local s1="$(printf '%s' "$r1" | od -A x -t x1z -w40 | head -n1)"
    local s2="$(printf '%s' "$r2" | od -A x -t x1z -w40 | head -n1)"
    if [ "$r1" != "$r2" ] || [ "$s1" != "$s2" ] ; then
        echo "Input: $s0"
        echo "sed:   $s1"
        echo "bash:  $s2"
        ret=1
    fi
}

do_test ''
do_test "one ; semicolon"
do_test "two ; semicolons ;"
do_test 'bs \ sc ; bs \ sc ;'
do_test $'\t'
do_test $'sc ; cntrl \x01 \x02 \x03 \x1f \t bs \\ bs \\ sc ; '
do_test $'sc ; tab \x09 del \x7f  bs \\ ;'

# The last cases show that the existing function doesn't actually work in
# the case of $PWD containing a newline character, because sed is
# line-oriented, so a newline character will never be replaced.
if [ "$1" = "all" ] ; then
    do_test $'embedded \n newline'
    do_test $'ending in newline\n'
fi

exit "$ret"
=== test.sh ===

profile.d/80-systemd-osc-context.sh

index ea8f456caf2eefd4d124128799ff932d05d5d01f..9bbb71285fa30fda6733e1fcd457393f1e736bb5 100644 (file)
 shopt -q promptvars || return 0
 
 __systemd_osc_context_escape() {
-    # Escape according to the OSC 3008 spec. Since this requires shelling out
-    # to 'sed' we'll only do it where it's strictly necessary, and skip it when
-    # processing strings we are pretty sure we won't need it for, such as
-    # uuids, id128, hostnames, usernames, since they all come with syntax
-    # requirements that exclude \ and ; anyway. This hence primarily is about
-    # escaping the current working directory.
-    echo "$1" | sed -e 's/\\/\\x5c/g' -e 's/;/\\x3b/g' -e 's/[[:cntrl:]]/⍰/g'
+    # Escape according to the OSC 3008 spec. We'll only do it where it's
+    # strictly necessary, and skip it when processing strings we are pretty
+    # sure we won't need it for, such as uuids, id128, hostnames, usernames,
+    # since they all come with syntax requirements that exclude \ and ;
+    # anyway. This hence primarily is about escaping the current working
+    # directory.
+    local systemd_value="$1"
+    systemd_value="${systemd_value//\\/\\x5c}"
+    systemd_value="${systemd_value//;/\\x3b}"
+    systemd_value="${systemd_value//[[:cntrl:]]/⍰}"
+    printf "%s" "$systemd_value"
 }
 
 __systemd_osc_context_common() {