Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Using sed how could I replace two patterns within a larger pattern on a single line?

Given a single line of text I want to find a pattern (Let's call this the outer pattern) and then within that outer pattern replace two inner patterns.

Here's a one line example of the input text:

Z:\source\private\main\developer\foo\setenv.sh(25):     export 'FONTCONFIG_PATH'="$WINE_SHARED_SUPPORT/X11/etc/fonts"

In the example above the outer pattern is /^.*\([[:digit:]]+\):/ which should equal Z:\source\private\main\developer\foo\setenv.sh(25): The two inner patterns are /^[A-Za-z]:/ and /\\/.

Another way to phrase my question is:

Using sed I know how to perform replacements of a pattern using the s command, but how do I limit the range of s command so it only works on the portion of the input string up to the (25):?

The ultimate result I am trying to get is the line of text is transformed into this:

/enlistments/source/private/main/developer/foo/setenv.sh(25):     export 'FONTCONFIG_PATH'="$WINE_SHARED_SUPPORT/X11/etc/fonts"
share|improve this question

This command converts your input example to your output example:

sed 's|\\|/|g;s|^[^/]*|/enlistments|'

If some of your input contains backslashes in the portion after the /^.*([[:digit:]]+):/ part, then you will have to divide and conquer to prevent those latter backslashes from being replaced.

sed 'h;s/^.*([[:digit:]]\+)://;x;s/^\(.*([[:digit:]]\+):\).*/\1/;s|\\|/|g;s|^[^/]*|/enlistments|;G;s/\n//'

Explanation (steps marked with an asterisk ([*]) apply to both commands):

  • h - copy the line to hold space
  • s/^.*([[:digit:]]\+):// - delete the first part of the line from the original in pattern space
  • x - swap pattern space and hold space
  • s/^\(.*([[:digit:]]\+):\).*/\1/ - keep the first part of the line from the copy (discard the last part)
  • s|\\|/|g - [*] change all the backslashes to slashes (in the divide-and-conquer version, only the portion in pattern space - the first part of the line - is affected)
  • s|^[^/]*|/enlistments| - [*] change whatever appears before the first slash into "/enlistments" - this could be made more selective if needed
  • G - append a newline and the contents of hold space onto the end of pattern space
  • s/\n// - remove the interior newline
share|improve this answer

There are two facts that I think you'll find useful:

  1. By default, sed will only replace the first occurrence of a string in a line. So given the following line:

    foo foo
    

    The command sed s/foo/bar/ will result in:

    bar foo
    
  2. You can limit a sed command to lines matching a specific pattern. If you want to perform the above substitution only on lines containing the string "xyz", you would use a command like this:

    sed '/xyz/ s/foo/bar/'
    

I think this should help you do what you want.

share|improve this answer

You could try

sed -r 's/^[A-Z]:\\|\\/\//g' | sed 's/^/\/enlistments/'

Now, this of course requires that there's no backslashes in the part after (25): to work. But as I suspect this will never be the case due to unix path structure this will never be the case.

share|improve this answer

This might work for you (GNU sed):

sed 's/([0-9]\+):/&\n/;h;s/\n.*//;y/\\/\//;s/[^/]*/\/enlistments/;G;s/\n.*\n//' file
share|improve this answer

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .