Introduction
Can you write command line tools? Maybe you can, but can you write a really useful command-line tool? This article discusses the use of Python to create a robust command-line tool with built-in Help menus, error handling, and option handling. For some strange reason, many people do not understand Python? 's standard library has all the tools you need to make a very powerful *nix command-line tool.
It can be said that Python is the best language for making *nix command-line tools, because it works in the philosophical Way of "batteries-included" and emphasizes the provision of highly readable code. But just as a reminder, when you discover how simple it is to create a command-line tool using Python, these ideas are dangerous and your life may be messed up. As far as I know, there have been no articles detailing the use of Python to create command-line tools, so I hope you like this article.
Set up
The Optparse module in the Python Standard library can accomplish most of the trivial work of creating command-line tools. Optparse is included in Python 2.3, so the module will be included in many *nix operating systems. If for some reason the operating system you are using does not contain the required modules, it is fortunate that the latest version of Python has been tested and compiled into almost any *nix operating system. Python-supported systems include IBM? AIX, HP-UX, Solaris, free BSD, Red Hat Linux, Ubuntu, OS X, IRIX, and even several Nokia handsets.
create Hello World command line tool
The first step in writing good command-line tools is to define the issues to be addressed. This is critical to the success of your tools. This is equally important for solving problems in the simplest possible way. The KISS (Keep It simple stupid, easy to keep) guideline is explicitly adopted here. Add options and add additional functionality only after you have implemented and tested your planned functionality.
Let's start by creating the Hello World Command line tool. As suggested above, we use the simplest possible terminology to define the problem.
Problem definition: I want to create a command-line tool that prints Hello world by default and provides options for printing names that are not available to people.
Based on the above instructions, you can provide a solution that contains a small amount of code.
Hello World Command Line Interface (CLI)
#!/usr/bin/env python
import optparse
def main ():
p = optparse. Optionparser ()
p.add_option ('--person ', '-P ', default= "World")
options, arguments = P.parse_args ()
print ' Hello%s '% Options.person
if __name__ = = ' __main__ ':
Main ()
If you run this code, the expected output is as follows:
But what we can do with a little bit of code is much more than that. We can get the automatically generated Help menu:
Python hello_cli.py--help
Usage:hello_cli.py [options]
options:
-H,--help show this help Message and exit
-P person,--person=person
As you can see from the Help menu, there are two ways to change the output of Hello world:
Python hello_cli.py-p Guido
Hello Guido
We have also implemented automatic generated error handling:
Python hello_cli.py--name Matz
Usage:hello_cli.py [options]
hello_cli.py:error:no such option:--name
If you haven't used Python's Optparse module, you might have been surprised and thought about using all these incredible tools that python can write. If you're just starting to touch python, you might be amazed at how Python makes everything so simple. The "XKCD" website has published a very interesting comic book on the theme "Python is so Simple" and has been included in the resources.
to create a useful command line tool
Now that we have made the groundwork, we can continue to create tools to solve specific problems. For this example, we'll use Python's network library and interactive tools called Scapy. Scapy can work properly on most *nix systems, sending packets on layers 2nd and 3rd, and allowing you to create very complex tools with only a few lines of Python code. If you want to start all over again, make sure you have the necessary software installed correctly.
Let's first define the new problem to be solved.
Question: I want to create a command-line tool that uses an IP address or subnet as a parameter and return the MAC address or MAC address list and their respective IP addresses to standard output.
Now that we have clearly defined the problem, let me try to break it down into as simple a part as possible, and then solve each of these parts individually. I've seen two separate parts of the problem. The first part is to write a function that receives the IP address or subnet range, and returns a list of MAC addresses or MAC addresses. We can then consider integrating it into the command-line tool after we have solved this problem.
Solution Part 1th: Create a Python function that determines the MAC address by IP address
Arping from
scapy import srp,ether,arp,conf
conf.verb=0
ans,unans=srp (ether (dst= "ff:ff:ff:ff:ff:ff")/ ARP (pdst= "10.0.1.1"),
timeout=2) for
snd, Rcv in ans:
print rcv.sprintf (r "%ether.src%%arp.psrc%")
The output of this command is:
sudo python arping.py
00:00:00:00:00:01 10.0.1.1
Note that using scapy to perform actions requires elevated permissions, so we must use sudo. For the purposes of this article, I also changed the actual output to include a pseudo MAC address. We have confirmed that we can find the MAC address by IP address. We need to defragment this code to accept the IP address or subnet and return the MAC address and IP address pairs.
arping function
#!/usr/bin/env python from
scapy import srp,ether,arp,conf
def arping (iprange= "10.0.1.0/24"):
Conf.verb =0
ans,unans=srp (ether (dst= "ff:ff:ff:ff:ff:ff")/arp (Pdst=iprange), timeout=2
)
collection = []
For snd, Rcv in ans: result
= rcv.sprintf (r "%arp.psrc%%ether.src%"). Split ()
collection.append (Result)
Return collection
#Print results
values = arping () to
Ip,mac in values:
Print Ip,mac
As you can see, we've written a function that accepts an IP address or network and returns a nested list of Ip/mac addresses. We are now ready for the second part to create a command-line interface for our tools.
Solution Part 2nd: Create a command-line tool from our arping function
In this case, we synthesize the idea in the earlier part of this article to create a complete command-line tool that solves our initial problem.
Arping CLI
#!/usr/bin/env python import optparse from scapy import srp,ether,arp,conf def arping (iprange= "10.0.1.0/24") : "" "arping function takes IP address or network, returns nested MAC/IP list" "Conf.verb=0 ANS,UNANS=SRP (EThe R (dst= "Ff:ff:ff:ff:ff:ff")/arp (Pdst=iprange), timeout=2) collection = [] for snd, RCV in Ans:result
= rcv.sprintf (r "%arp.psrc%%ether.src%"). Split () collection.append (Result) return collection Def main (): "" "Runs program and handles command line Options" "P = optparse. Optionparser (description= ' finds MAC address to IP address (es) ', prog= ' pyarping ', vers
ion= ' pyarping 0.1 ', usage= '%prog [10.0.1.1 or 10.0.1.0/24] ' options, arguments = P.parse_args () If len (arguments) = = 1:values = arping (iprange=arguments) for IP, mac in Values:print IP, Mac Else:p.pri
Nt_help () if __name__ = = ' __main__ ': Main ()
A few notes on the above script will help us understand how optparse works.
First, you must create a optparse. An instance of Optionparser (), and accept the optional arguments shown below:
Copy Code code as follows:
Description, prog, version, and usage
The meaning of these parameters can be self-evident, but I would like to confirm that you should understand that optparse, though powerful, is not omnipotent. It has a well-defined interface that can be used to quickly create command line tools.
Second, in the following line:
Copy Code code as follows:
Options, arguments = P.parse_args ()
The function of the row is to divide the options and parameters into different bits. In the code above, we expect just one argument, so I specify that there must be only one parameter value and pass the value to the Arping function.
If len (arguments) = = 1:
values = arping (iprange=arguments)
For further explanation, let's run the following command to see how it works:
sudo python arping.py 10.0.1.1
10.0.1.1 00:00:00:00:00:01
In the example above, the argument is 10.0.1.1, and because there is only one argument as I specified in the conditional statement, the parameter is passed to the Arping function. If there are options, they will be passed to the options in the options, arguments = P.parse_args () method. Let's take a look at what happens when we decompose the expected use case of the command-line tool and give it two parameters:
sudo python arping.py 10.0.1.1 10.0.1.3
usage:pyarping [10.0.1.1 or 10.0.1.0/24]
finds MAC address or IP address ( ES)
Options:
--version Show Program ' s version number and exit
-H,--help show the This help message and exit
Depending on the structure of the conditional statement I built for the parameter, if the number of arguments is not 1, it will automatically open the Help menu:
If len (arguments) = = 1:
values = arping (iprange=arguments)
for IP, mac in values:
print IP, mac
else:
P.print_help ()
This is an important way to control how the tool works, because you can use the number of parameters or the name of a specific option as a mechanism for controlling the flow of command-line tools. Because we covered the creation of options in the initial Hello world example, we then added several options to our command-line tool by slightly changing the main function:
Arping CLI main function
def main (): "" "Runs program and
handles command line Options" "
p = optparse. Optionparser (description= ' finds MAC address to IP address (es) ',
prog= ' pyarping ',
version= ' pyarping 0.1 ',
usage= '%prog [10.0.1.1 or 10.0.1.0/24] ')
P.add_option (' m ', '--mac ', action = ' store_true ', help= ' returns only MAC address ')
p.add_option ('-V ', '--verbose ', Action = ' store_true ', help= ' returns verbose output ')
options, arguments = P.parse_args ()
If len (arguments) = 1:
values = arping (iprange=arguments)
if Options.mac:
for IP, mac in values:
print mac
elif Options.verbose:
for IP, mac in values:
print ' IP:%s Mac:%s '% (IP, Mac)
else:
for IP, Mac in value S:
print IP, mac
else:
p.print_help ()
The main change you made was to create a conditional statement based on whether an option was specified. Note that unlike the Hello World command-line tool, we use only options as the true/false signal for our tools. For the – mac option, if this option is specified, our conditional statement elif will print only the MAC address.
The following is the output of the new option:
Arping output
sudo python arping2.py
Password:
usage:pyarping [10.0.1.1 or 10.0.1.0/24]
finds MAC address to IP address (es
Options:--version Show program ' s version number
and exit-H,--help Show this help message and Exi T
-M,--mac returns only MAC address
-V,--verbose returns verbose output
[ngift@m-6][h:11184][j:0] > sudo python arping2.py 10.0.1.1
10.0.1.1 00:00:00:00:00:01
[ngift@m-6][h:11185][j:0]> sudo python Arping2.py-m 10.0.1.1
00:00:00:00:00:01
[ngift@m-6][h:11186][j:0]> sudo python arping2.py-v 10.0.1.1
ip:10.0.1.1 mac:00:00:00:00:00:01
Drill down to create command line tools
Here are a few new ideas for deep learning. These ideas are discussed in depth in my book on the management of the Python *nix system, which will be published in the middle of 2008.
using the subprocess module in command line tools
The Subprocess module, included in Python 2.4 or later, is a unified interface for handling system calls and processes. You can easily replace the arping function above to use arping tools that apply to your particular *nix operating system. Here's a rough example of the idea:
Sub-process Arping
Import subprocess
import re
def arping (ipaddress= "10.0.1.1"): ""
"arping function takes IP address or Network, returns nested MAC/IP list "" "#Assuming use of the
arping on Red Hat Linux
p = subprocess. Popen ("/usr/sbin/arping-c 2%s"% IPAddress, Shell=true,
stdout=subprocess. PIPE) Out
= P.stdout.read () result
= Out.split () pattern
= Re.compile (":") as item
in result: If Re.search (pattern, item):
Print Item
arping ()
The following is the output of the function when it is run separately: [root@localhost]~# python pyarp.py [00:16:cb:c3:b4:10]
Note that you use subprocess to get the output of the arping command, and to match the MAC address with a compiled regular expression. Note that if you are using Python 2.3, you can replace subprocess with the Popen module, which is available in Python 2.4 or later.
Use Object-relational mapper in command-line tools, such as SQLAlchemy or Storm used with SQLite
Another possible option for command-line tools is to use the ORM (Object Relational Mapper) to store data records generated by command-line tools. There are quite a few ORM available for Python, but SQLAlchemy and Storm are exactly the two most commonly used. I decided to use Storm as an example by flipping a coin:
Storm ORM arping
#!/usr/bin/env python import optparse from storm.locals Import * from scapy import Srp,ether,arp,
Conf class Networkrecord (object): __storm_table__ = "Networkrecord" id = Int (primary=true) ip = rawstr () mac = Rawstr () hostname = RAWSTR () def arping (iprange= "10.0.1.0/24"): " "" "Arping function takes IP address or network, returns nested MAC/IP list" "Conf.verb=0 ans, UNANS=SRP (Ether (dst= "ff:ff:ff:ff:ff:ff")/arp (Pdst=iprange), timeout=2) collection = [] for snd , Rcv in Ans:result = rcv.sprintf (r "%arp.psrc%%ether.src%"). Split () collection.append (result) ret Urn Collection def main (): "" "Runs program and handles command line Options" "" P = optpa Rse. Optionparser () p = optparse.
Optionparser (description= ' finds macaddr of IP address (es) ', prog= ' pyarping ', version= ' pyarping 0.1 ', usage= '%prog [10.0.1.1 or 10.0.1.0/24] ') options, arg Uments = P.parse_args () If len (arguments) = = 1:database = Create_database ("SQLite:") store = Store
(database) Store.execute ("CREATE TABLE Networkrecord" (ID INTEGER PRIMARY KEY, IP varchar,\ Mac VARCHAR, hostname VARCHAR) "values = arping (iprange=arguments) machine = Networkrecord () s Tore.add (machine) #Creates Records for IP, mac in Values:machine.mac = Mac Machine.ip = The IP #Flushes to Database Store.flush () #Prints The record print "record number:%r"% machine. ID print "MAC address:%r"% Machine.mac print "IP address:%r"% Machine.ip Else:p.print_
Help () if __name__ = = ' __main__ ': Main ()
The main concern in this case is to create a class named Networkrecord that maps to an in-memory SQLite database. In the main function, I change the output of the Arping function to map to our record objects, update them to the database, and then retrieve them to print the results. This is obviously not a tool that can be used for production, but can serve as a descriptive example of the related steps in using ORM in our tools.
integrating config files in the CLI
Python INI config syntax
[AIX]
mac:00:00:00:00:02
ip:10.0.1.2
Hostname:aix.example.com
[HPUX]
mac:00:00:00:00:03
IP: 10.0.1.3
Hostname:hpux.example.com
[SOLARIS]
mac:00:00:00:00:04
ip:10.0.1.4
Hostname: Solaris.example.com
[REDHAT]
mac:00:00:00:00:05
ip:10.0.1.5
Hostname:redhat.example.com
[UBUNTU]
mac:00:00:00:00:06
ip:10.0.1.6
Hostname:ubuntu.example.com
[OS X
] mac:00:00:00:00:07
ip:10.0.1.7
Hostname:osx.example.com
Next, we need to use the Configparser module to parse these things:
Configparser function
#!/usr/bin/env python
import configparser
def readconfig (file= "Config.ini"):
config = Configparser.configparser ()
config.read (file)
sections = Config.sections () for
machine in sections:
#uncomment line below to
, parsed #print config.items (machine)
macaddr = Config.ite MS (machine) [0][1]
print machine, macaddr
readconfig ()
The output of the function is as follows:
OS X 00:00:00:00:07
SOLARIS 00:00:00:00:04
AIX 00:00:00:00:02
REDHAT 00:00:00:00:05
UBUNTU 00:00:00:00:06
HPUX 00:00:00:00:03
I leave the remaining questions as exercises for the reader to solve. What I'm going to do next is integrate the config file into my script so that I can compare the machine inventory recorded in my config file with the actual inventory of the MAC address that appears in the ARP cache. The IP address or hostname only works when it is traced to the computer, but the tools we implement can be useful for tracking the hardware address of a computer that exists on a network and determining whether it has previously appeared on the network.
Conclusion
We started by writing a few lines of code to create a very simple but powerful Hello World command line tool. You then use the Python network library to create a complex network tool. Finally, we continue to discuss some of the more advanced areas of research readers. In the Advanced Research section, we discussed the integration of the Subprocess module, object-relational mapper, and finally discussed the configuration file.
Although not widely known, anyone with an IT background can easily create command-line tools using Python. I hope this article will inspire you to create new command-line tools yourself.