multiSSH3 5.92__tar.gz → 5.94__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multissh3-5.92 → multissh3-5.94}/PKG-INFO +1 -1
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.py +78 -37
- {multissh3-5.92 → multissh3-5.94}/README.md +0 -0
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.92 → multissh3-5.94}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.92 → multissh3-5.94}/setup.cfg +0 -0
- {multissh3-5.92 → multissh3-5.94}/setup.py +0 -0
- {multissh3-5.92 → multissh3-5.94}/test/test.py +0 -0
- {multissh3-5.92 → multissh3-5.94}/test/testCurses.py +0 -0
- {multissh3-5.92 → multissh3-5.94}/test/testCursesOld.py +0 -0
- {multissh3-5.92 → multissh3-5.94}/test/testPerfCompact.py +0 -0
- {multissh3-5.92 → multissh3-5.94}/test/testPerfExpand.py +0 -0
|
@@ -47,7 +47,6 @@ try:
|
|
|
47
47
|
except ImportError:
|
|
48
48
|
pass
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
try:
|
|
52
51
|
# Check if functiools.cache is available
|
|
53
52
|
# cache_decorator = functools.cache
|
|
@@ -85,10 +84,10 @@ except Exception:
|
|
|
85
84
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
86
85
|
def cache_decorator(func):
|
|
87
86
|
return func
|
|
88
|
-
version = '5.
|
|
87
|
+
version = '5.94'
|
|
89
88
|
VERSION = version
|
|
90
89
|
__version__ = version
|
|
91
|
-
COMMIT_DATE = '2025-10-
|
|
90
|
+
COMMIT_DATE = '2025-10-21'
|
|
92
91
|
|
|
93
92
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
94
93
|
'~/multiSSH3.config.json',
|
|
@@ -346,7 +345,16 @@ DEFAULT_ERROR_ONLY = False
|
|
|
346
345
|
DEFAULT_NO_OUTPUT = False
|
|
347
346
|
DEFAULT_RETURN_ZERO = False
|
|
348
347
|
DEFAULT_NO_ENV = False
|
|
349
|
-
DEFAULT_ENV_FILE = '
|
|
348
|
+
DEFAULT_ENV_FILE = ''
|
|
349
|
+
DEFAULT_ENV_FILES = ['/etc/profile.d/hosts.sh',
|
|
350
|
+
'~/.bashrc',
|
|
351
|
+
'~/.zshrc',
|
|
352
|
+
'~/host.env',
|
|
353
|
+
'~/hosts.env',
|
|
354
|
+
'.env',
|
|
355
|
+
'host.env',
|
|
356
|
+
'hosts.env',
|
|
357
|
+
]
|
|
350
358
|
DEFAULT_NO_HISTORY = False
|
|
351
359
|
DEFAULT_HISTORY_FILE = '~/.mssh_history'
|
|
352
360
|
DEFAULT_MAX_CONNECTIONS = 4 * os.cpu_count()
|
|
@@ -387,6 +395,9 @@ if __ERROR_MESSAGES_TO_IGNORE_REGEX:
|
|
|
387
395
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile(__ERROR_MESSAGES_TO_IGNORE_REGEX)
|
|
388
396
|
else:
|
|
389
397
|
__ERROR_MESSAGES_TO_IGNORE_REGEX = re.compile('|'.join(ERROR_MESSAGES_TO_IGNORE))
|
|
398
|
+
if DEFAULT_ENV_FILE:
|
|
399
|
+
if DEFAULT_ENV_FILE not in DEFAULT_ENV_FILES:
|
|
400
|
+
DEFAULT_ENV_FILES.append(DEFAULT_ENV_FILE)
|
|
390
401
|
|
|
391
402
|
#%% Load mssh Functional Global Variables
|
|
392
403
|
__global_suppress_printout = False
|
|
@@ -394,7 +405,7 @@ __mainReturnCode = 0
|
|
|
394
405
|
__failedHosts = set()
|
|
395
406
|
__wildCharacters = ['*','?','x']
|
|
396
407
|
_no_env = DEFAULT_NO_ENV
|
|
397
|
-
|
|
408
|
+
_env_files = DEFAULT_ENV_FILES
|
|
398
409
|
__globalUnavailableHosts = dict()
|
|
399
410
|
__ipmiiInterfaceIPPrefix = DEFAULT_IPMI_INTERFACE_IP_PREFIX
|
|
400
411
|
__keyPressesIn = [[]]
|
|
@@ -476,35 +487,65 @@ def find_ssh_key_file(searchPath = DEDAULT_SSH_KEY_SEARCH_PATH):
|
|
|
476
487
|
return None
|
|
477
488
|
|
|
478
489
|
@cache_decorator
|
|
479
|
-
def readEnvFromFile(
|
|
490
|
+
def readEnvFromFile():
|
|
480
491
|
'''
|
|
481
492
|
Read the environment variables from env_file
|
|
482
493
|
Returns:
|
|
483
494
|
dict: A dictionary of environment variables
|
|
484
495
|
'''
|
|
485
|
-
global
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
envf =
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if line.startswith('#') or not line
|
|
496
|
+
global _env_files
|
|
497
|
+
global _no_env
|
|
498
|
+
envfs = _env_files if _env_files else DEFAULT_ENV_FILES
|
|
499
|
+
translator = str.maketrans('&|"', ';;\'')
|
|
500
|
+
replacement_re = re.compile(r'\$(?:[A-Za-z_]\w*|\{[A-Za-z_]\w*\})')
|
|
501
|
+
environemnt = {}
|
|
502
|
+
scrubCounter = 0
|
|
503
|
+
for envf in envfs:
|
|
504
|
+
envf = os.path.expanduser(os.path.expandvars(envf))
|
|
505
|
+
if os.path.exists(envf):
|
|
506
|
+
with open(envf,'r') as f:
|
|
507
|
+
lines = f.readlines()
|
|
508
|
+
for line in lines:
|
|
509
|
+
line = line.strip()
|
|
510
|
+
if not line or line.startswith('#') or '=' not in line:
|
|
500
511
|
continue
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
512
|
+
line = line.translate(translator)
|
|
513
|
+
commands = re.split(r";(?=(?:[^']*'[^']*')*[^']*$)", line)
|
|
514
|
+
for command in commands:
|
|
515
|
+
if not command or command.startswith('#') or '=' not in command or command.startswith('alias '):
|
|
516
|
+
continue
|
|
517
|
+
fields = re.split(r" (?=(?:[^']*'[^']*')*[^']*$)", command)
|
|
518
|
+
for field in fields:
|
|
519
|
+
try:
|
|
520
|
+
if field.startswith('export '):
|
|
521
|
+
field = field.replace('export ', '', 1).strip()
|
|
522
|
+
if not field or field.startswith('#') or '=' not in field:
|
|
523
|
+
continue
|
|
524
|
+
key, _, values = field.partition('=')
|
|
525
|
+
key = key.strip().strip("'")
|
|
526
|
+
values = values.strip().strip("'")
|
|
527
|
+
if '$' in values:
|
|
528
|
+
scrubCounter += 16
|
|
529
|
+
if key and values and key != values:
|
|
530
|
+
environemnt[key] = values
|
|
531
|
+
except Exception:
|
|
532
|
+
continue
|
|
533
|
+
while scrubCounter:
|
|
534
|
+
scrubCounter -= 1
|
|
535
|
+
found = False
|
|
536
|
+
for key, value in environemnt.items():
|
|
537
|
+
if '$' in value:
|
|
538
|
+
for match in replacement_re.findall(value):
|
|
539
|
+
ref_key = match.strip('${}')
|
|
540
|
+
ref_value = environemnt.get(ref_key) if ref_key != key else None
|
|
541
|
+
if not ref_value and not _no_env:
|
|
542
|
+
ref_value = os.environ.get(ref_key)
|
|
543
|
+
if ref_value:
|
|
544
|
+
environemnt[key] = value.replace(match, ref_value)
|
|
545
|
+
found = True
|
|
546
|
+
if not found:
|
|
547
|
+
break
|
|
548
|
+
return environemnt
|
|
508
549
|
|
|
509
550
|
def replace_magic_strings(string,keys,value,case_sensitive=False):
|
|
510
551
|
'''
|
|
@@ -3100,7 +3141,7 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
3100
3141
|
no_env=DEFAULT_NO_ENV,greppable=DEFAULT_GREPPABLE_MODE,skip_hosts = DEFAULT_SKIP_HOSTS,
|
|
3101
3142
|
file_sync = False, error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
3102
3143
|
copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY, no_history = DEFAULT_NO_HISTORY,
|
|
3103
|
-
history_file = DEFAULT_HISTORY_FILE, env_file =
|
|
3144
|
+
history_file = DEFAULT_HISTORY_FILE, env_file = DEFAULT_ENV_FILES,
|
|
3104
3145
|
repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
|
|
3105
3146
|
shortend = False) -> str:
|
|
3106
3147
|
argsList = []
|
|
@@ -3144,8 +3185,8 @@ def __formCommandArgStr(oneonone = DEFAULT_ONE_ON_ONE, timeout = DEFAULT_TIMEOUT
|
|
|
3144
3185
|
argsList.append(f'--unavailable_host_expiry={unavailable_host_expiry}' if not shortend else f'-uhe={unavailable_host_expiry}')
|
|
3145
3186
|
if no_env:
|
|
3146
3187
|
argsList.append('--no_env')
|
|
3147
|
-
if env_file and env_file !=
|
|
3148
|
-
argsList.
|
|
3188
|
+
if env_file and env_file != DEFAULT_ENV_FILES:
|
|
3189
|
+
argsList.extend([f'--env_file="{ef}"' for ef in env_file] if not shortend else [f'-ef="{ef}"' for ef in env_file])
|
|
3149
3190
|
if no_history:
|
|
3150
3191
|
argsList.append('--no_history' if not shortend else '-nh')
|
|
3151
3192
|
if history_file and history_file != DEFAULT_HISTORY_FILE:
|
|
@@ -3168,7 +3209,7 @@ def getStrCommand(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAULT_ONE_O
|
|
|
3168
3209
|
skip_hosts = DEFAULT_SKIP_HOSTS, curses_min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, curses_min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,
|
|
3169
3210
|
single_window = DEFAULT_SINGLE_WINDOW,file_sync = False,error_only = DEFAULT_ERROR_ONLY, identity_file = DEFAULT_IDENTITY_FILE,
|
|
3170
3211
|
copy_id = False, unavailable_host_expiry = DEFAULT_UNAVAILABLE_HOST_EXPIRY,no_history = DEFAULT_NO_HISTORY,
|
|
3171
|
-
history_file = DEFAULT_HISTORY_FILE, env_file =
|
|
3212
|
+
history_file = DEFAULT_HISTORY_FILE, env_file = DEFAULT_ENV_FILES,
|
|
3172
3213
|
repeat = DEFAULT_REPEAT,interval = DEFAULT_INTERVAL,
|
|
3173
3214
|
shortend = False,tabSeperated = False):
|
|
3174
3215
|
_ = called
|
|
@@ -3580,7 +3621,7 @@ def generate_default_config(args):
|
|
|
3580
3621
|
'DEFAULT_NO_OUTPUT': args.no_output,
|
|
3581
3622
|
'DEFAULT_RETURN_ZERO': args.return_zero,
|
|
3582
3623
|
'DEFAULT_NO_ENV': args.no_env,
|
|
3583
|
-
'
|
|
3624
|
+
'DEFAULT_ENV_FILES': args.env_file,
|
|
3584
3625
|
'DEFAULT_NO_HISTORY': args.no_history,
|
|
3585
3626
|
'DEFAULT_HISTORY_FILE': args.history_file,
|
|
3586
3627
|
'DEFAULT_MAX_CONNECTIONS': args.max_connections if args.max_connections != 4 * os.cpu_count() else None,
|
|
@@ -3669,7 +3710,7 @@ def get_parser():
|
|
|
3669
3710
|
parser.add_argument('-Q',"-no","--no_output", action='store_true', help=f"Do not print the output. (default: {DEFAULT_NO_OUTPUT})", default=DEFAULT_NO_OUTPUT)
|
|
3670
3711
|
parser.add_argument('-Z','-rz','--return_zero', action='store_true', help=f"Return 0 even if there are errors. (default: {DEFAULT_RETURN_ZERO})", default=DEFAULT_RETURN_ZERO)
|
|
3671
3712
|
parser.add_argument('-C','--no_env', action='store_true', help=f'Do not load the command line environment variables. (default: {DEFAULT_NO_ENV})', default=DEFAULT_NO_ENV)
|
|
3672
|
-
parser.add_argument("--env_file",
|
|
3713
|
+
parser.add_argument("--env_file", action='append', help=f"The files to load the mssh file based environment variables from. Can specify multiple. Load first to last. ( Still work with --no_env ) (default: {DEFAULT_ENV_FILES})", default=DEFAULT_ENV_FILES)
|
|
3673
3714
|
parser.add_argument("-m","--max_connections", type=int, help="Max number of connections to use (default: 4 * cpu_count)", default=DEFAULT_MAX_CONNECTIONS)
|
|
3674
3715
|
parser.add_argument("-j","--json", action='store_true', help=F"Output in json format. (default: {DEFAULT_JSON_MODE})", default=DEFAULT_JSON_MODE)
|
|
3675
3716
|
parser.add_argument('-w',"--success_hosts", action='store_true', help=f"Output the hosts that succeeded in summary as well. (default: {DEFAULT_PRINT_SUCCESS_HOSTS})", default=DEFAULT_PRINT_SUCCESS_HOSTS)
|
|
@@ -3688,7 +3729,7 @@ def get_parser():
|
|
|
3688
3729
|
parser.add_argument('-hf','--history_file', type=str, help=f'The file to store the history. (default: {DEFAULT_HISTORY_FILE})', default=DEFAULT_HISTORY_FILE)
|
|
3689
3730
|
parser.add_argument('--script', action='store_true', help='Run the command in script mode, short for -SCRIPT or --no_watch --skip_unreachable --no_env --no_history --greppable --error_only')
|
|
3690
3731
|
parser.add_argument('-e','--encoding', type=str, help=f'The encoding to use for the output. (default: {DEFAULT_ENCODING})', default=DEFAULT_ENCODING)
|
|
3691
|
-
parser.add_argument('-
|
|
3732
|
+
parser.add_argument('-dt','--diff_display_threshold', type=float, help=f'The threshold of lines to display the diff when files differ. {{0-1}} Set to 0 to always display the diff. Set to 1 to disable diff. (Only merge same) (default: {DEFAULT_DIFF_DISPLAY_THRESHOLD})', default=DEFAULT_DIFF_DISPLAY_THRESHOLD)
|
|
3692
3733
|
parser.add_argument('--force_truecolor', action='store_true', help=f'Force truecolor output even when not in a truecolor terminal. (default: {FORCE_TRUECOLOR})', default=FORCE_TRUECOLOR)
|
|
3693
3734
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
3694
3735
|
return parser
|
|
@@ -3784,7 +3825,7 @@ def process_keys(args):
|
|
|
3784
3825
|
def set_global_with_args(args):
|
|
3785
3826
|
global _emo
|
|
3786
3827
|
global __ipmiiInterfaceIPPrefix
|
|
3787
|
-
global
|
|
3828
|
+
global _env_files
|
|
3788
3829
|
global __DEBUG_MODE
|
|
3789
3830
|
global __configs_from_file
|
|
3790
3831
|
global _encoding
|
|
@@ -3795,7 +3836,7 @@ def set_global_with_args(args):
|
|
|
3795
3836
|
global FORCE_TRUECOLOR
|
|
3796
3837
|
_emo = False
|
|
3797
3838
|
__ipmiiInterfaceIPPrefix = args.ipmi_interface_ip_prefix
|
|
3798
|
-
|
|
3839
|
+
_env_files = args.env_file
|
|
3799
3840
|
__DEBUG_MODE = args.debug
|
|
3800
3841
|
_encoding = args.encoding
|
|
3801
3842
|
if args.return_zero:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|