Insert linefeed in sed (Mac OS X)

匿名 (未验证) 提交于 2019-12-03 08:44:33

问题:

How do I insert a newline in the replacement part of sed?

This code isn't working:

sed "s/\(1234\)/\n\1/g" input.txt > output.txt 

where input.txt is:

test1234foo123bar1234 

and output.txt should be:

test 1234foo123bar 1234 

but insted I get this:

testn1234foo123barn1234 

NOTE:

This question is specifically about the Mac OS X version of "sed", and the community has noted that it behaves differently than, say, Linux versions.

回答1:

Your sed version apparently does not support \n in RHS (right-hand side of substitution). You should read THE SED FAQ maintained by Eric Pement to choose one of possible solutions. I suggest trying first inserting literal newline character.

Below is the quote from it.


4.1. How do I insert a newline into the RHS of a substitution?

Several versions of sed permit \n to be typed directly into the RHS, which is then converted to a newline on output: ssed, gsed302a+, gsed103 (with the -x switch), sed15+, sedmod, and UnixDOS sed. The easiest solution is to use one of these versions.

For other versions of sed, try one of the following:

(a) If typing the sed script from a Bourne shell, use one backslash \ if the script uses 'single quotes' or two backslashes \\ if the script requires "double quotes". In the example below, note that the leading > on the 2nd line is generated by the shell to prompt the user for more input. The user types in slash, single-quote, and then ENTER to terminate the command:

 [sh-prompt]$ echo twolines | sed 's/two/& new\  >/'  two new  lines  [bash-prompt]$ 

(b) Use a script file with one backslash \ in the script, immediately followed by a newline. This will embed a newline into the "replace" portion. Example:

 sed -f newline.sed files   # newline.sed  s/twolines/two new\  lines/g 

Some versions of sed may not need the trailing backslash. If so, remove it.

(c) Insert an unused character and pipe the output through tr:

 echo twolines | sed 's/two/& new=/' | tr "=" "\n"   # produces  two new  lines 

(d) Use the G command:

G appends a newline, plus the contents of the hold space to the end of the pattern space. If the hold space is empty, a newline is appended anyway. The newline is stored in the pattern space as \n where it can be addressed by grouping \(...\) and moved in the RHS. Thus, to change the "twolines" example used earlier, the following script will work:

 sed '/twolines/{G;s/\(two\)\(lines\)\(\n\)/\1\3\2/;}' 

(e) Inserting full lines, not breaking lines up:

If one is not changing lines but only inserting complete lines before or after a pattern, the procedure is much easier. Use the i (insert) or a (append) command, making the alterations by an external script. To insert This line is new BEFORE each line matching a regex:

 /RE/i This line is new               # HHsed, sedmod, gsed 3.02a  /RE/{x;s/$/This line is new/;G;}     # other seds 

The two examples above are intended as "one-line" commands entered from the console. If using a sed script, i\ immediately followed by a literal newline will work on all versions of sed. Furthermore, the command s/$/This line is new/ will only work if the hold space is already empty (which it is by default).

To append This line is new AFTER each line matching a regex:

 /RE/a This line is new               # HHsed, sedmod, gsed 3.02a  /RE/{G;s/$/This line is new/;}       # other seds 

To append 2 blank lines after each line matching a regex:

 /RE/{G;G;}                    # assumes the hold space is empty 

To replace each line matching a regex with 5 blank lines:

 /RE/{s/.*//;G;G;G;G;}         # assumes the hold space is empty 

(f) Use the y/// command if possible:

On some Unix versions of sed (not GNU sed!), though the s/// command won't accept \n in the RHS, the y/// command does. If your Unix sed supports it, a newline after aaa can be inserted this way (which is not portable to GNU sed or other seds):

 s/aaa/&~/; y/~/\n/;    # assuming no other '~' is on the line! 


回答2:

Here's a single-line solution that works with any POSIX-compatible sed (including the FreeBSD version on OSX), assuming your shell is bash or ksh or zsh:

sed 's/\(1234\)/\'$'\n''\1/g' 

Note that you could use a single ANSI C-quoted string as the entire sed script, sed $'...' , but that would necessitate \-escaping all \ instances (doubling them), which is quite cumbersome and hinders readability, as evidenced by @tovk's answer).

  • $'\n' represents a newline and is an instance of ANSI C quoting, which allows you to create strings with control-character escape sequences.
  • The above splices the ANSI C-quoted string into the sed script as follows:
    • The script is simply broken into 2 single-quoted strings, with the ANSI C-quoted string stuck between the two halves:
    • 's/\(1234\)/\' is the 1st half - note that it ends in \, so as to escape the newline that will be inserted as the next char. (this escaping is necessary to mark the newline as part of the replacement string rather than being interpreted as the end of the command).
    • $'\n' is the ANSI C-quoted representation of a newline character, which the shell expands to an actual newline before passing the script to sed.
    • '\1/g' is the 2nd half.

Note that this solution works analogously for other control characters, such as $'\t' to represent a tab character.


Background info:



回答3:

The solaris version of sed I could convince to work this way (in bash):

echo test1234foo123bar1234 | sed 's/\(1234\)/\ \1/g' 

(you have to put the line break directly after the backslash).

In csh I had to put one more backslash:

echo test1234foo123bar1234 | sed 's/\(1234\)/\\ \1/g' 

The Gnu version of sed simply worked using \n:

echo test1234foo123bar1234 | sed 's/\(1234\)/\n\1/g' 


回答4:

Perl provides a richer "extended" regex syntax which is useful here:

perl -p -e 's/(?=1234)/\n/g' 

means "substitute a newline for the zero-width match following the pattern 1234". This avoids having to capture and repeat part the expression with backreferences.



回答5:

Unfortunately, for me, sed seems to ignore \ns in the replacement string.

$ echo test1234foo123bar1234 | sed "s/\(1234\)/\n\1/g" testn1234foo123barn1234 

If that happens for you as well, an alternative is to use:

$ echo test1234foo123bar1234 | sed "s/\(1234\)/\\`echo -e '\n\r'`\1/g" 

This should work anywhere and will produce:

test 1234foo123bar 1234 

For your example with an input.txt file as input and output.txt as output, use:

$ sed "s/\(1234\)/\\`echo -e '\n\r'`\1/g" input.txt > output.txt 


回答6:

Get a GNU sed.

$ brew install gnu-sed 

Then your command will work as expected:

$ gsed "s/\(1234\)/\n\1/g" input.txt test 1234foo123bar 1234 

nb: you may get GNU sed thanks to mac ports too.



回答7:

Try this:

$ echo test1234foo123bar1234 | sed "s/\(1234\)/\n\1/g" test 1234foo123bar 1234 

From Sed Gnu doc

g     Apply the replacement to all matches to the regexp, not just the first.  


回答8:

You may also use the $'string' feature of Bash:

man bash | less -p "\\$'"  printf  '%s' 'test1234foo123bar1234'  | sed $'s/\\(1234\\)/\\\n\\1/g' 


回答9:

The newline in the middle of the command can feel a bit clumsy:

$ echo abc | sed 's/b/\ /' a c 

Here are two solutions to this problem which I think should be quite portable (should work for any POSIX-compliant sh, printf, and sed):

Solution 1:

Remember to escape any \ and % characters for printf here:

$ echo abc | sed "$(printf 's/b/\\\n/')" a c 

To avoid the need for escaping \ and % characters for printf:

$ echo abc | sed "$(printf '%s\n%s' 's/b/\' '/')" a c 

Solution 2:

Make a variable containing a newline like this:

newline="$(printf '\nx')"; newline="${newline%x}" 

Or like this:

newline=' ' 

Then use it like this:

$ echo abc | sed "s/b/\\${newline}/" a c 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!