Inputing variables to awk script - shell

I am working on a script that detects CDP information from the network card. The script currently works on my computer, but i would like to make it available for other people. The script currently runs because in the code it has the name of my network card. I would like to make it ask for the network card name (perhaps list the ones available) before running the script. My code is:
#!/usr/bin/awk -f
function red(s) {
printf "\033[1;31m" s "\033[0m "
}
function green(s) {
printf "\033[1;32m" s "\033[0m "
}
function blue(s) {
printf "\033[1;34m" s "\033[0m "
}
BEGIN{
cmd = "tcpdump -nvi enp0s25 -s 1500 ether dst 01:00:0c:cc:cc:cc -c 1"
while ((cmd | getline) > 0) {
str="Device-ID:Cisco IOS Software:Port-ID:VTP:Address:VLAN:Duplex:VoIP:"
split(str,s,":")
for(i=1;i<=length(s);i++){
if ($0 ~ s[i] && s[i]){
if (i==1){
print "\n";
print red("Device Name: ") green($7);
}
else if (i==2){
print red("Software Version: ") green($0) ;
}
else if (i==3){ /*Port*/
print red($1 ": ") green($7);
}
else if (i==4){
print red($1 " " $2 " " $3 ": ") green($9);
}
else if (i==5){
print red("IP Address: ") green($9);
}
else if (i==6){
print red("VLAN: ") green($9);
}
else if (i==7){
print red("DUPLEX: ") green($7);
}
else if (i==8){
print red("Voice VLAN: ") green($13);
}
else{
s[i]=0;print " "}
}
}
}
}
As you can see, it runs the command with my NIC which is enp0s25. I need to make this a variable, that is entered by the user (maybe only once). the best approach would be to enumerate the cards and have the user pick the card he wants using a number. I have NO IDEA how to do this.

You're making life harder for youself by trying to use awk as a shell by encoding the call to tcpdump inside awk. Think about that - you're writing a shell script to call awk to call shell to call tcpdump instead of just having shell call tcpdump and pipe the output to awk.
Just write your shell script as:
tcpdump ... enp0s25 ... | awk 'script'
and then you can tweak it in the obvious way:
echo "enter a nic: "
IFS= read -r nic
tcpdump ... "$nic" ... | awk 'script'

#!/usr/bin/awk -f
function red(s) {
printf "\033[1;31m" s "\033[0m "
}
function green(s) {
printf "\033[1;32m" s "\033[0m "
}
function blue(s) {
printf "\033[1;34m" s "\033[0m "
}
function detectNic( ) {
printf "Enter the Ethernet card to listen or press enter to listen
to Default:";getline nic<"/dev/tty"
if (nic == ""){
command = "cat /var/defaultnic"
nic = command | getline
print "Default network card to listen: " $nic
return $nic
}
else{
return nic
}
}
BEGIN{
nic = detectNic();
cmd = "tcpdump -nv -s 1500 ether dst 01:00:0c:cc:cc:cc -c 1 -i"
while ((cmd nic | getline) > 0) {
str="Device-ID:Cisco IOS Software:Port-ID:VTP:Address:VLAN:Duplex:VoIP:"
split(str,s,":")
for(i=1;i<=length(s);i++){
if ($0 ~ s[i] && s[i]){
#DEVICE ID
if (i==1){
print "\n";
print red("Device Name: ") green($7);
}
#SOFTWARE VERSION
else if (i==2){
print red("Software Version: ") green($0) ;
}
#PORT
else if (i==3){
print red($1 ": ") green($7);
}
#VTP DOMAIN
else if (i==4){
print red($1 " " $2 " " $3 ": ") green($9);
}
#IP ADDRESS
else if (i==5){
print red("IP Address: ") green($9);
}
#CURRENT VLAN
else if (i==6){
print red("VLAN: ") green($9);
}
#DUPLEX
else if (i==7){
print red("DUPLEX: ") green($7);
}
#VOICE VLAN
else if (i==8){
print red("Voice VLAN: ") green($13);
}
else{
s[i]=0;print " "}
}
}
}
}

Related

Validate log file using shell

I want to validate a log file based on a reference file, I worked on a script but, it is not beautiful and is not optimal:
For each line I want to check the value of the fields,
- the field 7 equal to 1 I have to check columns 16 and 17
- the field 7 equal to 2 I have to check columns 25 and 27 and 30
- the field 7 equal to 3 I have to check columns 18 and 24 and 31
etc..
#!/bin/bash
LOG=SMS.log
awk -F\| ' {s=""}
$4!=0 {printf "API has wrong value"; s="; " }
$8=="" { printf "%sApplicationID is empty", s; s="; " }
$9=="" { printf "%shttp request method is empty", s; s="; " }
$7=="" { printf "%sOperationID is empty", s; s="; " }
$13 !~ /0|1|2/ {printf "%sresult(0,1,2) has a wrong value", s; s="; " }
# 1:create SMS
$7=="1" && $18=="" {printf "%sSender is missing", s; s="; " }
$7=="1" && $18 ~ /\/tel\:\+\*\*/ {printf "%sSender is cyphred !", s; s="; " }
$7=="1" && $20=="" {printf "%sAddress is missing", s; s="; " }
$7=="1" && $20 ~ /\/tel\:\+[0-9]/ {printf "%sAddress(es) is not cyphred", s; s="; " }
$7=="1" && $10 ~ /\/tel\:\+\*\*/ {printf "%sSender is cyphred on URI !", s; s="; " }
## 2:subscribe
$7=="2" && $25=="" {printf "%sdestination is missing", s; s="; " }
$7=="2" && $16=="201" && $27="" {printf "%sresourceId is missing", s; s="; "}
#3:unsubscribe
$7=="2" && $16=="201" && $25="" {printf "%sresource is missing", s; s="; "}
s { printf "\n"}
s
{printf "\n"}
' $LOG
Is it possible to update the code to be more optimal and beautiful.
Output:
Application is empty; Operation is empty; Http request method is empty
83ac|EDR|V1|0|V1|2019-05-14|7||||2019-05-14T08:00:42.758Z|8|0|||||XXXXX|||||||||789|||||||||5945548f|||||
I'd do it like this:
awk -F'|' '
##### Error Detection
$4 != 0 { prtErr("ApiWrong") }
$8 == "" { prtErr("AppIdEmpty") }
$9 == "" { prtErr("HttpEmpty") }
$7 == "" { prtErr("OpIdEmpty") }
$13 !~ /[012]/ { prtErr("RsltBad") }
$7 == 1 { # 1:create SMS
if ( $18 == "" ) { prtErr("SndMiss") }
if ( $18 ~ /\/tel:\+\*\*/ ) { prtErr("SndCyph") }
if ( $20 == "" ) { prtErr("AddrMiss") }
if ( $20 ~ /\/tel:\+[0-9]/ ) { prtErr("AddrNotCyph") }
if ( $10 ~ /\/tel:\+\*\*/ ) { prtErr("SndCyphUri") }
}
$7 == 2 { # 2:subscribe
if ( $25 == "" ) { prtErr("DestMiss") }
if ( $16=="201" && $27=="" ) { prtErr("RsrcIdMiss") }
}
$7 == 3 { # 3:unsubscribe
if ( $16=="201" && $25=="" ) { prtErr("RsrcMiss") }
}
##### Error Reporting
function prtDbg(code,str) { if (doDebug) prtMsg("DEBUG",code,str) }
function prtTrc(code,str) { if (doTrace) prtMsg("TRACE",code,str) }
function prtWrn(code,str) { prtMsg("WARNING",code,str) }
function prtErr(code,str) { prtMsg("ERROR",code,str) }
function prtMsg(level, code, str, map, msg) {
map["ApiWrong"] = "API has wrong value"
map["AppIdEmpty"] = "ApplicationID is empty"
map["HttpEmpty"] = "http request method is empty"
map["OpIdEmpty"] = "OperationID is empty"
map["RsltBad"] = "result(0,1,2) has a wrong value"
map["SndMiss"] = "Sender is missing"
map["SndCyph"] = "Sender is cyphred !"
map["AddrMiss"] = "Address is missing"
map["AddrNotCyph" = "Address(es) is not cyphred"
map["SndCyphUri"] = "Sender is cyphred on URI !"
map["DestMiss"] = "destination is missing"
map["RsrcIdMiss"] = "resourceId is missing"
map["RsrcMiss"] = "resource is missing"
map["default"] = "Unknown error code"
msg = (code in map ? map[code] : map["default"])
printf "%s: %s[%d]: (%s) %s\n", level, FILENAME, FNR, code, msg | "cat>&2"
if ( str != "" ) {
printf "%s: %s[%d]:\t%s\n", $0 | "cat>&2"
}
}
' "$log"
That decouples the text being printed from the error indication and centralizes/instruments all error messages for a common look/feel and ability to add extra info if necessary and to de-clutter the code that's detecting the errors. I also showed how to separate errors, from warnings, etc. (you choose which is which in your code) and add tracing/debugging functions that you can all over the code if you like but to and they won't do anything till you set the relevant "do..." flag on the command line.
Update to just produce the specific output you asked for (untested):
BEGIN { FS="|" }
##### General processing including error detection
$4 != 0 { logErr("ApiWrong") }
$8 == "" { logErr("AppIdEmpty") }
$9 == "" { logErr("HttpEmpty") }
$7 == "" { logErr("OpIdEmpty") }
$13 !~ /[012]/ { logErr("RsltBad") }
$7 == 1 { # 1:create SMS
if ( $18 == "" ) { logErr("SndMiss") }
if ( $18 ~ /\/tel:\+\*\*/ ) { logErr("SndCyph") }
if ( $20 == "" ) { logErr("AddrMiss") }
if ( $20 ~ /\/tel:\+[0-9]/ ) { logErr("AddrNotCyph") }
if ( $10 ~ /\/tel:\+\*\*/ ) { logErr("SndCyphUri") }
}
$7 == 2 { # 2:subscribe
if ( $25 == "" ) { logErr("DestMiss") }
if ( $16=="201" && $27=="" ) { logErr("RsrcIdMiss") }
}
$7 == 3 { # 3:unsubscribe
if ( $16=="201" && $25=="" ) { logErr("RsrcMiss") }
}
{ prtErrs() }
##### Error reporting primitives
function logErr(code) { _errs[code] }
function prtErrs( code, map, msg, gotErrs, sep) {
for (code in _errs) {
gotErrs = 1
break
}
if (gotErrs) {
map["ApiWrong"] = "API has wrong value"
map["AppIdEmpty"] = "ApplicationID is empty"
map["HttpEmpty"] = "http request method is empty"
map["OpIdEmpty"] = "OperationID is empty"
map["RsltBad"] = "result(0,1,2) has a wrong value"
map["SndMiss"] = "Sender is missing"
map["SndCyph"] = "Sender is cyphred !"
map["AddrMiss"] = "Address is missing"
map["AddrNotCyph"] = "Address(es) is not cyphred"
map["SndCyphUri"] = "Sender is cyphred on URI !"
map["DestMiss"] = "destination is missing"
map["RsrcIdMiss"] = "resourceId is missing"
map["RsrcMiss"] = "resource is missing"
printf "%s: %s[%d]: ", "ERROR", FILENAME, FNR | "cat>&2"
for (code in _errs) {
msg = (code in map ? map[code] : "Unknown error code (" code ")")
printf "%s%s", sep, msg | "cat>&2"
sep = "; "
}
printf "\n%s\n", $0 | "cat>&2"
delete _errs
}
}
and if you have GNU awk for arrays of arrays and length(array) then I'd do it as:
BEGIN { FS="|" }
##### General processing including error detection
$4 != 0 { logErr("Wrong","API") }
$8 == "" { logErr("Empty","AppId") }
$9 == "" { logErr("Empty","Http request method") }
$7 == "" { logErr("Empty","OperationID") }
$13 !~ /[012]/ { logErr("Wrong","Result(0,1,2)") }
$7 == 1 { # 1:create SMS
if ( $18 == "" ) { logErr("Miss","Sender") }
if ( $18 ~ /\/tel:\+\*\*/ ) { logErr("Cyph","Sender") }
if ( $20 == "" ) { logErr("Miss","Address") }
if ( $20 ~ /\/tel:\+[0-9]/ ) { logErr("NotCyph","Address(es)") }
if ( $10 ~ /\/tel:\+\*\*/ ) { logErr("UriCyph","Sender") }
}
$7 == 2 { # 2:subscribe
if ( $25 == "" ) { logErr("Miss","Destination") }
if ( $16=="201" && $27=="" ) { logErr("Miss","ResourceId") }
}
$7 == 3 { # 3:unsubscribe
if ( $16=="201" && $25=="" ) { logErr("Miss","Resource") }
}
{ prtErrs() }
##### Error reporting primitives
function logErr(type,item) { _errs[type][item] }
function prtErrs( map, type, msg, item, sep) {
if ( length(_errs) ) {
map["Wrong"] = "has wrong value"
map["Empty"] = "is empty"
map["Miss"] = "is missing"
map["Cyph"] = "is cyphred !"
map["NotCyph"] = "is not cyphred"
map["UriCyph"] = "is cyphred on URI !"
printf "%s: %s[%d]: ", "ERROR", FILENAME, FNR | "cat>&2"
for (type in _errs) {
msg = (type in map ? map[type] : "Unknown error type (" type ")")
for (item in _errs[type]) {
printf "%s%s %s", sep, item, msg | "cat>&2"
sep = "; "
}
}
printf "\n%s\n", $0 | "cat>&2"
delete _errs
}
}
First thing you could do is get rid of the s variable.
#!/bin/bash
LOG=SMS.log
awk -F\| '
function add_error(message){
error = error OFS message
}
$4!=0 {add_error("API has wrong value")}
$8=="" {add_error("ApplicationID is empty")}
$9=="" {add_error("http request method is empty")}
$7=="" {add_error("OperationID is empty")}
$13 !~ /0|1|2/ {add_error("result(0,1,2) has a wrong value")}
# 1:create SMS
$7=="1" && $18=="" {add_error("Sender is missing")}
$7=="1" && $18 ~ /\/tel\:\+\*\*/ {add_error("Sender is cyphred !")}
$7=="1" && $20=="" {add_error("Address is missing")}
$7=="1" && $20 ~ /\/tel\:\+[0-9]/ {add_error("Address(es) is not cyphred")}
$7=="1" && $10 ~ /\/tel\:\+\*\*/ {add_error("Sender is cyphred on URI !")}
## 2:subscribe
$7=="2" && $25=="" {add_error("destination is missing")}
$7=="2" && $16=="201" && $27="" {add_error("resourceId is missing")}
#3:unsubscribe
$7=="2" && $16=="201" && $25="" {add_error("resource is missing")}
{
print substr(error, length(OFS)+1); #Works even if error is empty
error = "";
}
' OFS="; " $LOG
I think that it is a bit strange to analyze your log file and create ... a new log file. Why don't you create a csv with 1 column per error and 1/0 values for each line/error ? Your result would be much more easier to analyze and would contains all the informations you need.

awk - piping string to external gfortran-built executable in unix

I have the following awk-script "create_grid.awk" which pipes a command through to an external exe (iri_win.exe/iri_unix.exe built with gfortran) and reads back the response via getline (creating a grid of values with two nested for-loops). Whereas everything works like a charm in windows the matter in the unix environment goes wrong.
!/bin/awk -f
BEGIN {
is_windows = 0;
if (index(tolower(ENVIRON["OS"]), "windows") > 0) {
is_windows = 1;
}
exit
}
{
}
END {
mystring=""
#myvar=""
#CMD = "cmdline | getline myvar"
#print "" > "iri_fortran_output_grid.txt"
for (j = -9 ; j <= -9; j+=2){
for (i = -2; i <= 2; i++){
pipe= "\"" j "," i ",0.300\n2017,823,0,12.750\n20200" "\""
#pipe= "\"49,12,0.300\n2017,823,1,12.750\n20200 \""
#pipe= "\"49,12,0.300\n2017,823,1,12.750\n20200\""
if (is_windows)
cmdline = "echo -e " pipe " | ./iri_win.exe"
else
cmdline = "echo -e " pipe " | ./iri_unix.exe"
print cmdline
if ( (cmdline | getline myvar) > 0 ) {
#close(cmdline | getline myvar)
print "latitude " j " , longitude " i " done, TEC: " myvar " ;"
}
else{
#close(cmdline | getline myvar)
print "error in latitude " j " , longitude " i
}
close(cmdline)
#close(getline)
#cmdline | getline myvar
#myvar = $0
mystring = mystring myvar " "
}
# print one line into result file
sub(/[ \t]+$/, "", mystring)
sub(/^[ \t]+/, "", mystring)
print mystring > "iri_fortran_output_grid.txt"
print "longitude " j " done"
mystring=""
fflush("iri_fortran_output_grid.txt")
fflush(stdout)
#close("iri_fortran_output_grid.txt")
}
}
Output of awk script in Unix OS:
loren32#nautilus:~/iri_exe_analysis$gawk -f ./create_grid.awk
At line 107 of file iri_4_tec_al_2.for (unit = 5, file = 'stdin')
Fortran runtime error: Bad real number in item 1 of list input
error in latitude -9 , longitude 0
echo -e "-9,1,0.300
2017,823,0,12.750
20200" | ./iri_unix.exe
...
Output in Unix OS only executing external exe :
loren32#nautilus:~/iri_exe_analysis$ echo -e "-9,1,0.300\n2017,823,1,12.750\n20200" | ./iri_unix.exe
18.452
As can be seen when I pipe my string via echo -e "stringcontent" to iri_unix.exe from bash in Unix it works but the call from within the awk script fails.
I suspect the quotes work differently in unix and somehow additional disturbing string data is sent to iri_unix.exe - hence the error message "Bad real number in item 1 of list input".
I wonder what is going wrong and how to correct my awk script to make it work in Unix OS. In Windows the script works fine
The workaround is to write
echo "..."
in the Unix Environment and leave out the -e option. That die the trick for me.

Need Algorithm to create a auto code from a CSV file

I have "N" columns in a csv file say Hardware,Sensors,Statistics(1,2,3 .....N) as shown below.
Each column has unique xml code that I need to generate with respect to the above table content.
<Hardware A>
<Sensors sen1>
<Stat1>Mean</Stat1>
<Stat2>Avg</Stat2>
<Stat3>Slope</Stat3>
</Sensors sen1>
<Sensors sen2>
<Stat1>Min</Stat1>
<Stat2>Max</Stat2>
<Stat3>Mean</Stat3>
</Sensors sen2>
....
....
</Hardware A>
I need to generate a code similar to above with respect to the table. Can anybody tell an Algorithm to implement this structure using SHELL SCRIPT
It'd be something like this in awk (untested obviously since you didn't provide testable sample input/output):
BEGIN { FS=","; fmt="%s %s>\n" }
NR==1 {
for (i=1;i<=NF;i++) {
tagName[i] = $i
}
next
}
$1 != "" {
if (prev != "") {
printf "</"fmt, tagName[1], prev
}
printf "<"fmt, tagName[1], $1
prev = $1
}
{
printf " <"fmt, tagName[2], $2
for (i=3;i<=NF;i++) {
printf " <%s>%s</%s>\n", tagName[i], $i, tagName[i]
}
printf " </"fmt, tagName[2], $2
}
END {
if (prev != "") {
printf "</"fmt, tagName[1], prev
}
}

Conditional insert of line breaks to number sequence (preferably using bash, awk, or sed)

I'm trying to add line breaks to a text file each time a subsequent number is smaller than the immediately preceding number (e.g. a break between "72.774" and "7.009") in a text file with this structure:
7.007 28.929 50.851 72.774 7.009 28.932 50.854 72.777 7.015 32.939 54.862 76.784
I want the output to be in this format:
7.007 28.929 50.851 72.774
7.009 28.932 50.854 72.777
7.015 32.939 54.862 76.784
Files do not always have the same number of numerical entries (either in total or before the series begins counting up again) nor are the same number of line breaks required in all text files.
I've been trying to use conditionals in awk or sed but haven't had any luck.
Thank you in advance for any suggestions/solutions.
note: edited to reflect 1st comment.
This may be what you want:
$ awk -v RS=' ' '{printf "%s%s", (NR>1?($0<p?ORS:OFS):""), $0; p=$0}' file
7.007 28.929 50.851 72.774
7.009 28.932 50.854 72.777
7.015 32.939 54.862 76.784
Here's one solution using awk:
{
for (i=1; i<=NF; ++i) {
if ($i < last) {
printf "\n"
last=-1
} else if (last > 0) {
printf " "
}
printf "%s", $i
last = $i
}
}
END { printf "\n" }
Example run:
$ awk -f foo.awk bar.txt
7.007 28.929 50.851 72.774 94.696 116.619 138.542 160.464 182.387 204.309 226.232 248.155 270.077 292 313.922 335.845 357.768 379.69 401.613 423.535 445.458 467.381
7.009 28.932 50.854 72.777 94.699 116.622 138.545 160.467 182.39 204.312 226.235 248.158 270.08 292.003 313.925 335.848 357.771 379.693 401.616 423.538 445.461 467.384 489.306
7.015 32.939 54.862 76.784 102.708 124.631 146.553 168.476 190.398 212.321 234.244 260.167 282.09 308.013 333.937 355.86 377.782 403.706
7.005 28.928 50.85 72.773 94.696 116.618 138.541 160.463 186.387 212.311 234.233 256.156 278.079 300.001 321.924 347.847
Another AWK solution:
awk 'BEGIN { RS=" "; ORS=" "; prev=-999 }
{ if ( $1<prev ) { printf "\n%.3f", $1 }
else { printf "%.3f ", $1 } prev=$1
}
END { print }'

Parsing iw wlan0 scan output

I wrote wlan manager script to handle open/ad-hoc/wep/wpa2 networks. Now im trying to parse iw wlan0 scan output to get nice scan feature to my script. My goal is to get output like this :
SSID channel signal encryption
wlan-ap 6 70% wpa2-psk
test 1 55% wep
What i have achived already is output like this :
$ iw wlan0 scan | grep 'SSID\|freq\|signal\|capability' | tac
SSID: Koti783
signal: -82.00 dBm
capability: ESS Privacy ShortPreamble SpectrumMgmt ShortSlotTime (0x0531)
freq: 2437
I have been trying to study bash/sed/awk but havent found yet a way to achieve what im trying. So what is good way to achieve that?
Here is my final solution based of Sudo_O answer:
$1 == "BSS" {
MAC = $2
wifi[MAC]["enc"] = "Open"
}
$1 == "SSID:" {
wifi[MAC]["SSID"] = $2
}
$1 == "freq:" {
wifi[MAC]["freq"] = $NF
}
$1 == "signal:" {
wifi[MAC]["sig"] = $2 " " $3
}
$1 == "WPA:" {
wifi[MAC]["enc"] = "WPA"
}
$1 == "WEP:" {
wifi[MAC]["enc"] = "WEP"
}
END {
printf "%s\t\t%s\t%s\t\t%s\n","SSID","Frequency","Signal","Encryption"
for (w in wifi) {
printf "%s\t\t%s\t\t%s\t%s\n",wifi[w]["SSID"],wifi[w]["freq"],wifi[w]["sig"],wifi[w]["enc"]
}
}'
Output:
$ sudo iw wlan0 scan | awk -f scan.awk
SSID Frequency Signal Encryption
netti 2437 -31.00 dBm Open
Koti783 2437 -84.00 dBm WPA
WLAN-AP 2462 -85.00 dBm WPA
it's generally bad practice to try parsing complex output of programs intended for humans to read (rather than machines to parse).
e.g. the output of iw might change depending on the language settings of the system and/or the version of iw, leaving you with a "manager" that only works on your development machine.
instead you might use the same interface that iw uses to get it's information: the library backend libnl
you might also want to have a look at the wireless-tools (iwconfig, iwlist,...) that use the libiw library.
Here is an GNU awk script to get you going that grabs the SSIDs and the channel for each unique BSS:
/^BSS / {
MAC = $2
}
/SSID/ {
wifi[MAC]["SSID"] = $2
}
/primary channel/ {
wifi[MAC]["channel"] = $NF
}
# Insert new block here
END {
printf "%s\t\t%s\n","SSID","channel"
for (w in wifi) {
printf "%s\t\t%s\n",wifi[w]["SSID"],wifi[w]["channel"]
}
}
It should be easy for you to add the new blocks for signal and encryption considering all the studying you have been doing.
Save the script to file such as wifi.awk and run like:
$ sudo iw wlan0 scan | awk -f wifi.awk
The output will be in the formatted requested:
SSID channel
wlan-ap 6
test 1
Here is a simple Bash function which uses exclusively Bash internals and spawns only one sub-shell:
#!/bin/bash
function iwScan() {
# disable globbing to avoid surprises
set -o noglob
# make temporary variables local to our function
local AP S
# read stdin of the function into AP variable
while read -r AP; do
## print lines only containing needed fields
[[ "${AP//'SSID: '*}" == '' ]] && printf '%b' "${AP/'SSID: '}\n"
[[ "${AP//'signal: '*}" == '' ]] && ( S=( ${AP/'signal: '} ); printf '%b' "${S[0]},";)
done
set +o noglob
}
iwScan <<< "$(iw wlan0 scan)"
Output:
-66.00,FRITZ!Box 7312
-56.00,ALICE-WLAN01
-78.00,o2-WLAN93
-78.00,EasyBox-7A2302
-62.00,dlink
-74.00,EasyBox-59DF56
-76.00,BELAYS_Network
-82.00,o2-WLAN20
-82.00,BPPvM
The function can be easily modified to provide additional fields by adding a necessary filter into the while read -r AP while-loop, eg:
[[ "${AP//'last seen: '*}" == '' ]] && ( S=( ${AP/'last seen: '} ); printf '%b' "${S[0]},";)
Output:
-64.00,1000,FRITZ!Box 7312
-54.00,492,ALICE-WLAN01
-76.00,2588,o2-WLAN93
-78.00,652,LN8-Gast
-72.00,2916,WHITE-BOX
-66.00,288,ALICE-WLAN
-78.00,800,EasyBox-59DF56
-80.00,720,EasyBox-7A2302
-84.00,596,ALICE-WLAN08
I am using such solution for openwrt:
wlan_scan.sh
#!/bin/sh
sudo iw dev wlan0 scan | awk -f wlan_scan.awk | sort
wlan_scan.awk
/^BSS/ {
mac = gensub ( /^BSS[[:space:]]*([0-9a-fA-F:]+).*?$/, "\\1", "g", $0 );
}
/^[[:space:]]*signal:/ {
signal = gensub ( /^[[:space:]]*signal:[[:space:]]*(\-?[0-9.]+).*?$/, "\\1", "g", $0 );
}
/^[[:space:]]*SSID:/ {
ssid = gensub ( /^[[:space:]]*SSID:[[:space:]]*([^\n]*).*?$/, "\\1", "g", $0 );
printf ( "%s %s %s\n", signal, mac, ssid );
}
result
-62.00 c8:64:c7:54:d9:05 a
-72.00 70:72:3c:1c:af:17 b
-81.00 78:f5:fd:be:33:cb c
There is a bug in the awk script above.
The following code will not work if the SSID has spaces in the name. The received result will be the first token of the SSID name only.
$1 == "SSID:" {
wifi[MAC]["SSID"] = $2
}
When printing $0, $1, $2:
$0: SSID: DIRECT-82-HP OfficeJet 8700
$1: SSID:
$2: DIRECT-82-HP
One possibly solution is to take a substr of $0 which contains leading spaces, the token "SSID: " and the provided multi-token network name.
Any other suggestions?
I've taken awk code from Ari Malinen and reworked it a bit, because iw output is not stable and changes, also there are other issues like spaces in SSID. I put it on github in case if I'll change it in the future.
#!/usr/bin/env awk -f
$1 ~ /^BSS/ {
if($2 !~ /Load:/) { #< Escape "BBS Load:" line
gsub("(\\(.*|:)", "", $2)
MAC = toupper($2)
wifi[MAC]["enc"] = "OPEN"
wifi[MAC]["WPS"] = "no"
wifi[MAC]["wpa1"] = ""
wifi[MAC]["wpa2"] = ""
wifi[MAC]["wep"] = ""
}
}
$1 == "SSID:" {
# Workaround spaces in SSID
FS=":" #< Changing field separator on ":", it should be
# forbidded sign for SSID name
$0=$0
sub(" ", "", $2) #< remove first whitespace
wifi[MAC]["SSID"] = $2
FS=" "
$0=$0
}
$1 == "capability:" {
for(i=2; i<=NF; i++) {
if($i ~ /0x[0-9]{4}/) {
gsub("(\\(|\\))", "", $i)
if (and(strtonum($i), 0x10))
wifi[MAC]["wep"] = "WEP"
}
}
}
$1 == "WPA:" {
wifi[MAC]["wpa1"] = "WPA1"
}
$1 == "RSN:" {
wifi[MAC]["wpa2"] = "WPA2"
}
$1 == "WPS:" {
wifi[MAC]["WPS"] = "yes"
}
$1 == "DS" {
wifi[MAC]["Ch"] = $5
}
$1 == "signal:" {
match($2, /-([0-9]{2})\.00/, m)
wifi[MAC]["Sig"] = m[1]
}
$1 == "TSF:" {
gsub("(\\(|d|,)", "", $4)
match($5, /([0-9]{2}):([0-9]{2}):/, m)
day = $4
hour = m[1]
min = m[2]
wifi[MAC]["TSF"] = day"d"hour"h"min"m"
}
END {
for (w in wifi) {
if (wifi[w]["wep"]) {
if (wifi[w]["wpa1"] || wifi[w]["wpa2"])
wifi[w]["enc"] = wifi[w]["wpa1"]wifi[w]["wpa2"]
else
wifi[w]["enc"] = "WEP"
}
printf "%s:%s:%s:%s:%s:%s:%s\n", w, wifi[w]["SSID"], wifi[w]["enc"], \
wifi[w]["WPS"], wifi[w]["Ch"], wifi[w]["Sig"], wifi[w]["TSF"]
}
}
Output:
A5FEF2C499BB:test-ssid2:OPEN:no:9:43:0d00h00m
039EFACA9A8B:test-ssid2:WPA1:no:9:33:0d00h00m
038BF3C1988B:test-ssid2:WPA2:no:9:35:0d00h00m
028EF3C2997B:test-ssid2:WPA1:no:9:35:0d00h03m
if you wonder what if($2 !~ /Load:/) does, well on some routers there might be "BSS Load:" string.

Resources