From e11978957fec23160f329fc23189bd06abc68f29 Mon Sep 17 00:00:00 2001 From: zlax Date: Sat, 11 Jun 2022 11:46:47 +0300 Subject: [PATCH] parallel command execution for aliases and groups --- setup.py | 2 +- sshch/sshch | 82 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/setup.py b/setup.py index df22e9a..dc56ba8 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def main(): long_description='SSH connection and aliases manager with curses and command line interface', long_description_content_type='text/x-rst', license='DWTWL 2.55', - version='1.05', + version='1.09.1', py_modules=['sshch'], scripts=['sshch/sshch'], keywords='sshch ssh aliases curses manager', diff --git a/sshch/sshch b/sshch/sshch index 35a6e54..507548c 100755 --- a/sshch/sshch +++ b/sshch/sshch @@ -7,7 +7,6 @@ try: import configparser except ImportError: import ConfigParser as configparser # Python 2.x import - from os import path from sys import argv from math import ceil @@ -18,9 +17,10 @@ import base64 import time import curses from curses import textpad, panel +from threading import Thread # https://gitlab.com/zlax/sshch -version = "1.09" +version = "1.09.1" # expand groups by default expand_default = True # path to conf dir and file, default: ~/.config/sshch.conf @@ -37,6 +37,10 @@ class GroupTree(object): self.parent = [] +def ThreadConnect(alias, command): + ConnectAlias(alias, command, True) + + def AddNewAlias(alias): if not conf.has_section(alias): conf.add_section(alias) @@ -91,7 +95,7 @@ def ConvertPassword(password): return password_string -def ConnectAlias(alias, command=False): +def ConnectAlias(alias, command=False, threading=False): exec_string = "" if conf.has_option(alias, "password"): password = base64.b32decode(base64.b16decode( @@ -102,6 +106,8 @@ def ConnectAlias(alias, command=False): if command: exec_string = exec_string + " " + command subprocess.Popen(exec_string, shell=True).communicate()[0] + if threading: + print ("... "+alias+" session output finished.") def HoldConnection(alias): @@ -200,7 +206,7 @@ def CMDRemove(alias): print("error: '" + alias + "' alias or group does not exists.") -def CMDConnect(aliases, command=False): +def CMDConnect(aliases, command=False, threading=False): groups = [] connectaliases = [] for a in conf.sections(): @@ -214,11 +220,17 @@ def CMDConnect(aliases, command=False): else: connectaliases.append(alias) connectaliases = setSeq(connectaliases) + threads = {} for alias in connectaliases: if conf.has_section(alias): print("Connecting to " + alias + "...") - ConnectAlias(alias, command) - print("... " + alias + " session finished.") + if threading: + threads[alias] = Thread(target=ThreadConnect, args=(alias, command)) + threads[alias].start() + else: + ConnectAlias(alias, command) + if not threading: + print("... " + alias + " session finished.") else: print("error: '" + alias + "' alias does not exists") @@ -228,6 +240,8 @@ def CMDList(option, opt, value, parser): def setSeq(seq): + # py2&3 fastest way to remove duplicates from a list: + # https://www.rupython.com/x432-4-496.html seen = set() seen_add = seen.add return [x for x in seq if not (x in seen or seen_add(x))] @@ -288,12 +302,14 @@ def GroupTreeRecursion(level, group, treelist, resultalias, resultstring, expand if expandlist == True or group in expandlist: resultstring.append(' '*(level-1)+">> "+group) for g in treelist[group].children: - resultalias, resultstring = GroupTreeRecursion(level+1, g, treelist, resultalias, resultstring, expandlist, previousgroups) + resultalias, resultstring = GroupTreeRecursion(level+1, g, + treelist, resultalias, resultstring, expandlist, previousgroups) for ga in treelist[group].aliases: if conf.has_option(ga, "exec_string"): resultalias.append(ga) - result = "".join([' '*(level-1)+" ~ ", str(ga), " (", (conf.get(ga, "exec_string") if - conf.has_option(ga, "exec_string") else ""), ")", + result = "".join([' '*(level-1)+" ~ ", str(ga), " (", + (conf.get(ga, "exec_string") if conf.has_option(ga, + "exec_string") else ""), ")", (" [password]" if conf.has_option(ga, "password") else "")]) resultstring.append(result) else: @@ -306,7 +322,8 @@ def GetTreeList(strings=True, expandlist=True): resultstring = [] aliases, groups, rootaliases, rootgroups, treelist = GroupChildAliases(False) for g in rootgroups: - resultalias, resultstring = GroupTreeRecursion(1, g, treelist, resultalias, resultstring, expandlist, []) + resultalias, resultstring = GroupTreeRecursion(1, g, treelist, + resultalias, resultstring, expandlist, []) for a in rootaliases: resultalias.append(a) result = "".join([str(a), " (", (conf.get(a, "exec_string") if @@ -324,7 +341,7 @@ def CMDFullList(option, opt, value, parser): print (p) -def CursesConnect(screen, aliases, command=False): +def CursesConnect(screen, aliases, command=False, threading=False): curses.endwin() groups = [] connectaliases = [] @@ -339,10 +356,16 @@ def CursesConnect(screen, aliases, command=False): else: connectaliases.append(alias) connectaliases = setSeq(connectaliases) + threads = {} for alias in connectaliases: print("Connecting to " + alias + "...") - ConnectAlias(alias, command) - print("... " + alias + " session finished.") + if threading: + threads[alias] = Thread(target=ThreadConnect, args=(alias, command)) + threads[alias].start() + else: + ConnectAlias(alias, command) + if not threading: + print("... " + alias + " session finished.") print("Press 'enter' to continue.") screen.getch() @@ -457,7 +480,10 @@ def CMDOptions(): help="add new group for aliases") opts.add_option('-c', '--command', action="store", type="string", dest="command", metavar="command", default=False, - help="execute command for aliases or group") + help="execute command for aliases and groups") + opts.add_option('-t', '--thread', action="store", type="string", + dest="thread", metavar="command", default=False, + help="parallel command execution for aliases and groups") opts.add_option('-k', '--keep', action="store", type="string", dest="keep", metavar="alias", default=False, help="hold connection with specified alias") @@ -483,7 +509,9 @@ def CMDOptions(): CMDRemove(options.remove) if options.keep: HoldConnection(options.keep) - if alias: + if options.thread: + CMDConnect(alias, options.thread, True) + elif alias: CMDConnect(alias, options.command) # curses template from: https://stackoverflow.com/a/30828805/6224462 @@ -499,6 +527,7 @@ def CursesMain(): " 'space'/'insert' - select\n", " 'r'/'F8' - remove selected alias/aliases\n", " 'c'/'F3' - execute specific command with selected alias/aliases\n", + " 't'/'F11' - parallel execute command with selected aliases and groups", " 'k'/'F7' - hold connection with selected alias\n", " 'enter'/'F9' - connect to selected alias/aliases,\n", " expand/collapse group\n", @@ -734,6 +763,25 @@ def CursesMain(): CursesConnect(screen, selected, command_string.replace("\n", "").rstrip()) curses.curs_set(0) + if (key_pressed == ord('t') or key_pressed == ord( + 'T') or key_pressed == curses.KEY_F11) and row_num != 0: + selected = [] + for i in range(1, row_num + 1): + if selected_strings[i] == "*": + selected.append(strings[i - 1]) + if not len(selected) > 0: + selected.append(strings[position - 1].split()[0].strip()) + curses.curs_set(1) + command_textpad = CursesTextpad(screen, 3, width - 8, + (height // 2) - 1, 4, + "".join([ + "Enter specific command to execute with selected ", + "alias/aliases:"] + ), "", normal_text, highlight_text) + command_string = command_textpad.edit(CursesTextpadConfirm) + CursesConnect(screen, selected, + command_string.replace("\n", "").rstrip(),True) + curses.curs_set(0) if (key_pressed == ord('k') or key_pressed == ord('K') or key_pressed == (curses.KEY_F7)) and row_num != 0: groups = [] @@ -823,12 +871,12 @@ def CursesMain(): page = page - 1 position = max_row + (max_row * (page - 1)) if key_pressed == curses.KEY_LEFT or (key_pressed == - curses.KEY_PPAGE): + curses.KEY_PPAGE): if page > 1: page = page - 1 position = 1 + (max_row * (page - 1)) if key_pressed == curses.KEY_RIGHT or (key_pressed == - curses.KEY_NPAGE): + curses.KEY_NPAGE): if page < pages: page = page + 1 position = (1 + (max_row * (page - 1)))