22

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.

14

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.

  • 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 '15 at 7:31
  • 1
    IIRC you can use \r instead of ^M to get new lines? – muru Apr 30 '15 at 8:18
10

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.

7

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"/
  • 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 '15 at 10:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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