Sed: matches a string containing a newline

I have a line like this:

# pap 

which basically converts to \t#\n\tpap , and I want to replace it:

  # pap python 

which corresponds to \t#\n\tpap\n\tpython .

I tried this with sed in many ways, but it does not work, perhaps because sed uses newlines differently. I tried:

 sed -i "s/\t#\n\tpap/\t#\tpython\n\tpap/" /etc/freeradius/sites-available/default 

... and many other ways without result. Any idea how I can make my replacement in this situation?

PS Ubuntu / bash

+6
source share
4 answers

try this line with gawk:

 awk -v RS="\0" -v ORS="" '{gsub(/\t#\n\tpap/,"yourNEwString")}7' file 

if you want sed process newlines, you must first read the entire file:

 sed ':a;N;$!ba;s/\t#\n\tpap/NewString/g' file 
+6
source

This may work for you (GNU sed):

 sed '/^\t#$/{n;/^\tpap$/{p;s//\tpython/}}' file 

If the line contains only \t# print it, then if the next line contains only \tpap , print it, and then replace this line with \tpython and print it.

+5
source

A GNU sed solution that does not require a full read of the entire file:

 sed '/^\t#$/ {n;/^\tpap$/a\\tpython'$'\n''}' file 
  • /^\t#$/ matches the lines for comment only (exactly \t# exactly), and in this case (only) the whole expression {...} is executed:
    • n loads and prints the next line.
    • /^\tpap/ exactly matches the next line \tpap .
    • if it matches, a\\tpython will output \n\tpython before the next line reading - note that the line of the attached line ( $'\n' ) is required to signal the end of the text passed to a (you can alternatively use several -e options )

(Aside: with BSD sed (OS X) it gets cumbersome because

  • Control characters. such as \n and \t not supported directly and must be spliced ​​in the form of literals quoted by ANSI C.
  • Leading spaces are invariably removed from the text argument to the a command, so the substitution approach should be used: s//&\'$'\n\t'python'/ replaces the pap string with itself plus the string to add:

     sed '/^'$'\t''#$/ {n; /^'$'\t''pap$/ s//&\'$'\n\t'python'/;}' file 

)


awk solution (POSIX compatible), which also does not require a full read of the entire file:

 awk '{print} /^\t#$/ {f=1;next} f && /^\tpap$/ {print "\tpython"} {f=0}' file 
  • {print} : prints each line of input
  • /^\t#$/ {f=1;next} : sets the flag f (for 'found') to 1 if only the comment line is found (matching \t# ) and goes to the next line.
  • f && /^\tpap$/ {print "\tpython"} : if the line is preceded by a comment line and exactly matches \tpap , an additional line \tpython .
  • {f=0} : resets the flag indicating the line for comment only.
+3
source

A few clean bash solutions:

Brief, but somewhat fragile, using the parameter extension:

 in=$'\t#\n\tpap\n' # input string echo "${in/$'\t#\n\tpap\n'/$'\t#\n\tpap\n\tpython\n'}" 
  • The parameter extension supports only patterns (wildcard expressions) as search strings, which limits the possibilities of matching:
  • Here, the assumption is made that pap followed by \n , while no assumptions are made that \t# precedes, which potentially leads to false positives.
  • If one could assume that \t#\n\tpap always enclosed in \n , echo "${in/$'\n\t#\n\tpap\n'/$'\n\t#\n\tpap\n\tpython\n'}" will work reliably; otherwise see below.

Reliable but verbose, using the =~ operator to match regular expressions:

The operator =~ supports extended regular expressions on the right side, and thus provides a more flexible and reliable match:

 in=$'\t#\n\tpap' # input string # Search string and string to append after. search=$'\t#\n\tpap' append=$'\n\tpython' out=$in # Initialize output string to input string. if [[ $in =~ ^(.*$'\n')?("$search")($'\n'.*)?$ ]]; then # perform regex matching out=${out/$search/$search$append} # replace match with match + appendage fi echo "$out" 
+1
source

Source: https://habr.com/ru/post/969815/


All Articles