如何在bash中检查ssh-agent是否已经运行?

89

我在我的Linux环境中有一个示例sh脚本,基本上是为当前shell运行ssh-agent,将一个密钥添加到其中并运行两个git命令:

#!/bin/bash
eval "$(ssh-agent -s)"
ssh-add /home/duvdevan/.ssh/id_rsa

git -C /var/www/duvdevan/ reset --hard origin/master
git -C /var/www/duvdevan/ pull origin master

脚本实际上运行得很好,但每次运行它时,我都会得到一个新的进程,所以我认为这可能会成为性能问题,并且最终可能会有无用的进程存在。

输出示例:

Agent pid 12109
Identity added: /home/duvdevan/.ssh/custom_rsa (rsa w/o comment)

此外,是否可以找到现有的 ssh-agent 进程并将我的密钥添加到其中?


4
请尝试使用 $SSH_AGENT_PID - choroba
1
我不能这样做,因为每天需要多次登录到计算机上,不止是我一个人,还有其他人。 - Zlatan Omerović
在注销时终止代理,或者使用代理转发仅在本地计算机上运行代理,如果您正在ssh到该计算机。 - chepner
另一种变体是通过 ps aux | grep ssh-agent 检查是否有任何代理进程正在运行。如果您将其进一步传输到 | wc -l,并且这个值等于“0”,则不再有运行中的进程。 - MichaelHuelsen
你可能会认为代理本身会有一个内置选项,只有在没有运行时才运行新进程...但是不,我们必须编写一些复杂的脚本! - ADTC
显示剩余5条评论
14个回答

79

真的,如何在bash中检查ssh-agent是否已经运行?

到目前为止,答案似乎没有回答原始问题...

这是对我有效的方法:

if ps -p $SSH_AGENT_PID > /dev/null
then
   echo "ssh-agent is already running"
   # Do something knowing the pid exists, i.e. the process with $PID is running
else
eval `ssh-agent -s`
fi

这段内容摘自这里


7
通常不起作用,无论是在图形会话(其中ssh-agent在本地运行或内置于密钥管理器中)还是在带有ssh -A的会话中,其中ssh-agent在本地运行。正确的方法可以在idbrii的答案中找到。 - Tino
1
可以确认 SSH_AGENT_PID 是不可靠的。在我的 Mac(High Sierra)和 SSH OpenSSH_7.8p1 上,当直接调用 ssh 连接,例如 ssh host 时,代理程序是使用 SSH_AUTH_SOCK 启动的,但 不是 SSH_AGENT_PID - Alexander Kucheryuk

63

另外,除此之外,找到现有的ssh-agent进程并将我的密钥添加到其中是否可能?

是的。我们可以将连接信息存储在一个文件中:

# Ensure agent is running
ssh-add -l &>/dev/null
if [ "$?" == 2 ]; then
    # Could not open a connection to your authentication agent.

    # Load stored agent connection info.
    test -r ~/.ssh-agent && \
        eval "$(<~/.ssh-agent)" >/dev/null

    ssh-add -l &>/dev/null
    if [ "$?" == 2 ]; then
        # Start agent and store agent connection info.
        (umask 066; ssh-agent > ~/.ssh-agent)
        eval "$(<~/.ssh-agent)" >/dev/null
    fi
fi

# Load identities
ssh-add -l &>/dev/null
if [ "$?" == 1 ]; then
    # The agent has no identities.
    # Time to add one.
    ssh-add -t 4h
fi

这段代码来自ssh代理的缺陷,它描述了你当前做法的缺陷以及如何使用ssh-ident来替代。


如果您只想在ssh-agent未运行时运行它,否则不执行任何操作:

if [ $(ps ax | grep [s]sh-agent | wc -l) -gt 0 ] ; then
    echo "ssh-agent is already running"
else
    eval $(ssh-agent -s)
    if [ "$(ssh-add -l)" == "The agent has no identities." ] ; then
        ssh-add ~/.ssh/id_rsa
    fi

    # Don't leave extra agents around: kill it on exit. You may not want this part.
    trap "ssh-agent -k" exit
fi

然而,这并不能确保 ssh-agent 可以被访问(仅仅因为它正在运行并不意味着我们有 $SSH_AGENT_PID 来供 ssh-add 连接)。


9
这应该是被接受的答案,至少前半部分是正确的(因为ssh-add -l是测试活动代理的正确方法)。我建议使用timeout 0.3 ssh-add -l,因为ssh-add可能会在断开的ssh连接上挂起���例如在tmux中。您的第一个脚本适用于KDE或通过ssh -A进行的远程会话。但是,像这里的所有其他答案一样,后半部分几乎没用,因为通常没有本地运行的ssh-agent。顺便说一下:为什么在grep [s]sh-agent中要加[s]而不是grep -F ssh-agent(这可以节省一些循环)。 - Tino
4
通常情况下本地没有运行ssh-agent,但在OP的情况下,他们的问题是多个ssh-agent。为什么会出现[s]?ps ax|grep -F ssh-agent将返回grep进程,因为ps输出包括程序参数(试试看)。在正则表达式中使用字符类将防止grep匹配自身。另请参见https://dev59.com/FWox5IYBdhLWcg3wLhei#9375940。 - idbrii
1
这也是一个很棒的答案,因为它告诉我们“如何判断ssh-agent已经挂掉”或“如何在不知道pid的情况下判断ssh-agent是否正在运行”。当与代理转发一起使用时,配合screentmux非常有用。 - Dallaylaen
我从来没有考虑过执行 eval "$(<~/.ssh-agent)",而是更愿意使用 source ~/.ssh-agent。只是好奇这两者是否有任何区别。 - chutz
1
@chutz:ssh-agent文档建议使用eval(可能是因为它们不使用中间文件),但我不确定是否有区别。这可能是一个很好的问题(最接近的我找到的是这个)。 - idbrii
我的zsh一行代码:if [[ $(ssh-add -l &>/dev/null)? -eq 2 ]] ; then eval $(ssh-agent); fi - Cody

10

ps -p $SSH_AGENT_PID > /dev/null || eval "$(ssh-agent -s)"

这是一条单行命令。第一次运行将启动ssh-agent,第二次运行将不会启动ssh-agent。简单而优雅!


注:该命令用于在Linux或MacOS系统上启动ssh-agent,以便在使用ssh连接时无需每次输入密码。

1
不是十分完美,但是相当优雅的解决方案 :) - victmo
2
在某些系统中,如果 $SSH_AGENT_PID 没有设置,您可能会收到警告。此外,在运行 eval ssh-agent -s 后,您将收到一条消息。我修改了上面的内容,使其完全静音: ps -p "000$SSH_AGENT_PID" > /dev/null || eval "$(ssh-agent -s) > /dev/null" - victmo

10
如果你希望在脚本退出后立即将其终止,只需在eval行之后添加以下代码:
trap "kill $SSH_AGENT_PID" exit
或:
trap "ssh-agent -k" exit

$SSH_AGENT_PID会在ssh-agent -seval中被设置。

你可以通过扫描/tmp/ssh-*来找到正在运行的ssh-agent,并从中重建SSH_AGENT变量(SSH_AUTH_SOCKSSH_AGENT_PID)。


为什么我不应该像@alok在问题的评论中所说的那样,在我的脚本末尾添加kill -9 $SSH_AGENT_PID - Zlatan Omerović
2
如果脚本在运行时被杀死(使用可中断的信号),该命令将不会运行。而使用trap可以保证其执行。 - Friek
此外,除了在开发过程中杀死一个有错误的程序之外,kill -9 永远不应该是必要的。在几乎所有情况下,仅使用 kill 就足够了。 - chepner
1
是的。我刚刚看到ssh-agent -k会杀死进程并取消设置SSH_AUTH_SOCKSSH_AGENT_PID变量 - 我正在使用您的解决方案与trap。谢谢! - Zlatan Omerović

6

使用$SSH_AGENT_PID只能测试ssh-agent,但是当身份没有被添加时会丢失身份验证信息。

$ eval `ssh-agent`
Agent pid 9906
$ echo $SSH_AGENT_PID
9906
$ ssh-add -l
The agent has no identities.

因此,可以使用类似以下expect脚本的示例,通过ssh-add -l进行检查:

$ eval `ssh-agent -k`
Agent pid 9906 killed
$ ssh-add -l
Could not open a connection to your authentication agent.
$ ssh-add -l &>/dev/null
$ [[ "$?" == 2 ]] && eval `ssh-agent`
Agent pid 9547
$ ssh-add -l &>/dev/null
$ [[ "$?" == 1 ]] && expect $HOME/.ssh/agent
spawn ssh-add /home/user/.ssh/id_rsa
Enter passphrase for /home/user/.ssh/id_rsa: 
Identity added: /home/user/.ssh/id_rsa (/home/user/.ssh/id_rsa)
$ ssh-add -l
4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /home/user/.ssh/id_rsa (RSA)

当在bash脚本中同时运行ssh-agentssh-add -l时:

#!/bin/bash
ssh-add -l &>/dev/null
[[ "$?" == 2 ]] && eval `ssh-agent`
ssh-add -l &>/dev/null
[[ "$?" == 1 ]] && expect $HOME/.ssh/agent

那么它将一直检查和确保连接正在运行:

$ ssh-add -l
4096 SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /home/user/.ssh/id_rsa (RSA)


您也可以使用do while来模拟以上脚本中的命令重复。

3

对于我来说,所接受的答案在Ubuntu 14.04下并没有起作用。

我必须使用以下测试来检查ssh-agent是否正在运行:

[[ ! -z ${SSH_AGENT_PID+x} ]]

我要开始使用ssh代理:

exec ssh-agent bash

否则,SSH_AGENT_PID 将不会被设置。
以下内容似乎在 Ubuntu 14.04 和 18.04 下都有效。
#!/bin/bash
sshkey=id_rsa
# Check ssh-agent
if [[ ! -z ${SSH_AGENT_PID+x} ]]
then
    echo "[OK] ssh-agent is already running with pid: "${SSH_AGENT_PID}
else
    echo "Starting new ssh-agent..."
    `exec ssh-agent bash`
    echo "Started agent with pid: "${SSH_AGENT_PID}
fi
# Check ssh-key
if [[ $(ssh-add -L | grep ${sshkey} | wc -l) -gt 0 ]]
then
    echo "[OK] SSH key already added to ssh-agent"
else
    echo "Need to add SSH key to ssh-agent..."
    # This should prompt for your passphrase
    ssh-add ~/.ssh/${sshkey}
fi

2

关于查找正在运行的ssh代理,之前的答案要么无效,要么依赖于像$HOME/.ssh_agent这样的神奇文件。这些方法需要我们相信用户从未运行过代理,并将其输出保存到此文件中。

相反,我的方法依赖于很少更改的默认UNIX域套接字模板,以在可用的可能性中找到可访问的ssh-agent

# (Paste the below code to your ~/.bash_profile and ~/.bashrc files)
C=$SSH_AUTH_SOCK
R=n/a
unset SSH_AUTH_SOCK
for s in $(ls $C /tmp/ssh-*/agent.* 2>/dev/null | sort -u) ; do
  if SSH_AUTH_SOCK=$s ssh-add -l >/dev/null ; then R=$? ; else R=$? ; fi
  case "$R" in
    0|1) export SSH_AUTH_SOCK=$s ; break ;;
  esac
done
if ! test -S "$SSH_AUTH_SOCK" ; then
  eval $(ssh-agent -s)
  unset SSH_AGENT_PID
  R=1
fi
echo "Using $SSH_AUTH_SOCK"
if test "$R" = "1" ; then
  ssh-add
fi

在这种方法中,SSH_AGENT_PID是未知的,因为对于非root用户来说很难推断出它。我认为对于用户来说实际上并不需要它,因为他们通常不想停止代理程序。在我的系统上,设置SSH_AUTH_SOCK就足以与代理进行密码验证之类的通信。该代码应该可以在任何兼容shell的shell上运行。

2
感谢这里的所有回答。多年来,我已经多次使用此线程来调整我的方法。想要分享一下我的当前适用于Linux和OSX的ssh-agent.sh检查器/启动脚本。
以下是我的$HOME/.bash.d/ssh-agent.sh代码块。
function check_ssh_agent() {
  if [ -f $HOME/.ssh-agent ]; then
    source $HOME/.ssh-agent > /dev/null
  else
    # no agent file
    return 1
  fi

  if [[ ${OSTYPE//[0-9.]/} == 'darwin' ]]; then
    ps -p $SSH_AGENT_PID > /dev/null  
    # gotcha: does not verify the PID is actually an ssh-agent
    # just that the PID is running
    return $?
  fi

  if [ -d /proc/$SSH_AGENT_PID/ ]; then
    # verify PID dir is actually an agent
    grep ssh-agent /proc/$SSH_AGENT_PID/cmdline  > /dev/null  2> /dev/null; 
    if [ $? -eq 0 ]; then
      # yep - that is an agent
      return 0
    else
      # nope - that is something else reusing the PID
      return 1
    fi
  else
    # agent PID dir does not exist - dead agent
    return 1
  fi 
}

function launch_ssh_agent() {
  ssh-agent > $HOME/.ssh-agent
  source $HOME/.ssh-agent
  # load up all the pub keys
  for I in $HOME/.ssh/*.pub ; do
    echo adding ${I/.pub/}
    ssh-add ${I/.pub/}
  done
}

check_ssh_agent
if [ $? -eq 1 ];then
  launch_ssh_agent
fi

我通过以下方式从我的.bashrc启动上述内容:
if [ -d $HOME/.bash.d ]; then
  for I in $HOME/.bash.d/*.sh; do
    source $I  
  done
fi

希望这能帮助其他人快速上手。如果你想和我一起改进,可以查看我创建的公共代码片段:https://gist.github.com/dayne/a97a258b487ed4d5e9777b61917f0a72

1
cat /usr/local/bin/ssh-agent-pro << 'EOF'
#!/usr/bin/env bash
SSH_AUTH_CONST_SOCK="/var/run/ssh-agent.sock"

if [[ x$(wc -w <<< $(pidof ssh-agent)) != x1 ]] || [[ ! -e ${SSH_AUTH_CONST_SOCK} ]]; then
  kill -9 $(pidof ssh-agent) 2>/dev/null
  rm -rf ${SSH_AUTH_CONST_SOCK}
  ssh-agent -s -a ${SSH_AUTH_CONST_SOCK} 1>/dev/null
fi

echo "export SSH_AUTH_SOCK=${SSH_AUTH_CONST_SOCK}"
echo "export SSH_AGENT_PID=$(pidof ssh-agent)"
EOF
echo "eval \$(/usr/local/bin/ssh-agent-pro)" >> /etc/profile
. /etc/profile

然后您只需执行ssh-add xxxx一次,每次登录时都可以使用ssh-agent。


@Wang-Zhang 不错的 ssh-agent 封装。我喜欢它 :+1: - SebMa

1
我注意到仅有一个运行的代理是不够的,因为有时候,SSH_AUTH_SOCK 变量被设置或指向一个不存在的套接字文件。
因此,要连接到已在您的机器上运行的 ssh-agent,您可以执行以下操作:
$ pgrep -u $USER -n ssh-agent -a
1906647 ssh-agent -s
$ ssh-add -l
Could not open a connection to your authentication agent.
$ test -z "$SSH_AGENT_PID" && export SSH_AGENT_PID=$(pgrep -u $USER -n ssh-agent)
$ test -z "$SSH_AUTH_SOCK" && export SSH_AUTH_SOCK=$(ls /tmp/ssh-*/agent.$(($SSH_AGENT_PID-1)))
$ ssh-add -l
The agent has no identities.

1
这是一个漂亮而巧妙的技巧,解决了将代理 PID 与现有套接字配对的问题!这里是最佳答案。 - bubak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接