petersohn
petersohn

Reputation: 11730

Replace shortest string match in bash

In bash, there are several useful string manipulation patterns, such as removing shortest/longest substring from beginning/end of the string:

${var#substring}
${var##substring}
${var%substring}
${var%%substring}

Then there is also the replacement pattern, that replaces a substring from any part of the string:

${var/substring/replacement}

The problem with this is that it is greedy and always replaces the longest match. For example if I have a directory name like /a/b/foo-bar/x/y/z and I want to replace any subdirectory name starting with foo- to baz, then it won't work as I expect. I expect the result to be /a/b/baz/x/y/z. I tried the following command:

${PWD/\/foo-*\///baz/}

The result in this case is /a/b/baz/z, because the pattern matches the longest substring starting with /foo- and ending with /. Is there any way to get the correct result without calling sed or any other external string manipulation program?

Upvotes: 3

Views: 1903

Answers (4)

anubhava
anubhava

Reputation: 785581

In pure BASH you can do this (using BASH regex capabilities):

s='/a/b/foo-bar/x/y/z'
p="$s"
[[ "$s" =~ ^(.*/)'foo-'[^/]*(.*)$ ]] && p="${BASH_REMATCH[1]}baz${BASH_REMATCH[2]}"

echo "$p"
/a/b/baz/x/y/z

Upvotes: 2

gniourf_gniourf
gniourf_gniourf

Reputation: 46853

Of course, you can always use extended globs:

shopt -s extglob

var=/a/b/foo-bar/x/y/z/foo-bar2/1/2/3
echo "${var//\/foo-*([^\/])\///baz/}"

will happily output

/a/b/baz/x/y/z/baz/1/2/3

Upvotes: 4

PM 2Ring
PM 2Ring

Reputation: 55489

Here's a way to do it in pure Bash that replaces all subdirectories in the path that match the pattern. It splits the path into an array, does the replacement on each path component into a new array, and then uses printf to replace the path separators.

name='/a/b/foo-bar/x/foo-y/z';
IFS=$'/';
aname=($name);
bname=();
for i in "${aname[@]}";
do bname+=("${i/foo-*/baz}");done;
printf -v newname "%s/" "${bname[@]}";

printf "%s\n" "$newname"

output

/a/b/baz/x/baz/z/

(Sorry about all the ;s, I just did a quick copy & paste from the shell)

Upvotes: 1

Kent
Kent

Reputation: 195179

you cannot use regex in bash's built-in string substitution. If you have to stick to the built-in string manipulation, you have to extract the substring foo-bar first then use it in your ${PWD/$match/baz}.

You can do it very easily however, if you could use regex. There are a lot of handy string handlers under linux/unix, could do that very easily. sed for example:

kent$  sed 's#/foo-[^/]*#/baz#' <<< '/a/b/foo-bar/x/y/z'
/a/b/baz/x/y/z

Upvotes: 0

Related Questions