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

时间:2016-06-02 08:43:04

标签: bash shell unix ssh sh

我有一个脚本,我需要启动一个命令,然后将一些额外的命令作为命令传递给该命令。我试过了

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

3 个答案:

答案 0 :(得分:17)

shell脚本是一系列命令。 shell将读取脚本文件,并依次执行这些命令。

在通常的情况下,这里没有意外;但是一个常见的初学者错误是假设某些命令将从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,因为该命令在执行你传入执行的脚本(命令序列)时会终止。

答案 1 :(得分:15)

添加到tripleee&#39; 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中执行的扩展表达式

示例:

#!/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 :(得分:8)

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

摘自手册页:

  

Expect 是一个&#34;会谈&#34;根据脚本到其他交互式程序。 在脚本之后, 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之类的程序,任何可能从stdin获取信息的函数/程序调用都会使下一个命令失败:

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