Best way to handle objects property:value in the stdin in bash - bash

Bonjour,
I launch nslookup someServer. I consider I get a serie of object (as in powers hell) separated by empty lines and not simply a stdout.
$ nslookup someServer
Server: 10.0.0.1
Address: 10.0.0.1#53
Name: someServer
Address: 10.0.0.5
$
How to get the object who have both properties Name and Address?
nslookup someServer | haveboth Name Address | wc -l
Does it exists in GNU utilities?

are you simply looking for a way to check that you have both of these values in your output?
Then you could use perl in oneline mode (probably not the most pretty solution imaginable, but does what you want and could be expanded to check more things easily)
nslookup someServer | perl -ne '$v{"NAME"}++ if /Name/; $v{"ADDRESS"}++ if /Address/; END{ print "Has both values\n" if $v{"NAME"} && $v{"ADDRESS"} }'
this goes through your output and counts the occurrences of Name and Address and then prints a message if it has more than zero of both.
If using perl in this way is a viable option, then I can recommend this page for further reading on perl oneliners
EDIT:
in case you want to have access to the values that are stored in your property: you can use
nslookup someServer | perl -F: -lane '$v{"NAME"} = $F[1] if /Name/; $v{"ADDRESS"} = $F[1] if /Address/; END{ print $v{"ADDRESS"}." ".$v{"NAME"} if exists $v{"ADDRESS"} && $v{"NAME"} }'
This will split a given line on : as field separator and store the value in the variable instead of simple counting occurrences. Note that done in this way it would only store the last occurrence.

Related

Shell script breaks with grep

Sorry if this a basic/stupid question.
I have no experience in shell scripting but am keen to learn and develop.
I want to create a script that reads a file, extracts an IP address from one line, extracts a port number from another line and sends them both toa a variable so I can telnet.
My file looks kinda like this;
Server_1_ip=192.168.1.1
Server_2_ip=192.168.1.2
Server_port=7777
I want to get the IP only of server_1
And the port.
What I have now is;
Cat file.txt | while read line; do
my_ip="(grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' <<< "$line")"
echo "$my_ip"
done < file.txt
This works but how do I specify server_1_ip?
Next I did the same thing to find the port but somehow the port doesn't show, instead it shows "server_port" and not the number behind it
Do i need to cat twice or can I combine the searches? And why does it pass the IP to variable but not the port?
Many thanks in advance for any input.
Awk may be a better fit:
awk -F"=" '$1=="Server_1_ip"{sip=$2}$1=="Server_port"{sport=$2}END{print sip, sport}' yourfile
This awk says:
Split each row into columns delimited by an equal sign: -F"="
If the first column has the string "Server_1_ip" then store the value in the second column to awk variable sip: $1=="Server_1_ip"{sip=$2}
If the first column as the string "Server_port" then store the value in the second column to awk variable sport: $1=="Server_port"{sport=$2}
Once the entire file has been processed, then print out the value in the two variables: END{print sip, sport}
You could do something like:
#!/bin/bash
while IFS='=' read key value; do
case "$key" in
Server_1_ip) target_ip="$value";;
Server_port) target_port="$value";;
esac
done < input
This is almost certainly not the appropriate solution, since it requires you to statically define the string Server_1_ip, but it's not entirely clear what you are trying to do. You could eval the lines, but that is risky.
How exactly you want to determine the host name to match will greatly influence the desired solution. Perhaps you just want something like:
#!/bin/bash
target_host="${1-Server_1_ip}"
while IFS='=' read key value; do
case "$key" in
$target_host) target_ip="$value";;
Server_port) target_port="$value";;
esac
done < input
Here's a sed variant:
sed -n -e'/Server_1_ip=\(.*\)/{s//\1/;h}; /Server_port=\([0-9]*\)/{s// \1/;H;g;s/\n//p;q}' inputfile
-n stops normal output,
apply -e commands
First find target server, ie. Server_1_ip= with a subexpression that remembers the value (assumed its well formed IPv4). Apply a command block that replaces the pattern space (aka current line) with the saved subexpression and then copies the pattern space to the hold buffer; end command block.
Continue looking for port line. Apply command block that removes the prefix leaving the port number; append the port to the hold buffer (so now you have IP newline port in hold); copy the hold buffer back to pattern space; delete the newline and print result; quit.
Note: GNU and BSD sed can vary especially with trying to join lines, ie. s/\n//.

How to extract port from IP:PORT after a specific string?

I have a file like this;
someip=[2a05:6a4:2a3a:53asd:0:0:0:1]
someip=[2a05:6a4:2a3a:123a:0:0:0:1]
someip=192.168.1.1
someip=192.167.2.1
anotherip=127.0.0.1:1234
and I want to extract only 1234 and pass it into a variable. I usually do this with this command;
grep -o -E '[^:]+$' file.txt
This would return 1234 if there wasn't those IPs in someip=. Because of them, it tries to return those IP addresses too.
How can I only extract 1234 from this file? Maybe there's a way to make this grep command work only on a line that contains the string anotherip= ?
Or is there a way to get only 1234 from the below string? (but this 1234 can be different like 12345, 578214 etc)
something something 127.0.0.1:1234 something something something
Using the Perl mode supported by some versions of grep:
grep -P -o ':\K\d+$'
Look for lines which end in a colon followed by a string of digits and discard the colon and everything that appears before it.
An awk version
awk -F":" '/:[0-9]+/ && !/]/ {print $2}' file
1234

Search all occurences of a instance ids in the variable

I have a bash variable which has the following content:
SSH exit status 255 for i-12hfhf578568tn
i-12hdfghf578568tn is able to connect
i-13456tg is not able to connect
SSH exit status 255 for 1.2.3.4
I want to search the string starting with i- and then extract only that instance id. So, for the above input, I want to have output like below:
i-12hfhf578568tn
i-12hdfghf578568tn
i-13456tg
I am open to use grep, awk, sed.
I am trying to achieve my task by using following command but it gives me whole line:
grep -oE 'i-.*'<<<$variable
Any help?
You can just change your grep command to:
grep -oP 'i-[^\s]*' <<<$variable
Tested on your input:
$ cat test
SSH exit status 255 for i-12hfhf578568tn
i-12hdfghf578568tn is able to connect
i-13456tg is not able to connect
SSH exit status 255 for 1.2.3.4
$ var=`cat test`
$ grep -oP 'i-[^\s]*' <<<$var
i-12hfhf578568tn
i-12hdfghf578568tn
i-13456tg
grep is exactly what you need for this task, sed would be more suitable if you had to reformat the input and awk would be nice if you had either to reformat a string or make some computation of some fields in the rows, columns
Explanation:
-P is to use perl regex
i-[^\s]* is a regex that will match literally i- followed by 0 to N non space character, you could change the * by a + if you want to impose that there is at least 1 char after the - or you could use {min,max} syntax to impose a range.
Let me know if there is something unclear.
Bonus:
Following the comment of Sundeep, you can use one of the improved versions of the regex I have proposed (the first one does use PCRE and the second one posix regex):
grep -oP 'i-\S*' <<<$var
or
grep -o 'i-[^[:blank:]]*' <<<$var
You could use following too(I tested it with GNU awk):
echo "$var" | awk -v RS='[ |\n]' '/^i-/'
You can also use this code (Tested in unix)
echo $test | grep -o "i-[0-z]*"
Here,
-o # Prints only the matching part of the lines
i-[0-z]* # This regular expression, matches all the alphabetical and numerical characters following 'i-'.

Portable way to resolve host name to IP address

I need to resolve a host name to an IP address in a shell script. The code must work at least in Cygwin, Ubuntu and OpenWrt(busybox).
It can be assumed that each host will have only one IP address.
Example:
input
google.com
output
216.58.209.46
EDIT:
nslookup may seem like a good solution, but its output is quite unpredictable and difficult to filter. Here is the result command on my computer (Cygwin):
>nslookup google.com
Unauthorized answer:
Serwer: UnKnown
Address: fdc9:d7b9:6c62::1
Name: google.com
Addresses: 2a00:1450:401b:800::200e
216.58.209.78
I've no experience with OpenWRT or Busybox but the following one-liner will should work with a base installation of Cygwin or Ubuntu:
ipaddress=$(LC_ALL=C nslookup $host 2>/dev/null | sed -nr '/Name/,+1s|Address(es)?: *||p')
The above works with both the Ubuntu and Windows version of nslookup. However, it only works when the DNS server replies with one IP (v4 or v6) address; if more than one address is returned the first one will be used.
Explanation
LC_ALL=C nslookup sets the LC_ALL environment variable when running the nslookup command so that the command ignores the current system locale and print its output in the command’s default language (English).
The 2>/dev/null avoids having warnings from the Windows version of nslookup about non-authoritative servers being printed.
The sed command looks for the line containing Name and then prints the following line after stripping the phrase Addresses: when there's more than one IP (v4 or 6) address -- or Address: when only one address is returned by the name server.
The -n option means lines aren't printed unless there's a p commandwhile the-r` option means extended regular expressions are used (GNU sed is the default for Cygwin and Ubuntu).
If you want something available out-of-the-box on almost any modern UNIX, use Python:
pylookup() {
python -c 'import socket, sys; print socket.gethostbyname(sys.argv[1])' "$#" 2>/dev/null
}
address=$(pylookup google.com)
With respect to special-purpose tools, dig is far easier to work with than nslookup, and its short mode emits only literal answers -- in this case, IP addresses. To take only the first address, if more than one is found:
# this is a bash-specific idiom
read -r address < <(dig +short google.com | grep -E '^[0-9.]+$')
If you need to work with POSIX sh, or broken versions of bash (such as Git Bash, built with mingw, where process substitution doesn't work), then you might instead use:
address=$(dig +short google.com | grep -E '^[0-9.]+$' | head -n 1)
dig is available for cygwin in the bind-utils package; as bind is most widely used DNS server on UNIX, bind-utils (built from the same codebase) is available for almost all Unix-family operating systems as well.
Here's my variation that steals from earlier answers:
nslookup blueboard 2> /dev/null | awk '/Address/{a=$3}END{print a}'
This depends on nslookup returning matching lines that look like:
Address 1: 192.168.1.100 blueboard
...and only returns the last address.
Caveats: this doesn't handle non-matching hostnames at all.
TL;DR; Option 2 is my preferred choice for IPv4 address. Adjust the regex to get IPv6 and/or awk to get both. There is a slight edit to option 2 suggested use given in EDIT
Well a terribly late answer here, but I think I'll share my solution here, esp. because the accepted answer didn't work for me on openWRT(no python with minimal setup) and the other answer errors out "no address found after comma".
Option 1 (gives the last address from last entry sent by nameserver):
nslookup example.com 2>/dev/null | tail -2 | tail -1 | awk '{print $3}'
Pretty simple and straight forward and doesn't really need an explanation of piped commands.
Although, in my tests this always gave IPv4 address (because IPv4 was always last line, at least in my tests.) However, I read about the unexpected behavior of nslookup. So, I had to find a way to make sure I get IPv4 even if the order was reversed - thanks regex
Option 2 (makes sure you get IPv4):
nslookup example.com 2>/dev/null | sed 's/[^0-9. ]//g' | tail -n 1 | awk -F " " '{print $2}'
Explanation:
nslookup example.com 2>/dev/null - look up given host and ignore STDERR (2>/dev/null)
sed 's/[^0-9. ]//g' - regex to get IPv4 (numbers and dots, read about 's' command here)
tail -n 1 - get last 1 line (alt, tail -1)
awk -F " " '{print $2} - Captures and prints the second part of line using " " as a field separator
EDIT: A slight modification based on a comment to make it actually more generalized:
nslookup example.com 2>/dev/null | printf "%s" "$(sed 's/[^0-9. ]//g')" | tail -n 1 | printf "%s" "$(awk -F " " '{print $1}')"
In the above edit, I'm using printf command substitution to take care of any unwanted trailing newlines.

IP address and Country on same line with AWK

I'm looking for a one-liner that based on a list of IPs will append the country from where the IP is based
So if I have this as and input:
87.229.123.33
98.12.33.46
192.34.55.123
I'd like to produce this:
87.229.123.33 - GB
98.12.33.46 - DE
192.34.55.123 - US
I've already got a script that returns the country for IP but I need to glue it all together with awk, so far this is waht I came up with:
$ get_ips | nawk '{ print $1; system("ip2country " $1) }'
This is all cool but the ip and the country are not displayed on the same line, how can I merge the system output and the ip on one line ?
If you have a better way of doing this, I'm open to suggestions.
You can use printf instead of print:
{ printf("%s - ", $1); system("ip2country " $1); }
The proper one-liner solution in awk is:
awk '{printf("%s - ", $1) ; system("ip2country \"" $1 "\"")}' < inputfile
However I think it would be much faster if You would use a python program looking like that:
#!/usr/bin/python
# 'apt-get install python-geoip' if needed
import GeoIP
gi = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
for line in file("ips.txt", "r"):
line = line[:-1] # strip the last from the line
print line, "-", gi.country_code_by_addr(line)
As You can see, the geoip object is initialized only once and then it is reused for all queries. See a python binding for geoip. Also be aware that Your awk solution forks a new process 2 times per line!
I don't know how many entries You need to process, but if it's much of it, You should consider something that doesn't fork and keeps the geoip database in memory.
I'll answer with a perl one-liner because I know that syntax better than awk. The "chomp" will cut off the newline that is bothering you.
get_ips | perl -ne 'chomp; print; print `ip2country $_`'

Resources