I have a file that looks similar to this:

module.exports = {
    blank: {
        area: 'frontend',
        name: 'Magento/blank',
        locale: 'en_US',
        files: [
        dsl: 'less'
    backend: {
        area: 'adminhtml',
        name: 'Magento/backend',
        locale: 'en_US',
        files: [
        dsl: 'less'

I need to add another entry inside module.exports. I use vim inside a bash script in the following way:

        area: 'frontend',\r
        name: '$ESCAPED_THEME_PATH',\r
        locale: 'en_US',\r
        files: [\r
        dsl: 'less'\r

vim -c "%s/}\n/},$THEME_CONFIG\n/|wq" dev/tools/grunt/configs/themes.js

This works fine apart from the fact that I do not want to use \r as it seems to be breaking grunt. I would like to use \n instead, but if I replace \r in the code above with \n it does not work (THEME_CONFIG has no line breaks).

I have tried \\n, $'\n' but all I get is one line.

  • 3
    You should use the right tool for the right job and I think that here Vim isn't the right tool: in a non interactive bash shell you will most certainly have less trouble using sed (or maybe awk)
    – statox
    Apr 30, 2019 at 10:21
  • 4
    Confusingly, \n in a search pattern is a newline, but when used in a replacement pattern, it is a NULL character. Why this is the case I do not know, but the docs do say to use \r or equivalently <CR> / ^M; the latter of which you can insert by pressing Ctrl+V Enter. Anyway, I agree with statox in that sed or awk would be a better choice for this, seeing as how you're only running a substitute command. You wouldn't have to deal with this quirk and could just use \n :)
    – ZeroKnight
    Apr 30, 2019 at 10:44
  • 2
    This answer has a great explanation for why \n is NULL in replacements.
    – ZeroKnight
    Apr 30, 2019 at 10:50

3 Answers 3


The problem isn't the \r characters in your string, but the fact that the string also contains literal newline characters, and Vim is including these in the replacement as NUL characters after the linebreaks. If you remove the latter from your bash script, then the replacement works correctly:

THEME_CONFIG="\r    $LOWERCASE_THEME_TITLE: {\r        area: 'frontend',\r        name: '$ESCAPED_THEME_PATH',\r        locale: 'en_US',\r [...]

If you (understandably) don't want to write your bash string on a single very long, line, there are a number of ways you can split it up.

Note that you’re also including another NUL character in your substitution which needs to be changed to a \r linebreak:

vim -c "%s/}\n/},$THEME_CONFIG\n/|wq" ...
                              NUL character

Here's a complete solution, using line continuations to break up the string over several lines:

"        area: 'frontend',\r"\
"        name: '$ESCAPED_THEME_PATH',\r"\
"        locale: 'en_US',\r"\
"        files: [\r"\
"            'css\/styles-m',\r"\
"            'css\/styles-l'\r"\
"        ],\r"\
"        dsl: 'less'\r"\
"    }\r"

vim -c "%s/}\n/},$THEME_CONFIG\r/|wq" themes.js
  • Actually I am. I had to go to the beginning of every line and hit backspace in order for grunt to stop complaining. May 1, 2019 at 7:49
  • @KrzysztofWołowski Hmmm. What were you deleting from the file when you hit backspace? What's different between the incorrect file produced by Vim and the correct one produced by gawk?
    – Rich
    May 1, 2019 at 8:28
  • @KrzysztofWołowski Ah, I see the problem, actually. Your string has newlines in it, as well as \r characters.
    – Rich
    May 1, 2019 at 8:35
  • That may be the case but I remove \r from the string the output will be just one line. May 1, 2019 at 8:51
  • @KrzysztofWołowski Don't remove the \r characters. Remove the newlines.
    – Rich
    May 1, 2019 at 8:55

Thank you guys for the answers. awk (gawk in this particular case) is indeed the way to go. Though the solution has nothing to do with vim I paste it here just in case somebody stumbles upon this question.

        area: 'frontend',
        name: '$ESCAPED_THEME_PATH',
        locale: 'en_US',
        files: [
        dsl: 'less'

gawk -i inplace -v replacement="},\n$THEME_CONFIG" -v RS="" '{sub(/}\n/,replacement)}1' dev/tools/grunt/configs/themes.js  

Two methods to append something in ex mode. You don't need to add \r in these methods.

But in this case, you should use sed or awk, feel free to ignore this answer.


ex -sc +'if !search("}\\n") | q | endif' +'norm! A,' +"a|$THEME_CONFIG" +x theme.js
  • +'if !search("}\n") | q | endif' exit if search failed. :append doesn't work well with global or if, you need to exit immediately if search failed.
  • +'norm! A,' add , after }
  • +"a|$THEME_CONFIG" append a block of text below current line. I didn't add the ending ., but it still works.
  • +x save and exit

Characterwise paste

              v comma added here
        area: 'frontend',
        name: '$ESCAPED_THEME_PATH',
        locale: 'en_US',
        files: [
        dsl: 'less'
     ^ no line break between } and "

ex -ns +"let @\"=\"$THEME_CONFIG\"" +'g/}\n/ norm! $p' +x  term.js
  • -ns ignore recovery file, silent.
  • +"let@\"=\"$THEME_CONFIG\"" set default register to desired content. Because you want to add , directly after }, you must paste in characterwise mode, so you need to make sure THEME_CONFIG doesn't end in newline, otherwise your default register ended up in linewise mode. check :h :let-@.
  • +'g/}\n/ norm! $p' search, go to line end, paste. check :h :g.

Your Answer

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

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