Maintain $ (dollar sign) when performing find and replace - regex

My script appends a block of text to the end of a file.
### Start My Block
$IPT -A INPUT -s 8.8.8.8 -j ACCEPT
$IPT -A INPUT -s 8.8.8.4 -j ACCEPT
### End My Block
If my text block doesn't exists, it successfully appends the file as shown above using the following:
HEAD="### Start My Block"
TAIL="### End My Block"
REPLACEMENT_CONTENT="
\$IPT -A INPUT -s 8.8.8.8 -j ACCEPT
\$IPT -A INPUT -s 8.8.8.4 -j ACCEPT"
echo -e "$HEAD" >> $FILE
echo -e "$REPLACEMENT_CONTENT" >> $FILE
echo -e "$TAIL" >> $FILE
If my text block exists, it attempts to replace the block's contents using the following:
HEAD="### Start My Block"
TAIL="### End My Block"
REPLACEMENT_CONTENT="
\$IPT -A INPUT -s 4.4.4.4 -j ACCEPT
\$IPT -A INPUT -s 4.4.2.2 -j ACCEPT"
perl -0777 -i -pe "s/($HEAD).*(\\n$TAIL)/\$1$REPLACEMENT_CONTENT\\$2/s" $FILE
The problem is I lose the "$IPT" and end up using the following:
### Start My Block
-A INPUT -s 4.4.4.4 -j ACCEPT
-A INPUT -s 4.4.2.2 -j ACCEPT
### End My Block
When I output the above via ">>", with the dollar sign escaped \$, the $IPT is included. When I revisit the file to replace existing content between the $HEAD and $TAIL and use the perl function with an updated block, the escaped dollar sign doesn't seem to hold. My guess is it's being interpreted and I'm not sure how to prevent that if escaping isn't enough.
Does anyone know how I can modify the command above to maintain the "$IPT"?

Yes, you are right your $IPT get interpreted in your regex inside perl.
So, using \$IPT this you are escaping $ for the shell. But you also need to escape $ for perl, so that it shouldn't interpret. For that you should do like this:
REPLACEMENT_CONTENT="
\\\$IPT -A INPUT -s 8.8.8.8 -j ACCEPT
\\\$IPT -A INPUT -s 8.8.8.4 -j ACCEPT"
This escapes \ once and then $ itself sending perl \$ and thus it did not get interpreted.
OR
Just wrap REPLACEMENT_CONTENT in single quotes.
REPLACEMENT_CONTENT='
\$IPT -A INPUT -s 8.8.8.8 -j ACCEPT
\$IPT -A INPUT -s 8.8.8.4 -j ACCEPT'
And after that perl part:
perl -0777 -i -pe "s/($HEAD).*($TAIL)/\$1$REPLACEMENT_CONTENT\n\$2/s" $FILE

Related

Hard regex with sed

In a script.sh file, I have the following line:
ExecStart=ssh -nN -R 46:192.168.0.1:56 192.168.0.2
I try to replace with sed the second port (56 here) knowing that its value can vary between 1 and 65535.
So I tried that without success :
sed -i -e "s/:.*[[:space/]]/other port number/2g' script.sh
Could you help me solve my regex?
You may use:
sed -i "s/:[0-9]\{1,5\} /:other port number /" script.sh
$ other_port_number="123"
$ echo "ExecStart=ssh -nN -R 46:192.168.0.1:56 192.168.0.2" | sed "s/:[0-9]\{1,5\} /:$other_port_number /"
ExecStart=ssh -nN -R 46:192.168.0.1:123 192.168.0.2

perl regex doesn't match and include next line [duplicate]

I'm trying to use perl (v5.14.2) via a bash shell (GNU Bash-4.2) in Kubuntu (GNU/Linux) to search and replace a string that includes a newline character, but I'm not succeeding yet.
Here's the text file I'm searching:
<!-- filename: prac1.html -->
hello
kitty
blah blah blah
When I use a text editor's (Kate's) search-and-replace functionality or when I use a regex tester (http://regexpal.com/), I can easily get this regex to work:
hello\nkitty
But when using perl in the command line, none of the following commands have worked:
perl -p -i -e 's,hello\nkitty,newtext,' prac1.html
perl -p -i -e 's,hello.kitty,newtext,s' prac1.html
perl -p -i -e 's,hello.*kitty,newtext,s' prac1.html
perl -p -i -e 's,hello[\S\s]kitty,newtext,' prac1.html
perl -p -i -e 's,hello[\S\s]*kitty,newtext,' prac1.html
Actually, I got desperate and tried many other patterns, including all of these (different permutations in the "single-line" and "multi-line" modes):
perl -p -i -e 's,hello\nkitty,newtext,' prac1.html
perl -p -i -e 's,hello.kitty,newtext,' prac1.html
perl -p -i -e 's,hello\nkitty,newtext,s' prac1.html
perl -p -i -e 's,hello.kitty,newtext,s' prac1.html
perl -p -i -e 's,hello\nkitty,newtext,m' prac1.html
perl -p -i -e 's,hello.kitty,newtext,m' prac1.html
perl -p -i -e 's,hello\nkitty,newtext,ms' prac1.html
perl -p -i -e 's,hello.kitty,newtext,ms' prac1.html
perl -p -i -e 's,hello[\S\s]kitty,newtext,' prac1.html
perl -p -i -e 's,hello[\S\s]*kitty,newtext,' prac1.html
perl -p -i -e 's,hello$[\S\s]^kitty,newtext,' prac1.html
perl -p -i -e 's,hello$[\S\s]*^kitty,newtext,' prac1.html
perl -p -i -e 's,hello[\S\s]kitty,newtext,s' prac1.html
perl -p -i -e 's,hello[\S\s]*kitty,newtext,s' prac1.html
perl -p -i -e 's,hello$[\S\s]^kitty,newtext,s' prac1.html
perl -p -i -e 's,hello$[\S\s]*^kitty,newtext,s' prac1.html
perl -p -i -e 's,hello[\S\s]kitty,newtext,m' prac1.html
perl -p -i -e 's,hello[\S\s]*kitty,newtext,m' prac1.html
perl -p -i -e 's,hello$[\S\s]^kitty,newtext,m' prac1.html
perl -p -i -e 's,hello$[\S\s]*^kitty,newtext,m' prac1.html
perl -p -i -e 's,hello[\S\s]kitty,newtext,ms' prac1.html
perl -p -i -e 's,hello[\S\s]*kitty,newtext,ms' prac1.html
perl -p -i -e 's,hello$[\S\s]^kitty,newtext,ms' prac1.html
perl -p -i -e 's,hello$[\S\s]*^kitty,newtext,ms' prac1.html
(I also tried using \r \r\n \R \f \D etc., and global mode as well.)
Can anyone spot the issue or suggest a solution?
Try doing this, I make this possible by modifying the input record separator (a newline by default) :
perl -i -p00e 's,hello\nkitty,newtext,' prac1.html
from perldoc perlrun :
-0[octal/hexadecimal]
specifies the input record separator ($/ ) as an octal or hexadecimal
number. If there are no digits, the null character is the separator.
Other switches may precede or follow the digits. For example, if you
have a version of find which can print filenames terminated by the
null character, you can say this:
find . -name '*.orig' -print0 | perl -n0e unlink
The special value 00 will cause Perl to slurp files in paragraph mode.
Any value 0400 or above will cause Perl to slurp files whole, but by
convention the value 0777 is the one normally used for this purpose.
The problem is that "-p" has already implicitly wrapped this loop around your "-e", and the "<>" is splitting the input into lines, so your regexp never gets a chance to see more than one line.
LINE:
while (<>) {
... # your program goes here
} continue {
print or die "-p destination: $!\n";
}
See the perlrun manpage for more information.

Extract data from one file and save to another

I'm running ansible playbook task using shell command to extract data from file based on regex match and save it to another file.
I tried using awk and sed but not able to get the regex working.
awk '$NF == "-m.comment.*\""' iptable.txt" > file1.txt
sed 's/\/.*' iptable.txt > file2.txt
I need to save any content from -m comment till the double quotes. to file1.txt and remaining content to file2.txt. If the line doesnot have comment field, then it should be saved only to file2.txt.
-P INPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p icmp -m state --state NEW -m comment --comment "Echo Request" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m comment --comment "tcp" -j ACCEPT
Expected output:
cat file1.txt
-m comment --comment "Echo Request"
-m comment --comment "tcp"
cat file2.txt
-P INPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p icmp -m state --state NEW -j ACCEPT
-A INPUT -p tcp -m state --state NEW -j ACCEPT
With GNU awk for the 3rd arg to match():
awk 'match($0,/(.*)(-m.comment.*")(.*)/,a) {
print a[2] " > foo"
print a[1] a[3] " > bar"
}' file
-m comment --comment "Echo Request" > foo
-A INPUT -p icmp -m state --state NEW -j ACCEPT > bar
With any awk:
awk 'match($0,/-m.comment.*"/) {
print substr($0,RSTART,RLENGTH) " > foo"
print substr($0,1,RSTART-1) substr($0,RSTART+RLENGTH) " > bar"
}' file
Just change " > foo" to > "foo" and ditto for bar to really write to new files.
If that's not exactly what you need then edit your question to clarify your requirements and provide more truly representative sample input/output.
Oh and when you wrote:
$NF == "-m.comment.*\""
There's nothing wrong with -m.comment.*\" as a regexp per-se BUT == is telling awk to do a literal string comparison, not a regexp comparison.
Given your updated question, just tweak the above to:
awk 'match($0,/(.*)(-m.comment.*")(.*)/,a) {
print a[2] " > foo"
print a[1] a[3] " > bar"
next
}
{ print $0 " > bar" }' file
-P INPUT ACCEPT > bar
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT > bar
-m comment --comment "Echo Request" > foo
-A INPUT -p icmp -m state --state NEW -j ACCEPT > bar
-m comment --comment "tcp" > foo
-A INPUT -p tcp -m state --state NEW -j ACCEPT > bar

Renaming files with sed, escaping issues

I'm trying to write a bash script to remove spaces, underscores and dots and replace them with dashes. I also set to lowercase and remove brackets. That's the (long) second sed command, which seems to work.
The first sed call escapes the original names with spaces with '\ ' like when I tab complete, and this is the issue I think.
If I replace 'mv -i' with 'echo' I get what I think I want: the original filename escaped with backslashes and then the new name. If I paste this into the terminal it works, but with mv in the script the spaces cause problems. The escaping doesn't work.
#!/bin/bash
for a in "$#"; do
mv -i $(echo "$a" | sed -e 's/ /\\\ /g') $(echo "$a" | sed -e 's/\(.*\)/\L\1/' -e 's/_/-/g' -e 's/ /-/g' -e 's/---/--/g' -e 's/(//g' -e 's/)//g' -e 's/\[//g' -e 's/\]//g' -e 's/\./-/g' -e 's/-\([^-]*\)$/\.\1/')
done
The other solution is to put quotes around the names, but I can't work out how I would do this. I feel like I've got close, but I'm stumped.
I've also considered the 'rename' command, but you cannot do multiple operations like you can with sed.
Please point out any other issues, this is one of my first scripts. I'm not sure I got the "$#" or "$a" bits completely correct.
Cheers.
edit:
sample input filename
I am a Badly [named] (file) - PLEASE.rename_me.JPG
should become
i-am-a-badly-named-file--please-rename-me.jpg
edit2: my solution, tweaked from gniourf_gniourf's really helpful pure bash answer:
#!/bin/bash
for a in "$#"; do
b=${a,,} #lowercase
b=${b//[_[:space:]\.]/-} #subst dot,space,underscore with dash
b=${b//---/--} #remove triple dash
b=${b//[()\[\]]/} #remove brackets
if [ "${b%-*}" != "$b" ]; then #if there is a dash (prevents filename.filename)
b=${b%-*}.${b##*-} #replace final dash with a dot for extension
fi
if [ "$a" != "$b" ]; then #if there has been a change
echo '--->' "$b" #
#mv -i -- "$a" "$b" #rename
fi
done
This only fails if the file had spaces etc and no extension (e.g this BAD_filename becomes this-bad.filename. But these are media files and should have an extension, so I would have to sort them anyway.
Again, corrections and improvements welcome. I'm new at this stuff
Try doing this with rename :
rename 's/[_\s\.]/-/g' *files
from the shell prompt. It's very useful, you can put some perl code inside if needed.
You can remove the -n (dry-run mode switch) when your tests become valids.
There are other tools with the same name which may or may not be able to do this, so be careful.
If you run the following command (linux)
$ file $(readlink -f $(type -p rename))
and you have a result like
.../rename: Perl script, ASCII text executable
then this seems to be the right tool =)
If not, to make it the default (usually already the case) on Debian and derivative like Ubuntu :
$ sudo update-alternatives --set rename /path/to/rename
(replace /path/to/rename to the path of your perl's rename command.
Last but not least, this tool was originally written by Larry Wall, the Perl's dad.
Just for the records, look:
$ a='I am a Badly [named] (file) - PLEASE.rename_me.JPG'
$ # lowercase that
$ echo "${a,,}"
i am a badly [named] (file) - please.rename_me.jpg
$ # Cool! let's save that somewhere
$ b=${a,,}
$ # substitution 's/[_ ]/-/g:
$ echo "${b//[_ ]/-}"
i-am-a-badly-[named]-(file)---please.rename-me.jpg
$ # or better, yet:
$ echo "${b//[_[:space:]]/-}"
i-am-a-badly-[named]-(file)---please.rename-me.jpg
$ # Cool! let's save that somewhere
$ c=${b//[_[:space:]]/-}
$ # substitution 's/---/--/g' (??)
$ echo "${c//---/--}"
i-am-a-badly-[named]-(file)--please.rename-me.jpg
$ d=${c//---/--}
$ # substitution 's/()[]//g':
$ echo "${d//[()\[\]]/}"
i-am-a-badly-named-file--please.rename-me.jpg
$ e="${d//[()\[\]]/}"
$ # substitution 's/\./-/g':
$ echo "${e//\./-}"
i-am-a-badly-named-file--please-rename-me-jpg
$ f=${e//\./-}
$ # substitution 's/-\([^-]*\)$/\.\1/':
$ echo "${f%-*}.${f##*-}"
i-am-a-badly-named-file--please-rename-me.jpg
$ # Done!
Now, here's a 100% bash implementation of what you're trying to achieve:
#!/bin/bash
for a in "$#"; do
b=${a,,}
b=${b//[_[:space:]]/-}
b=${b//---/--}
b=${b//[()\[\]]/}
b=${b//\./-}
b=${b%-*}.${b##*-}
mv -i -- "$a" "$b"
done
yeah, done!
All this standard and known as shell parameter expansion.
Remark. For a more robust script, you could check whether a has an extension (read: a period in its name), otherwise the last substitution of the algorithm fails a little bit. For this, put the following line just below the for statement:
[[ a != *.* ]] && { echo "Oh no, file \`$a' has no extension..."; continue; }
(and isn't the *.* part of this line so cute?)

Using the eval command to create a command alias

For the life of me I cannot get the bash script to execute the alias command to set the hostname of a workstation the alias name to the WOL (Wakeup On Lan) equivalent command. I figure there must be an issue with quoting somewhere that I am missing.
#!/bin/bash
WOLHosts=`nvram get wol_hosts`
WOLList=($(echo "$WOLHosts" | grep -o '[A-F0-9]\{2\}:[A-F0-9]\{2\}:[A-F0-9]\{2\}:[A-F0-9]\{2\}:[A-F0-9]\{2\}:[A-F0-9]\{2\}=[^=]*=[0-9]*[.][0-9]*[.][0-9]*[.][0-9]*' ))
if [ "${#WOLList[#]}" -gt 0 ]
then
for Match in ${WOLList[#]}
do
Command=`echo "$Match" | sed -r "s/([A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2})=([^=]*)=([0-9]*[.][0-9]*[.][0-9]*[.][0-9]*)/alias \2='\/usr\/sbin\/wol -i \3 \1'/"`
Name=`echo "$Match" | sed -r "s/([A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2})=([^=]*)=([0-9]*[.][0-9]*[.][0-9]*[.][0-9]*)/\2/"`
Com=`echo "$Match" | sed -r "s/([A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2}:[A-F0-9]{2})=([^=]*)=([0-9]*[.][0-9]*[.][0-9]*[.][0-9]*)/\/usr\/sbin\/wol -i \3 \1/"`
alias $Name="$Com"
eval $Command
echo "$Command"
done
fi
exit 0
Here is some sample data and output that I am currently receiving with the script:
Input (into WOLHosts):
00:1F:D0:26:72:53=Justin-PC=192.168.1.255 00:16:17:DD:12:7B=Justin-HTPC=192.168.1.255 00:1C:25:BC:C3:85=justinlaptop=192.168.1.255
The output produced by the vi WOecho "$Command" is:
alias Justin-PC='/usr/sbin/wol -i 192.168.1.255 00:1F:D0:26:72:53'
alias Justin-HTPC='/usr/sbin/wol -i 192.168.1.255 00:16:17:DD:12:7B'
alias justinlaptop='/usr/sbin/wol -i 192.168.1.255 00:1C:25:BC:C3:85'
Since you appear to be running this as a script, your current shell will not receive the aliases -- the aliases will disappear then the bash process driving the script ends.
Try: . script.sh or source script.sh