You are not logged in.

#1 2010-01-17 05:18:28

gohu
Member
From: France
Registered: 2010-01-17
Posts: 32

Python script to parse 'iwlist scan' into a table

Hi,
I've written a small python script that parses the output of the command "iwlist interface scan" into a table.

Why ?

Like many arch linux users I think, I use netcfg instead of something like network manager or wicd. So the most natural way to scan for wifi networks in range is iwlist scan. But the output of this command is huge and I find it difficult to retrieve information. So this script parses it into a table : one network, one line.

Example output

Name    Address             Quality   Channel   Encryption
wifi_1  01:23:45:67:89:AB   100 %     11        WPA v.1     
wifi_2  01:23:45:67:89:AC    76 %     11        WEP         
wifi_3  01:23:45:67:89:AD    51 %     11        Open        
wifi_4  01:23:45:67:89:AE    50 %     11        WPA v.1     
wifi_5  01:23:45:67:89:AF    43 %     4         Open        
wifi_6  01:23:45:67:89:AG    43 %     4         WPA v.1

Details

It reads from stdin so you use it like that: iwlist wlan0 scan | iwlistparse.py
The width of the columns is determined by what's in it.
You can easily do a bit more than just parsing the info: in the example above, the quality has been calculated to percents from a ratio (e.g. 46/70).
It is sorted, too.

Customization

It's python so it's easy to customize. See the comments in the code.

Code

#!/usr/bin/env python
#
# iwlistparse.py
# Hugo Chargois - 17 jan. 2010 - v.0.1
# Parses the output of iwlist scan into a table

import sys

# You can add or change the functions to parse the properties of each AP (cell)
# below. They take one argument, the bunch of text describing one cell in iwlist
# scan and return a property of that cell.

def get_name(cell):
    return matching_line(cell,"ESSID:")[1:-1]

def get_quality(cell):
    quality = matching_line(cell,"Quality=").split()[0].split('/')
    return str(int(round(float(quality[0]) / float(quality[1]) * 100))).rjust(3) + " %"

def get_channel(cell):
    return matching_line(cell,"Channel:")

def get_encryption(cell):
    enc=""
    if matching_line(cell,"Encryption key:") == "off":
        enc="Open"
    else:
        for line in cell:
            matching = match(line,"IE:")
            if matching!=None:
                wpa=match(matching,"WPA Version ")
                if wpa!=None:
                    enc="WPA v."+wpa
        if enc=="":
            enc="WEP"
    return enc

def get_address(cell):
    return matching_line(cell,"Address: ")

# Here's a dictionary of rules that will be applied to the description of each
# cell. The key will be the name of the column in the table. The value is a
# function defined above.

rules={"Name":get_name,
       "Quality":get_quality,
       "Channel":get_channel,
       "Encryption":get_encryption,
       "Address":get_address,
       }

# Here you can choose the way of sorting the table. sortby should be a key of
# the dictionary rules.

def sort_cells(cells):
    sortby = "Quality"
    reverse = True
    cells.sort(None, lambda el:el[sortby], reverse)

# You can choose which columns to display here, and most importantly in what order. Of
# course, they must exist as keys in the dict rules.

columns=["Name","Address","Quality","Channel","Encryption"]




# Below here goes the boring stuff. You shouldn't have to edit anything below
# this point

def matching_line(lines, keyword):
    """Returns the first matching line in a list of lines. See match()"""
    for line in lines:
        matching=match(line,keyword)
        if matching!=None:
            return matching
    return None

def match(line,keyword):
    """If the first part of line (modulo blanks) matches keyword,
    returns the end of that line. Otherwise returns None"""
    line=line.lstrip()
    length=len(keyword)
    if line[:length] == keyword:
        return line[length:]
    else:
        return None

def parse_cell(cell):
    """Applies the rules to the bunch of text describing a cell and returns the
    corresponding dictionary"""
    parsed_cell={}
    for key in rules:
        rule=rules[key]
        parsed_cell.update({key:rule(cell)})
    return parsed_cell

def print_table(table):
    widths=map(max,map(lambda l:map(len,l),zip(*table))) #functional magic

    justified_table = []
    for line in table:
        justified_line=[]
        for i,el in enumerate(line):
            justified_line.append(el.ljust(widths[i]+2))
        justified_table.append(justified_line)
    
    for line in justified_table:
        for el in line:
            print el,
        print

def print_cells(cells):
    table=[columns]
    for cell in cells:
        cell_properties=[]
        for column in columns:
            cell_properties.append(cell[column])
        table.append(cell_properties)
    print_table(table)

def main():
    """Pretty prints the output of iwlist scan into a table"""
    cells=[[]]
    parsed_cells=[]

    for line in sys.stdin:
        cell_line = match(line,"Cell ")
        if cell_line != None:
            cells.append([])
            line = cell_line[-27:]
        cells[-1].append(line.rstrip())

    cells=cells[1:]

    for cell in cells:
        parsed_cells.append(parse_cell(cell))

    sort_cells(parsed_cells)

    print_cells(parsed_cells)

main()

I hope you find it useful. Please report bugs, I haven't tested it a lot. You may have to customize it though, because I think not all iwlist scan outputs are the same. Again, see comments, it should be easy.

Offline

#2 2010-01-17 06:28:25

iphitus
Forum Fellow
From: Melbourne, Australia
Registered: 2004-10-09
Posts: 4,927

Re: Python script to parse 'iwlist scan' into a table

Nice.

As you found, iwlist scan can very. Maybe a future version could use wpa_supplicant, as it is better supported across different drivers and offers a consistent/table based output. There's an example in /usr/lib/network/wireless, list_networks() and wpa_supplicant_scan_info()

Offline

#3 2010-01-17 13:58:36

patroclo7
Member
From: Bassano del Grappa, ITALY
Registered: 2006-01-11
Posts: 913

Re: Python script to parse 'iwlist scan' into a table

Nice.

'wpa_cli scan_results' is also nice. It is strange that scan and scan_results commands are not mentioned in the man page for wpa_cli. Does anyone now where to find an updated list of commands?


Mortuus in anima, curam gero cutis

Offline

#4 2010-04-04 14:05:26

lolwutaf2
Member
Registered: 2010-03-24
Posts: 7

Re: Python script to parse 'iwlist scan' into a table

Thank you!

Offline

#5 2010-04-04 14:50:14

Daenyth
Forum Fellow
From: Boston, MA
Registered: 2008-02-24
Posts: 1,244

Re: Python script to parse 'iwlist scan' into a table

patroclo7 wrote:

Nice.

'wpa_cli scan_results' is also nice. It is strange that scan and scan_results commands are not mentioned in the man page for wpa_cli. Does anyone now where to find an updated list of commands?

I'd file a bug report upstream. They probably forgot to update the documentation


Any thought of integrating this with wifi-select or something?

Offline

#6 2010-04-04 17:01:23

firecat53
Member
From: Sammamish, Wa
Registered: 2007-05-14
Posts: 1,435
Website

Re: Python script to parse 'iwlist scan' into a table

Did you consider using Popen & PIPE to run iwlist scan right from your code? Might simplify and shorten the command a bit. Disclaimer -- I'm new with python!!

Scott

Online

#7 2010-04-04 19:18:30

gohu
Member
From: France
Registered: 2010-01-17
Posts: 32

Re: Python script to parse 'iwlist scan' into a table

firecat53 wrote:

Did you consider using Popen & PIPE to run iwlist scan right from your code? Might simplify and shorten the command a bit. Disclaimer -- I'm new with python!!

Scott

Yes, that would be the way to run iwlist scan in the script.

But I prefer to keep it outside.
I find it closer to the UNIX philosophy (programs that do one thing that are chained with pipes).
And since iwlist requires root privileges, it is safer too. You don't have to trust that my script won't do anything nasty if it has root privileges, you can just run it without.

If you want a short command you can alias it.

Offline

#8 2010-04-04 20:52:26

polymetr
Member
Registered: 2009-12-04
Posts: 40

Re: Python script to parse 'iwlist scan' into a table

Good Idea. I have one proposal. Maybe would be better to rewrite your code with a classes. Then we could have a base to add any additional so as do not touch former code.

Offline

#9 2010-05-20 23:13:56

thiago907
Member
From: Brazil, Fortaleza
Registered: 2007-09-25
Posts: 28

Re: Python script to parse 'iwlist scan' into a table

great! was think about do something like this but with awk but anyway i`ll use yours instead. thanks!

Offline

#10 2012-12-21 21:24:53

WLAN_BEGINER
Member
Registered: 2012-12-21
Posts: 1

Re: Python script to parse 'iwlist scan' into a table

This tool is very helpfull. I am trying to add a new function to the existing code to parse the signal level parameter from the output of iwlist wlan0 scan, but I am getting lot of issues .Since I am new to python script can anyone help me to extract the signal level also from the scan put put.

The parametr to be used is Signal level=-44 dBm ,I am trying to create a one more column with Signal level and print its out in the table.
Example:-
Signal level
-44db

The error I am getting

  File "iwlist_parser_Testing.py", line 146, in <module>
    main()
  File "iwlist_parser_Testing.py", line 144, in main
    print_cells(parsed_cells)
  File "iwlist_parser_Testing.py", line 123, in print_cells
    print_table(table)
  File "iwlist_parser_Testing.py", line 102, in print_table
    widths=map(max,map(lambda l:map(len,l),zip(*table))) #functional magic
  File "iwlist_parser_Testing.py", line 102, in <lambda>
    widths=map(max,map(lambda l:map(len,l),zip(*table))) #functional magic
TypeError: object of type 'NoneType' has no len()


Could some pls help me to solve this issue

Thanks

Offline

#11 2014-02-18 17:09:25

ktulu
Member
Registered: 2014-02-18
Posts: 1

Re: Python script to parse 'iwlist scan' into a table

The script doesn't recognize WPA2 encryption, and reports such networks as if using WEP encrpytion.

This can be solved by adding the following lines after line 33:

                wpa2=match(matching,"IEEE 802.11i/WPA2 Version ")
                if wpa2!=None:
                    #enc="WPA2 v."+wpa2
                    enc="WPA2"

Offline

Board footer

Powered by FluxBB