multiSSH3 6.40__tar.gz → 6.41__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.
Files changed (112) hide show
  1. {multissh3-6.40/multiSSH3.egg-info → multissh3-6.41}/PKG-INFO +31 -3
  2. {multissh3-6.40 → multissh3-6.41}/README.md +30 -2
  3. {multissh3-6.40 → multissh3-6.41}/mssh_complete.py +776 -83
  4. {multissh3-6.40 → multissh3-6.41/multiSSH3.egg-info}/PKG-INFO +31 -3
  5. {multissh3-6.40 → multissh3-6.41}/multiSSH3.py +2 -2
  6. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_blesh.py +4 -3
  7. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_engine.py +36 -1
  8. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_shells.py +824 -55
  9. {multissh3-6.40 → multissh3-6.41}/LICENSE +0 -0
  10. {multissh3-6.40 → multissh3-6.41}/MANIFEST.in +0 -0
  11. {multissh3-6.40 → multissh3-6.41}/multiSSH3.egg-info/SOURCES.txt +0 -0
  12. {multissh3-6.40 → multissh3-6.41}/multiSSH3.egg-info/dependency_links.txt +0 -0
  13. {multissh3-6.40 → multissh3-6.41}/multiSSH3.egg-info/entry_points.txt +0 -0
  14. {multissh3-6.40 → multissh3-6.41}/multiSSH3.egg-info/top_level.txt +0 -0
  15. {multissh3-6.40 → multissh3-6.41}/pytest.ini +0 -0
  16. {multissh3-6.40 → multissh3-6.41}/setup.cfg +0 -0
  17. {multissh3-6.40 → multissh3-6.41}/setup.py +0 -0
  18. {multissh3-6.40 → multissh3-6.41}/tests/bench_complete.py +0 -0
  19. {multissh3-6.40 → multissh3-6.41}/tests/complete_shell_harness.py +0 -0
  20. {multissh3-6.40 → multissh3-6.41}/tests/conftest.py +0 -0
  21. {multissh3-6.40 → multissh3-6.41}/tests/data/run_command_golden.json +0 -0
  22. {multissh3-6.40 → multissh3-6.41}/tests/replay.py +0 -0
  23. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.bash5.1 +0 -0
  24. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.bash5.3 +0 -0
  25. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.centos7 +0 -0
  26. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.current +0 -0
  27. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.el9 +0 -0
  28. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.legacy +0 -0
  29. {multissh3-6.40 → multissh3-6.41}/tests/shells/Dockerfile.modern +0 -0
  30. {multissh3-6.40 → multissh3-6.41}/tests/shells/run_matrix.sh +0 -0
  31. {multissh3-6.40 → multissh3-6.41}/tests/test_cli_branches.py +0 -0
  32. {multissh3-6.40 → multissh3-6.41}/tests/test_cli_option_words.py +0 -0
  33. {multissh3-6.40 → multissh3-6.41}/tests/test_cli_processing.py +0 -0
  34. {multissh3-6.40 → multissh3-6.41}/tests/test_closed_output.py +0 -0
  35. {multissh3-6.40 → multissh3-6.41}/tests/test_color_helpers.py +0 -0
  36. {multissh3-6.40 → multissh3-6.41}/tests/test_command_terminal.py +0 -0
  37. {multissh3-6.40 → multissh3-6.41}/tests/test_compact_hostnames.py +0 -0
  38. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_cli.py +0 -0
  39. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_context.py +0 -0
  40. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_diagnose.py +0 -0
  41. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_editor.py +0 -0
  42. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_eval.py +0 -0
  43. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_hosts.py +0 -0
  44. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_hoststate.py +0 -0
  45. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_index.py +0 -0
  46. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_keys.py +0 -0
  47. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_lex.py +0 -0
  48. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_mssh_helpers.py +0 -0
  49. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_password_spellings.py +0 -0
  50. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_picker.py +0 -0
  51. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_probe.py +0 -0
  52. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_start.py +0 -0
  53. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_store.py +0 -0
  54. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_suggest.py +0 -0
  55. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_terminal.py +0 -0
  56. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_untrusted.py +0 -0
  57. {multissh3-6.40 → multissh3-6.41}/tests/test_complete_zsh_options.py +0 -0
  58. {multissh3-6.40 → multissh3-6.41}/tests/test_config_cli.py +0 -0
  59. {multissh3-6.40 → multissh3-6.41}/tests/test_config_validate.py +0 -0
  60. {multissh3-6.40 → multissh3-6.41}/tests/test_config_values.py +0 -0
  61. {multissh3-6.40 → multissh3-6.41}/tests/test_control_master_config.py +0 -0
  62. {multissh3-6.40 → multissh3-6.41}/tests/test_copy_id.py +0 -0
  63. {multissh3-6.40 → multissh3-6.41}/tests/test_curses_branches.py +0 -0
  64. {multissh3-6.40 → multissh3-6.41}/tests/test_curses_event_loop.py +0 -0
  65. {multissh3-6.40 → multissh3-6.41}/tests/test_curses_tui.py +0 -0
  66. {multissh3-6.40 → multissh3-6.41}/tests/test_curses_wide_chars.py +0 -0
  67. {multissh3-6.40 → multissh3-6.41}/tests/test_expand_hostnames.py +0 -0
  68. {multissh3-6.40 → multissh3-6.41}/tests/test_file_trust.py +0 -0
  69. {multissh3-6.40 → multissh3-6.41}/tests/test_harness_smoke.py +0 -0
  70. {multissh3-6.40 → multissh3-6.41}/tests/test_helpers_extra.py +0 -0
  71. {multissh3-6.40 → multissh3-6.41}/tests/test_history_replay.py +0 -0
  72. {multissh3-6.40 → multissh3-6.41}/tests/test_host.py +0 -0
  73. {multissh3-6.40 → multissh3-6.41}/tests/test_host_validity.py +0 -0
  74. {multissh3-6.40 → multissh3-6.41}/tests/test_hosts_file.py +0 -0
  75. {multissh3-6.40 → multissh3-6.41}/tests/test_identities_only.py +0 -0
  76. {multissh3-6.40 → multissh3-6.41}/tests/test_ipmi_lockout_wait.py +0 -0
  77. {multissh3-6.40 → multissh3-6.41}/tests/test_ipmi_logins.py +0 -0
  78. {multissh3-6.40 → multissh3-6.41}/tests/test_key_path_home.py +0 -0
  79. {multissh3-6.40 → multissh3-6.41}/tests/test_login_drop_retry.py +0 -0
  80. {multissh3-6.40 → multissh3-6.41}/tests/test_main_lifecycle.py +0 -0
  81. {multissh3-6.40 → multissh3-6.41}/tests/test_narrow_terminal.py +0 -0
  82. {multissh3-6.40 → multissh3-6.41}/tests/test_no_hosts.py +0 -0
  83. {multissh3-6.40 → multissh3-6.41}/tests/test_option_no_effect.py +0 -0
  84. {multissh3-6.40 → multissh3-6.41}/tests/test_options_after_command.py +0 -0
  85. {multissh3-6.40 → multissh3-6.41}/tests/test_output_merge.py +0 -0
  86. {multissh3-6.40 → multissh3-6.41}/tests/test_output_robustness.py +0 -0
  87. {multissh3-6.40 → multissh3-6.41}/tests/test_packaging.py +0 -0
  88. {multissh3-6.40 → multissh3-6.41}/tests/test_password_handling.py +0 -0
  89. {multissh3-6.40 → multissh3-6.41}/tests/test_passwords_out_of_ps.py +0 -0
  90. {multissh3-6.40 → multissh3-6.41}/tests/test_run_command.py +0 -0
  91. {multissh3-6.40 → multissh3-6.41}/tests/test_run_command_branches.py +0 -0
  92. {multissh3-6.40 → multissh3-6.41}/tests/test_run_command_characterization.py +0 -0
  93. {multissh3-6.40 → multissh3-6.41}/tests/test_run_command_exit.py +0 -0
  94. {multissh3-6.40 → multissh3-6.41}/tests/test_run_on_hosts.py +0 -0
  95. {multissh3-6.40 → multissh3-6.41}/tests/test_run_on_hosts_modes.py +0 -0
  96. {multissh3-6.40 → multissh3-6.41}/tests/test_runtime_branch_matrix.py +0 -0
  97. {multissh3-6.40 → multissh3-6.41}/tests/test_runtime_dir.py +0 -0
  98. {multissh3-6.40 → multissh3-6.41}/tests/test_shared_lines.py +0 -0
  99. {multissh3-6.40 → multissh3-6.41}/tests/test_smoke_optional.py +0 -0
  100. {multissh3-6.40 → multissh3-6.41}/tests/test_ssh_error_text.py +0 -0
  101. {multissh3-6.40 → multissh3-6.41}/tests/test_ssh_timeouts.py +0 -0
  102. {multissh3-6.40 → multissh3-6.41}/tests/test_sshpass_exit.py +0 -0
  103. {multissh3-6.40 → multissh3-6.41}/tests/test_start_run_on_hosts.py +0 -0
  104. {multissh3-6.40 → multissh3-6.41}/tests/test_stopped_commands.py +0 -0
  105. {multissh3-6.40 → multissh3-6.41}/tests/test_stream_reader.py +0 -0
  106. {multissh3-6.40 → multissh3-6.41}/tests/test_streamed_output.py +0 -0
  107. {multissh3-6.40 → multissh3-6.41}/tests/test_tui_ssh_notices.py +0 -0
  108. {multissh3-6.40 → multissh3-6.41}/tests/test_unavailable_hosts.py +0 -0
  109. {multissh3-6.40 → multissh3-6.41}/tests/test_underscore_settings.py +0 -0
  110. {multissh3-6.40 → multissh3-6.41}/tests/test_waiting.py +0 -0
  111. {multissh3-6.40 → multissh3-6.41}/tests/test_wide_wrap.py +0 -0
  112. {multissh3-6.40 → multissh3-6.41}/tests/vt_grid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 6.40
3
+ Version: 6.41
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -500,7 +500,7 @@ A new block is appended to your rc file; changing or removing it replaces the fi
500
500
 
501
501
  - **Hosts:** host strings you typed before, env file aliases, `/etc/hosts`, ssh config `Host` entries, `known_hosts` and the ARP table. Large clusters are offered as ranges (`node[0001-2000]`) until what you typed narrows them down. Hosts mssh recently found unreachable come last (with the user this line gives them: `root@node05` and `node05` are told apart).
502
502
  - **Options and values:** option names, and values you used before (`-u`, `-t`, ...). Paths complete as files. Passwords are never completed or shown, the ones in your config file included.
503
- - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name holding whitespace or a shell character goes in quoted for this shell, which is wrong for the remote command (`my\ file` runs `my file` as a command of its own, `a\&b` makes the remote shell run `b`), and TAB inside a quoted argument (`mssh grp cat 'my f<TAB>`) offers nothing useful; both are to be fixed after 6.40 ([limitations](docs/limitations.md#local-file-names-with-whitespace-or-shell-characters-are-wrong-in-a-remote-command), [limitations](docs/limitations.md#inside-a-quoted-argument-the-engine-completes-a-quoted-command)).
503
+ - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name goes in quoted for the remote shell and then for this one (`a&b` goes in as `a\\\&b`: mssh gets `a\&b`, which the remote shell reads as `a&b`), and one holding whitespace is not offered there (`my file` would be run as a command of its own); a `~/`, `$HOME/` or `$USER/` you typed in the word stays as typed, for whichever shell expands it as typed (this one, or in a quoted command the shell the quote leaves it to). In a command typed in quotes, which mssh hands on whole, the names go in the same way inside your quote, one holding whitespace too (`mssh grp 'cat my<TAB>` gives `'cat my\ file'`). Inside a quoted argument (`mssh grp cat 'zz<TAB>`), which mssh joins with the command's other words, the command's own completion and the local file names go in for your quote, quoted for the remote shell, none holding whitespace, in every shell; once the argument holds whitespace (`mssh grp cat 'my f<TAB>`) TAB offers nothing useful, so type the whole command in quotes instead (`mssh grp 'cat my f<TAB>`) ([limitations](docs/limitations.md#inside-a-quoted-argument-names-holding-whitespace-are-not-offered)).
504
504
 
505
505
  Ranking uses the rest of the line on both sides of the cursor: after `mssh gpu[01-16] "` the commands you run on those hosts come first, and in the hosts of `mssh ▮ "systemctl restart slurmd"` the hosts you restarted slurmd on come first.
506
506
 
@@ -984,7 +984,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
984
984
 
985
985
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
986
986
 
987
- Below is a sample help message output from multiSSH3 6.40 @ 2026-11-01 (no config file, 120 columns; the lines after the options list the programs found on that machine)
987
+ Below is a sample help message output from multiSSH3 6.41 @ 2026-11-03 (no config file, 120 columns; the lines after the options list the programs found on that machine)
988
988
 
989
989
  ```bash
990
990
  $ mssh -h
@@ -1398,6 +1398,34 @@ __DEBUG_MODE = False
1398
1398
 
1399
1399
  > `__DEBUG_MODE` is used to control whether to print host thread execution debug information to its stderr.
1400
1400
 
1401
+ ## Upgrading to 6.41 from 6.40
1402
+
1403
+ Run `mssh --completion install` again (bash, ble.sh, zsh and fish) to get the new glue. What you may notice:
1404
+
1405
+ - Fix (bash, ble.sh, zsh, fish): a local file name completed for a remote command typed as words goes in quoted for the remote shell and then for this one. `mssh h cat a<TAB>` with a local file `a&b` now inserts `a\\\&b` (ble.sh may write `'a\&b'`), so mssh gets `a\&b` and the remote shell runs `cat` on `a&b`; 6.40 inserted `a\&b`, so the remote shell got `cat a&b` and ran `b` on every host. A name starting with `~` gets its backslash for the remote shell too: a local directory named `~` goes in as `\\~/` (6.40 gave `\~/`, and the remote shell read its home). A directory so named still gets its `/` and no space, and the names inside it complete the same way; a directory you typed is written again as the remote shell's word (`mssh h cat a\&dir/f<TAB>`, which mssh would pass on as `a&dir/f`, gives `a\\\&dir/file`). Names holding a `$` or a backtick (`Outer$Inner.class`) go in quoted so this shell does not expand them.
1406
+ - Change (bash, ble.sh): what you typed with an expansion in it stays as typed, up to the last directory holding one: `~/`, `~user/`, `$HOME/`, `${VAR}/`, `$PWD/scratch/$USER/`, so the shell that would expand it as typed still does (this one for a command typed as words, as `ssh h cat ~/x` does). In 6.40, readline with bash 4.2 to 5.1 and ble.sh put a backslash before such a typed `$HOME` or `$USER`, leaving it to the remote shell. Where that part holds a character the remote shell reads, typed for this shell alone (`a\&b/$X/`, `x\(1\)/$X/`), nothing is offered. ble.sh may write that part again as its value when that is shorter (`$HOME/zz<TAB>` can become `/home/you/zzfile`). A `~bi<TAB>` being typed gives `~bin/`, and `$HOME<TAB>` gives `$HOME/`, under ble.sh too; where other variables start with the name typed (`$ZZW` beside `$ZZWX`), TAB lists them, as bash does. A word holding a glob (`zz*b<TAB>`) gets nothing under readline: bash's own completion put the names it matches in quoted for this shell alone. The names a remote command's completion gives with `-o noquote` go in as readline puts them in for the command run here (not quoted for the remote shell); ble.sh, which inserts them as they are, gets them quoted for both shells.
1407
+ - Fix (bash, ble.sh, zsh, fish): a local file name holding whitespace is no longer offered for a remote command typed as words: `mssh h cat my\ file` made mssh run `my file` as a command of its own. Whitespace is what mssh splits words on, a no-break space too. Type such a command in quotes. Where nothing else is left, TAB offers nothing, not the local file names; a command's completion that answers with nothing (git's and grub's empty word) gets nothing under ble.sh too, as under readline.
1408
+ - Fix (bash, ble.sh, fish; zsh did so already): in a remote command typed in quotes, which mssh hands on whole, a local file name goes in quoted for the remote shell inside your quote: `mssh h 'cat a<TAB>` gives `'cat a\&b'` and `mssh h "cat a<TAB>` gives `"cat a\\&b"`, so the remote shell runs `cat` on `a&b` (6.40 gave `'cat a&b'`, and the remote shell ran `b`); a name holding whitespace goes in too (`'cat my\ file'`). In `$'...'` a name goes in with its backslashes and quotes escaped for it. The command typed before the word stays as typed, and so does a `$PWD/` or `~/` at the start of the word (`mssh h "ls $PWD/zzd<TAB>` gives `"ls $PWD/zzdir"`); a directory typed in the word is written as the remote shell's word (`mssh h "cat a&b/zz<TAB>` gives `"cat a\\&b/zzfile"`), and a word holding the remote shell's quoting completes (`mssh h "cat zz\\&a<TAB>`, as TAB itself writes it), with bash-completion loaded too; where the command's own completion offers nothing, the local file names come, as for a command typed as words. The command's first word goes in the same way (`mssh h "./my<TAB>` gives `"./my\\ script"`; 6.40 and the earlier 6.41 rounds wrote it as for a command typed as words); in a later part of the command (after a `;`) it comes from any local file, not only programs, and under ble.sh a first word naming a directory whose name changes for the remote shell gets no `/` (`mssh h "./zzd<TAB>` gives `"./zzd\\&x" `).
1409
+ - With ble.sh on bash before 4.3, the names inside a directory whose name needs quoting for the remote shell (`mssh h cat a\\\&dir/<TAB>`) are not offered: ble.sh there quoted the typed part again ([limitations](docs/limitations.md#blesh-before-bash-43-names-inside-a-directory-that-changes-are-left-out)). ble.sh's own file names, quoted for this shell alone, are no longer offered for a remote command's word: `mssh h rm sub/zzb<TAB>` put in `sub/zzb{a,b}`, which this shell expanded to two names.
1410
+ - In zsh, a name typed with this shell's quoting alone (`mssh h cat a\&<TAB>`, which mssh would pass on as `a&`) no longer completes, nor one inside a directory typed bare in a quoted command (`mssh h "cat a&b/zz<TAB>`); bash replaces such a directory with the remote shell's word ([limitations](docs/limitations.md#zsh-a-directory-the-remote-shell-would-read-otherwise-gets-no-names)).
1411
+ - Fix (ble.sh): a word that is only an unset variable (`mssh h rm $ZZNONE<TAB>`) gets nothing; ble.sh's own default put in the names the empty value matches, `rm \*` in an empty directory, so every host ran `rm *` (6.40 did the same).
1412
+ - Fix (bash, ble.sh, zsh, fish): in a quoted argument of a command typed as words (`mssh h cat "zz<TAB>`), which mssh joins with the command's other words, the names come from the command's own completion, written for your quote and quoted for the remote shell, a word holding whitespace is left out, and history offers no whole command there: `"zz\\ a"` made mssh run `cat` and `zz\ a` as commands of their own, and `mssh h sudo 'sys<TAB>` gave `sudo 'systemctl restart nginx'`.
1413
+ - Fix (ble.sh with bash-completion 2.12 or later): the names for the command after `sudo`, `nohup` or `time` complete as for the command itself (`mssh h sudo cat zza<TAB>` gave `zzamp\`, and `mssh h "sudo cat zza<TAB>` put `"zzamp&b"` in place of the whole command): ble.sh's bash-completion integration took them for its own word.
1414
+ - Known limits of the names offered ([limitations](docs/limitations.md#found-in-641s-review)):
1415
+ - ble.sh: a name holding a brace (`a{b,c}`) is not offered for a remote command typed as words; type the command in quotes ([limitations](docs/limitations.md#blesh-a-name-holding-a-brace-is-not-offered-for-a-remote-command-typed-as-words)).
1416
+ - readline and ble.sh: inside a directory whose name holds a `"`, `'` or `\`, nothing is offered ([limitations](docs/limitations.md#readline-and-blesh-no-names-inside-a-directory-whose-name-holds-a-quote-or-a-backslash)).
1417
+ - ble.sh: in a quoted command, a word with a `$` expansion partway through (`mssh h "cat Outer$Inn<TAB>`) gets no file names ([limitations](docs/limitations.md#blesh-an-expansion-partway-through-a-quoted-commands-word-gets-no-file-names)).
1418
+ - readline: a word holding a glob (`zz*b<TAB>`) gets nothing ([limitations](docs/limitations.md#readline-tab-on-a-glob-offers-nothing)).
1419
+ - readline: in a double-quoted command or argument a name holding `!` is not offered; use single quotes, or `set +H` and type it ([limitations](docs/limitations.md#readline-names-holding-an-exclamation-mark-are-not-offered-in-a-double-quoted-command)).
1420
+ - readline: a word with its own open quote inside a quoted command (`mssh h "cat 'zz<TAB>`) gets nothing ([limitations](docs/limitations.md#readline-a-quote-opened-inside-a-quoted-command-gets-no-names)).
1421
+ - readline and ble.sh: `mssh h ls ${VAR:-x}/zz<TAB>` writes a broken word (`${VAR:-x\\\}/...`), as 6.40 did ([limitations](docs/limitations.md#bash-a-default-value-expansion-in-a-remote-word-is-written-broken)).
1422
+ - In a quoted command, a word holding a `;`, `|` or `(` quoted for the remote shell (`mssh h "ls zz\\;<TAB>`) gets nothing more ([limitations](docs/limitations.md#a-name-holding---or--in-a-quoted-command-cannot-be-continued)).
1423
+ - Whitespace in names is read as UTF-8: in a Latin-1 or GBK locale (untested) a name may be offered or left out wrongly ([limitations](docs/limitations.md#whitespace-in-names-is-read-as-utf-8)).
1424
+ - ble.sh: in a quoted word, letters typed out of order find nothing; only names that begin with what was typed are offered ([limitations](docs/limitations.md#blesh-letters-typed-out-of-order-in-a-quoted-word-find-nothing)).
1425
+ - bash: a directory completed in a quoted command closes the quote (`mssh h "ls zzd<TAB>` gives `"ls zzdir" `), as in 6.40, and so does one in a quoted argument whose name changes for the remote shell (`ls "zzd\\&x" `); to be fixed after 6.41 ([limitations](docs/limitations.md#a-directory-completed-in-a-quoted-command-or-argument-closes-the-quote)).
1426
+ - When PATH finds an mssh-complete older than the glue (a system install beside a newer venv, `sudo -i`), quoted arguments complete as in 6.40; install the same version ([limitations](docs/limitations.md#with-an-engine-older-than-the-glue-quoted-arguments-behave-as-in-640)).
1427
+ - zsh: `mssh h "ls $PWD/zzd<TAB>` rewrites the line to `ls\ /the/directory/zzd` and completes nothing, as in 6.40; to be fixed after 6.41 ([limitations](docs/limitations.md#zsh-a-variable-in-a-double-quoted-command-is-expanded-and-nothing-completes)).
1428
+
1401
1429
  ## Upgrading to 6.40 from 6.39
1402
1430
 
1403
1431
  Fixes for the six items deferred from 6.39 (bash and ble.sh completion, `--store_config_file` under sudo) and for three found beside them. Run `mssh --completion install` again in bash, with or without ble.sh, to get the new glue: the completion fixes below are in it (zsh's and fish's glues did not change). What you may notice:
@@ -474,7 +474,7 @@ A new block is appended to your rc file; changing or removing it replaces the fi
474
474
 
475
475
  - **Hosts:** host strings you typed before, env file aliases, `/etc/hosts`, ssh config `Host` entries, `known_hosts` and the ARP table. Large clusters are offered as ranges (`node[0001-2000]`) until what you typed narrows them down. Hosts mssh recently found unreachable come last (with the user this line gives them: `root@node05` and `node05` are told apart).
476
476
  - **Options and values:** option names, and values you used before (`-u`, `-t`, ...). Paths complete as files. Passwords are never completed or shown, the ones in your config file included.
477
- - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name holding whitespace or a shell character goes in quoted for this shell, which is wrong for the remote command (`my\ file` runs `my file` as a command of its own, `a\&b` makes the remote shell run `b`), and TAB inside a quoted argument (`mssh grp cat 'my f<TAB>`) offers nothing useful; both are to be fixed after 6.40 ([limitations](docs/limitations.md#local-file-names-with-whitespace-or-shell-characters-are-wrong-in-a-remote-command), [limitations](docs/limitations.md#inside-a-quoted-argument-the-engine-completes-a-quoted-command)).
477
+ - **Commands:** commands from your mssh history, the rest of a command from this machine's own completion (`mssh grp systemctl rest<TAB>`; in bash as bash-completion completes `sudo CMD`, with a few gaps in [limitations](docs/limitations.md#autocomplete)), and the magic strings (`#HOST#`, ...). A command typed as separate words gets the next words of commands you ran, with the quotes they ran with (`'ssh*'` comes as `\'ssh\*\'`, so the remote shell gets the same command), never a word holding a space: mssh runs each word as its own command when one of them holds a space, so type such a command in quotes (`mssh grp "grep 'connection refused' /var/log/syslog"`). A local file name goes in quoted for the remote shell and then for this one (`a&b` goes in as `a\\\&b`: mssh gets `a\&b`, which the remote shell reads as `a&b`), and one holding whitespace is not offered there (`my file` would be run as a command of its own); a `~/`, `$HOME/` or `$USER/` you typed in the word stays as typed, for whichever shell expands it as typed (this one, or in a quoted command the shell the quote leaves it to). In a command typed in quotes, which mssh hands on whole, the names go in the same way inside your quote, one holding whitespace too (`mssh grp 'cat my<TAB>` gives `'cat my\ file'`). Inside a quoted argument (`mssh grp cat 'zz<TAB>`), which mssh joins with the command's other words, the command's own completion and the local file names go in for your quote, quoted for the remote shell, none holding whitespace, in every shell; once the argument holds whitespace (`mssh grp cat 'my f<TAB>`) TAB offers nothing useful, so type the whole command in quotes instead (`mssh grp 'cat my f<TAB>`) ([limitations](docs/limitations.md#inside-a-quoted-argument-names-holding-whitespace-are-not-offered)).
478
478
 
479
479
  Ranking uses the rest of the line on both sides of the cursor: after `mssh gpu[01-16] "` the commands you run on those hosts come first, and in the hosts of `mssh ▮ "systemctl restart slurmd"` the hosts you restarted slurmd on come first.
480
480
 
@@ -958,7 +958,7 @@ Option names count in full only: `--user` is not `--username` and `-c` is not `-
958
958
 
959
959
  Putting the command after `--` is no substitute for quotes: every word after the hosts is a command of its own, so `mssh web1 -- bash -c 'echo hi'` runs `bash`, `-c` and `echo hi` one after another (when each word is a single word, as in `mssh web1 -- ls -la`, mssh asks whether to join them).
960
960
 
961
- Below is a sample help message output from multiSSH3 6.40 @ 2026-11-01 (no config file, 120 columns; the lines after the options list the programs found on that machine)
961
+ Below is a sample help message output from multiSSH3 6.41 @ 2026-11-03 (no config file, 120 columns; the lines after the options list the programs found on that machine)
962
962
 
963
963
  ```bash
964
964
  $ mssh -h
@@ -1372,6 +1372,34 @@ __DEBUG_MODE = False
1372
1372
 
1373
1373
  > `__DEBUG_MODE` is used to control whether to print host thread execution debug information to its stderr.
1374
1374
 
1375
+ ## Upgrading to 6.41 from 6.40
1376
+
1377
+ Run `mssh --completion install` again (bash, ble.sh, zsh and fish) to get the new glue. What you may notice:
1378
+
1379
+ - Fix (bash, ble.sh, zsh, fish): a local file name completed for a remote command typed as words goes in quoted for the remote shell and then for this one. `mssh h cat a<TAB>` with a local file `a&b` now inserts `a\\\&b` (ble.sh may write `'a\&b'`), so mssh gets `a\&b` and the remote shell runs `cat` on `a&b`; 6.40 inserted `a\&b`, so the remote shell got `cat a&b` and ran `b` on every host. A name starting with `~` gets its backslash for the remote shell too: a local directory named `~` goes in as `\\~/` (6.40 gave `\~/`, and the remote shell read its home). A directory so named still gets its `/` and no space, and the names inside it complete the same way; a directory you typed is written again as the remote shell's word (`mssh h cat a\&dir/f<TAB>`, which mssh would pass on as `a&dir/f`, gives `a\\\&dir/file`). Names holding a `$` or a backtick (`Outer$Inner.class`) go in quoted so this shell does not expand them.
1380
+ - Change (bash, ble.sh): what you typed with an expansion in it stays as typed, up to the last directory holding one: `~/`, `~user/`, `$HOME/`, `${VAR}/`, `$PWD/scratch/$USER/`, so the shell that would expand it as typed still does (this one for a command typed as words, as `ssh h cat ~/x` does). In 6.40, readline with bash 4.2 to 5.1 and ble.sh put a backslash before such a typed `$HOME` or `$USER`, leaving it to the remote shell. Where that part holds a character the remote shell reads, typed for this shell alone (`a\&b/$X/`, `x\(1\)/$X/`), nothing is offered. ble.sh may write that part again as its value when that is shorter (`$HOME/zz<TAB>` can become `/home/you/zzfile`). A `~bi<TAB>` being typed gives `~bin/`, and `$HOME<TAB>` gives `$HOME/`, under ble.sh too; where other variables start with the name typed (`$ZZW` beside `$ZZWX`), TAB lists them, as bash does. A word holding a glob (`zz*b<TAB>`) gets nothing under readline: bash's own completion put the names it matches in quoted for this shell alone. The names a remote command's completion gives with `-o noquote` go in as readline puts them in for the command run here (not quoted for the remote shell); ble.sh, which inserts them as they are, gets them quoted for both shells.
1381
+ - Fix (bash, ble.sh, zsh, fish): a local file name holding whitespace is no longer offered for a remote command typed as words: `mssh h cat my\ file` made mssh run `my file` as a command of its own. Whitespace is what mssh splits words on, a no-break space too. Type such a command in quotes. Where nothing else is left, TAB offers nothing, not the local file names; a command's completion that answers with nothing (git's and grub's empty word) gets nothing under ble.sh too, as under readline.
1382
+ - Fix (bash, ble.sh, fish; zsh did so already): in a remote command typed in quotes, which mssh hands on whole, a local file name goes in quoted for the remote shell inside your quote: `mssh h 'cat a<TAB>` gives `'cat a\&b'` and `mssh h "cat a<TAB>` gives `"cat a\\&b"`, so the remote shell runs `cat` on `a&b` (6.40 gave `'cat a&b'`, and the remote shell ran `b`); a name holding whitespace goes in too (`'cat my\ file'`). In `$'...'` a name goes in with its backslashes and quotes escaped for it. The command typed before the word stays as typed, and so does a `$PWD/` or `~/` at the start of the word (`mssh h "ls $PWD/zzd<TAB>` gives `"ls $PWD/zzdir"`); a directory typed in the word is written as the remote shell's word (`mssh h "cat a&b/zz<TAB>` gives `"cat a\\&b/zzfile"`), and a word holding the remote shell's quoting completes (`mssh h "cat zz\\&a<TAB>`, as TAB itself writes it), with bash-completion loaded too; where the command's own completion offers nothing, the local file names come, as for a command typed as words. The command's first word goes in the same way (`mssh h "./my<TAB>` gives `"./my\\ script"`; 6.40 and the earlier 6.41 rounds wrote it as for a command typed as words); in a later part of the command (after a `;`) it comes from any local file, not only programs, and under ble.sh a first word naming a directory whose name changes for the remote shell gets no `/` (`mssh h "./zzd<TAB>` gives `"./zzd\\&x" `).
1383
+ - With ble.sh on bash before 4.3, the names inside a directory whose name needs quoting for the remote shell (`mssh h cat a\\\&dir/<TAB>`) are not offered: ble.sh there quoted the typed part again ([limitations](docs/limitations.md#blesh-before-bash-43-names-inside-a-directory-that-changes-are-left-out)). ble.sh's own file names, quoted for this shell alone, are no longer offered for a remote command's word: `mssh h rm sub/zzb<TAB>` put in `sub/zzb{a,b}`, which this shell expanded to two names.
1384
+ - In zsh, a name typed with this shell's quoting alone (`mssh h cat a\&<TAB>`, which mssh would pass on as `a&`) no longer completes, nor one inside a directory typed bare in a quoted command (`mssh h "cat a&b/zz<TAB>`); bash replaces such a directory with the remote shell's word ([limitations](docs/limitations.md#zsh-a-directory-the-remote-shell-would-read-otherwise-gets-no-names)).
1385
+ - Fix (ble.sh): a word that is only an unset variable (`mssh h rm $ZZNONE<TAB>`) gets nothing; ble.sh's own default put in the names the empty value matches, `rm \*` in an empty directory, so every host ran `rm *` (6.40 did the same).
1386
+ - Fix (bash, ble.sh, zsh, fish): in a quoted argument of a command typed as words (`mssh h cat "zz<TAB>`), which mssh joins with the command's other words, the names come from the command's own completion, written for your quote and quoted for the remote shell, a word holding whitespace is left out, and history offers no whole command there: `"zz\\ a"` made mssh run `cat` and `zz\ a` as commands of their own, and `mssh h sudo 'sys<TAB>` gave `sudo 'systemctl restart nginx'`.
1387
+ - Fix (ble.sh with bash-completion 2.12 or later): the names for the command after `sudo`, `nohup` or `time` complete as for the command itself (`mssh h sudo cat zza<TAB>` gave `zzamp\`, and `mssh h "sudo cat zza<TAB>` put `"zzamp&b"` in place of the whole command): ble.sh's bash-completion integration took them for its own word.
1388
+ - Known limits of the names offered ([limitations](docs/limitations.md#found-in-641s-review)):
1389
+ - ble.sh: a name holding a brace (`a{b,c}`) is not offered for a remote command typed as words; type the command in quotes ([limitations](docs/limitations.md#blesh-a-name-holding-a-brace-is-not-offered-for-a-remote-command-typed-as-words)).
1390
+ - readline and ble.sh: inside a directory whose name holds a `"`, `'` or `\`, nothing is offered ([limitations](docs/limitations.md#readline-and-blesh-no-names-inside-a-directory-whose-name-holds-a-quote-or-a-backslash)).
1391
+ - ble.sh: in a quoted command, a word with a `$` expansion partway through (`mssh h "cat Outer$Inn<TAB>`) gets no file names ([limitations](docs/limitations.md#blesh-an-expansion-partway-through-a-quoted-commands-word-gets-no-file-names)).
1392
+ - readline: a word holding a glob (`zz*b<TAB>`) gets nothing ([limitations](docs/limitations.md#readline-tab-on-a-glob-offers-nothing)).
1393
+ - readline: in a double-quoted command or argument a name holding `!` is not offered; use single quotes, or `set +H` and type it ([limitations](docs/limitations.md#readline-names-holding-an-exclamation-mark-are-not-offered-in-a-double-quoted-command)).
1394
+ - readline: a word with its own open quote inside a quoted command (`mssh h "cat 'zz<TAB>`) gets nothing ([limitations](docs/limitations.md#readline-a-quote-opened-inside-a-quoted-command-gets-no-names)).
1395
+ - readline and ble.sh: `mssh h ls ${VAR:-x}/zz<TAB>` writes a broken word (`${VAR:-x\\\}/...`), as 6.40 did ([limitations](docs/limitations.md#bash-a-default-value-expansion-in-a-remote-word-is-written-broken)).
1396
+ - In a quoted command, a word holding a `;`, `|` or `(` quoted for the remote shell (`mssh h "ls zz\\;<TAB>`) gets nothing more ([limitations](docs/limitations.md#a-name-holding---or--in-a-quoted-command-cannot-be-continued)).
1397
+ - Whitespace in names is read as UTF-8: in a Latin-1 or GBK locale (untested) a name may be offered or left out wrongly ([limitations](docs/limitations.md#whitespace-in-names-is-read-as-utf-8)).
1398
+ - ble.sh: in a quoted word, letters typed out of order find nothing; only names that begin with what was typed are offered ([limitations](docs/limitations.md#blesh-letters-typed-out-of-order-in-a-quoted-word-find-nothing)).
1399
+ - bash: a directory completed in a quoted command closes the quote (`mssh h "ls zzd<TAB>` gives `"ls zzdir" `), as in 6.40, and so does one in a quoted argument whose name changes for the remote shell (`ls "zzd\\&x" `); to be fixed after 6.41 ([limitations](docs/limitations.md#a-directory-completed-in-a-quoted-command-or-argument-closes-the-quote)).
1400
+ - When PATH finds an mssh-complete older than the glue (a system install beside a newer venv, `sudo -i`), quoted arguments complete as in 6.40; install the same version ([limitations](docs/limitations.md#with-an-engine-older-than-the-glue-quoted-arguments-behave-as-in-640)).
1401
+ - zsh: `mssh h "ls $PWD/zzd<TAB>` rewrites the line to `ls\ /the/directory/zzd` and completes nothing, as in 6.40; to be fixed after 6.41 ([limitations](docs/limitations.md#zsh-a-variable-in-a-double-quoted-command-is-expanded-and-nothing-completes)).
1402
+
1375
1403
  ## Upgrading to 6.40 from 6.39
1376
1404
 
1377
1405
  Fixes for the six items deferred from 6.39 (bash and ble.sh completion, `--store_config_file` under sudo) and for three found beside them. Run `mssh --completion install` again in bash, with or without ble.sh, to get the new glue: the completion fixes below are in it (zsh's and fish's glues did not change). What you may notice: