54

I have a file with a bunch of user defaults in. I want to change some of the text, but I'm struggling coming up with a matcher and replacer. Using the following example:

###############################################################################
# Trackpad, mouse, keyboard, Bluetooth accessories, and input                 #
###############################################################################

# Trackpad: enable tap to click for this user and for the login screen
defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true

I'd like to replace # Trackpad: ... with running "Trackpad: ..."

Breaking the problem down, I came up with something using a regex tester:

/\n\n#\s(.*)/g

If I try and use this in Vim it doesn't work for me:

:/\n\n#\s(.*)/running "\1"/g

I guess my problem boils down to two specific questions:

  1. How can I avoid searching for \n characters, and instead make sure # doesn't appear at the end of the search group?
  2. How can I effectively use capture groups?

There are some great answers below. Hard to choose between all three, however I feel the chosen answer is the most accurate for my original spec. I recommend you try all three answers with the actual file to see how you feel about them.

3 Answers 3

38

Just to be clear… I believe you asked for this to be the result of the substitution?

###############################################################################
# Trackpad, mouse, keyboard, Bluetooth accessories, and input                 #
###############################################################################

running "Trackpad: enable tap to click for this user and for the login screen"
defaults write com.apple.driver.AppleBluetoothMultitouch.trackpad Clicking -bool true

In that case, I recommend the following command:

:%s/\n\n#\s\+\(.*\)/^M^Mrunning "\1"/

Explanation of the pattern

:s/PATTERN/REPLACEMENT/ is the substitute command. The percent sign in :%s makes it work on the whole file, rather than just the current line.

The \n\n says that the line of interest must occur after a blank line. If you didn't care about the preceding blank line, then ^ would suffice.

#\s\+ matches a hash character followed by one or more whitespace characters. \(.*\) captures all subsequent text on the line.

Explanation of the replacement text

^M^M inserts two ends of lines to replace the \n\n that were present in the pattern. Otherwise, the text would get moved to the end of the line preceding the blank line. To type each ^M, press Ctrl-V Ctrl-M.

Then, insert the string running, followed by whatever was captured in the parentheses within double-quotes.

2
  • I am unable to edit your answer but I believe you meant :%s/\v\n\n#\s+(.*)/^M^Mrunning "\1"/ (added the "magic" flag). It is really hard to pick a correct answer for my original question, but I feel this answer is the closest to my original expected answer. It is also the only one that works throughout the whole file without any need for selecting a range.
    – squarefrog
    Apr 30, 2015 at 7:31
  • 1
    IIRC you can use \r instead of ^M to get new lines?
    – muru
    Apr 30, 2015 at 8:18
18

I would use something like:

:s/^#\s\+\(.\{-}\):/running "\1":/
  • ^# to match the # character anchored at the start of the line (this answers question 1)
  • \s\+ to match any whitespace one or more times
  • \( to start a group (this answers question 2)
  • .\{-}\ to match any character 0 or more times in a non-greedy way; this is diffferent from .* in that it tries to match as little as possible, and not as much as possible. Try adding a : character in the comment to see why this matters
  • \) to end the subgroup.
  • : matches a literal :

We then replace this with the text you want, and use \1 to refer to the group we captured.

I came up with something using a regex tester

Regular expression syntax is a bit like wiki syntax: there are a bunch of them, they all look alike at a glance, none of them is obviously better than any other, but there are many differences.

Today, the so called "Perl compatible" regular expressions are the de-facto default in most languages, but Vim regular expressions are not compatible with Perl-compatible expressions! Vim regexp syntax goes back to at least the '70's, when Perl wasn't even around.

You can see this with the subgroups, where you have to use \( and not ( (this is compatible with POSIX 'basic' syntax, but not with the more common POSIX 'extended' syntax or Perl syntax). You can control this by adding the \v flag in a pattern (See :help /\v for details), this will make it "more compatible", but not completely (You still have to use .{-} for non-greedy matches for example)

So this might explain why using "a regex tester" almost, but not quite, works.

http://www.vimregex.com/ as a good overview/cheatsheet of Vim regexps.

2
  • 1
    Great answer, especially with why regex != regex. As for your search and replace, it's a bit tricky as the comment may or may not have a :. See the full file for details github.com/squarefrog/dotfiles/blob/master/osx/…
    – squarefrog
    Apr 29, 2015 at 10:36
  • this does not find & replace in the whole file
    – woodz
    Mar 7 at 22:19
9

You can:

  • search for lines that don't end in #: :/[^#]$/
  • replace #\s(.*) at the start of the line: s/\v^#\s(.*)/running "\1"/

To use groups, you need to either:

  • escape the brackets so that they become part of the regex syntax: \(.*\), or
  • use "magic" by beginning the expression with \v: s/\v...

Combining it:

:/[^#]$/s/\v^#\s(.*)/running "\1"/
2
  • 1
    This works brilliantly, thanks for including an explanation of what the tags mean rather than just the text I needed to write.
    – squarefrog
    Apr 29, 2015 at 10:21
  • Thanks for making it clear that the brackets have to be escaped in order to become part of the regex syntax. I always had an issue with regex groups in getting them to work.
    – Adama
    Aug 5, 2019 at 9:24

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.