15 Commits
v0.5 ... v0.9

Author SHA1 Message Date
2c8f072499 Merge pull request #3 from GHPS/master
Added Python 3.x support
2017-11-20 09:57:30 +03:00
GHPS
68bb493c44 Added Python 3.x support 2017-11-19 23:34:25 +01:00
e75a733d0b README fix 2017-09-13 12:05:17 +03:00
c057e1e8ed Adding autocompetion instructions to README and fixing lack of 'exec_string' parameter crashing 2017-09-13 12:03:00 +03:00
0266d29308 Merge pull request #2 from jeff-99/master
added bash autocompletion script for sshch aliases
2017-09-12 20:48:32 +03:00
Jeffrey Slort
de2f544178 added bash autocompletion script for sshch aliases 2017-09-11 10:53:03 +02:00
083144d656 add quotes for sshpass (for & symbol in password) 2017-09-05 15:16:23 +03:00
afa565104c missprint 2017-07-25 17:39:18 +03:00
d5844ab267 add -k feature (hold connection) 2017-07-25 17:21:54 +03:00
48eea214fa Merge pull request #1 from Difrex/master
Code cleanup
2017-07-25 16:48:14 +03:00
Denis Zheleztsov
99374da328 PEP8 2017-07-25 16:39:02 +03:00
37b6f6d5e0 fixed screenshot 2017-07-25 13:39:45 +03:00
4fb25041f9 misprint in readme 2017-07-25 13:10:53 +03:00
4ec5c96775 add setup file and update readme 2017-07-25 13:07:47 +03:00
980f3ac9be add screenshot 2017-07-25 12:47:03 +03:00
6 changed files with 332 additions and 163 deletions

30
LICENSE Normal file
View File

@@ -0,0 +1,30 @@
sshch
sshch is released under the DWTW license
This program is free software; you can redistribute it and/or modify it under the terms of the Do What Thou Wilt License.
DO WHAT THAU WILT
TO PUBLIC LICENSE
Version 2.5
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. Do what thou wilt shall be the whole of the Law.
Anyone is allowed to copy and distribute the copies of this license agreement in whole or in part, as well as modify it without any other limitations.
DWTW a license with a single requirement: DO WHAT THOU WILT
The license provides more freedom than any other one (such as GPL or BSD) and does not require saving the license text on copying.
DWTW an accomplished and eligible license for free text (including the software, documentation and artwork).
The license does not contain "no warranty" clause. DWTW can be used in countries that do not legally acknowledge the transition to public domain.
Summary:
An author-creator gives his or her source code to the world for free, without becoming distracted by worldly thinking regarding how and why the others will use it.

View File

@@ -1,3 +1,31 @@
Simpe python script for fast access to ssh hosts. SSH connection manager with curses interface
======
sshch is released under DWTWL 2.5 license
### Screenshot
![sshch](https://raw.githubusercontent.com/zlaxy/sshch/master/sshch_screenshot.png)
### Installing
To install for all users:
```
sudo python setup.py install
```
To install just for current user:
```
mkdir ~/.local/bin
cp sshch/sshch ~/.local/bin/
```
### Using
To run curses interface:
```
sshch
```
To run command line help:
```
sshch -h
```
**If you want to use unsafe 'password' feature you must install 'sshpass' first.**
under DWTWL 2.5 license: https://soundragon.su/license/license.html If you want to use bash autocompletion function with sshch, copy autocompletion script to /etc/bash_completion.d/:
```
sudo cp sshch_bash_completion.sh /etc/bash_completion.d/sshch
```
(changes will come into effect with new bash session)

35
setup.py Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python
"""Setup script for sshch"""
def main():
from distutils.core import setup
setup(name='sshch',
author='zlaxy',
url='https://github.com/zlaxy/sshch/',
description='Ssh connection manager',
license='DWTWL 2.5',
version='0.8',
py_modules=['sshch'],
scripts=['sshch/sshch'],
# http://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console :: Curses',
'Intended Audience :: System Administrators',
'License :: Freeware',
'Natural Language :: English',
'Operating System :: POSIX',
'Programming Language :: Python :: 2.7',
'Topic :: Internet',
'Topic :: System :: Networking',
'Topic :: System :: Systems Administration',
'Topic :: Utilities'])
return 0
if __name__ == '__main__':
import sys
sys.exit(main())

289
sshch.py → sshch/sshch Normal file → Executable file
View File

@@ -1,23 +1,30 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division from __future__ import division
from __future__ import print_function
try:
import configparser
except ImportError:
import ConfigParser as configparser # Python 2.x import
from os import path from os import path
from sys import argv from sys import argv
from math import * from math import ceil
from getpass import getpass from getpass import getpass
from optparse import OptionParser from optparse import OptionParser
from curses import textpad, panel
import ConfigParser
import subprocess import subprocess
import base64 import base64
import time
import curses import curses
from curses import textpad, panel
# https://github.com/zlaxy/sshch # https://github.com/zlaxy/sshch
version="0.5" version = "0.9"
# path to conf file, default: ~/.config/sshch.conf # path to conf file, default: ~/.config/sshch.conf
conf_file = path.expanduser("~") + '/.config/sshch.conf' conf_file = path.expanduser("~") + '/.config/sshch.conf'
def AddNewAlias(alias): def AddNewAlias(alias):
if not conf.has_section(alias): if not conf.has_section(alias):
conf.add_section(alias) conf.add_section(alias)
@@ -26,111 +33,140 @@ def AddNewAlias(alias):
else: else:
return "error: '" + alias + "' already exists" return "error: '" + alias + "' already exists"
def SetAliasString(alias, string): def SetAliasString(alias, string):
conf.set(alias, "exec_string", string) conf.set(alias, "exec_string", string)
conf.write(open(conf_file, "w")) conf.write(open(conf_file, "w"))
def SetPassword(alias, string): def SetPassword(alias, string):
string = base64.b64encode(base64.b16encode( string = base64.b64encode(base64.b16encode(
base64.b32encode(string))) base64.b32encode(string)))
conf.set(alias, "password", string) conf.set(alias, "password", string)
conf.write(open(conf_file, "w")) conf.write(open(conf_file, "w"))
def RemoveAliases(aliases): def RemoveAliases(aliases):
for alias in aliases: for alias in aliases:
conf.remove_section(alias) conf.remove_section(alias)
conf.write(open(conf_file, "w")) conf.write(open(conf_file, "w"))
def ConnectAlias(alias, command=False): def ConnectAlias(alias, command=False):
print "Connecting to " + alias + "..."
exec_string = "" exec_string = ""
if conf.has_option(alias, "password"): if conf.has_option(alias, "password"):
password = base64.b32decode(base64.b16decode( password = base64.b32decode(base64.b16decode(
base64.b64decode(conf.get(alias, "password")))) base64.b64decode(conf.get(alias, "password"))))
exec_string = "sshpass -p " + password + " " exec_string = 'sshpass -p "' + password + '" '
if conf.has_option(alias, "exec_string"):
exec_string = exec_string + conf.get(alias, "exec_string") exec_string = exec_string + conf.get(alias, "exec_string")
if command: if command:
exec_string = exec_string + " " + command exec_string = exec_string + " " + command
p = subprocess.Popen(exec_string, shell=True) # Variables bellow is newer used
streamdata = p.communicate()[0] subprocess.Popen(exec_string, shell=True).communicate()[0]
print "... " + alias + " session finished."
def HoldConnection(alias):
print("Connecting to " + alias + ". Press CTRL+C to cancel.")
time.sleep(1)
while True:
ConnectAlias(alias)
time.sleep(5)
def CMDAdd(alias): def CMDAdd(alias):
result = AddNewAlias(alias) result = AddNewAlias(alias)
if result == True: if result:
prompt_add = ("Enter connection string for new alias " + prompt_add = ("".join(["Enter connection string for new alias ",
"(example: ssh user@somehost.com):\n") "(example: ssh user@somehost.com):\n"]))
string = "" string = ""
while string == "": while string == "":
string = raw_input (prompt_add) string = input(prompt_add)
SetAliasString(alias, string) SetAliasString(alias, string)
else: else:
print result print(result)
def CMDEdit(alias): def CMDEdit(alias):
if conf.has_section(alias): if conf.has_section(alias):
prompt_edit = ("Enter connection string for existing alias " + prompt_edit = ("".join(["Enter connection string for existing alias ",
"(example: ssh user@somehost.com):\n") "(example: ssh user@somehost.com):\n"]))
string = "" string = ""
while string == "": while string == "":
string = raw_input (prompt_edit) string = input(prompt_edit)
SetAliasString(alias, string) SetAliasString(alias, string)
else: else:
print "error: '" + alias + "' alias is not exists" print("error: '" + alias + "' alias is not exists")
def CMDPassword(alias): def CMDPassword(alias):
if conf.has_section(alias): if conf.has_section(alias):
prompt_pass = ("[UNSAFE] Enter password for sshpass: ") prompt_pass = ("[UNSAFE] Enter password for sshpass: ")
string = "" string = ""
string = getpass(prompt_pass) string = getpass(prompt_pass)
if not string == "": SetPassword(alias, string) if not string == "":
SetPassword(alias, string)
else: else:
print "error: '" + alias + "' alias is not exists" print("error: '" + alias + "' alias is not exists")
def CMDRemove(alias): def CMDRemove(alias):
if conf.has_section(alias): if conf.has_section(alias):
prompt_remove = ("Type 'yes' if you sure to remove '" + prompt_remove = ("Type 'yes' if you sure to remove '" + alias + "' alias: ")
alias + "' alias: ") string = input(prompt_remove)
string = raw_input (prompt_remove)
if string == "yes": if string == "yes":
RemoveAliases([alias]) RemoveAliases([alias])
else: else:
print "'" + alias + "' alias was not deleted." print("'" + alias + "' alias was not deleted.")
else: else:
print "error: '" + alias + "' alias is not exists." print("error: '" + alias + "' alias is not exists.")
def CMDConnect(aliases, command=False): def CMDConnect(aliases, command=False):
for alias in aliases: for alias in aliases:
if conf.has_section(alias): if conf.has_section(alias):
print("Connecting to " + alias + "...")
ConnectAlias(alias, command) ConnectAlias(alias, command)
print("... " + alias + " session finished.")
else: else:
print "error: '" + alias + "' alias is not exists" print("error: '" + alias + "' alias is not exists")
def CMDList(option, opt, value, parser): def CMDList(option, opt, value, parser):
print ', '.join(str(p) for p in conf.sections()) print(', '.join(str(p) for p in conf.sections()))
def CMDFullList(option, opt, value, parser): def CMDFullList(option, opt, value, parser):
print '\n'.join((str(p) + " - " + conf.get(p, "exec_string") + for p in conf.sections():
(" [password]" if conf.has_option(p, "password") else "")) to_print = "".join([str(p), " - ", (conf.get(p, "exec_string") if
for p in conf.sections()) conf.has_option(p, "exec_string") else ""),
(" [password]" if conf.has_option(p, "password") else "")])
print(to_print)
def CursesConnect(screen, aliases, command=False): def CursesConnect(screen, aliases, command=False):
curses.endwin() curses.endwin()
for alias in aliases: for alias in aliases:
print("Connecting to " + alias + "...")
ConnectAlias(alias, command) ConnectAlias(alias, command)
print "Press 'enter' to continue." print("... " + alias + " session finished.")
print("Press 'enter' to continue.")
screen.getch() screen.getch()
def CursesExit(error=False): def CursesExit(error=False):
curses.endwin() curses.endwin()
if error: print error if error:
print(error)
exit() exit()
def CursesTextpadConfirm(value): def CursesTextpadConfirm(value):
if value == 10: if value == 10:
value = 7 value = 7
return value return value
def CursesTextpad(screen, h, w, y, x, title="", value="", def CursesTextpad(screen, h, w, y, x, title="", value="",
text_colorpair=0, deco_colorpair=0): text_colorpair=0, deco_colorpair=0):
new_window = curses.newwin(h + 3, w + 2, y - 1, x - 1) new_window = curses.newwin(h + 3, w + 2, y - 1, x - 1)
@@ -147,6 +183,7 @@ def CursesTextpad(screen, h, w, y, x, title="", value="",
sub_window.attron(text_colorpair) sub_window.attron(text_colorpair)
return textbox_field return textbox_field
def CursesPanel(screen, h, w, y, x, text, def CursesPanel(screen, h, w, y, x, text,
text_colorpair=0, deco_colorpair=0, confirm=0): text_colorpair=0, deco_colorpair=0, confirm=0):
new_window = curses.newwin(h, w, y, x) new_window = curses.newwin(h, w, y, x)
@@ -156,7 +193,6 @@ def CursesPanel(screen, h, w, y, x, text,
new_window.attroff(deco_colorpair) new_window.attroff(deco_colorpair)
sub_window = new_window.subwin(h - 2, w - 2, y + 1, x + 1) sub_window = new_window.subwin(h - 2, w - 2, y + 1, x + 1)
sub_window.insstr(0, 0, text) sub_window.insstr(0, 0, text)
# sub_window.addnstr(0, 0, text, ((h - 2) * (w - 2) - 1))
panel = curses.panel.new_panel(new_window) panel = curses.panel.new_panel(new_window)
curses.panel.update_panels() curses.panel.update_panels()
screen.refresh() screen.refresh()
@@ -194,23 +230,25 @@ def CursesPanel(screen, h, w, y, x, text,
else: else:
screen.getch() screen.getch()
def CMDOptions(): def CMDOptions():
class FormatedParser(OptionParser): class FormatedParser(OptionParser):
def format_epilog(self, formatter): def format_epilog(self, formatter):
return self.epilog return self.epilog
usage = "usage: %prog [options] [aliases]" usage = "usage: %prog [options] [aliases]"
progname = path.basename(__file__) progname = path.basename(__file__)
epilog = ("Examples:\n " + epilog = ("".join(["Examples:\n ",
progname + " existingalias\n " + progname, " existingalias\n ",
progname + " -a newremoteserver\n " + progname, " -a newremoteserver\n ",
progname + " --edit=newremoteserver -p newremoteserver\n " + progname, " --edit=newremoteserver -p newremoteserver\n ",
progname + ' -c "ls -l" newremoteserver\n ' + progname, ' -c "ls -l" newremoteserver\n ',
progname + " -c reboot existingalias newremoteserver\n" progname, " -c reboot existingalias newremoteserver\n",
"Examples of connection string:\n " + "Examples of connection string:\n ",
"ssh user@somehost.com\n " + "ssh user@somehost.com\n ",
"ssh gates@8.8.8.8 -p 667\n " + "ssh gates@8.8.8.8 -p 667\n ",
"ssh root@somehost.com -t tmux a\n" + "ssh root@somehost.com -t tmux a\n",
"Also, you can edit config file manually: " + conf_file + "\n") "Also, you can edit config file manually: ", conf_file, "\n"]))
opts = FormatedParser(usage=usage, version="%prog " + version, opts = FormatedParser(usage=usage, version="%prog " + version,
epilog=epilog) epilog=epilog)
opts.add_option('-l', '--list', action="callback", opts.add_option('-l', '--list', action="callback",
@@ -224,6 +262,9 @@ def CMDOptions():
opts.add_option('-c', '--command', action="store", type="string", opts.add_option('-c', '--command', action="store", type="string",
dest="command", metavar="command", default=False, dest="command", metavar="command", default=False,
help="add command for executing alias") help="add command for executing alias")
opts.add_option('-k', '--keep', action="store", type="string",
dest="keep", metavar="alias", default=False,
help="hold connection with specified alias")
opts.add_option('-e', '--edit', action="store", type="string", opts.add_option('-e', '--edit', action="store", type="string",
dest='edit', metavar="alias", default=False, dest='edit', metavar="alias", default=False,
help="edit existing connection string") help="edit existing connection string")
@@ -234,27 +275,37 @@ def CMDOptions():
dest='remove', metavar="alias", default=False, dest='remove', metavar="alias", default=False,
help="remove existing alias of connection string") help="remove existing alias of connection string")
options, alias = opts.parse_args() options, alias = opts.parse_args()
if options.add: CMDAdd(options.add) if options.add:
if options.edit: CMDEdit(options.edit) CMDAdd(options.add)
if options.password: CMDPassword(options.password) if options.edit:
if options.remove: CMDRemove(options.remove) CMDEdit(options.edit)
if alias: CMDConnect(alias, options.command) if options.password:
CMDPassword(options.password)
if options.remove:
CMDRemove(options.remove)
if options.keep:
HoldConnection(options.keep)
if alias:
CMDConnect(alias, options.command)
# curses template from: https://stackoverflow.com/a/30828805/6224462 # curses template from: https://stackoverflow.com/a/30828805/6224462
def CursesMain(): def CursesMain():
help_screen = (" Press:\n" + help_screen = ("".join([" Press:\n",
" 'z'/'x' or arrows - navigation\n" + " 'z'/'x' or arrows - navigation\n",
" 'a'/'F2' - add new alias\n" + " 'a'/'F2' - add new alias\n",
" 'e'/'F4' - edit existing alias\n" + " 'e'/'F4' - edit existing alias\n",
" 'p'/'F6' - set alias's password for sshpass [UNSAFE]\n" + " 'p'/'F6' - set alias's password for sshpass [UNSAFE]\n",
" 'space'/'insert' - select\n" + " 'space'/'insert' - select\n",
" 'r'/'F8' - remove selected alias/aliases\n" + " 'r'/'F8' - remove selected alias/aliases\n",
" 'c'/'F3' - execute specific command with selected alias/aliases\n" + " 'c'/'F3' - execute specific command with selected alias/aliases\n",
" 'enter'/'F9' - connect to selected alias/aliases\n" + " 'k'/'F7' - hold connection with selected alias\n",
" 'q'/'F10' - quit\n" + " 'enter'/'F9' - connect to selected alias/aliases\n",
" Run program with '--help' option to view command line help.\n" + " 'q'/'F10' - quit\n",
" Also, you can edit config file manually:\n" + " Run program with '--help' option to view command line help.\n",
" " + conf_file) " Also, you can edit config file manually:\n",
" ", conf_file]))
strings = conf.sections() strings = conf.sections()
row_num = len(strings) row_num = len(strings)
selected_strings = [" " for i in range(0, row_num + 1)] selected_strings = [" " for i in range(0, row_num + 1)]
@@ -279,21 +330,21 @@ def CursesMain():
page = 1 page = 1
for i in range(1, max_row + 1): for i in range(1, max_row + 1):
if row_num == 0: if row_num == 0:
box.addnstr(1, 1, "There aren't any aliases yet. Press 'a'" + box.addnstr(1, 1, "There aren't any aliases yet. Press 'a' to add new one.",
" to add new one.", width - 6, highlight_text) width - 6, highlight_text)
else: else:
if conf.has_option(strings[i - 1], "password"):
password = " [password]"
else:
password = ""
exec_string = ["[", selected_strings[i], "] ", str(i), " ",
strings[i - 1], " (", (conf.get(strings[i - 1],
"exec_string") if conf.has_option(strings[i - 1],
"exec_string") else ""), ")", password]
if (i == position): if (i == position):
box.addnstr(i, 2, "[" + selected_strings[i] + "] " + box.addnstr(i, 2, "".join(exec_string), width - 6, highlight_text)
str(i) + " " + strings[i - 1] + " (" +
conf.get(strings[i - 1], "exec_string") + ")" +
(" [password]" if conf.has_option(strings[i - 1],
"password") else ""), width - 6, highlight_text)
else: else:
box.addnstr(i, 2, "[" + selected_strings[i] + "] " + box.addnstr(i, 2, "".join(exec_string), width - 6, normal_text)
str(i) + " " + strings[i - 1] + " (" +
conf.get(strings[i - 1], "exec_string") + ")" +
(" [password]" if conf.has_option(strings[i - 1],
"password") else ""), width - 6, normal_text)
if i == row_num: if i == row_num:
break break
screen.refresh() screen.refresh()
@@ -316,7 +367,8 @@ def CursesMain():
add_alias = new_alias_textpad.edit(CursesTextpadConfirm) add_alias = new_alias_textpad.edit(CursesTextpadConfirm)
if not add_alias.rstrip() == "": if not add_alias.rstrip() == "":
add_result = AddNewAlias(add_alias.rstrip()) add_result = AddNewAlias(add_alias.rstrip())
if not add_result == True: CursesPanel(screen, 3, if not add_result:
CursesPanel(screen, 3,
width - 6, (height // 2) - 1, 3, add_result, width - 6, (height // 2) - 1, 3, add_result,
normal_text, highlight_text) normal_text, highlight_text)
else: else:
@@ -341,7 +393,9 @@ def CursesMain():
while edit_string.rstrip() == "": while edit_string.rstrip() == "":
string_textpad = CursesTextpad(screen, 3, width - 8, string_textpad = CursesTextpad(screen, 3, width - 8,
(height // 2) - 1, 4, "Enter new execution string:", (height // 2) - 1, 4, "Enter new execution string:",
conf.get(strings[position - 1], "exec_string"), (conf.get(strings[position - 1],
"exec_string") if conf.has_option(strings[position - 1],
"exec_string") else ""),
normal_text, highlight_text) normal_text, highlight_text)
edit_string = string_textpad.edit(CursesTextpadConfirm) edit_string = string_textpad.edit(CursesTextpadConfirm)
SetAliasString(strings[position - 1], SetAliasString(strings[position - 1],
@@ -352,8 +406,8 @@ def CursesMain():
set_password = "" set_password = ""
set_password = CursesPanel(screen, 4, width - 6, set_password = CursesPanel(screen, 4, width - 6,
(height // 2) - 1, 3, (height // 2) - 1, 3,
" Enter user password for sshpass and press 'enter':" + " Enter user password for sshpass and press 'enter':\n>",
"\n>", normal_text, highlight_text, "password") normal_text, highlight_text, "password")
if not set_password == "": if not set_password == "":
SetPassword(strings[position - 1], set_password) SetPassword(strings[position - 1], set_password)
if (key_pressed == ord('r') or key_pressed == ord( if (key_pressed == ord('r') or key_pressed == ord(
@@ -364,11 +418,12 @@ def CursesMain():
if selected_strings[i] == "*": if selected_strings[i] == "*":
selected.append(strings[i - 1]) selected.append(strings[i - 1])
if len(selected) > 0: if len(selected) > 0:
remove_confirm = ("Are you sure to remove " + remove_confirm = (
str(len(selected)) + " selected aliases? (y/N)") "".join(["Are you sure to remove ",
str(len(selected)), " selected aliases? (y/N)"]))
else: else:
remove_confirm = ("Are you sure to remove '" + remove_confirm = ("".join(["Are you sure to remove '",
strings[position - 1] + "' alias? (y/N)") strings[position - 1], "' alias? (y/N)"]))
selected.append(strings[position - 1]) selected.append(strings[position - 1])
remove_result = CursesPanel(screen, 4, width - 6, remove_result = CursesPanel(screen, 4, width - 6,
(height // 2) - 1, 3, remove_confirm, normal_text, (height // 2) - 1, 3, remove_confirm, normal_text,
@@ -392,11 +447,17 @@ def CursesMain():
selected.append(strings[position - 1]) selected.append(strings[position - 1])
command_textpad = CursesTextpad(screen, 3, width - 8, command_textpad = CursesTextpad(screen, 3, width - 8,
(height // 2) - 1, 4, (height // 2) - 1, 4,
"Enter specific command to execute with selected " + "".join([
"alias/aliases:", "", normal_text, highlight_text) "Enter specific command to execute with selected ",
"alias/aliases:"]
), "", normal_text, highlight_text)
command_string = command_textpad.edit(CursesTextpadConfirm) command_string = command_textpad.edit(CursesTextpadConfirm)
CursesConnect(screen, selected, CursesConnect(screen, selected,
command_string.replace("\n", "").rstrip()) command_string.replace("\n", "").rstrip())
if (key_pressed == ord('k') or key_pressed == ord('K') or
key_pressed == (curses.KEY_F7)) and row_num != 0:
curses.endwin()
HoldConnection(strings[position - 1])
if (key_pressed == ord("\n") or key_pressed == ( if (key_pressed == ord("\n") or key_pressed == (
curses.KEY_F9)) and row_num != 0: curses.KEY_F9)) and row_num != 0:
selected = [] selected = []
@@ -410,7 +471,8 @@ def CursesMain():
curses.KEY_IC)) and row_num != 0: curses.KEY_IC)) and row_num != 0:
if selected_strings[position] == ' ': if selected_strings[position] == ' ':
selected_strings[position] = '*' selected_strings[position] = '*'
else: selected_strings[position] = ' ' else:
selected_strings[position] = ' '
if page == 1: if page == 1:
if position < i: if position < i:
position = position + 1 position = position + 1
@@ -472,25 +534,23 @@ def CursesMain():
for i in range(1 + (max_row * (page - 1)), max_row + 1 + for i in range(1 + (max_row * (page - 1)), max_row + 1 +
(max_row * (page - 1))): (max_row * (page - 1))):
if row_num == 0: if row_num == 0:
box.addnstr(1, 1, "There aren't any aliases yet. " + box.addnstr(1, 1, "There aren't any aliases yet. Press 'a' to add new one.",
"Press 'a' to add new one.", width - 6, width - 6, highlight_text)
highlight_text)
else: else:
if (i + (max_row * (page - 1)) == (position + if conf.has_option(strings[i - 1], "password"):
(max_row * (page - 1)))): password = " [password]"
box.addnstr(i - (max_row * (page - 1)), 2, "[" +
selected_strings[i] + "] " + str(i) + " " +
strings[i - 1] + " (" + conf.get(strings[i - 1],
"exec_string") + ")" + (" [password]" if
conf.has_option(strings[i - 1], "password") else
""), width - 6, highlight_text)
else: else:
box.addnstr(i - (max_row * (page - 1)), 2, "[" + password = ""
selected_strings[i] + "] " + str(i) + " " + exec_string = ["[", selected_strings[i], "] ", str(i), " ",
strings[i - 1] + " (" + conf.get(strings[i - 1], strings[i - 1], " (", (conf.get(strings[i - 1],
"exec_string") + ")" + (" [password]" if "exec_string") if conf.has_option(strings[i - 1],
conf.has_option(strings[i - 1], "password") else "exec_string") else ""), ")", password]
""), width - 6, normal_text) if (i + (max_row * (page - 1)) == (position + (max_row * (page - 1)))):
box.addnstr(i - (max_row * (page - 1)), 2, "".join(
exec_string), width - 6, highlight_text)
else:
box.addnstr(i - (max_row * (page - 1)), 2, "".join(
exec_string), width - 6, normal_text)
if i == row_num: if i == row_num:
break break
screen.refresh() screen.refresh()
@@ -498,8 +558,14 @@ def CursesMain():
key_pressed = screen.getch() key_pressed = screen.getch()
CursesExit() CursesExit()
if __name__ == "__main__": if __name__ == "__main__":
conf = ConfigParser.RawConfigParser() try:
input = raw_input # Fix for Python 2.x
except NameError:
pass
conf = configparser.RawConfigParser()
if not path.exists(conf_file): if not path.exists(conf_file):
open(conf_file, 'w') open(conf_file, 'w')
conf.read(conf_file) conf.read(conf_file)
@@ -508,18 +574,17 @@ if __name__ == "__main__":
CMDOptions() CMDOptions()
except KeyboardInterrupt: except KeyboardInterrupt:
exit() exit()
except ConfigParser.Error: except configparser.Error:
print ("Error: can't parse your config file, please check" + print("Error: can't parse your config file, please check it manually or make new one")
" it manually or make new one")
exit() exit()
else: else:
try: try:
CursesMain() CursesMain()
except KeyboardInterrupt: except KeyboardInterrupt:
CursesExit() CursesExit()
except ConfigParser.NoOptionError: except configparser.NoOptionError:
CursesExit("Error: can't parse your config file, please " + CursesExit("".join(["Error: can't parse your config file, please ",
"check it manually or make new one") "check it manually or make new one"]))
except curses.error: except curses.error:
CursesExit("Error: can't show some curses element, maybe " + CursesExit("".join(["Error: can't show some curses element, maybe ",
"your terminal is too small") "your terminal is too small"]))

11
sshch_bash_completion.sh Normal file
View File

@@ -0,0 +1,11 @@
_sshch_complete()
{
local cur_word alias_list
cur_word="${COMP_WORDS[COMP_CWORD]}"
alias_list=`sshch -l | sed 's/,//g'`
COMPREPLY=($(compgen -W "$alias_list" -- $cur_word))
return 0
}
complete -F _sshch_complete sshch

BIN
sshch_screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB