How to Do Simple Arithmetic in Sed Addresses

Is it possible to do simple arithmetic in sed addresses?

You're right, one can't directly do math in sed1, even addresses. But you can use some trickery to do what you want:

Second-last row:

$ seq 5 | sed -n -e '${ # On the last line
> g # Replace the buffer with the hold space
> p # and print it
> }
> h' # All lines, store the current line in the hold space.
4

Between START and END:

$ cat test.in
1
START
2
3
END
4
$ cat test.in | sed '/^START$/,/^END$/{
> /^START$/d
> /^END$/d
> p
> }
> d'
2
3
$ cat test.in | sed -n -e '/^START$/,/^END$/!d' -e '/^START/d' -e '/^END$/d' -e p
2
3

I'm using a BSD (mac) sed; on GNU systems you can use ; between lines instead of a newline. Or stick it in a script.

1: Sed is Turing complete, so you can do math, but it's unwieldy at best: http://rosettacode.org/wiki/A%2BB#sed

Yes, I know, UUOC; it's for illustration only

sed regex replace with mathematics and quotation

If you use the p option, you'll see the issue:

$ sed -E 's/(.*)(port=\")([0-9]+)(\".*)/echo \"\1\2$((\3+50))\4\"/gpe' ip.txt
echo "<AB port="$((10000+50))" address="0.0.0.0" adpcpu="1"/>"
<AB port=10050 address=0.0.0.0 adpcpu=1/>

You can workaround by using single quotes:

$ sed -E 's/(.*port=")([0-9]+)(.*)/echo \x27\1\x27$((\2+50))\x27\3\x27/pe' ip.txt
echo '<AB port="'$((10000+50))'" address="0.0.0.0" adpcpu="1"/>'
<AB port="10050" address="0.0.0.0" adpcpu="1"/>

$ sed -E 's/(.*port=")([0-9]+)(.*)/echo \x27\1\x27$((\2+50))\x27\3\x27/e' ip.txt
<AB port="10050" address="0.0.0.0" adpcpu="1"/>

I'll also suggest to use perl instead:

$ perl -pe 's/port="\K\d+/$&+50/e' ip.txt
<AB port="10050" address="0.0.0.0" adpcpu="1"/>

How to use sed to identify patterns in multiple lines

One possible sed solution:

sed -r 's/^[[:digit:]]+\. /# /g' <inputfile>
  • -r : treat search pattern as an extended regex
  • /^[[:digit]]+\. /# /g : look for lines that start with 1 or more digits followed by a period and a space, and if found replace with a # followed by a space
  • leave all other lines as they are (ie, don't make any changes)

For example:

$ cat datfile
1. numberedlist
2. one
3. two
where in the world is waldo
10. pickles
15. jam
# I'm just a comment
sky blue
100. bash
101. ksh
102. csh
72.don't touch this
# rubber ducky

And a test run of our sed script:

$ sed -r 's/^[[:digit:]]+\. /# /g' datfile
# numberedlist
# one
# two
where in the world is waldo
# pickles
# jam
# I'm just a comment
sky blue
# bash
# ksh
# csh
72.don't touch this
# rubber ducky

What Does an Empty Regular Expression in a Sed Address Range Do?

Let me simplify the input file as:

Line1
Line2
Line3
Line4
Line5

and the test script as:

sed -n "/[134]/,//p"

which will print the all lines, corresponding to your test results.
As noted, the empty regex repeats the previous regex, then the sed command
above is equivalent to:

sed -n "/[134]/,/[134]/p"

BTW the address range operator of sed works as follows:

  • If the left address matches, returns true without evaluating
    right address on the same line (unlike the range operator of awk which evaluates the right
    condition immediately on the same line).

Let's see how the operator works line by line.

  • On the first line Line1, the left start address matches and
    returns true.
  • On the second line, the right stop address is evaluated without
    match then the operator keeps true.
  • On the third line, the right stop address matches then it
    changes the status to false (after printing the line).
  • On the fourh line, the left start address matches and
    returns true again.
  • On the fifth line, the right stop address does not match and keeps true.

If you change the regex to /[135]/, you will see a different result.
(Line1, 2, 3, 5 will be printed skipping Line4.)

Using sed to substitute mathematical expressions

You selected the PCRE2 option at regex101.com, while sed only supports POSIX regex flavor.

Here, you are using a POSIX BRE flavor, so the expression will look like

#!/bin/bash
s="( - xi**2 + 2*xi - 1)"
sed 's/( - xi\*\*2 + 2\*xi - 1)/k1/g' <<< "$s"

See the online demo. In POSIX BRE, this expression means:

  • ( - a ( char (when not escaped, matches a ( char)
  • - xi - a fixed string
  • \*\* - a ** string (* is a quantifier in POSIX BRE that means zero or more)
  • 2 + 2 - a fixed 2 + 2 string as + is a mere + char in POSIX BRE
  • \* - a * char
  • xi - 1) - a literal xi - 1) fixed string, ) in POSIX BRE matches a literal ) char.

If you plan to use POSIX ERE, you will need to escape (, ) and + in your regex:

sed -E 's/\( - xi\*\*2 \+ 2\*xi - 1\)/k1/g' 

Note the difference between -e (that says that the next thing is the script to the commands to be executed) and -E (that means the regex flavor is POSIX ERE).

sed + match the first word in line and the second word that begin with abc

Not a sed answer, but this is a clear translation of your requirements:

awk '$1 == "ADDRESS" && substr($2,0,3) == "abc"'

Sed: Better way to address the n-th line where n are elements of an array

sed is for doing s/old/new, that is all, and when you add a shell loop to the mix you've really gone off the rails (see https://unix.stackexchange.com/q/169716/133219). To delete lines whose numbers are stored in an array is (using seq to generate input since no sample input/output provided in the question):

$ a=( 3 7 8 )
$ seq 10 |
awk -v a="${a[*]}" 'BEGIN{split(a,tmp); for (i in tmp) nrs[tmp[i]]} !(NR in nrs)'
1
2
4
5
6
9
10

and if you wanted to stop processing with awk once the last target line has been deleted and let tail finish the job then you could figure out the max value in the array up front and then do awk on just the part up to that last target line:

max=$( printf '%s\n' "${a[@]}" | sort -rn | head -1 )
head -"$max" file | awk '...' file > out
tail +"$((max+1))" file >> out

idk if that'd really be any faster than just letting awk process the whole file since awk is very efficient, especially when you're not referencing any fields and so it doesn't do any field splitting, but you could give it a try.

How do I remove multiple bracket within nested bracket using sed?

As it is not an easy task for sed/awk regex and relevant functions,
please let me show a perl solution, although perl is not tagged in
your question.

perl -i -pe 's/(?<=\$\(\().+?(?=\)\))/ $_ = $&; s#\$\{(.+?)}#$1#g; $_ /ge' "$file"

Input file example:

s=$(( ${s} ** 2 ))
sum=$(( ${a} + ${b} ))
echo $(( (${var} * ${var2}) / ${var3} ))
echo ${d} $((${t1} + ${t2})) ${e}

Modified result:

s=$(( s ** 2 ))
sum=$(( a + b ))
echo $(( (var * var2) / var3 ))
echo ${d} $((t1 + t2)) ${e}
  • The perl substitution s/pattern/expression/e replaces the matched
    pattern with the perl expression instead of literal (or fixed)
    replacement. You can perform a dynamic replacement with this mechanism.
  • (?<=\$\(\().+?(?=\)) matches a substring which is preceded by $((
    and followed )). Then the matched substring will be the content within
    $(( .. )). The perl variable $& is assigned to it.
  • The expression $_ = $&; s#\$\{(.+?)}#$1#g; $_ is a perl code to remove
    the paired ${ and } from the matched substring above. The g option
    after the delimiter # works as that of sed.

Count total number of pattern between two pattern (using sed if possible) in Linux

A very cryptic perl answer:

perl -nE 's/\{(.*?)\}/ say ($1 =~ tr{=}{=}) /ge'

The tr function returns the number of characters transliterated.


With the new requirements, we can make a couple of small changes:

perl -0777 -nE 's/\{(.*?)\}/ say ($1 =~ tr{=}{=}) /ges'
  • -0777 reads the entire file/stream into a single string
  • the s flag to the s/// function allows . to handle newlines like a plain character.

how to edit a line having IPv4 address using sed command

The regex you have used as POSIX BRE cannot match the expected strings due to \d shorthand class that sed does not support, the misused dot inside a range quantifier and incorrect escaping of grouping and range quantifier delimiters.

You may use

sed -E -i 's/[0-9]{1,3}(\.[0-9]{1,3}){3}/ & iburst/g' ntp_file

The POSIX ERE (enabled with the -E option) expression means to match

  • [0-9]{1,3} - one to three digits
  • (\.[0-9]{1,3}){3} - three occurrences of a dot and one to three digits

The replacement pattern is & iburst where & stands for the whole match.

The g flag replaces all occurrences.



Related Topics



Leave a reply



Submit