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

Bash 将命令作为输入传递给另一个命令(su、ssh、sh等),bash,shell,unix,ssh,sh,Bash,Shell,Unix,Ssh,Sh,我有一个脚本,需要在其中启动一个命令,然后将一些附加命令作为命令传递给该命令。我试过了 su echo I should be root now: who am I exit echo done. 。。。但是它不起作用:su成功了,但是命令提示符却在盯着我看。如果在提示下键入exit,则echo和我是谁等将开始执行!而回显完成。根本不会执行 类似地,我需要在ssh上进行此操作: ssh remotehost # this should run under my account on remot

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

su
echo I should be root now:
who am I
exit
echo done.
。。。但是它不起作用:
su
成功了,但是命令提示符却在盯着我看。如果在提示下键入
exit
,则
echo
我是谁
等将开始执行!而
回显完成。
根本不会执行

类似地,我需要在
ssh
上进行此操作:

ssh remotehost
# this should run under my account on remotehost
su
## this should run as root on remotehost
whoami
exit
## back
exit
# back
我如何解决这个问题

我正在寻找以一般方式解决这一问题的答案,而这些答案并非特定于
su
ssh
。目的是让这个问题成为这个特定模式的一个问题


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

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

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

现在,在执行脚本时,标准输入仍然是终端(除非使用重定向),但命令是从脚本文件而不是从标准输入读取的。(事实上,相反的操作会非常麻烦-任何
读取
都会占用脚本的下一行,
cat
会吞掉脚本的所有其余部分,并且无法与之交互!)脚本文件只包含用于执行它的shell实例的命令(当然,您仍然可以使用here文档等将输入嵌入为命令参数)

换句话说,这些“被误解”的命令(
su
ssh
sh
sudo
bash
等)在单独运行时(没有参数)会启动一个交互式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
另一方面,您通常不需要显式退出,因为当命令执行了您传入执行的脚本(命令序列)后,它将终止。

添加到的:

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

如果脚本的该块包含参数扩展、命令替换和/或算术扩展,则必须稍微不同地使用shell的here document功能,具体取决于希望在何处执行这些扩展

1.所有扩展必须在父shell的范围内执行。 那么here文档的分隔符必须是无引号的

command <<DELIMITER
...
DELIMITER
command <<'DELIMITER'
...
DELIMITER
2.所有扩展必须在子shell的范围内执行。 那么here文档的分隔符必须是引号

command <<DELIMITER
...
DELIMITER
command <<'DELIMITER'
...
DELIMITER
3.有些扩展必须在子shell中执行,有些扩展必须在父shell中执行。 然后,here文档的分隔符必须是无引号的,并且必须转义必须在子shell中执行的扩展表达式

command <<DELIMITER
...
DELIMITER
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
#!/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
#!/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

如果您想要一个适用于任何类型程序的通用解决方案,可以使用
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
您可以在以下页面中查看完整示例:

注意:如果在命令开始时可以读取一次标准输入,或者如果分配了tty,则@tripleee提出的答案将有效,并且不适用于任何交互式程序

使用管道时的错误示例

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

sshuse@host相关:为什么不使用希望作为root用户运行的命令创建一个
shell脚本
,并且当命令只是一些微不足道的命令时,会使简单的情况变得不那么复杂(然后您必须管理两个脚本文件,并确保