当前位置: 首页 > news >正文

myssh

#!/usr/bin/env bash # # myssh.sh — MobaXterm / Linux 通用:纯 bash,不依赖 Python。 # 默认地址簿: /.myssh/book.tsv(可用 MYSSH_DB 或 -f 指定) # 每行: user<TAB>host<TAB>label<TAB>密码的base64 # # 用法: # ./myssh.sh # ./myssh.sh -l 关键字 # ./myssh.sh user@host [-l label] # # MobaXterm: 若从 Windows 拷贝脚本,请保持 LF 换行;CRLF 会导致 shebang 失效,sh/dash 跑不起来。 # 若仍用 sh 执行,下面会尝试自动切到 bash。 # if [ -z "${BASH_VERSION:-}" ]; then exec bash "$0" "$@" fi set -euo pipefail DEFAULT_DB="/.myssh/book.tsv" die() { echo "myssh: $*" >&2; exit 1; } b64_encode() { # Must never fail under "set -e": pipefail + failing base64/openssl would otherwise exit the whole script. local out out=$(printf '%s' "$1" | base64 -w0 2>/dev/null) || true if [[ -n "${out:-}" ]]; then printf '%s' "$out"; return 0; fi out=$(printf '%s' "$1" | openssl base64 -e -A 2>/dev/null) || true if [[ -n "${out:-}" ]]; then printf '%s' "$out"; return 0; fi out=$(printf '%s' "$1" | base64 2>/dev/null | tr -d '\n\r') || true if [[ -n "${out:-}" ]]; then printf '%s' "$out"; return 0; fi die "cannot base64-encode password (need GNU/base64 or openssl in PATH)" } b64_decode() { local out out=$(printf '%s' "$1" | base64 -d 2>/dev/null) || true if [[ -n "${out:-}" ]]; then printf '%s' "$out"; return 0; fi out=$(printf '%s' "$1" | openssl base64 -d -A 2>/dev/null) || true if [[ -n "${out:-}" ]]; then printf '%s' "$out"; return 0; fi printf '' } usage() { cat <<'EOF' myssh.sh — bash SSH helper with local address book (no Python). Usage: myssh.sh myssh.sh -l KEYWORD myssh.sh user@host [-l LABEL] Options: -l, --label LABEL With user@host: save label. Alone: filter list by label substring. -f, --file PATH Book file (default: ~/.myssh/book.tsv or MYSSH_DB). -h, --help This help. MobaXterm: run inside local bash; chmod +x myssh.sh; optional sshpass for saved passwords. EOF } TARGET="" LABEL_SET=0 LABEL_VAL="" DB_PATH="" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; -l|--label) [[ $# -ge 2 ]] || die "missing value for $1" LABEL_SET=1 LABEL_VAL="$2" shift 2 ;; -f|--file) [[ $# -ge 2 ]] || die "missing value for $1" DB_PATH="$2" shift 2 ;; -*) die "unknown option: $1" ;; *) [[ -z "$TARGET" ]] || die "unexpected extra argument: $1" TARGET="$1" shift ;; esac done DB="${DB_PATH:-${MYSSH_DB:-$DEFAULT_DB}}" mkdir -p "$(dirname "$DB")" 2>/dev/null || true umask 077 # --- 读写记录(TSV)--- load_all_lines() { [[ -f "$DB" ]] || return 0 # 跳过空行 grep -v '^[[:space:]]*$' "$DB" || true } write_all_lines() { local tmp tmp="$(mktemp "${TMPDIR:-/tmp}/myssh.XXXXXX")" if [[ $# -eq 0 ]]; then : >"$tmp" else printf '%s\n' -- "$@" >"$tmp" fi chmod 600 "$tmp" 2>/dev/null || true mv -f "$tmp" "$DB" chmod 600 "$DB" 2>/dev/null || true } find_record_pw_label() { # args: user host -> sets FOUND_PW FOUND_LABEL via nameref or globals local u="$1" h="$2" FOUND_PW="" FOUND_LABEL="" local line user host label pwb64 while IFS=$'\t' read -r user host label pwb64 || [[ -n "${user:-}" ]]; do [[ -z "${user:-}" ]] && continue if [[ "$user" == "$u" && "$host" == "$h" ]]; then FOUND_PW="$(b64_decode "${pwb64:-}")" FOUND_LABEL="${label:-}" return 0 fi done < <(load_all_lines) return 1 } merge_save() { local u="$1" h="$2" pw="$3" lab="$4" local pwb64 new_line user host label oldb pwb64="$(b64_encode "$pw")" new_line=$(printf '%s\t%s\t%s\t%s' "$u" "$h" "$lab" "$pwb64") local -a out=() local found=0 while IFS=$'\t' read -r user host label oldb || [[ -n "${user:-}" ]]; do [[ -z "${user:-}" ]] && continue if [[ "$user" == "$u" && "$host" == "$h" ]]; then out+=("$new_line") found=1 else out+=("$(printf '%s\t%s\t%s\t%s' "$user" "$host" "${label:-}" "${oldb:-}")") fi done < <(load_all_lines) if [[ "$found" -eq 0 ]]; then out+=("$new_line") fi if [[ ${#out[@]} -eq 0 ]]; then write_all_lines else write_all_lines "${out[@]}" fi } label_contains() { local hay="$1" nd="$2" local lh lnd lh=$(printf '%s' "$hay" | tr '[:upper:]' '[:lower:]') lnd=$(printf '%s' "$nd" | tr '[:upper:]' '[:lower:]') [[ "$lh" == *"$lnd"* ]] } run_ssh() { local u="$1" h="$2" pw="$3" local t="${u}@${h}" if [[ -n "$pw" ]] && command -v sshpass >/dev/null 2>&1; then SSHPASS="$pw" sshpass -e ssh "$t" return $? fi if [[ -n "$pw" ]] && ! command -v sshpass >/dev/null 2>&1; then echo "myssh: sshpass not found; running plain ssh (install sshpass for saved password)." >&2 fi ssh "$t" } pick_ssh_from_lines() { # Arg $1: path to TSV file (do not feed list on stdin — stdin must stay the terminal for "read"). local book="${1:?pick_ssh_from_lines: missing book file}" [[ -f "$book" ]] || { echo " (empty)" >&2; return 1; } local -a users=() hosts=() pws=() local i=0 user host label pwb64 pw while IFS=$'\t' read -r user host label pwb64 || [[ -n "${user:-}" ]]; do [[ -z "${user:-}" ]] && continue pw="$(b64_decode "${pwb64:-}")" i=$((i + 1)) users+=("$user") hosts+=("$host") pws+=("$pw") printf ' %3d [%s] %s@%s\n' "$i" "${label:-}" "$user" "$host" done <"$book" if [[ "$i" -eq 0 ]]; then echo " (empty)" >&2 return 1 fi local raw echo -n "Select 1-${i} (Enter to cancel): " >&2 if [[ -r /dev/tty ]]; then read -r raw < /dev/tty || true else read -r raw || true fi raw="${raw:-}" [[ -n "$raw" ]] || return 1 [[ "$raw" =~ ^[0-9]+$ ]] || { echo "myssh: invalid selection" >&2; return 1; } if [[ "$raw" -lt 1 || "$raw" -gt "$i" ]]; then echo "myssh: out of range" >&2 return 1 fi SSH_PICK_U="${users[$((raw - 1))]}" SSH_PICK_H="${hosts[$((raw - 1))]}" SSH_PICK_PW="${pws[$((raw - 1))]}" return 0 } MYSSH_PW_RESULT="" read_password() { local prompt="$1" MYSSH_PW_RESULT="" if [[ ! -t 0 ]]; then if [[ "${MYSSH_PASSWORD+x}" == "x" ]]; then echo "myssh: stdin not a TTY; using MYSSH_PASSWORD from environment." >&2 MYSSH_PW_RESULT="${MYSSH_PASSWORD-}" return 0 fi die "stdin is not a TTY. Use a real terminal, SSH keys (empty password), or export MYSSH_PASSWORD for this session." fi echo "myssh: password is hidden (no stars); type then press Enter." >&2 # Avoid $(read_password): command substitution runs in a subshell and can break TTY reads on some terminals. read -r -s -p "$prompt" MYSSH_PW_RESULT || true echo "" >&2 } # ----- 主流程 ----- if [[ -z "$TARGET" && "$LABEL_SET" -eq 1 ]]; then echo "Entries matching label '${LABEL_VAL}':" tmpf="$(mktemp "${TMPDIR:-/tmp}/myssh.f.XXXXXX")" while IFS=$'\t' read -r user host label pwb64 || [[ -n "${user:-}" ]]; do [[ -z "${user:-}" ]] && continue if label_contains "${label:-}" "$LABEL_VAL"; then printf '%s\t%s\t%s\t%s\n' "$user" "$host" "${label:-}" "${pwb64:-}" fi done < <(load_all_lines) >"$tmpf" if ! pick_ssh_from_lines "$tmpf"; then rm -f "$tmpf" exit 0 fi rm -f "$tmpf" run_ssh "$SSH_PICK_U" "$SSH_PICK_H" "$SSH_PICK_PW" exit $? fi if [[ -z "$TARGET" ]]; then echo "Saved entries:" tmpf="$(mktemp "${TMPDIR:-/tmp}/myssh.f.XXXXXX")" load_all_lines >"$tmpf" if ! pick_ssh_from_lines "$tmpf"; then rm -f "$tmpf" exit 0 fi rm -f "$tmpf" run_ssh "$SSH_PICK_U" "$SSH_PICK_H" "$SSH_PICK_PW" exit $? fi # user@host (use RUSER/RHOST: avoid clobbering login USER / rare HOST env quirks) [[ "$TARGET" == *"@"* ]] || die "expected user@host, got: $TARGET" RUSER="${TARGET%@*}" RHOST="${TARGET#*@}" [[ -n "$RUSER" && -n "$RHOST" ]] || die "invalid user@host: $TARGET" HAVE=0 PREV_PW="" PREV_LABEL="" if find_record_pw_label "$RUSER" "$RHOST"; then HAVE=1 PREV_PW="$FOUND_PW" PREV_LABEL="$FOUND_LABEL" fi if [[ "$HAVE" -eq 1 ]]; then read_password "Password [Enter to keep saved, or type new]: " PW="$MYSSH_PW_RESULT" [[ -n "$PW" ]] || PW="$PREV_PW" else read_password "Password (empty for key-only; entry still saved): " PW="$MYSSH_PW_RESULT" fi if [[ "$LABEL_SET" -eq 1 ]]; then EFFECTIVE_LABEL="$LABEL_VAL" elif [[ "$HAVE" -eq 1 ]]; then EFFECTIVE_LABEL="$PREV_LABEL" else EFFECTIVE_LABEL="" fi echo "myssh: saving book -> $DB" >&2 merge_save "$RUSER" "$RHOST" "$PW" "$EFFECTIVE_LABEL" echo "myssh: starting ssh..." >&2 run_ssh "$RUSER" "$RHOST" "$PW" exit $?
http://www.zskr.cn/news/1359697.html

相关文章:

  • 企业级应用通过Taotoken实现AI能力冗余与故障转移设计
  • ACS770还能打吗?最近测试了一款国产霍尔电流传感器
  • 硬核根基,智能载体:华清远见嵌入式“硬件+仿真+课程+师资”产教融合与实践教学方案
  • RAG-重排序策略
  • 【AI营销】为什么你的网站内容再好,AI也只引用其中一页?
  • 阿里云ACP云计算| 20人团考全员通过,恭喜!
  • 【限时解密】Lindy自动化方案未公开的4层权限熔断机制:为什么92%的企业跳过这步就触发合规雷区?
  • 3种实战方法搞定Docker镜像加速:从零到精通完全指南
  • Taotoken的Token Plan套餐如何帮助初创团队控制AI成本
  • 5款必备Illustrator脚本:让你的设计效率提升300%
  • 回收福禄克Fluke 5730A多功能校准器
  • AI Agent写诉状=执业风险?司法部新规生效倒计时30天,这4类文书必须人工复核
  • 如何快速找到互作基因?酵母筛库破解互作奥秘
  • Poppler Windows版:终极PDF处理方案,3分钟零配置部署指南
  • 如何通过NVIDIA Profile Inspector深度优化游戏性能:解锁显卡隐藏设置的完整指南
  • 防城港6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 别只看页面:盲盒源码小程序V6MAX系统与盲盒app源码程序解析 - 壹软科技
  • 个人报告6:学习资料上传与可配置解释来源
  • Flutter_01 工具准备1
  • 2026年全国青少年信息素养大赛初赛真题(算法应用主题赛C++小学组初赛真题2:文末附答案和解析)
  • 2026年全国青少年信息素养大赛初赛真题(算法应用主题赛C++小学组初赛真题1:文末附答案和解析)
  • DeepSeek-R1 在 CANN 上的推理部署
  • 最新论文降重工具横向测评|新手零踩雷选择指南
  • 钦州6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • RimSort终极指南:3步解决环世界MOD加载顺序混乱的完整方案
  • 股票低开必读:5条黄金口诀,教你反手掌握主动权
  • 从开题到定稿零返工:okbiye 毕业论文 AI 写作,把格式和内容难题都解决了
  • 资产治理:QNAP 存算融合架构理顺工程机械装配车间异构图纸流转
  • CTF-流量分析(布尔注入和时间盲注)
  • 【LeetCode刷题日记】一篇搞定二叉搜索树:从性质到实战,700.搜索+98.验证二叉搜索树