From: Rasmus Villemoes Date: Mon, 20 Jul 2026 12:49:06 +0000 (+0200) Subject: profile/systemd-osc-context: do not use sed for escaping X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9ca54880462db7d0c2f91d6e8f114294458fe08d;p=thirdparty%2Fsystemd.git profile/systemd-osc-context: do not use sed for escaping 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 === --- diff --git a/profile.d/80-systemd-osc-context.sh b/profile.d/80-systemd-osc-context.sh index ea8f456caf2..9bbb71285fa 100644 --- a/profile.d/80-systemd-osc-context.sh +++ b/profile.d/80-systemd-osc-context.sh @@ -35,13 +35,17 @@ 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() {