ChatGPT解决这个技术问题 Extra ChatGPT

在 Bash 中 ssh 和运行多个命令的最干净的方法是什么?

我已经设置了 ssh 代理,我可以在 Bash 脚本中的外部服务器上运行命令,执行以下操作:

ssh blah_server "ls; pwd;"

现在,我真正想做的是在外部服务器上运行很多长命令。将所有这些括在引号之间会非常难看,我真的宁愿避免多次使用 ssh'ing 来避免这种情况。

那么,有没有一种方法可以让我一次性用括号或其他东西做到这一点?我正在寻找类似的东西:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

基本上,只要它是干净的,我会对任何解决方案感到满意。

编辑

为了澄清,我说这是一个更大的 bash 脚本的一部分。其他人可能需要处理脚本,所以我想保持干净。我不想有一个 bash 脚本,其中一行看起来像:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

因为它非常丑陋且难以阅读。

嗯,如何将所有这些都放入服务器上的脚本中,然后通过一次 ssh 调用来调用它?
@Nikolai 如果命令依赖于客户端,可以将它们写入 shell 脚本,然后 scpssh 并运行。我认为这将是最干净的方式。
这是一个更大的 bash 脚本的一部分,所以我不想把它分成一半在我的个人计算机上,另一半在服务器上并通过 ssh 运行。如果可能的话,我真的很想把它作为一个脚本从我的个人计算机上运行。真的没有干净的方法可以将一堆命令封装在 ssh 中吗?
最好的方法不是使用 bash,而是使用 Perl、Python、Ruby 等。
为什么要避免将远程命令放在引号中?您可以在引号内添加换行符,数量不限;并且使用字符串而不是标准输入意味着标准输入可用于例如读取远程脚本的输入。 (尽管在 Unix 上,单引号通常优于双引号,除非您特别需要本地 shell 来评估字符串的某些部分。)

P
Paul Tomblin

Bash Here Document 怎么样:

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

为避免@Globalz 在评论中提到的问题,您可以(取决于您在远程站点上执行的操作)将第一行替换为

ssh otherhost /bin/bash << EOF

请注意,您可以在 Here 文档中进行变量替换,但您可能必须处理引用问题。例如,如果您引用“限制字符串”(即上面的 EOF),那么您不能进行变量替换。但不引用限制字符串,变量将被替换。例如,如果您在 shell 脚本中定义了上面的 $NAME,您可以这样做

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

它会在目标 otherhost 上创建一个文件,其名称与您分配给 $NAME 的名称相同。其他关于 shell 脚本引用的规则也适用,但是这里太复杂了。


这看起来正是我想要的!效果如何?你会碰巧有一个链接到一个解释它的页面吗?
+1 我自己只是在想——这里有一个地方可以阅读:tldp.org/LDP/abs/html/here-docs.html
我也得到这个输出到我的本地:伪终端将不会被分配,因为标准输入不是终端。
对你来说,引用这个词(即第一个'EOF')可能很重要,以防止命令行扩展。请参阅:男人 bash |少 +/'这里的文档'
Paul,我之所以建议它,是因为在这个问题上有近 20 万的浏览量,看起来很多人在使用 ssh 编写脚本时都会来到这里。在编写脚本时需要注入值是很常见的。如果不是因为其他问题和评论中的噪音,我只会提出一个并继续前进,但在这个阶段不太可能看到它。单行脚注可能会让人头疼。
b
bosmacs

在本地编辑您的脚本,然后将其通过管道传输到 ssh,例如

cat commands-to-execute-remotely.sh | ssh blah_server

其中 commands-to-execute-remotely.sh 看起来像您上面的列表:

ls some_folder
./someaction.sh
pwd;

这有一个很大的优势,即您确切地知道远程脚本正在执行什么 - 引用没有问题。如果您需要动态命令,您可以使用带有子shell 的shell 脚本,仍然通过管道进入ssh,即( echo $mycmd $myvar ; ...) | ssh myhost - 与cat 用法一样,您可以确切地知道ssh 命令流中的内容。当然,为了便于阅读,脚本中的子 shell 可以是多行的 - 请参阅 linuxjournal.com/content/bash-sub-shells
您可以使用 commands-to-execute-remotely.sh 中的参数来执行此操作吗?
我认为这比 paultomblin 解决方案有用得多。由于脚本可以重定向到任何 ssh 服务器,而无需打开文件。它也更具可读性。
是的,但是使用 echo 或 here 文档(请参阅最佳答案):使用:$localvar 解释本地定义的变量,\$remotevar 远程解释远程定义的变量,\$(something with optionnal args) 获取执行的输出在远程服务器上。您可以通过 1 进行 ssh 的示例(或者,如此处所示,多个 ssh 命令):echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh user1@hop1 ssh user2@hop2 ssh user@finalserver bash
嗯...这与使用输入重定向(即 ssh blah_server < commands-to-execute-remotely.sh)有何不同?
A
Andrei B

为了匹配您的示例代码,您可以将命令包装在单引号或双引号内。例如

ssh blah_server "
  ls
  pwd
"

我喜欢这种格式,但遗憾的是它对于将标准数据存储到变量中没有用。
Signus,“将标准数据存储到变量中”是什么意思?
@Signus完全可以按照您的描述进行操作,尽管您可能希望在远程命令周围使用单引号而不是双引号(或转义需要在双引号内转义的运算符,以防止您的本地shell拦截和对它们进行插值)。
我无法在双引号代码中添加这样的命令 fileToRemove=$(find . -type f -name 'xxx.war')。 fileToRemove 里面应该有一个文件名,但它有一个空字符串。有什么需要逃避的吗?
t
terminus

我看到两种方法:

首先,您制作一个这样的控制套接字:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

并运行你的命令

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

这样您就可以编写 ssh 命令而无需实际重新连接到服务器。

第二种是动态生成脚本,scp执行它并运行。


R
R J

这也可以如下进行。将您的命令放入脚本中,我们将其命名为 commands-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

保存文件

现在在远程服务器上运行它。

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

对我来说从来没有失败过。


跟我当初想的差不多!但为什么需要 bash -s
-s 是为了兼容性。从 man bash -s 如果存在 -s 选项,或者在选项处理后没有保留参数,则从标准输入读取命令。此选项允许在调用交互式 shell 时设置位置参数。
RJ,谢谢!在我的第一条评论中,看起来我们只是做ssh user@remote < /path/to/commands-inc.sh(它似乎对我有用)。好吧,我猜您的版本确保我们使用 bash 外壳,而不是其他外壳 - 这就是目的,不是吗?
谢谢。是的,我们中的一些人有旧版本的 bash 和其他 shell 的笔记和习惯。 :-)
@RJ 不确定它与我的答案有何不同:)。无论如何干杯
J
Jai Prakash

将所有命令放到一个脚本中,它可以像这样运行

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh

有点奇怪,但效果很好。并且 args 可以传递给脚本(在重定向之前或之后)。例如 'ssh @ "bash -s" arg1 <./remote-commands.sh arg2' 例如 'ssh @ "bash -s" - - -arg1 <./remote-commands.sh arg2'
我有一个类似的问题,上面有另一个答案,但是包含 bash -s 的目的是什么?
@flow2k -s 表示 bash 从标准输入读取命令。以下是来自 manIf the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell. 的说明
@JaiPrakash 谢谢你,但我实际上在想我们是否可以完全取消 bash 命令,即 ssh remote-user@remote-host <./remote-commands.sh。我尝试了我的方法,它似乎有效,但我不确定它是否在某些方面存在缺陷。
@flow2k 根据我对手册页的理解,没有该选项不会有任何不足。如果您尝试传递任何参数,则它是必需的。 - 谢谢
D
DimiDak

不确定是否是最干净的长命令,但肯定是最简单的:

ssh user@host "cmd1; cmd2; cmd3"

最好的简单!
一旦您需要在远程命令中附加引用,这就会崩溃。您需要传递到远程 shell 的任何美元符号或反引号都需要反斜杠,或者您需要额外的引用。单引号而不是双引号是解决简单引用难题的好方法,但在这种情况下,往往只会将问题推到另一个角落,因为远程 shell 会占用一层转义。
E
Eric Leschinski

SSH 并在 Bash 中运行多个命令。

在字符串中用分号分隔命令,传递给 echo,全部通过管道传递到 ssh 命令。例如:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux

arnab,将来,请简要描述您的代码的作用。然后说说它是怎么工作的,然后放上代码。然后将代码放在代码块中,以便于阅读。
从问题中,您提供了 OP 想要避免的确切内容:“我不希望 bash 脚本的一行看起来像......”
s
sjas

这适用于创建脚本,因为您不必包含其他文件:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF

# you can use '$(which bash) -s' instead of my "bash -s" as well
# but bash is usually being found in a standard location
# so for easier memorizing it i leave that out
# since i dont fat-finger my $PATH that bad so it cant even find /bin/bash ..

“$(which bash) -s” 部分为您提供本地机器上 bash 的位置,而不是远程机器上的位置。我认为你想要 '$(which bash) -s' 代替(单引号来抑制本地参数替换)。
Paul Tomblin provided the Bash Here Document 6 年前的回答。这个答案增加了什么价值?
-s 标志,我引用了他 您可能可以将第一行替换为...,而我无法仅调用 bash 就可以逃脱,我依稀记得。
M
Michael Petrochuk

使用多行字符串和多个 bash 脚本发布的答案对我不起作用。

长的多行字符串很难维护。

单独的 bash 脚本不维护局部变量。

这是一种在保持本地上下文的同时 ssh 和运行多个命令的实用方法。

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"

您正在考虑这一点,好像有人想要这样做的唯一原因是他们是否坐在命令行上。这个问题还有其他常见原因,例如使用 rundeck、jenkins 等工具跨分布式环境执行命令。
@KamilCuk 的答案是(很多!)更好的版本,但更复杂且难以阅读和理解。
A
Aconcagua

对于像我一样在这里跌跌撞撞的人-我成功地逃脱了分号和换行符:

第一步:分号。这样,我们就不会破坏 ssh 命令:

ssh <host> echo test\;ls
                    ^ backslash!

列出了远程主机 /home 目录(以 root 身份登录),而

ssh <host> echo test;ls
                    ^ NO backslash

列出了当前的工作目录。

下一步:拆线:

                      v another backslash!
ssh <host> echo test\;\
ls

这再次列出了远程工作目录 - 改进了格式:

ssh <host>\
  echo test\;\
  ls

如果真的比这里的文档或虚线周围的引号更好 - 好吧,不是我决定......

(使用 bash,Ubuntu 14.04 LTS。)


更好的是你可以使用 && 和 ||也这样 - echo test \&\& ls
@MiroKropacek 这也很好。更好的?取决于你想要什么;有条件地运行第二个命令,然后是的,无条件地运行它(无论第一个命令是成功还是失败),然后不是......
@MiroKropacek,我不必逃避 &&在命令周围加上双引号时:ssh host "echo test && ls"
引用或反斜杠是必要的,但显然不是两者都需要,在这里(尽管在某些情况下,您确实需要从本地和远程 shell 中引用 shell 元字符)。
J
Jonathan

将系统配置为默认使用单 ssh 会话和多路复用的最简单方法。

这可以通过为套接字创建一个文件夹来完成:

mkdir ~/.ssh/controlmasters

然后将以下内容添加到您的 .ssh 配置中:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

现在,您无需修改任何代码。这允许在不创建多个会话的情况下多次调用 ssh 和 scp,这在本地和远程机器之间需要更多交互时很有用。

感谢@terminus 的回答,http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing


K
KamilCuk

在 Bash 中 ssh 和运行多个命令的最干净的方法是什么?

我推荐使用这个转义函数。该函数接受一个参数 - 一个要转义的函数。然后 sshqfunc 输出函数的 declare -f,然后输出一个字符串,该字符串将调用带有正确引用的 "$@" 参数的函数。然后整个被引用 "%q" 并添加 bash -c。如果遥控器没有 bash,您可以将 bash 更改为 sh。

sshqfunc() { echo "bash -c $(printf "%q" "$(declare -f "$@"); $1 \"\$@\"")"; };

然后用你想在遥控器上做的工作定义一个函数。该函数是正常定义的,因此它将被正确地“干净”。您可以在本地测试此类功能。定义后,将正确转义的函数传递给遥控器。

work() {
   ls
   pwd
   echo "Some other command"
}

ssh host@something "$(sshqfunc work)"

传递 你也可以传递参数,它们将作为位置参数传递给你的函数。函数之后的右下一个参数将分配给 $0 - 通常使用 --_ 之类的占位符将参数与调用分开。

work() {
   file=$1
   num=$2
   ls "$file"
   echo "num is $num"
}

ssh host@something "$(sshqfunc work)" -- /this/file 5

但请注意,如果有任何魔术字符,也应正确引用参数:

ssh host@something "$(sshqfunc work)" -- "$(printf "%q" "$var1" "$var2")"

总体上很好的建议,但对于不精通 Bash 的人来说绝对无法理解或维护。
S
Soufiane Sakhi

对于简单的命令,您可以使用:

ssh <ssh_args> command1 '&&' command2

或者

ssh <ssh_args> command1 \&\& command2