1

Say I have this:

str="@test/string" echo $str @test/string echo ${str#@} test/string 

works as expected, but

echo ${str//\//-} ksh: ${str//\//-}: bad substitution 

fails. (expected @test-string)

What is a correct way to replace/substitute characters like this?


echo $KSH_VERSION KSH version @(#)PD KSH v5.2.14 99/07/13.2 
5
  • I don't have ksh, but echo ${str/\//-} worked as expected, note there is only one / after str. Commented May 19, 2023 at 12:59
  • that looks it should work, and it works with Bash and ksh "sh (AT&T Research) 93u+ 2012-08-01", i.e. ksh -c 'str="@test/string"; echo "${str//\//-}"' prints @test-string. Commented May 19, 2023 at 13:14
  • @Archemar, the double slash means to replace all matching parts, not just one. try str=aba; echo "${str/a/x}"; echo "${str//a/x}" Commented May 19, 2023 at 13:15
  • 1
    @steeldriver pdksh is actively developed on OpenBSD. It's not a bug that a shell does not implement a non-standard parameter substitution. It's unclear what the "mistakes" that were corrected refers to in the changelog. Commented May 19, 2023 at 17:25
  • 1
    @Kusalananda my bad - I misread the first (successful) command as ${str//@} for some reason Commented May 19, 2023 at 18:42

3 Answers 3

1

Yes ${var//pattern/replacement} is a ksh93 operator while pdksh was intended as a clone of the original ksh whose last version was ksh88i¹. pdksh as a project has been discontinued, but it survives as it's the sh of a few BSDs, including OpenBSD and MirBSD. MirBSD sh/ksh also called mksh also maintained as a portable shell and and is available in many Linux distribution (it's even used as sh on Android). It did add that ksh93 operator in 2008 in version R33.

POSIXly, to replace a character with another in a variable, you can use the transliterator utility, but you need to feed the contents of the variable on its stdin and get the result from its stdout:

newstr=$(printf %s "$str" | tr / -; echo .) newstr=${newstr%.} 

(the added .\n to work around the fact that command substitution strips trailing newline characters).

You could also use the split+glob operator to avoid running a separate command:

IFS=/ # split on / set -o noglob # disable the glob part set -- $str'' # split+glob by leaving $str unquoted IFS=. # join on . in "$*" newstr="$*" 

¹ While ksh93 was a not-fully backward compatible rewrite, with many new features many of which still experimental and that was rarely used until its code was released as open source in the 2000s

1

The pdksh shell, which is the default shell for new users on e.g. OpenBSD, does not have the non-standard parameter substitution that you mention.

To replace all non-overlapping matches of some pattern with some new string, you may use sed:

str=$( printf '%s\n' "$str" | sed 's/PATTERN/REPLACEMENT/g' ) 

Note that this uses PATTERN as a POSIX basic regular expression, that REPLACEMENT must escape &, \1 and other strings that sed will interpret in special ways, and that the command substitution strips all trailing newline characters from the end of the string.

Example:

$ str=@title/section1/section2 $ str=$( printf '%s\n' "$str" | sed 's/\//-/g' ) $ printf '%s\n' "$str" @title-section1-section2 

In this case, when only a single character is being replaced, one could replace the sed substitution with y/\//-/, or replace the whole sed command with the tr command tr / -.

You may also choose to write a shell loop:

while true; do case $str in *PATTERN*) str=${str%%PATTERN*}REPLACEMENT${str#*PATTERN} ;; *) break esac done 

In this code, PATTERN is a shell pattern ("filename globbing pattern"). The code repeatedly replaces the first match of the pattern in the string with the replacement string, until there are no more matches. Because of the loop, there is no guarantee that later iterations do not perform replacements in previously inserted replacement strings.

Example:

$ str=@title/section1/section2 $ while true; do case $str in (*/*) str=${str%%/*}-${str#*/} ;; (*) break; esac; done $ printf '%s\n' "$str" @title-section1-section2 
2
  • 2
    Technically, ${a//x/y} is a ksh93 operator, while ${a#x} is a ksh88 one (which ended up being specified by POSIX which came up before 1993). pdksh (from the late 80s) was intended as a clone of ksh88 (though is missing a few features such as process substitution), not ksh93. mksh (MirBSD shell), like the OpenBSD shell are both based on pdksh and did add a few features from ksh93 including that one, though looking at the git history, in the case of OpenBSD's shell, that was added in 2017 but reverted a few hours later as the implementation had been found to be buggy. Commented May 19, 2023 at 17:54
  • 1
    Technically, for something closed to ksh93's ${var//pattern/replacement}, you may want: printf '%s\n' "$var" | sed -e :1 -e '$!{N;b1' -e '}' -e 's/pattern/replacement/g' (where newlines in the pattern have to be expressed as \n). Commented May 19, 2023 at 18:00
-1

Bound to be a better way, but:

$(echo "$str" | tr '/' '-') 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.