将命令作为输入传递给另一个命令(su、ssh、sh 等)

Posted

技术标签:

【中文标题】将命令作为输入传递给另一个命令(su、ssh、sh 等)【英文标题】:Pass commands as input to another command (su, ssh, sh, etc) 【发布时间】:2016-10-01 21:25:44 【问题描述】:

我有一个脚本,我需要在其中启动一个命令,然后将一些附加命令 作为命令 传递给该命令。我试过了

su
echo I should be root now:
who am I
exit
echo done.

...但它不起作用:su 成功,但命令提示符只是盯着我看。如果我在提示符下键入exitechowho am i 等就会开始执行!而echo done. 根本不会被执行。

同样,我需要在 ssh 上工作:

ssh remotehost
# this should run under my account on remotehost
su
## this should run as root on remotehost
whoami
exit
## back
exit
# back

我该如何解决这个问题?

我正在寻找能够以一般方式解决此问题的答案,而不是特别针对 sussh 的答案。目的是让这个问题成为这个特定模式的canonical。

【问题讨论】:

相关:***.com/questions/17758235/… 你为什么不创建一个shell-script 来使用你想以root 身份运行的命令呢?然后你所要做的就是运行sudo sh yourshellscript.sh @JoseSerodio 这不适用于 ssh 场景,例如(或者您必须先将脚本 scp 到远程主机),并且在命令只是一些琐碎的(然后您必须管理两个脚本文件,并确保调用脚本知道另一个脚本的路径)。当然,它不容易扩展到某些命令是动态的场景(例如,以调用脚本中执行的计算为条件)。 【参考方案1】:

添加到tripleee 的answer:

重要的是要记住,脚本部分被格式化为另一个 shell 的 here-document 是在具有自己环境的不同 shell 中执行的(甚至可能在不同的机器上)。

如果您的脚本块包含参数扩展、命令替换和/或算术扩展,那么您必须稍微不同地使用 shell 的 here-document 工具,具体取决于您希望在哪里执行这些扩展。

1。所有扩展都必须在父 shell 范围内执行。

那么这里文档的分隔符必须不加引号

command <<DELIMITER
...
DELIMITER

例子:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

输出:

a=0
mylogin=leon
a=0
mylogin=leon

2。所有扩展都必须在子 shell 范围内执行。

那么这里文档的分隔符一定要引用

command <<'DELIMITER'
...
DELIMITER

例子:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<'END'
    a=1
    mylogin=$(whoami)
    echo a=$a
    echo mylogin=$mylogin
END
echo a=$a
echo mylogin=$mylogin

输出:

a=1
mylogin=root
a=0
mylogin=leon

3。一些扩展必须在子 shell 中执行,一些 - 在父 shell 中执行。

那么这里文档的分隔符必须不加引号,并且你必须转义那些必须在子shell中执行的扩展表达式

例子:

#!/bin/bash

a=0
mylogin=$(whoami)
sudo sh <<END
    a=1
    mylogin=\$(whoami)
    echo a=$a
    echo mylogin=\$mylogin
END
echo a=$a
echo mylogin=$mylogin

输出:

a=0
mylogin=root
a=0
mylogin=leon

【讨论】:

【参考方案2】:

shell 脚本是一系列命令。 shell 会读取脚本文件,并一个接一个地执行这些命令。

在通常情况下,这里没有意外;但是一个常见的初学者错误是假设 some 命令将从 shell 中接管,并开始在脚本文件中执行以下命令,而不是当前运行此脚本的 shell。但这不是它的工作原理。

基本上,脚本完全像交互式命令一样工作,但需要正确理解它们的工作方式完全。以交互方式,shell 读取一个命令(从标准输入),运行该命令(使用来自标准输入的输入),完成后,它读取另一个命令(从标准输入)。

现在,当执行一个脚本时,标准输入仍然是终端(除非你使用了重定向)但是命令是从脚本文件中读取的,而不是从标准输入中读取的。 (相反的情况确实很麻烦 - 任何read 都会消耗脚本的下一行,cat 会吞噬脚本的所有其余部分,并且无法与之交互!)脚本文件 only 包含执行它的 shell 实例的命令(尽管您当然仍然可以使用 here 文档等将输入嵌入为命令参数)。

换句话说,这些“被误解”的命令(susshshsudobash 等)在单独运行时(不带参数)将启动一个交互式 shell,并在交互式会话,这显然很好;但是从脚本运行时,这通常不是您想要的。

所有这些命令都可以通过交互式终端会话以外的方式接受命令。通常,每个命令都支持一种将命令作为选项或参数传递的方式:

su root -c 'who am i'
ssh user@remote uname -a
sh -c 'who am i; echo success'

其中许多命令也将接受标准输入上的命令:

printf 'uname -a; who am i; uptime' | su
printf 'uname -a; who am i; uptime' | ssh user@remote
printf 'uname -a; who am i; uptime' | sh

这也让您可以方便地使用这里的文档:

ssh user@remote <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

sh <<'____HERE'
    uname -a
    who am i
    uptime
____HERE

对于接受单个命令参数的命令,该命令可以是带有多个命令的shbash

sudo sh -c 'uname -a; who am i; uptime'

顺便说一句,您通常不需要显式的exit,因为该命令在执行您传入执行的脚本(命令序列)后无论如何都会终止。

【讨论】:

不会 a | ssh ..@... 只是将标准输入传递给服务器端的命令,因此需要 a | ssh ...@... 'sh' @andlrc 不,不需要。在没有显式命令的情况下,ssh 运行一个 shell。 ... 虽然这将是用户的登录 shell,现在很少使用 sh script 命令不能移植地接受命令作为命令行参数,尽管 Linux script 具有 script -c 'your command here'【参考方案3】:

如果您想要一个适用于任何类型程序的通用解决方案,您可以使用expect 命令。

从手册页摘录:

Expect 是一个根据脚本与其他交互式程序“对话”的程序。 遵循脚本,Expect 知道可以做什么对程序的期望以及正确的响应应该是什么。解释语言提供分支和高级控制结构来指导对话。此外,用户可以在需要时直接进行控制和交互,然后将控制权返回给脚本。

这是一个使用 expect 的工作示例:

set timeout 60

spawn sudo su -

expect "*?assword"  send "*secretpassword*\r" 
send_user "I should be root now:"

expect "#"  send "whoami\r" 
expect "#"  send "exit\r" 
send_user "Done.\n"
exit

然后可以使用简单的命令启动脚本:

$ expect -f custom.script

您可以在以下页面查看完整示例:http://www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands

注意:@tripleee 提出的答案只有在标准输入可以在命令开始时读取一次,或者如果已分配 tty 时才有效,并且不适用于任何互动节目。

使用管道时的错误示例

echo "su whoami" |ssh remotehost
--> su: must be run from a terminal

echo "sudo whoami" |ssh remotehost
--> sudo: no tty present and no askpass program specified

在 SSH 中,您可能会强制使用多个 -t 参数分配 TTY,但当 sudo 要求输入密码时,它会失败。

如果不使用像 expect 这样的程序,任何对可能从标准输入获取信息的函数/程序的调用都会导致下一个命令失败:

ssh use@host <<'____HERE'
  echo "Enter your name:"
  read name
  echo "ok."
____HERE
--> The `echo "ok."` string will be passed to the "read" command

【讨论】:

expect 是一个有用的工具,并且在这里切题,但作为一个简单的 shell 语法问题的答案,它几乎肯定是矫枉过正。在我 20 多年的 shell 编程生涯中,我很少需要使用它(Perl、Python、Ruby 等良好的通用脚本语言的可用性使得学习的必要性降低了)。 你的问题很简单,但解决方案不是只要你希望它是通用的。您提出的解决方案没有考虑到您不能同时在&lt;stdin&gt; 上推送所有内容。但我同意你的看法,大多数时候我们都期望使用expect

以上是关于将命令作为输入传递给另一个命令(su、ssh、sh 等)的主要内容,如果未能解决你的问题,请参考以下文章

shell 进程替换

shell脚本构建基本脚本

shell脚本构建基本脚本

Linux学习笔记——su_sudo_ssh_pdsh命令

SSH 从本地文件运行命令并传递本地环境变量

配置SSH