List IPs line by line with bash script - bash

I need list of IPs by dig command I'm using bash script but some of domain like google.com have many IPs I need only one result
#!/bin/bash
while read domain; do
ipaddr=$(dig +short $domain)
echo -e "$ipaddr" >> results.csv
done < domainlist.txt
output ( if we take google an example )
173.194.35.101
173.194.35.102
173.194.35.96
173.194.35.110
173.194.35.98
173.194.35.100
173.194.35.99
173.194.35.104
173.194.35.103
173.194.35.97
173.194.35.105
I need only the first line

#!/bin/bash
while read domain; do
ipaddr=$(dig +short $domain | head -1)
echo -e "$ipaddr" >> results.csv
done < domainlist.txt
Check if this is ok.
ipaddr=$(dig +short $domain | head -1)
Piping through head -1 should return the first ip from the list of ip.s returned by dig command.

Pipe it through head :
ipaddr=$(dig +short $domain | head -n 1)

Related

Bash script with long command as a concatenated string

Here is a sample bash script:
#!/bin/bash
array[0]="google.com"
array[1]="yahoo.com"
array[2]="bing.com"
pasteCommand="/usr/bin/paste -d'|'"
for val in "${array[#]}"; do
pasteCommand="${pasteCommand} <(echo \$(/usr/bin/dig -t A +short $val)) "
done
output=`$pasteCommand`
echo "$output"
Somehow it shows an error:
/usr/bin/paste: invalid option -- 't'
Try '/usr/bin/paste --help' for more information.
How can I fix it so that it works fine?
//EDIT:
Expected output is to get result from the 3 dig executions in a string delimited with | character. Mainly I am using paste that way because it allows to run the 3 dig commands in parallel and I can separate output using a delimiter so then I can easily parse it and still know the dig output to which domain (e.g google.com for first result) is assigned.
First, you should read BashFAQ/050 to understand why your approach failed. In short, do not put complex commands inside variables.
A simple bash script to give intended output could be something like that:
#!/bin/bash
sites=(google.com yahoo.com bing.com)
iplist=
for site in "${sites[#]}"; do
# Capture command's output into ips variable
ips=$(/usr/bin/dig -t A +short "$site")
# Prepend a '|' character, replace each newline character in ips variable
# with a space character and append the resulting string to the iplist variable
iplist+=\|${ips//$'\n'/' '}
done
iplist=${iplist:1} # Remove the leading '|' character
echo "$iplist"
outputs
172.217.18.14|98.137.246.7 72.30.35.9 98.138.219.231 98.137.246.8 72.30.35.10 98.138.219.232|13.107.21.200 204.79.197.200
It's easier to ask a question when you specify input and desired output in your question, then specify your try and why doesn't it work.
What i want is https://i.postimg.cc/13dsXvg7/required.png
$ array=("google.com" "yahoo.com" "bing.com")
$ printf "%s\n" "${array[#]}" | xargs -n1 sh -c '/usr/bin/dig -t A +short "$1" | paste -sd" "' _ | paste -sd '|'
172.217.16.14|72.30.35.9 98.138.219.231 98.137.246.7 98.137.246.8 72.30.35.10 98.138.219.232|204.79.197.200 13.107.21.200
I might try a recursive function like the following instead.
array=(google.com yahoo.com bing.com)
paster () {
dn=$1
shift
if [ "$#" -eq 0 ]; then
dig -t A +short "$dn"
else
paster "$#" | paste -d "|" <(dig -t A +short "$dn") -
fi
}
output=$(paster "${array[#]}")
echo "$output"
Now finally clear with expected output:
domains_arr=("google.com" "yahoo.com" "bing.com")
out_arr=()
for domain in "${domains_arr[#]}"
do
mapfile -t ips < <(dig -tA +short "$domain")
IFS=' '
# Join the ips array into a string with space as delimiter
# and add it to the out_arr
out_arr+=("${ips[*]}")
done
IFS='|'
# Join the out_arr array into a string with | as delimiter
echo "${out_arr[*]}"
If the array is big (and not just 3 sites) you may benefit from parallelization:
array=("google.com" "yahoo.com" "bing.com")
parallel -k 'echo $(/usr/bin/dig -t A +short {})' ::: "${array[#]}" |
paste -sd '|'

Sed: execute alternative command when output from previous one is empty

I wanted to modify dig command to automatically apply a reverse lookup to any A record I receive in output.
Therefore, I've created the following function:
dt ()
{
remove=$(echo $# | sed 's^https*^^' | sed 's^[,/*:]^^g' );
dig any +trace +nocl +nodnssec $remove | sed "s/\($remove.*A\s*\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\)\)/dig -x \2 +short | xargs echo \"\1 ->\"/e"
}
With this code I have the following output (only A record part is shown so to avoid the question getting bigger):
domain.com. 1200 A 198.54.115.174 -> server224-3.web-hosting.com.
However, now I also need to make a whois lookup using the IP I receive from dig output, but only in case dig -x \2 +short doesn't give any result (stackoverflow.com can be a good example of a domain with A records that do not have PTR).
I tried something like this to check exit code of regular host command (since dig implies that output is successful even if it's empty) and execute proper command depending on the result:
dig any +trace +nocl +nodnssec "$remove" | sed -e "s/\($remove.*A\s*\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\)\)/echo -n \"\1 -> \"; host \2 | grep -q ''; if [ ${PIPESTATUS[0]} -eq 0 ]; then dig -x \2 +short; else whois \2 | grep 'network:Network-Name:';fi; /e"
But it seems that sed somehow affects the value of ${PIPESTATUS[0]} array.
I wanted to do these modifications in sed because I needed something that will print lines on the go. If I use variables and modify the output from them, the function will work slower, at least visually.
Maybe awk can be of use here, but I am not sure how I should write the code using this command.
Is there a way around this problem? Can sed be used for this purpose or should I use some other tool? Thanks.
The good old' bash gives you variety of tools grep, awk, xargs, while read and so on. Using sed with e command with inside checking PIPESTATUS and executing xargs.... is just unreadable and very long too read. It's not clear to me what do you want to do, as too much happens on one line.
The dig command results zero status on success - you can check it's output if it has zero length when it "fails".
Consider the following script:
dt () {
remove=$(
<<<$1 \
sed 's#^http[s]?://\([^/]*\).*#\1#'
)
dig any +trace +nocl +nodnssec "$remove" |
tr -s '\t' |
awk -v "remove=${remove}." '{
if ($1 == remove && $3 == "A") {
print
}
}' |
while IFS= read -r line; do
IFS=$'\t' read -r _ _ _ ip _ <<<"$line"
if ! dig_result=$(dig -x "$ip" +short); then
echo "Dig error" >&2
exit 1
fi
if [ -z "$dig_result" ]; then
printf "%s -> no-result-from-dig\n" "$line"
else
printf "%s -> %s\n" "$line" "$dig_result"
fi
done
}
First dig is run. Notice the "$remove" is quoted.
Just a precaution - I squeze the tabulators in the output.
Then I filter - the first column should have "$remove". with a dot and the third column should be an "A"
Then for each line (as there are meny)
I get the ip address (ok, maybe ip=$(<<<"$line" cut -f4) would be cleaner and simpler).
I get the result from the dig -x using the ip address
If the result is empty I print "no-result-from-dig" if it not, I print the result.
Running:
dt stackoverflow.com
dt google.pl
Outputs:
stackoverflow.com. 300 A 151.101.65.69 -> no-result-from-dig
stackoverflow.com. 300 A 151.101.193.69 -> no-result-from-dig
stackoverflow.com. 300 A 151.101.129.69 -> no-result-from-dig
stackoverflow.com. 300 A 151.101.1.69 -> no-result-from-dig
google.pl. 300 A 216.58.215.67 -> waw02s16-in-f3.1e100.net.
The reason your command did not work is that $PIPESTATUS was quoted inside " quotes thus expanded before running the shell. You should escape the $ so that's \$PIPESTATUS or pass it inside nonexpading single quotes " ... "'$PIPESTATUS'" ... ".

Ping Script with filter

I have a text file with host names and IP addresses like so (one IP and one host name per row). The IP addresses and host names can be separated by a spaces and/or a pipe, and the host name may be before or after the IP address
10.10.10.10 HW-DL11_C023
11.11.11.11 HW-DL11_C024
10.10.10.13 | HW-DL12_C023
11.11.11.12 | HW-DL12_C024
HW-DL13_C023 11.10.10.10
HW-DL13_C024 21.11.11.11
HW-DL14_C023 | 11.10.10.10
HW-DL14_C024 | 21.11.11.11
The script below should be able to ping hosts with a common denominator e.g. DL13 (there are two devices and it will ping only those two). What am I doing wrong, as I simply can`t make it work?
The script is in the same directory as the data; I don`t get errors, and everything is formatted. The server is Linux.
pingme () {
hostfile="/home/rex/u128789/hostfile.txt"
IFS= mapfile -t hosts < <(cat $hostfile)
for host in "${hosts[#]}"; do
match=$(echo "$host" | grep -o "\-$1_" | sed 's/-//' | sed 's/_//')
if [[ "$match" = "$1" ]]; then
hostname=$(echo "$host" | awk '{print $2}')
ping -c1 -W1 $(echo "$host" | awk '{print $1}') > /dev/null
if [[ $? = 0 ]]; then
echo "$hostname is alive"
elif [[ $? = 1 ]]; then
echo "$hostname is dead"
fi
fi
done
}
Try adding these two lines to your code:
pingme () {
hostfile="/home/rex/u128789/hostfile.txt"
IFS= mapfile -t hosts < <(cat $hostfile)
for host in "${hosts[#]}"; do
echo "Hostname: $host" # <-------- ADD THIS LINE -------
match=$(echo "$host" | grep -o "\-$1_" | sed 's/-//' | sed 's/_//')
echo "...matched with $match" # <-------- ADD THIS LINE -------
if [[ "$match" = "$1" ]]; then
hostname=$(echo "$host" | awk '{print $2}')
ping -c1 -W1 $(echo "$host" | awk '{print $1}') > /dev/null
if [[ $? = 0 ]]; then
echo "$hostname is alive"
elif [[ $? = 1 ]]; then
echo "$hostname is dead"
fi
fi
done
}
Then when you run it, you should see a list of your hosts, at least.
If you don't then you're not reading your file successfully.
If you do, there's a problem in your per-host logic.
Congratulations! You've divided your problem into two smaller problems. Once you know which half has the problem, keep dividing the problem in half until the smallest possible problem is staring you in the face. You'll probably know the solution at that point. If not, add your findings to the question and we'll help out from there.
The original code doesn't handle the pipe separator or the possibly reversed hostname and IP address in the input file. It also makes a lot of unnecessary use of external programs (grep, sed, ...).
Try this:
# Enable extended glob patterns - e.g. +(pattern-list)
shopt -s extglob
function pingme
{
local -r host_denom=$1
local -r hostfile=$HOME/u128789/hostfile.txt
local ipaddr host tmp
# (Add '|' to the usual characters in IFS)
while IFS=$'| \t\n' read -r ipaddr host ; do
# Swap host and IP address if necessary
if [[ $host == +([0-9]).+([0-9]).+([0-9]).+([0-9]) ]] ; then
tmp=$host
host=$ipaddr
ipaddr=$tmp
fi
# Ping the host if its name contains the "denominator"
if [[ $host == *-"$host_denom"_* ]] ; then
if ping -c1 -W1 -- "$ipaddr" >/dev/null ; then
printf '%s is alive\n' "$host"
else
printf '%s is dead\n' "$host"
fi
fi
done < "$hostfile"
return 0
}
pingme DL13
The final line (call the pingme function) is just an example, but it's essential to make the code do something.
REX, you need to be more specific about your what IP's you are trying to get from this example. You also don't ping enough times IMO and your script is case sensitive checking the string (not major). Anyway,
First, check that your input and output is working correctly, in this example I'm just reading and printing, if this doesn't work fix permissions etc :
file="/tmp/hostfile.txt"
while IFS= read -r line ;do
echo $line
done < "${file}"
Next, instead of a function first try to make it work as a script, in this example I manually set "match" to DL13, then I read each line (like before) and (1) match on $match, if found I remove the '|', and then read the line into an array of 2. if the first array item is an a IP (contains periods) set it as the IP the other as hostname, else set the opposite. Then run the ping test.
# BASH4+ Example:
file="/tmp/hostfile.txt"
match="dl13"
while IFS= read -r line ;do
# -- check for matching string (e.g. dl13 --
[[ "${line,,}" =~ "${match,,}" ]] || continue
# -- We found a match, split out host/ip into vars --
line=$(echo ${line//|})
IFS=' ' read -r -a items <<< "$line"
if [[ "${items[0]}" =~ '.' ]] ;then
host="${items[1]}" ; ip="${items[0]}"
else
host="${items[0]}" ; ip="${items[1]}"
fi
# -- Ping test --
ping -q -c3 "${ip}" > /dev/null
if [ $? -eq 0 ] ;then
echo "$host is alive!"
else
echo "$host is toast!"
fi
done < "${file}"

Is this bash for loop statement correct?

Here's the code:
totLines=$(wc -l < extractedips.txt)
for((a=0;a!=$totLines;a++))
{
head -n$a extractedips.txt | nslookup >> ip_extracted.txt
}
I'm not sure what I'm doing wrong.
Yes! Despite what people are saying, this is a valid bash for loop!
Note, however, that it's a bash extension. It's not a valid sh for loop. You can not use this form of loop in e.g. a shell script declared with #!/bin/sh or run with sh yourscript.
The thing that doesn't work is your for loop contents.
You're trying to get the n'th line, but head -n 42 gets the first 42 lines, not line number 42.
You're using 0-based indexing, while head is 1-based.
You're piping to nslookup, but nslookup expects an argument and not stdin.
The shortest fix to your problem is:
totLines=$(wc -l < extractedips.txt)
for ((a=1; a<=totLines; a++)); do
nslookup "$(head -n "$a" extractedips.txt | tail -n 1)" >> ip_extracted.txt
done
However, the more efficient and canonical way of doing it is with a while read loop:
while IFS= read -r line
do
nslookup "$line"
done < extractedips.txt > ip_extracted.txt
You should use do and done instead of curly braces.
Like this:
totLines=$(wc -l < extractedips.txt)
for ((a=0; a!=totLines; a++)); do
head -n "$a" extractedips.txt | nslookup >> ip_extracted.txt
done
However, this code will do some weird stuff... Are you trying to pass it line by line into nslookup ?
What about this?
nslookup < extractedips.txt > ip_extracted.txt
Or you might want this:
while read -r line; do
nslookup "$line" >> ip_extracted.txt
done < extractedips.txt
Looks like you need sth. like this
for i in $(cat extractedips.txt)
do
echo $i | nslookup >> ip_extracted.txt
done
You don't need your counter variable, have a look at here for a better understanding.
Why I think your statement is wrong/not what you want and my answer don't deserve a negative voting :)
This statement will echo the first $a lines inside file extractedips.txt
head -n$a extractedips.txt | nslookup >> ip_extracted.txt
First up you have a file with ip's
cat extractedips.txt
127.0.0.1
192.168.0.1
173.194.44.88
Now we if you do
for a in 1 2 3
head -n$a
done
the script will output
127.0.0.1
127.0.0.1
192.168.0.1
127.0.0.1
192.168.0.1
173.194.44.88
But I guess you need a list with all all ip's only once. If you don't need duplicate ip's you could also remove them. This will give you a sorted list with duplicates removed.
for a in $(sort test.txt | uniq -u)
do
echo $a | nslookup >> ip_extracted.txt
done
Update
As Aleks commented this will not work if the text file contain a '*' char. The shell will expand all files in current directory and echo the filename. I didn't know that :/

ping an output within if loop

Using the script below I want to be able to get the IP address that is outputted at the end of it and then ping it.
#!/usr/local/bin/bash
echo -n "Enter server number:"
read userinput
lookupip="d $userinput"
if [[ $userinput -lt 0 || $userinput -gt 9999 ]] #checks that the input is within the desired range
then
echo "Input outside acceptable range."
else
#grep gets just the IP address
$lookupip | grep -E -o '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' | sed '1 ! d'
I can't figure out how to do this with the output:
> or >> filename | xargs ping
as using " or ` around the grep command (or putting it in a variable like so:
ipgrep=$(grepcommand)
ipgrep=`grepcommand`
or variables doesn't seem to work.
Derp, I only had to add:
| xargs ping -c 1
after:
sed '1 ! d'

Resources