Skip to main content

cparrun - combinated parallel run of something as multiple processes

Project description

cparrun

cparrun - combinated parallel run of something as multiple processes (as many as you want and each restricted by timeout).


Could be used as command line utility or python module. Command line utility mode could interpret arguments as multiple combinations or run in parallel set of lines from stdin as shell commands.

Output results are in JSON format with separated stdout, stderr, return_code, status.
In case of python module the result will be list of dicts and the same structure.
Main purpose: parallel debugging/troubleshooting, mass checking or just to use with slow or unstable network/corporate utilities in parallel for more perfomance. Added timeout to avoid eternal freezing or long execution of utilities. Started like a parallel manager for mass invocation thousands of external utilities from python, evolved to a python module and then to a cli utility. Outputting results in JSON format greatly simplifies any further automatic data processing. For example you could filter from thousands some kind of "failed" and re-run them again and again (simple jq filter example is given below).

Installation

pip install cparrun

Usage

cparrun [OPTIONS] -- <command expression to expand>

or

<long set of commands separated by new lines> | cparrun --stdin [OPTIONS] 

Options

--parallel               -p  parallel processes count, default: 10
--timeout                -t  timeout for processes, default: 10 
--start_token,--start    -s  start separator for mark expandable args, default: %[  
--end_token,--end        -e  end separator for mark expandable args, default: ]% 
--stdin,--in             -i  get list of commands from stdin 
--stdin-filter-comments      filter shell comments in stdin (lines started with #), default: True 
--dry-run,--print-only       print commands without running 
--help 

Examples

A simple generator of 6 parallel ping cals to given hostnames

cparrun -- 'ping -c2 %[google.com, gmail.com, kubernetes.io, github.com, python.org, nonexistentdomain.example]%'
[
    {
        "task_id": "brqtmq",
        "command": "ping -c2 google.com",
        "stdout": "PING google.com (172.217.17.142): 56 data bytes\n64 bytes from 172.217.17.142: icmp_seq=0 ttl=114 time=30.617 ms\n64 bytes from 172.217.17.142: icmp_seq=1 ttl=114 time=29.582 ms\n\n--- google.com ping statistics ---\n2 packets transmitted, 2 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 29.582/30.099/30.617/0.518 ms\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "XEY5Wa",
        "command": "ping -c2 gmail.com",
        "stdout": "PING gmail.com (172.217.20.69): 56 data bytes\n64 bytes from 172.217.20.69: icmp_seq=0 ttl=114 time=30.408 ms\n64 bytes from 172.217.20.69: icmp_seq=1 ttl=114 time=29.422 ms\n\n--- gmail.com ping statistics ---\n2 packets transmitted, 2 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 29.422/29.915/30.408/0.493 ms\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "sSaF9a",
        "command": "ping -c2 kubernetes.io",
        "stdout": "PING kubernetes.io (15.197.167.90): 56 data bytes\n64 bytes from 15.197.167.90: icmp_seq=0 ttl=246 time=16.015 ms\n64 bytes from 15.197.167.90: icmp_seq=1 ttl=246 time=16.320 ms\n\n--- kubernetes.io ping statistics ---\n2 packets transmitted, 2 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 16.015/16.168/16.320/0.152 ms\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "yNICrl",
        "command": "ping -c2 github.com",
        "stdout": "PING github.com (140.82.121.4): 56 data bytes\n64 bytes from 140.82.121.4: icmp_seq=0 ttl=50 time=32.427 ms\n64 bytes from 140.82.121.4: icmp_seq=1 ttl=50 time=31.123 ms\n\n--- github.com ping statistics ---\n2 packets transmitted, 2 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 31.123/31.775/32.427/0.652 ms\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "mLZsgu",
        "command": "ping -c2 python.org",
        "stdout": "PING python.org (151.101.0.223): 56 data bytes\n64 bytes from 151.101.0.223: icmp_seq=0 ttl=55 time=22.050 ms\n64 bytes from 151.101.0.223: icmp_seq=1 ttl=55 time=22.302 ms\n\n--- python.org ping statistics ---\n2 packets transmitted, 2 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 22.050/22.176/22.302/0.126 ms\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "7L9zon",
        "command": "ping -c2 nonexistentdomain.example",
        "stdout": "",
        "stderr": "ping: cannot resolve nonexistentdomain.example: Unknown host\n",
        "return_code": 68,
        "status": "[ERROR] FINISHED "
    }
]

The example generates a set of requests of DNS NS/SOA/MX records for given domains from DNS recursors in parallel (3x3x3=27 combinations and run them). Output results is JSON

cparrun --parallel=8 -- "dig -t %['NS', 'SOA', 'MX']% %['google.com', 'gmail.com', 'kubernetes.io']% %['@1.1.1.1', '@8.8.8.8', @'nonexistentdomain.example.']% +short"

    {
        "task_id": "Bm3fRR",
        "command": "dig -t 'NS' 'google.com' '@1.1.1.1' +short",
        "stdout": "ns1.google.com.\nns4.google.com.\nns2.google.com.\nns3.google.com.\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    },
    {
        "task_id": "n8acXt",
        "command": "dig -t 'SOA' 'google.com' '@1.1.1.1' +short",
        "stdout": "ns1.google.com. dns-admin.google.com. 872309766 900 900 1800 60\n",
        "stderr": "",
        "return_code": 0,
        "status": "[OK] FINISHED"
    }, ...
    
    {
        "task_id": "cKqtGN",
        "command": "dig -t 'MX' 'facebook.com' @'nonexistingdomain.somedomain.' +short",
        "stdout": "",
        "stderr": "dig: couldn't get address for 'nonexistingdomain.somedomain.': not found\n",
        "return_code": 10,
        "status": "[ERROR] FINISHED "
    }
]

Just make combinations and print them (this example makes 3x3x3=27 combinations)

cparrun --parallel=8 --dry-run -- "dig -t %['NS', 'SOA', 'MX']% %['google.com', 'gmail.com', 'facebook.com']% %['@1.1.1.1', '@8.8.8.8', @'nonexistingdomain.somedomain.']% +short"

Send of list of something to run in parallel to stdin, JSON results are filtered by jq utility (shows only ERROR here). Lines starting with '#' are ignored.

echo 'ping -c2 8.8.8.8
sleep 10
curl -s google.com
wget nonexistentdomain.com123
host example.com
host gmail.com
# this is comment and will be ignored
host example2.com
' | cparrun --stdin --timeout=2 | jq '.[] | select(.status | contains("ERROR"))'
{
  "task_id": "AfyBvJ",
  "command": "sleep 10",
  "stdout": null,
  "stderr": null,
  "return_code": null,
  "status": "[ERROR] TERMINATED (timeout)"
}
{
  "task_id": "hEyXJR",
  "command": "wget nonexistentdomain.com123",
  "stdout": "",
  "stderr": "Prepended http:// to 'nonexistentdomain.com123'\n--2026-02-20 21:45:16--  http://nonexistentdomain.com123/\nResolving nonexistentdomain.com123 (nonexistentdomain.com123)... failed: nodename nor servname provided, or not known.\nwget: unable to resolve host address ‘nonexistentdomain.com123’\n",
  "return_code": 4,
  "status": "[ERROR] FINISHED "
}

Simple stdin example from file. Place tons of something in file separated by new line and run them in parallel. ( # comments filtered by default)

cat 100500_commands_list.txt | cparrun --stdin --parallel=50 --timeout=5

Other examples https://github.com/itledevs/cparrun/wiki

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cparrun-2026.1.1.tar.gz (9.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cparrun-2026.1.1-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file cparrun-2026.1.1.tar.gz.

File metadata

  • Download URL: cparrun-2026.1.1.tar.gz
  • Upload date:
  • Size: 9.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for cparrun-2026.1.1.tar.gz
Algorithm Hash digest
SHA256 510c695723870ca132c615d233cf2c7f8905a0b18221e34fd4b49b5725189388
MD5 7f82b870aa2f7ccce3106c9f25460cc3
BLAKE2b-256 5d62ac67a5538bc4378f9f8853cbbc49f3afa3b9262715bca189dc4aa61697a7

See more details on using hashes here.

File details

Details for the file cparrun-2026.1.1-py3-none-any.whl.

File metadata

  • Download URL: cparrun-2026.1.1-py3-none-any.whl
  • Upload date:
  • Size: 8.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for cparrun-2026.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 981389189a246d666b8e1ebafc972b75d15adda0126d092ea1384748885c821a
MD5 6e729c7dfde48a19c6fd8aafe447123b
BLAKE2b-256 37307cc00fc509fe7d76a08e637261e961e13f98345e2ed767142ba1ae6d5531

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page