xip.py: Executing Commands per IP Address

Batch ProcessingDuring a penetration test, I had to execute specific commands against some IP networks. Those networks were represented under the CIDR form (network/subnet). Being a lazy guy, I spent some time to write a small Python script to solve this problem. The idea was based on the “xargs” UNIX command which is used to build complex command lines. From the xargs man page:

xargs reads items from the standard input, delimited by blanks (which can be protected with double or single quotes or a backslash) or newlines, and executes the command (default is /bin/echo) one or more times with any initial-arguments followed by items read from standard input. Blank lines on the standard input are ignored.

I called the tool logically “xip.py” as it allows you to execute a provided command for each IP address from a subnet or a range. The syntax is simple:

$ ./xip.py -h
Usage: xip.py [options]

 --version             show program's version number and exit
 -h, --help            show this help message and exit
                       IP Addresses subnets to expand
 -c COMMAND, --command=COMMAND
                       Command to execute for each IP ("{}" will be replaced by the IP)
 -o OUTPUT, --output=OUTPUT
                       Send commands output to a file
 -s, --split           Split outfile files per IP address
 -d, --debug           Debug output

The IP addresses can be added in two formats: x.x.x.x/x or x.x.x.x-x. Multiple subnets can be delimited by commas and subnet starting with a “-” will be excluded. Examples:

$ ./xip.py -i,,- -c "echo {}"

This command will return:

Like the “find” UNIX command, “{}” are replaced by the IP address (multiple {} pairs can be used). With the “-o <file>” option, the command output will be stored to the file (stderr & stdout). You can split the output across multiple files using the switch “-s“. In this case, <file> will end the IP addresses.

This is a quick and dirty tool which helped me a lot. I already have some ideas to improve it, if I’ve time… The script is available on my github repository.


  1. Hi Ben,
    Thank a lot for this tip! That’s why I like this community!
    I was not aware of the “parallel” tool.


  2. Hi, nice effort but in my opinion overly complicated as it can be solved better with a simple shellscript oneliner:

    nmap -sL -n | grep “for” | cut -f5 -d” ” | parallel echo {}

    1) nmap -sL -n does a list scan but no DNS reverse lookup, i.e. it just instantly lists the IPs.
    2) use grep and cut to get rid of non-IP output
    3) GNU parallel FTW: too few people know about and use this precious treasure of a software

    ad 1) nmap supports a range of notations out of the box:
    192.168.0-2.1-3 (etc.)
    ad 3) GNU parallel gives you real concurrency out of the box using available resources such as multiple processors …Python on the other hand suffers from GIL.
    -j4 runs four jobs in parallel
    -k keep order (e.g. when job 4 finishes earlier wait to output result from job 4 until after jobs 1-3 finished)
    -want to use multiple systems and run commands remotely? parallel can do it, and so much more.

    Have a nice weekend,

  3. Hi Philip,
    For three simple reasons:
    1. To solve a boring issue I faced during a project.
    2. A IPv4 /16 is “only” 65536 IPs, this is still manageable. A IPv6 /48 is… quite big to deal with 🙂
    3. This tool just expand CIDR or ranges into IP addresses and call an external command. It doesn’t perform any task at network level by itself
    But of course, IPv6 support can be added!


  4. Why are you still writing legacy IP software? I can understand having to support legacy IP in deployed networkings, but I really don’t understand writing new software that does not support IPv6.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.