847

I want to remove the prefix/suffix from a string. For example, given:

string="hello-world"
prefix="hell"
suffix="ld"

How do I get the following result?

"o-wor"
1
  • 26
    Be very wary when linking to the so-called Advanced Bash Scripting Guide; it contains a mixture of good advice and terrible. Commented Oct 19, 2016 at 3:38

9 Answers 9

1290
$ prefix="hell"
$ suffix="ld"
$ string="hello-world"
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

This is documented in the Shell Parameter Expansion section of the manual:

${parameter#word}
${parameter##word}

The word is expanded to produce a pattern and matched according to the rules described below (see Pattern Matching). If the pattern matches the beginning of the expanded value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the # case) or the longest matching pattern (the ## case) deleted. […]

${parameter%word}
${parameter%%word}

The word is expanded to produce a pattern and matched according to the rules described below (see Pattern Matching). If the pattern matches a trailing portion of the expanded value of parameter, then the result of the expansion is the value of parameter with the shortest matching pattern (the % case) or the longest matching pattern (the %% case) deleted. […]

Sign up to request clarification or add additional context in comments.

11 Comments

Is there a way to combine the two in one line? I tried ${${string#prefix}%suffix} but it doesn't work.
@static_rtti No, unfortunately you cannot nest parameter substitution like this. I know, it's a shame.
@AdrianFrühwirth : the whole language is a shame, but it's so useful :)
@static_rtti , there is a workaround: echo basename ${string/hell} ld (where the grey part is between backticks)
@ccpizza Parameter substitution does not know such modifiers but if it's a fixed string you could always do e.g. ${foo#[Bb][Aa][Rr]}. Not pretty but still possibly better than an unnecessary subshell/fork, depending on the situation.
|
164

Using sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

Within the sed command, the ^ character matches text beginning with $prefix, and the trailing $ matches text ending with $suffix.

Adrian Frühwirth makes some good points in the comments below, but sed for this purpose can be very useful. The fact that the contents of $prefix and $suffix are interpreted by sed can be either good OR bad- as long as you pay attention, you should be fine. The beauty is, you can do something like this:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

which may be what you want, and is both fancier and more powerful than bash variable substitution. If you remember that with great power comes great responsibility (as Spiderman says), you should be fine.

A quick introduction to sed can be found at http://evc-cit.info/cit052/sed_tutorial.html

A note regarding the shell and its use of strings:

For the particular example given, the following would work as well:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

...but only because:

  1. echo doesn't care how many strings are in its argument list, and
  2. There are no spaces in $prefix and $suffix

It's generally good practice to quote a string on the command line because even if it contains spaces it will be presented to the command as a single argument. We quote $prefix and $suffix for the same reason: each edit command to sed will be passed as one string. We use double quotes because they allow for variable interpolation; had we used single quotes the sed command would have gotten a literal $prefix and $suffix which is certainly not what we wanted.

Notice, too, my use of single quotes when setting the variables prefix and suffix. We certainly don't want anything in the strings to be interpreted, so we single quote them so no interpolation takes place. Again, it may not be necessary in this example but it's a very good habit to get into.

3 Comments

Unfortunately, this is bad advice for several reasons: 1) Unquoted, $string is subject to word splitting and globbing. 2) $prefix and $suffix can contain expressions that sed will interpret, e.g. regular expressions or the character used as delimiter which will break the whole command. 3) Calling sed two times is not necessary (you can -e 's///' -e '///' instead) and the pipe could also be avoided. For example, consider string='./ *' and/or prefix='./' and see it break horribly due to 1) and 2).
Fun note: sed can take almost anything as a delimiter. In my case, since I was parsing prefix-directories out of paths, I couldn't use /, so I used sed "s#^$prefix##, instead. (Fragility: filenames can't contain #. Since I control the files, we're safe, there.)
@Olie: As I understood your original comment, you were saying that the limitation of your choice to use # as sed's delimiter meant that you couldn't handle files containing that character.
35
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

Notes:

#$prefix : adding # makes sure that substring "hell" is removed only if it is found in beginning. %$suffix : adding % makes sure that substring "ld" is removed only if it is found in end.

Without these, the substrings "hell" and "ld" will get removed everywhere, even it is found in the middle.

3 Comments

Thanks for the Notes! qq: in your code example you also have a forward slash / right after the string, what is that for?
/ separates the current string and the sub string. sub-string here is the suffix in th posted question.
I don't understand why / is required. Could you elaborate on that? What if I omit /?
27

Do you know the length of your prefix and suffix? In your case:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

Or more general:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

But the solution from Adrian Frühwirth is way cool! I didn't know about that!

1 Comment

This does not "remove the prefix/suffix", but does instead remove as many characters, which only matters if it's not certain that the string actually starts/ends with the pre-/suffix but is an important caveat about this solution to keep in mind.
23

I use grep for removing prefixes from paths (which aren't handled well by sed):

echo "$input" | grep -oP "^$prefix\K.*"

\K removes from the match all the characters before it.

3 Comments

grep -P is a nonstandard extension. More power to you if it's supported on your platform, but this is dubious advice if your code needs to be reasonably portable.
@tripleee Indeed. But I think a system with GNU Bash installed also have a grep that supports PCRE.
No, MacOS for example has Bash out of the box but not GNU grep. Earlier versions actually had the -P option from BSD grep but they removed it.
11

Using the =~ operator:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ [[ "$string" =~ ^$prefix(.*)$suffix$ ]] && echo "${BASH_REMATCH[1]}"
o-wor

Comments

10

NOTE: Not sure if this was possible back in 2013 but it's certainly possible today (10 Oct 2021) so adding another option ...


Since we're dealing with known fixed length strings (prefix and suffix) we can use a bash substring to obtain the desired result with a single operation.

Inputs:

string="hello-world"
prefix="hell"
suffix="ld"

Plan:

  • bash substring syntax: ${string:<start>:<length>}
  • skipping over prefix="hell" means our <start> will be 4
  • <length> will be total length of string (${#string}) minus the lengths of our fixed length strings (4 for hell / 2 for ld)

This gives us:

$ echo "${string:4:(${#string}-4-2)}"
o-wor

NOTE: the parens can be removed and still obtain the same result


If the values of prefix and suffix are unknown, or could vary, we can still use this same operation but replace 4 and 2 with ${#prefix} and ${#suffix}, respectively:

$ echo "${string:${#prefix}:${#string}-${#prefix}-${#suffix}}"
o-wor

1 Comment

Great option! Worth calling out: a key difference between this solution and the others is that if the source string does not start with prefix or end with suffix, then other solutions will not clip anything, where this solution will clip the length of the suffix away. This is not necessarily a problem, just a limitation to be aware of. If you're not sure if the string starts or ends with the prefix/suffix, simply wrap this statement in the appropriate if-statement to check before trimming.
9

Small and universal solution:

expr "$string" : "$prefix\(.*\)$suffix"

4 Comments

If you are using Bash, you should probably not be using expr at all. It was a sort of convenient kitchen sink utility back in the days of the original Bourne shell, but is now way past its best-before date.
Uh, why? expr is old, but never changes, and will probably always be available. As long as you invoke an external binary (as opposed to using BASH expressions), grep, sed or expr are pretty much equivalent (perl / awk would be costlier).
Fantastic, this is simplest, I've ever seen.
expr is very brittle, and its syntax is obscure. And of course, Bash has this built in, so calling an external utility is ugly and costly.
6

Using @Adrian Frühwirth answer:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

use it like this

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.