[Linux] SSH列表选择登录与文件传送

[Linux] SSH列表选择登录与文件传送

系统安装fzf, rsync和sshpass

保存以下脚本, 赋予执行权限并放在$PATH路径下

#!/usr/bin/env python
"""
sys require: fzf rsync sshpass
 
登录:
sshc # 选择登录服务器(fzf)
sshc <名称> # 指定名称登录服务器
sshc . # 登录上次登录的服务器

文件传送:
sshc push -l <本地文件路径> -r <服务器文件路径> # 选择服务器上传文件
sshc pull -l <本地文件路径> -r <服务器文件路径> # 选择服务器下载文件
sshc pull --name <名称> -l <本地文件路径> -r <服务器文件路径> # 指定服务器下载文件

建议使用公钥而非密码登录
"""
import sys
from pathlib import Path
import json
import subprocess

conf_file = Path.home() / '.ssh' / 'ssh_server.json'
last_login_file = Path.home() / '.ssh' / 'last_login'


# 获取服务器列表配置
def get_config():
    if not Path(conf_file).exists:
        # 配置文件内容示例
        example = """
        {
          "servers": [
            {"enable": 0, "name": "termux", "user": "u0_a355", "host": "192.168.31.239", "port": "8022", "passwd": ""},
            {"enable": 0, "name": "sshd4a", "user": "v", "host": "192.168.31.239", "port": "2222", "passwd": ""},
            {"enable": 0, "name": "laptop", "user": "root", "host": "192.168.31.189", "port": "22", "passwd": ""}
          ]
        }
        """
        with open(conf_file, 'w') as f:
            f.write(example)
        print(f'请在{conf_file}中配置services')
        exit(1)
    with open(conf_file, 'r+') as f:
        return json.load(f)

cfg = get_config()
objs = [obj for obj in cfg['servers'] if obj['enable'] == 1] # 只收集启用的服务器


# 使用fzf选择一个服务器
def chose_server():
    names = [i["name"] for i in objs]
    selected_name = run_fzf_cmd(names)
    if not selected_name:
        exit(0)
    return get_server(selected_name)

# 指定名称获取服务器
def get_server(name):
    obj = next((d for d in objs if d.get("name") == name and d.get("enable") == 1), None)
    assert (obj), f"未找到配置: {name}"
    assert (obj.get('user')), f"未配置user: {name}"
    assert (obj.get('host')), f"未配置host: {name}"
    port = obj.get('port')
    if not port:
        obj['port'] = '22'
    return obj


# 使用fzf选择服务器
def run_fzf_cmd(options):
    options_str = "\n".join(options)
    process = subprocess.Popen(
        ["fzf", "--reverse"],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        text=True  # 设置为True以便处理文本而不是字节
    )
    
    selected, _ = process.communicate(input=options_str)
    return selected.strip()

# 使用fzf的python包装选择服务器
def fzf_chose(options):
    # pip install pyfzf
    from pyfzf.pyfzf import FzfPrompt
    fzf = FzfPrompt()
    selected_name = fzf.prompt(options, "--reverse")
    if not selected_name:
        exit(0)
    return selected_name[0]


# 获取上次登录服务器
def last_login():
    if Path(last_login_file).exists():
        with open(last_login_file, 'r') as f:
            return f.read()
    return None
    
# 记录本次登录服务器
def record_login(name):
    # 保存最后一次登录server
    last = last_login()
    if last != name:
        with open(last_login_file, 'w') as f:
            f.write(name)


# 登录一个服务器
def ssh(obj):
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd, 'ssh', '-o', 'StrictHostKeyChecking=no', '-p', obj["port"], f'{obj["user"]}@{obj["host"]}']
    else:
        command = ['ssh', '-o', 'StrictHostKeyChecking=no', '-p', obj["port"], f'{obj["user"]}@{obj["host"]}']
    record_login(obj['name'])
    subprocess.run(command)

# 使用rsync传送文件
def rsync(obj, local_path, remot_path, type='pull'):
    remote_path = f'{obj["user"]}@{obj["host"]}:{remot_path}'
    param_tuple = (local_path, remote_path) if type == 'push' else (remote_path, local_path)

    ssh_cmd = f'ssh -o StrictHostKeyChecking=no -p {obj["port"]}'
    passwd = obj.get('passwd')
    if passwd:
        ssh_cmd = f'sshpass -p {passwd} {ssh_cmd}'

    command = ['rsync', '-partial', '-avz', '-e', ssh_cmd, param_tuple[0], param_tuple[1]]
    subprocess.run(command)

# 使用scp传送文件
def scp(obj, local_path, remot_path, type='pull'):
    remote_path = f'{obj["user"]}@{obj["host"]}:{remot_path}'
    param_tuple = (local_path, remote_path) if type == 'push' else (remote_path, local_path)
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd,'scp', '-p', obj["port"], param_tuple[0], param_tuple[1]]
    else:
        command = ['scp', '-p', obj["port"], param_tuple[0], param_tuple[1]]
    subprocess.run(command)


def main():
    if len(sys.argv) <= 1:
        # 交互式选择登录服务器
        obj = chose_server()
        ssh(obj)
        return
    
    elif len(sys.argv) == 2:
        # 指定名称登录服务器
        name = sys.argv[1]
        if name == '.': # 使用.来使用上次登录的服务
            name = last_login()
            assert (name), "未找到上次登录信息"
        obj = get_server(name)
        ssh(obj)
        return
    
    else:
        # 使用rsync传送文件(交互式选择服务器)
        import argparse
        parser = argparse.ArgumentParser()
        parser.add_argument('type', type=str, help='pull或push')
        parser.add_argument('-l', type=str, required=True, help='本地文件')
        parser.add_argument('-r', type=str, required=True, help='远程文件')
        parser.add_argument('--name', type=str, help='指定服务器传送文件')
        args = parser.parse_args()

        local_path = args.l
        remote_path = args.r
        type = args.type
        name = args.name

        obj = get_server(name) if name else chose_server()
        rsync(obj, local_path, remote_path, type)
        return

if __name__ == "__main__":
    main()

第一次运行会生成配置文件$HOME/.ssh/ssh_server.json, 修改服务器列表后就可以使用了

在这里插入图片描述


更新: 参数简化固定位置, 增加服务器间文件传送

#!/usr/bin/env python
"""
sys require: fzf rsync sshpass
 
登录:
sshc # 选择登录服务器(fzf)
sshc <名称> # 指定名称登录服务器
sshc . # 登录上次登录的服务器

文件传送:
sshc push <本地文件路径> <服务器文件路径> # 选择服务器上传文件
sshc pull <服务器文件路径> <本地文件路径> # 选择服务器下载文件
sshc push <本地文件路径> <服务器文件路径> <名称> # 指定服务器上传文件
sshc pull <服务器文件路径> <本地文件路径> <名称> # 指定服务器下载文件

服务器间传送文件:
sshc cp <名称1>:<服务器文件路径> <名称2>:<服务器文件路径> # 将名称1的服务器文件传送到名称2的服务器
sshc cp <服务器1文件路径> <服务器2文件路径> <任意值> # 选择两次服务器把第一个服务器的文件传送到第二个服务器

建议使用公钥而非密码登录
"""
import sys
from pathlib import Path
import json
import subprocess

conf_file = Path.home() / '.ssh' / 'ssh_server.json'
last_login_file = Path.home() / '.ssh' / 'last_login'


# 获取服务器列表配置
def get_config():
    if not Path(conf_file).exists:
        # 配置文件内容示例
        example = """
        {
          "servers": [
            {"enable": 0, "name": "termux", "user": "u0_a355", "host": "192.168.31.239", "port": "8022", "passwd": ""},
            {"enable": 0, "name": "sshd4a", "user": "v", "host": "192.168.31.239", "port": "2222", "passwd": ""},
            {"enable": 0, "name": "laptop", "user": "root", "host": "192.168.31.189", "port": "22", "passwd": ""}
          ]
        }
        """
        with open(conf_file, 'w') as f:
            f.write(example)
        print(f'请在{conf_file}中配置services')
        exit(1)
    with open(conf_file, 'r+') as f:
        return json.load(f)

cfg = get_config()
objs = [obj for obj in cfg['servers'] if obj['enable'] == 1] # 只收集启用的服务器


# 使用fzf选择一个服务器
def chose_server():
    names = [i["name"] for i in objs]
    selected_name = run_fzf_cmd(names)
    if not selected_name:
        exit(0)
    return get_server(selected_name)

# 指定名称获取服务器
def get_server(name):
    obj = next((d for d in objs if d.get("name") == name and d.get("enable") == 1), None)
    assert (obj), f"未找到配置: {name}"
    assert (obj.get('user')), f"未配置user: {name}"
    assert (obj.get('host')), f"未配置host: {name}"
    port = obj.get('port')
    if not port:
        obj['port'] = '22'
    return obj


# 使用fzf选择服务器
def run_fzf_cmd(options):
    options_str = "\n".join(options)
    process = subprocess.Popen(
        ["fzf", "--reverse"],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        text=True  # 设置为True以便处理文本而不是字节
    )
    
    selected, _ = process.communicate(input=options_str)
    return selected.strip()

# 使用fzf的python包装选择服务器
def fzf_chose(options):
    # pip install pyfzf
    from pyfzf.pyfzf import FzfPrompt
    fzf = FzfPrompt()
    selected_name = fzf.prompt(options, "--reverse")
    if not selected_name:
        exit(0)
    return selected_name[0]


# 获取上次登录服务器
def last_login():
    if Path(last_login_file).exists():
        with open(last_login_file, 'r') as f:
            return f.read()
    return None
    
# 记录本次登录服务器
def record_login(name):
    # 保存最后一次登录server
    last = last_login()
    if last != name:
        with open(last_login_file, 'w') as f:
            f.write(name)


# 登录一个服务器
def ssh(obj):
    command = ['ssh', '-o', 'StrictHostKeyChecking=no', '-p', obj["port"], obj2path(obj)]
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd] + command
    record_login(obj['name'])
    subprocess.run(command)

# 使用rsync传送文件
def rsync(obj, type, path1, path2):
    if type == 'pull':
        path1 = obj2path(obj, path1)
    else:
        path2 = obj2path(obj, path2)

    ssh_cmd = f'ssh -o StrictHostKeyChecking=no -p {obj["port"]}'
    passwd = obj.get('passwd')
    if passwd:
        ssh_cmd = f'sshpass -p {passwd} {ssh_cmd}'

    command = ['rsync', '-partial', '-avz', '-e', ssh_cmd, path1, path2]
    subprocess.run(command)

# 使用scp传送文件
def scp(obj, local_path, remot_path, type='pull'):
    remote_path = obj2path(obj, remot_path)
    param_tuple = (local_path, remote_path) if type == 'push' else (remote_path, local_path)
    command = ['scp', '-p', obj["port"], param_tuple[0], param_tuple[1]]
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd] + command
    subprocess.run(command)

def obj2path(obj, path=None):
    r = f'{obj["user"]}@{obj["host"]}'
    if path:
        r = f'{r}:{path}'
    return r

# 使用rsync在两台服务器之前传送文件
def rsync_server_cp(source_obj, source_path, target_obj, target_path):
    # 会有内外部ip问题?
    target_port = target_obj["port"]
    target_path = obj2path(target_obj, target_path)

    ssh_cmd = f"ssh -o StrictHostKeyChecking=no -p {target_port}"
    target_passwd = target_obj.get('passwd')
    if target_passwd:
        ssh_cmd = f'sshpass -p {target_passwd} {ssh_cmd}'
    rsync_cmd = f'rsync -partial -avz -e "{ssh_cmd}" "{source_path}" "{target_path}"'

    command = ['ssh', '-o', 'StrictHostKeyChecking=no', '-p', source_obj["port"], obj2path(source_obj), rsync_cmd]
    source_passwd = source_obj.get('passwd')
    if source_passwd:
        command = ['sshpass', '-p', source_passwd] + command
    subprocess.run(command)


def main():
    argv = sys.argv
    arglen = len(argv)

    if arglen <= 1:
        # 交互式选择登录服务器
        obj = chose_server()
        ssh(obj)
        return
    
    p1 = argv[1]

    if arglen == 2:
        # 指定名称登录服务器
        name = p1
        if name == '.': # 使用.来使用上次登录的服务
            name = last_login()
            assert (name), "未找到上次登录信息"
        obj = get_server(name)
        ssh(obj)
        return
    
    action = p1

    if action == 'pull' or action == 'push':
        # 使用rsync传送文件
        path1 = argv[2]
        path2 = argv[3]

        if arglen > 4:
            name = argv[4]
            obj = get_server(name)
        else:
            obj = chose_server()

        rsync(obj, action, path1, path2)
        return

    if action == 'cp':
        # 在两台服务器之间传送文件
        source_path = argv[2]
        target_path = argv[3]
        if len(argv) == 4:
            # 三个参数表示在文件参数中指定了服务器
            def parse_file_param(str):
                parts = str.split(':', 1)
                assert(len(parts) == 2), f"参数格式不正确: {str}"
                return (parts[0], parts(1))
            (source_name, source_path) = parse_file_param(source_path)
            (target_name, target_path) = parse_file_param(target_path)
            source_obj = get_server(source_name)
            target_obj = get_server(target_name)
        elif arglen > 4:
            # 四个参数表示手动选择服务器(第四个参数仅作为flag)
            source_obj = chose_server()
            target_obj = chose_server()
        rsync_server_cp(source_obj, source_path, target_obj, target_path)
        return

if __name__ == "__main__":
    main()


二更: 文件传送参数重新整理, 允许省略目标路径(设置默认上传目录)

#!/usr/bin/env python
"""
sys require: fzf rsync sshpass
 
登录:
sshc # 选择登录服务器(fzf)
sshc <名称> # 指定名称登录服务器
sshc . # 登录上次登录的服务器

文件传送:
sshc pull <[-n <名称>:]服务器文件路径> [<本地文件路径>] # 下载文件
sshc push <本地文件路径> <[-n <名称>:][服务器文件路径]> # 上传文件

服务器间传送文件:
sshc cp <[-n <名称1>:]服务器文件路径> <[-n <名称2>:][服务器文件路径]> # 将名称1的服务器文件传送到名称2的服务器

文件传送如果未传目标路径时, 将传送到工作目录(本地)或push_dir(服务器)

建议使用公钥而非密码登录
"""
import sys
import os
from pathlib import Path
import json
import subprocess

conf_file = Path.home() / '.ssh' / 'ssh_server.json'
last_login_file = Path.home() / '.ssh' / 'last_login'


# 获取服务器列表配置
def get_config():
    if not Path(conf_file).exists:
        # 配置文件内容示例
        example = """
        {
          "servers": [
            {"enable": 0, "name": "termux", "user": "u0_a355", "host": "192.168.31.239", "port": "8022", "passwd": "", "push_dir": "/sdcard/Download/"},
            {"enable": 0, "name": "sshd4a", "user": "v", "host": "192.168.31.239", "port": "2222", "passwd": "", "push_dir": "/sdcard/Download/"},
            {"enable": 0, "name": "laptop", "user": "root", "host": "192.168.31.189", "port": "22", "passwd": "", "push_dir": "/home/root/Downloads/"}
          ]
        }
        """
        with open(conf_file, 'w') as f:
            f.write(example)
        print(f'请在{conf_file}中配置services')
        exit(1)
    with open(conf_file, 'r+') as f:
        return json.load(f)

cfg = get_config()
objs = [obj for obj in cfg['servers'] if obj['enable'] == 1] # 只收集启用的服务器


# 使用fzf选择一个服务器
def chose_server():
    names = [i["name"] for i in objs]
    selected_name = run_fzf_cmd(names)
    if not selected_name:
        exit(0)
    return get_server(selected_name)

# 指定名称获取服务器
def get_server(name):
    obj = next((d for d in objs if d.get("name") == name and d.get("enable") == 1), None)
    assert (obj), f"未找到配置: {name}"
    assert (obj.get('user')), f"未配置user: {name}"
    assert (obj.get('host')), f"未配置host: {name}"
    port = obj.get('port')
    if not port:
        obj['port'] = '22'
    return obj


# 使用fzf选择服务器
def run_fzf_cmd(options):
    options_str = "\n".join(options)
    process = subprocess.Popen(
        ["fzf", "--reverse"],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        text=True  # 设置为True以便处理文本而不是字节
    )
    
    selected, _ = process.communicate(input=options_str)
    return selected.strip()

# 使用fzf的python包装选择服务器
def fzf_chose(options):
    # pip install pyfzf
    from pyfzf.pyfzf import FzfPrompt
    fzf = FzfPrompt()
    selected_name = fzf.prompt(options, "--reverse")
    if not selected_name:
        exit(0)
    return selected_name[0]


# 获取上次登录服务器
def last_login():
    if Path(last_login_file).exists():
        with open(last_login_file, 'r') as f:
            return f.read()
    return None
    
# 记录本次登录服务器
def record_login(name):
    # 保存最后一次登录server
    last = last_login()
    if last != name:
        with open(last_login_file, 'w') as f:
            f.write(name)


# 登录一个服务器
def ssh(obj):
    command = ['ssh', '-o', 'StrictHostKeyChecking=no', '-p', obj["port"], obj2path(obj)]
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd] + command
    record_login(obj['name'])
    subprocess.run(command)

# 使用rsync传送文件
def rsync(obj, type, path1, path2):
    if type == 'pull':
        path1 = obj2path(obj, path1)
        if not path2:
            path2 = os.getcwd()
    else:
        if not path2:
            path2 = obj.get("push_dir")
            assert(path2), f"服务器未配置默认推送目录: {obj['name']}"
        path2 = obj2path(obj, path2)

    ssh_cmd = f'ssh -o StrictHostKeyChecking=no -p {obj["port"]}'
    passwd = obj.get('passwd')
    if passwd:
        ssh_cmd = f'sshpass -p {passwd} {ssh_cmd}'

    command = ['rsync', '-partial', '-avz', '-e', ssh_cmd, path1, path2]
    subprocess.run(command)

# 使用scp传送文件
def scp(obj, local_path, remot_path, type='pull'):
    if type == 'pull' and not local_path:
        local_path = os.getcwd()

    remote_path = obj2path(obj, remot_path)
    param_tuple = (local_path, remote_path) if type == 'push' else (remote_path, local_path)
    command = ['scp', '-p', obj["port"], param_tuple[0], param_tuple[1]]
    passwd = obj.get('passwd')
    if passwd:
        command = ['sshpass', '-p', passwd] + command
    subprocess.run(command)

# 使用rsync在两台服务器之前传送文件
def rsync_server_cp(source_obj, source_path, target_obj, target_path):
    # 会有内外部ip问题?
    target_port = target_obj["port"]
    if not target_path:
        target_path = target_obj.get("push_dir")
        assert(target_path), f"服务器未配置默认推送目录: {target_obj['name']}"
    target_path = obj2path(target_obj, target_path)

    ssh_cmd = f"ssh -o StrictHostKeyChecking=no -p {target_port}"
    target_passwd = target_obj.get('passwd')
    if target_passwd:
        ssh_cmd = f'sshpass -p {target_passwd} {ssh_cmd}'
    rsync_cmd = f'rsync -partial -avz -e "{ssh_cmd}" "{source_path}" "{target_path}"'

    command = ['ssh', '-o', 'StrictHostKeyChecking=no', '-p', source_obj["port"], obj2path(source_obj), rsync_cmd]
    source_passwd = source_obj.get('passwd')
    if source_passwd:
        command = ['sshpass', '-p', source_passwd] + command
    subprocess.run(command)


def obj2path(obj, path=None):
    r = f'{obj["user"]}@{obj["host"]}'
    if path:
        r = f'{r}:{path}'
    return r

def parse_file_param(str):
    parts = str.split(':', 1)
    return (parts[0], None if len(parts) == 1 else parts[1])

def parse_server_path_param(arr, begin_idx):
    p1 = arr_get(arr, begin_idx)
    p2 = arr_get(arr, begin_idx + 1)
    
    if p1 == '-n':
        (name, path) = parse_file_param(p2)
        return (get_server(name), path, arr[2:])
    else:
        return (chose_server(), p1, arr[1:])

def arr_get(arr, idx, default=None):
    return default if len(arr) <= idx else arr[idx]


def main():
    argv = sys.argv
    arglen = len(argv)

    if arglen <= 1:
        # 交互式选择登录服务器
        obj = chose_server()
        ssh(obj)
        return
    
    p1 = argv[1]

    if arglen == 2:
        # 指定名称登录服务器
        name = p1
        if name == '.': # 使用.来使用上次登录的服务
            name = last_login()
            assert (name), "未找到上次登录信息"
        obj = get_server(name)
        ssh(obj)
        return
    
    action = p1

    if action == 'pull' or action == 'push':
        # 使用rsync传送文件
        if action == 'pull':
            (obj, path1, argv1) = parse_server_path_param(argv[2:], 0)
            path2 = arr_get(argv1, 0)
        else:
            path1 = argv[2]
            (obj, path2, _) = parse_server_path_param(argv[3:], 0)

        rsync(obj, action, path1, path2)
        return

    if action == 'cp':
        # 在两台服务器之间传送文件
        (source_obj, source_path, argv1) = parse_server_path_param(argv[2:], 0)
        (target_obj, target_path, _) = parse_server_path_param(argv1, 0)

        rsync_server_cp(source_obj, source_path, target_obj, target_path)
        return

if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值