trying to understand nested brackets in sed script - bash

/^Host.*latency.*/{
$!N
/MAC Address/{
s/.*(\(.*\)) .*MAC Address: \(.*\) .*/\1 -> \2/
}
}
/[Nn]map/d
s/^Host .*is up/& but MAC Address cannot be found/
I am trying to understand sed script as above.Can some one help me to understand I never used sed as above.
Its use is
nmap -sP 192.168.1.0/20 | sed -f sedcript.sh

If you mean the nested parentheses in (\(.*\)). The outer pair are literal and the inner, escaped pair capture the string matched by the enclosed regular expression. The backreference \1 outputs this captured string. The \2 backreference outputs the string captured by the second pair of escaped parentheses.
If you mean the curly braces, they surround blocks of commands. The outer group says that if the line matches ^Host.*latency.* then execute the enclosed command. The $!N command appends the next line of the file if the current line isn't the last. If the combination of the lines matches MAC Address then the block inside the next pair of curly braces is executed (the substitution is performed).
You can read it like this:
/^Host.*latency.*/{ - If the line matches this regex, then
$!N - Append the next line if the current line isn't the last line
/MAC Address/{ - If the combined lines match this regex, then
s/.*(\(.*\)) .*MAC Address: \(.*\) .*/\1 -> \2/ - Make this substitution
} - End if
} - End if
/[Nn]map/d - If the pattern space (combined lines) matches this, then delete it
s/^Host .*is up/& but MAC Address cannot be found/ - Make this substitution

Related

sed command replace muti line with reg

I got a text like this:
TOKEN = decrypt_aes(
"189272123124aqephkiz3")
And I want to change it into:
TOKEN = "189272123124aqephkiz3"
How can I make this?
I can do this when its in a single line with following command:enter code here`
sed -i "s/decrypt_aes(\(.*\))/\1/g"
But I don`t no how to do when its in multi line
If your file contains only the two lines you showed, then this might help with GNU sed:
sed -i 'N; s/decrypt_aes(\n *//; s/)//' file
From man sed:
N: Append the next line of input into the pattern space.
You can use
sed -i '/.*decrypt_aes($/{N;s/^\([^=]*=\).*\(".*"\))$/\1 \2/}' file
Details:
/.*decrypt_aes($/ - matches a line that ends with decrypt_aes( substring, and if it matches, the block that follows is executed
N - append a newline and the next line to the pattern space
s/^\([^=]*=\).*\(".*"\))$/\1 \2/ - replaces
^\([^=]*=\).*\(".*"\))$ - start of string in the pattern space (^), any zero or more chars other than = and then a = are captured into Group 1 (\1), then any text (.*), then a "..." substring (captured into Group 2 (\2)) and then a ) at the end of string
\1 \2 is the replacement pattern, which is Group 1 value + space + Group 2 value.
See the online demo:
#!/bin/bash
s='TOKEN = decrypt_aes(
"189272123124aqephkiz3")'
sed '/.*decrypt_aes($/{N;s/^\([^=]*=\).*\(".*"\))$/\1 \2/}' <<< "$s"
Output:
TOKEN = "189272123124aqephkiz3"
This might work for you (GNU sed):
sed 'N;s/decrypt_aes.*\(".*"\).*/\1/;P;D' file
Append the following line and if the combined lines matches decrpty_aes followed by "...", replace the match by the string "...".
N.B. If no match is found, each line will be printed as is.

sed replace string with pipe and stars

I have the following string:
|**barak**.version|2001.0132012031539|
in file text.txt.
I would like to replace it with the following:
|**barak**.version|2001.01.2012031541|
So I run:
sed -i "s/\|\*\*$module\*\*.version\|2001.0132012031539/|**$module**.version|$version/" text.txt
but the result is a duplicate instead of replacing:
|**barak**.version|2001.01.2012031541|**barak**.version|2001.0132012031539|
What am I doing wrong?
Here is the value for module and version:
$ echo $module
barak
$ echo $version
2001.01.2012031541
Assumptions:
lines of interest start and end with a pipe (|) and have one more pipe somewhere in the middle of the data
search is based solely on the value of ${module} existing between the 1st/2nd pipes in the data
we don't know what else may be between the 1st/2nd pipes
the version number is the only thing between the 2nd/3rd pipes
we don't know the version number that we'll be replacing
Sample data:
$ module='barak'
$ version='2001.01.2012031541'
$ cat text.txt
**barak**.version|2001.0132012031539| <<<=== leave this one alone
|**apple**.version|2001.0132012031539|
|**barak**.version|2001.0132012031539| <<<=== replace this one
|**chuck**.version|2001.0132012031539|
|**barak**.peanuts|2001.0132012031539| <<<=== replace this one
One sed solution with -Extended regex support enabled and making use of a capture group:
$ sed -E "s/^(\|[^|]*${module}[^|]*).*/\1|${version}|/" text.txt
Where:
\| - first occurrence (escaped pipe) tells sed we're dealing with a literal pipe; follow-on pipes will be treated as literal strings
^(\|[^|]*${module}[^|]*) - first capture group that starts at the beginning of the line, starts with a pipe, then some number of non-pipe characters, then the search pattern (${module}), then more non-pipe characters (continues up to next pipe character)
.* - matches rest of the line (which we're going to discard)
\1|${version}| - replace line with our first capture group, then a pipe, then the new replacement value (${version}), then the final pipe
The above generates:
**barak**.version|2001.0132012031539|
|**apple**.version|2001.0132012031539|
|**barak**.version|2001.01.2012031541| <<<=== replaced
|**chuck**.version|2001.0132012031539|
|**barak**.peanuts|2001.01.2012031541| <<<=== replaced
An awk alternative using GNU awk:
awk -v mod="$module" -v vers="$version" -F \| '{ OFS=FS;split($2,map,".");inmod=substr(map[1],3,length(map[1])-4);if (inmod==mod) { $3=vers } }1' file
Pass two variables mod and vers to awk using $module and $version. Set the field delimiter to |. Split the second field into array map using the split function and using . as the delimiter. Then strip the leading and ending "**" from the first index of the array to expose the module name as inmod using the substr function. Compare this to the mod variable and if there is a match, change the 3rd delimited field to the variable vers. Print the lines with short hand 1
Pipe is only special when you're using extended regular expressions: sed -E
There's no reason why you need extended here, stick with basic regex:
sed "
# for lines matching module.version
/|\*\*$module\*\*.version|/ {
# replace the version
s/|2001.0132012031539|/|$version|/
}
" text.txt
or as an unreadable one-liner
sed "/|\*\*$module\*\*.version|/ s/|2001.0132012031539|/|$version|/" text.txt

Adding zero to part of string using sed

I have SNMP outputs like:
IP-MIB::ipNetToMediaPhysAddress.5122.192.19.3.25 = STRING: 34:8:4:56:f4:70
As you can see mac-address output is incorrect, and i fix it with sed:
echo IP-MIB::ipNetToMediaPhysAddress.5122.192.19.3.25 = STRING: 34:8:4:56:f4:70 |
sed -e 's/\b\(\w\)\b/0\1/g'
Output:
IP-MIB::ipNetToMediaPhysAddress.5122.192.19.03.25 = STRING: 34:08:04:56:f4:70
It fixes address but changes IP as well from 192.19.3.25 to 192.19.03.25. How can I avoid it and force to perform sed only after STRING: or only after last space in the string ?
The MAC address is colon-separated. You can use that to limit the substitutions. This will perform the substitutions that you are interested in but only if the word character is next to a colon:
sed -e 's/\b\w:/0&/g; s/:\(\w\)\b/:0\1/g'
For example:
$ echo IP-MIB::ipNetToMediaPhysAddress.5122.192.19.3.25 = STRING: 34:8:4:56:f4:70 | sed -e 's/\b\w:/0&/g; s/:\(\w\)\b/:0\1/g'
IP-MIB::ipNetToMediaPhysAddress.5122.192.19.3.25 = STRING: 34:08:04:56:f4:70
How it works
s/\b\w:/0&/g
This performs the substitution if the word character is preceded by a word break, \b, and followed by a colon. Since we just need to put a zero in front of the entire matched text, not just some section of it, we can omit the parens and just use & to copy the matched text.
s/:\(\w\)\b/:0\1/g
If there are any remaining substitutions that need to be done where the word character is preceded by a colon and followed by a word break, this does them.
Note: We are using GNU extensions that may not be portable.
Another way with sed if the MAC address is at end of line
echo IP-MIB::ipNetToMediaPhysAddress.5122.192.19.3.25 = STRING: 4:8:d:56:f4:7 |
sed -E '
s/$/:/
:A
s/([^[:xdigit:]])([[:xdigit:]]:)/\10\2/
tA
s/:$//'

How do prevent whitespace from appearing in these bash variables?

I'm reading in values from an .ini file, and sometimes may get trailing or leading whitespace.
How do I amend this first line to prevent that?
db=$(sed -n 's/.*DB_USERNAME *= *\([^ ]*.*\)/\1/p' < config.ini);
echo -"$db"-
Result;
-myinivar -
I need;
-myinivar-
Use parameter expansion.
echo "=${db% }="
You don't need the .* inside the capturing group (or the semicolon at the end of line):
db="$(sed -n 's/.*DB_USERNAME *= *\([^ ]*\).*/\1/p' < config.ini)"
To elaborate:
.* matches anything at all
DB_USERNAME matches that literal string
* (a single space followed by an asterisk) matches any number of spaces
= matches that literal string
* (a single space followed by an asterisk) matches any number of spaces
\( starts the capturing group that is used for \1 later
[^ ] matches anything which is not a space character
* repeats that zero or more times
\) ends the capturing group
.* matches anything at all
Therefore, the result will be all the characters after DB_USERNAME = and any number of spaces, up to the next space or end of line, whichever comes first.
You can use echo to trim whitespace:
db='myinivar '
echo -"$(echo $db)"-
-myinivar-
Use crudini which handles these ini file edge cases transparently
db=$(crudini --get config.ini '' DB_USERNAME)
To get rid of more than one trailing space, use %% which removes the longest matching pattern from the end of the string
echo "=${db%% *}="

pattern matching in ruby

cud any body tell me how this expression works
output = "#{output.gsub(/grep .*$/,'')}"
before that opearation value of ouptput is
"df -h | grep /mnt/nand\r\n/dev/mtdblock4 248.5M 130.7M 117.8M 53% /mnt/nand\r\n"
but after opeartion it comes
"df -h | \n/dev/mtdblock4 248.5M 248.5M 130.7M 117.8M 53% /mnt/nand\r\n "
plzz help me
Your expression is equivalent to:
output.gsub!(/grep .*$/,'')
which is much easier to read.
The . in the regular expression matches all characters except newline by default. So, in the string provided, it matches "grep /mnt/nand", and will substitute a blank string for that. The result is the provided string, without the matched substring.
Here is a simpler example:
"hello\n\n\nworld".gsub(/hello.*$/,'') => "\n\n\nworld"
In both your provided regex, and the example above, the $ is not necessary. It is used as an anchor to match the end of a line, but since the pattern immediately before it (.*) matches everything up to a newline, it is redundant (but does not cause harm).
Since gsub returns a string, your first line is exactly the same as
output = output.gsub(/grep .*$/, '')
which takes the string and removes any occurance of the regexp pattern
/grep .*$/
i.e. all parts of the string that start with 'grep ' until the end of the string or a line break.
There's a good regexp tester/reference here. This one matches the word "grep", then a space, then any number of characters until the next line-break (\r or \n). "." by itself means any character, and ".*" together means any number of them, as many as possible. "$" means the end of a line.
For the '$', see here http://www.regular-expressions.info/reference.html
".*$" means "take every character from the end of the string" ; but the parser will interpret the "\n" as the end of a line, so it stops here.

Resources