我已经设置了一个ssh代理,我可以在bash脚本的外部服务器上运行命令,执行如下操作:
1
| ssh blah_server"ls; pwd;" |
现在,我真正想做的是在外部服务器上运行大量的长命令。把所有这些都放在引号之间是很难看的,我真的希望避免多次使用ssh'ing来避免这种情况。
那么,有没有一种方法可以做到这一点,用括号或其他什么东西括起来?我在找一些符合以下条件的东西:
1 2 3 4 5
| ssh blah_server (
ls some_folder;
./someaction.sh;
pwd;
) |
基本上,只要解决方案是干净的,我都会满意的。
编辑
为了澄清这一点,我说这是一个更大的bash脚本的一部分。其他人可能需要直接处理这个脚本,所以我想保持它的整洁。我不想让一个bash脚本有一行看起来像:
1
| ssh blah_server"ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';" |
因为它非常难看,很难阅读。
- 嗯,把所有这些都放到服务器上的脚本中,然后用一个ssh调用来调用它,怎么样?
- @Nikolai如果命令依赖于客户端,那么可以将它们写入shell脚本,然后运行scp、ssh。我想这是最干净的方法。
- 这是一个更大的bash脚本的一部分,所以我不想把它分成一半在我的个人电脑上,另一半在服务器上,通过ssh运行。如果可能的话,我真的想把它作为一个脚本从我的个人电脑上运行。在ssh中是否真的没有一种干净的方法来封装一堆命令?
- 最好的方法不是使用bash,而是使用perl、python、ruby等。
- 为什么要避免将远程命令放在引号中?您可以在引号中添加新行,只要您愿意;使用字符串而不是标准输入意味着标准输入可用于读取远程脚本的输入。(尽管在Unix上,单引号通常优于双引号,除非您特别需要本地shell来计算字符串的某些部分。)
- @Khachik看到了另一个如何在服务器的客户端运行脚本的答案。例如,"s sh>@"bash-s"<./remote-commands.sh--arg1 arg2"。(不是这个问题的答案,它询问如何使用内联命令进行操作)
这里的bash文档如何:
1 2 3 4 5 6
| ssh otherhost << EOF
ls some_folder;
./someaction.sh 'some params'
pwd
./some_other_action 'other params'
EOF |
为了避免@globalz在评论中提到的问题,您可以(取决于您在远程站点上所做的)将第一行替换为
1
| ssh otherhost /bin/bash << EOF |
注意,您可以在这里的文档中进行变量替换,但是您可能需要处理引用问题。例如,如果您引用"限制字符串"(即上面的EOF),那么就不能进行变量替换。但在不引用极限字符串的情况下,变量被替换。例如,如果您在shell脚本中定义了上面的$NAME,则可以这样做。
1 2 3
| ssh otherhost /bin/bash << EOF
touch"/tmp/${NAME}"
EOF |
它将在目的地otherhost上创建一个文件,文件名为您分配给$NAME的任何文件。关于shell脚本引用的其他规则也同样适用,但在这里太复杂了。
- 这正是我想要的!工作怎么样?你会有一个链接到解释它的页面吗?
- +我只是在想我自己——这里有一个阅读的地方:tldp.org/ldp/abs/html/here-docs.html
- 当我使用这样的HereDoc时,"每日消息"登录会输出到本地。有没有一种方法可以阻止这个显示?
- 我还将这个输出发送到本地:不会分配伪终端,因为stdin不是终端。
- 为了防止命令行的扩展,引用单词(即第一个"e of")可能很重要。参见:Man Bash Less+/"这里是文档"
- 我希望在脚本中使用这个标签,通过使用<-eof,它将使用标签,但注意最后一个eof必须在同一行上。
- 我似乎必须全部引用,这样我才能将输出传输到像这样的位置[代码]ssh piback"/bin/bash</dev/null mkdir-p/mnt/mail backup mount-o ro,noload/dev/cruz/mail backup/mnt/mail backup tar-czp/mnt/mail backup umount/mnt/mail backup lvremove-f/dev/cruz/mailbackup>/dev/null eof"cat->pibak-mail.tar.gz
- 评论系统不允许我格式化以前的评论,所以它是一团糟
- @AKC42看起来很像我的远程备份脚本,除了我使用rsync with --link-dest保留多个版本。
- @paultomblin这就像你的脚本一样,只是为了让它正常工作,我不得不引用(和)从/bin/bash一直到末尾的eof。
- 因为某种原因,"这里的文件"对我来说不起作用。它始终存在于特定命令之后(可能是该命令返回非零退出代码)。但是,这是有效的-ssh$host'ls;pwd;cmd3;cmd4'
- 另外,stackoverflow.com/a/21761956/89771
- 当我用~作为主文件夹时,它甚至起作用了。它占用了远程服务器用户的主文件夹,就像我想要的那样。
- 当我在eof括号内使用"sudo-s"命令时,不会要求我输入密码(就像在脚本中运行单个ssh命令时一样)。我们该怎么做?
- @理查德在剧本中不使用sudo -S。你不应该在脚本中输入密码,尤其是如果你有sudo特权的话。
- 我没有在脚本中输入密码。我在终端机上打字。但在这里不起作用
- @理查德,因为你用的是-S,它说从stdin而不是从终端获取密码。
- 如果命令失败,也可以使用/bin/sh -e代替/bin/bash立即退出。
- 保罗,我想知道你是否可以在这里加上一个关于报价的说明?因为Heredocs不知道它们被用来控制另一个shell,所以包含空格或引号的变量可能会变得危险。在bash的最新版本中,这是最容易做到的,例如ssh <<-EOF mv ${source@Q} ${dest@Q} EOF。对于旧版本,printf '%q'"$myVariable"上的变体应该足够了。
- @Koyae最初的问题没有包括任何需要特别引用的内容。让我们不要把一个有七年历史的问题过于复杂化,而要详细说明一些有趣但不一定相关的细节。
- 保罗,我只是建议这样做,因为在这个问题上有近20万个视图,看起来很多人在使用ssh编写脚本时都会来这里。在编写脚本时,通常需要注入值。如果不是因为其他问题和评论中的噪音,我只会做一个,并在我的路上,但这在这个阶段不太可能被看到。一行脚注可能会减轻人们的一些主要头痛。
- 请考虑将"/bin/bash"转换为标准答案,不要将其作为可选答案提供,因为没有它就无法正常工作。不管怎样,答案都是。
在本地编辑脚本,然后将其导入ssh,例如
1
| cat commands-to-execute-remotely.sh | ssh blah_server |
其中EDOCX1[1]与上面的列表类似:
1 2 3
| 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中的论点来做这个吗?
- 我认为这比鲍尔托姆布林的解决方案有用得多。因为脚本可以重定向到任何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?
为了匹配您的示例代码,您可以将命令包装在单qoutes或双qoutes中。例如
1 2 3 4
| ssh blah_server"
ls
pwd
" |
- 我喜欢这种格式,但是遗憾的是,它对于将std数据存储到变量中没有用处。
- 先生,"将标准数据存储到变量中"是什么意思?
- @signus执行您描述的操作是完全可能的,尽管您可能希望在远程命令周围使用单引号而不是双引号(或者转义需要在双引号内转义的运算符,以防止本地shell截取和插入它们)。
- 我不能在双引号中输入这样的命令filetoremove=$(find)。-类型F-名称"xxx.war")。filetoremove内部应该有一个文件名,但它有一个空字符串。有什么东西需要逃走吗?
我看到两种方式:
首先你要做一个这样的控制插座:
1
| ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> |
运行你的命令
1
| ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand> |
这样,您就可以编写一个ssh命令,而无需实际重新连接到服务器。
第二步是动态生成脚本,scp运行它。
这也可以按如下方式进行。把你的命令放在一个脚本里,让我们把它命名为commands-inc.sh。
1 2 3 4
| #!/bin/bash
ls some_folder
./someaction.sh
pwd |
保存文件
现在在远程服务器上运行它。
1
| ssh user@remote 'bash -s' < /path/to/commands-inc.sh |
我从不失败。
- 与我最初的想法相似!但为什么需要bash -s?
- 另外,#!/bin/bash真的被使用了吗?
- -s是为了兼容性而存在的。从man bash-s中,如果存在-s选项,或者在选项处理之后没有参数,则从标准输入中读取命令。此选项允许在调用交互式shell时设置位置参数。
- 但你是对的,江户一号〔10〕不是真正需要的。这是在远程服务器上运行现有本地脚本的示例。
- RJ,谢谢!我的第一条评论是,看起来我们只是做了ssh user@remote < /path/to/commands-inc.sh(它似乎对我有用)。好吧,我想你的版本确保我们使用的是bashshell,而不是其他shell——这就是目的,不是吗?
- 谢谢。是的,我们中的一些人有我们的笔记,以及旧版本的bash和其他shell的习惯。-)
ssh并在bash中运行多个命令。
在字符串中用分号分隔命令,传递给echo,所有命令都通过管道传输到ssh命令。例如:
1 2 3 4 5 6 7 8
| 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脚本有一行看起来像……"
把所有的命令放到一个脚本上,它可以像
1
| ssh <remote-user>@<remote-host>"bash -s" <./remote-commands.sh |
- 有点奇怪,但效果很好。可以将参数传递给脚本(在重定向之前或之后)。例如,"s sh@"bash-s"arg1<./remote-commands.sh arg2'例如"ssh@"bash-s"--arg1<./remote-commands.sh arg2'
- 我有一个类似的问题和上面的另一个答案,但是包含bash -s的目的是什么?
- @flow2k-s用于指示bash从标准输入读取命令。这是man页If 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。我试过我的方法,但似乎奏效了,尽管我不确定它是否在某种程度上有缺陷。
- @从我对手册页的理解来看,如果没有这个选项,不会有任何不足。如果您试图传递任何参数,则是必需的。——谢谢
对于像我这样在这里跌跌撞撞的人,我成功地摆脱了分号和换行符:
第一步:分号。这样,我们就不会破坏ssh命令:
1 2
| ssh <host> echo test\;ls
^ backslash! |
列出了远程主机/主目录(以根目录登录),而
1 2
| ssh <host> echo test;ls
^ NO backslash |
列出了当前工作目录。
下一步:拆线:
1 2 3
| v another backslash!
ssh <host> echo test\;\
ls |
这再次列出了远程工作目录-改进的格式:
1 2 3
| ssh <host>\
echo test\;\
ls |
如果真的比这里更好的文件或断行引用-好吧,不是我来决定…
(使用bash,Ubuntu 14.04 LTS。)
这对于创建脚本很有效,因为您不必包括其他文件:
1 2 3 4 5 6 7
| #!/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 |
- "$(哪个bash)-s"部分提供了bash在本地计算机上的位置,而不是远程计算机上的位置。我认为您需要使用"$(which bash)-s"(单引号来抑制局部参数替换)。
- PaulTomblin在6年前提供了bash-here文档答案。这个答案的附加值是多少?
- -s旗,我举他为例,你可以用……来代替第一行,而我仅仅打电话给我记忆模糊的bash就没法逃脱了。
使用多行字符串和多个bash脚本发布的答案对我不起作用。
- 长的多行字符串很难维护。
- 单独的bash脚本不维护局部变量。
这里是一种在保持本地上下文的情况下进行ssh和运行多个命令的功能性方法。
1 2 3 4 5 6 7 8 9 10
| 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等工具在分布式环境中执行命令。
将系统配置为默认情况下使用多路复用的单个ssh会话的最简单方法。
这可以通过为套接字创建一个文件夹来完成:
1
| mkdir ~/.ssh/controlmasters |
然后将以下内容添加到.ssh配置中:
1 2 3 4 5
| 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/和http s://en.wikibooks.org/wiki/openssh/cookbook/multiplexing。
不确定长命令是否最干净,但肯定最简单:
1
| ssh user@host"cmd1; cmd2; cmd;3" |