parricc
parricc

Reputation: 89

sed challenge: escaping multiple backslashes in a specific part of line

My goal is to change all occurrences of a pattern, but only between two other patterns.

#!/bin/bash
while [ "$( sed -n -e "/^echo.*foo.*>>/p" file.txt | wc -l )" -gt 0 ]; do
    sed -i -e "/^echo.*foo.*>>/ s/foo/moo/" file.txt
done

The above code will change foo to moo if it occurs between ^echo and >>.

Before:

foo
echo "foo"
foo >> foo
foo echo "foo" >> foo
echo "poo" >> foo
echo "foo" >> foo
echo "foo foo" >> foo
echo "foofoofoo" >> foo

After:

foo
echo "foo"
foo >> foo
foo echo "foo" >> foo
echo "poo" >> foo
echo "moo" >> foo
echo "moo moo" >> foo
echo "moomoomoo" >> foo

However, here is the ugly challenge. What I am actually wanting to do is to escape all backslashes in the same area like follows:

Before:

\
echo "\"
\ >> \
\ echo "\" >> \
echo "poo" >> \
echo "\" >> \
echo "\ \" >> \
echo "\\\" >> \

After:

\
echo "\"
\ >> \
\ echo "\" >> \
echo "poo" >> \
echo "\\" >> \
echo "\\ \\" >> \
echo "\\\\\\" >> \

Without using Perl or Python, what would be a way to do this? Thanks in advance! :)

Upvotes: 1

Views: 62

Answers (3)

Ed Morton
Ed Morton

Reputation: 203189

Good grief, just use awk:

$ awk 'match($0,/^(echo)(.*)(>>.*)/,a) {gsub(/\\/,"\\\\",a[2]); $0=a[1] a[2] a[3]}1' file
\
echo "\"
\ >> \
\ echo "\" >> \
echo "poo" >> \
echo "\\" >> \
echo "\\ \\" >> \
echo "\\\\\\" >> \

The above uses GNU awk for the 3rd arg to match(), with other awks you'd use substr()s.

Upvotes: 1

Casimir et Hippolyte
Casimir et Hippolyte

Reputation: 89547

You can do it using a substitution and without a while loop:

sed '/^echo.*\\.*>>/{s/~/~~/g;s/\\/\\\\~/g;:a;s/^\(echo.*\\\\\)~\(.*>>\)/\1\2/;ta;s/\\\\~/\\/g;s/~~/~/g;}' file

details:

/^echo.*\\.*>>/ { # condition
    s/~/~~/g;      # protect the '~'
    s/\\/\\\\~/g;  # change all backslashes to double + '~'
    :a;            # define the label "a"
    s/^\(echo.*\\\\\)~\(.*>>\)/\1\2/; # change a "\\~" inside "echo...>>" to "\\"
    ta; # if something is replaced go to label "a"
    s/\\\\~/\\/g;  # restore backslashes outside "echo...>>" 
    s/~~/~/g; # restore '~'
}

Note: if needed, you can be more rigorous using [^>] instead of the dots, or to be totally sure \([^>]*\(>[^>][^>]*\)*\) in the third replacement, but for this last one you must change the replacement string to \1:

s/^\(echo\([^>]*\(>[^>][^>]*\)*\)*\\\\\)~/\1/;

Upvotes: 0

John Goofy
John Goofy

Reputation: 1419

I have copied your shell script, but it doesn't work on my station.

But if your file looks like this:

$ cat file
foo
echo "foo"
foo >> foo
foo echo "foo" >> foo
echo "poo" >> foo
echo "moo" >> foo
echo "moo moo" >> foo
echo "moomoomoo" >> foo

You can try this

sed -e 's/foo/\\/g' -e 's/moo/\\\\/g' file

If you want to have it inline you have to set the -i flag.

Upvotes: 0

Related Questions