将命令作为输入传递给另一个命令(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
成功,但命令提示符只是盯着我看。如果我在提示符下键入exit
,echo
和who 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
我该如何解决这个问题?
我正在寻找能够以一般方式解决此问题的答案,而不是特别针对
su
或ssh
的答案。目的是让这个问题成为这个特定模式的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 文档等将输入嵌入为命令参数)。
换句话说,这些“被误解”的命令(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
对于接受单个命令参数的命令,该命令可以是带有多个命令的sh
或bash
:
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 等良好的通用脚本语言的可用性使得学习的必要性降低了)。
你的问题很简单,但解决方案不是只要你希望它是通用的。您提出的解决方案没有考虑到您不能同时在<stdin>
上推送所有内容。但我同意你的看法,大多数时候我们都期望使用expect
。以上是关于将命令作为输入传递给另一个命令(su、ssh、sh 等)的主要内容,如果未能解决你的问题,请参考以下文章