简体   繁体   English

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

[英]Pass commands as input to another command (su, ssh, sh, etc)

I have a script where I need to start a command, then pass some additional commands as commands to that command.我有一个脚本,我需要在其中启动一个命令,然后将一些其他命令作为命令传递给该命令。 I tried我试过

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

... but it doesn't work: The su succeeds, but then the command prompt is just staring at me. ... 但它不起作用: su成功,但命令提示符只是盯着我看。 If I type exit at the prompt, the echo and who am i etc start executing!如果我在提示符下键入exitechowho am i等开始执行! And the echo done. echo done. doesn't get executed at all.根本不会被执行。

Similarly, I need for this to work over ssh :同样,我需要它在ssh上工作:

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

How do I solve this?我该如何解决这个问题?

I am looking for answers which solve this in a general fashion, and which are not specific to su or ssh in particular.我正在寻找以一般方式解决此问题的答案,而不是特定于sussh的答案。 The intent is for this question to become a canonical for this particular pattern.目的是让这个问题成为这个特定模式的规范

Adding to tripleee 's answer :添加到三人组答案

It is important to remember that the section of the script formatted as a here-document for another shell is executed in a different shell with its own environment (and maybe even on a different machine).重要的是要记住,格式化为另一个 shell 的 here-document 的脚本部分是在具有自己环境的不同 shell 中执行的(甚至可能在不同的机器上)。

If that block of your script contains parameter expansion, command substitution, and/or arithmetic expansion, then you must use the here-document facility of the shell slightly differently, depending on where you want those expansions to be performed.如果您的脚本块包含参数扩展、命令替换和/或算术扩展,那么您必须稍微不同地使用 shell 的 here-document 工具,具体取决于您希望在哪里执行这些扩展。

1. All expansions must be performed within the scope of the parent shell. 1.所有的扩展都必须在父shell的范围内进行。

Then the delimiter of the here document must be unquoted .那么这里文档的分隔符必须不加引号

command <<DELIMITER
...
DELIMITER

Example:例子:

#!/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

Output:输出:

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

2. All expansions must be performed within the scope of the child shell. 2、所有的扩展都必须在子shell的范围内进行。

Then the delimiter of the here document must be quoted .那么here文档的分隔符一定要加引号

command <<'DELIMITER'
...
DELIMITER

Example:例子:

#!/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

Output:输出:

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

3. Some expansions must be performed in the child shell, some - in the parent. 3. 一些扩展必须在子 shell 中执行,一些 - 在父 shell 中。

Then the delimiter of the here document must be unquoted and you must escape those expansion expressions that must be performed in the child shell .那么此处文档的分隔符必须不加引号,并且您必须转义必须在子 shell 中执行的那些扩展表达式

Example:例子:

#!/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

Output:输出:

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

A shell script is a sequence of commands. shell 脚本是一系列命令。 The shell will read the script file, and execute those commands one after the other. shell 将读取脚本文件,并一个接一个地执行这些命令。

In the usual case, there are no surprises here;在通常情况下,这里没有惊喜; but a frequent beginner error is assuming that some commands will take over from the shell, and start executing the following commands in the script file instead of the shell which is currently running this script.但是一个常见的初学者错误是假设某些命令将从 shell 中接管,并开始在脚本文件中执行以下命令,而不是当前运行此脚本的 shell。 But that's not how it works.但这不是它的工作原理。

Basically, scripts work exactly like interactive commands, but how exactly they work needs to be properly understood.基本上,脚本的工作方式与交互式命令完全一样,但需要正确理解它们的工作方式。 Interactively, the shell reads a command (from standard input), runs that command (with input from standard input), and when it's done, it reads another command (from standard input).交互式地,shell 读取一个命令(从标准输入),运行该命令(使用来自标准输入的输入),当它完成时,它读取另一个命令(从标准输入)。

Now, when executing a script, standard input is still the terminal (unless you used a redirection) but the commands are read from the script file, not from standard input.现在,在执行脚本时,标准输入仍然是终端(除非您使用重定向),但命令是从脚本文件中读取的,而不是从标准输入中读取的。 (The opposite would be very cumbersome indeed - any read would consume the next line of the script, cat would slurp all the rest of the script, and there would be no way to interact with it!) The script file only contains commands for the shell instance which executes it (though you can of course still use a here document etc to embed inputs as command arguments). (相反的情况确实很麻烦 - 任何read都会消耗脚本的下一行, cat会吞下脚本的所有其余部分,并且无法与之交互!)脚本文件包含用于执行它的 shell 实例(尽管您当然仍然可以使用此处的文档等将输入嵌入为命令参数)。

In other words, these "misunderstood" commands ( su , ssh , sh , sudo , bash etc) when run alone (without arguments) will start an interactive shell, and in an interactive session, that's obviously fine;换句话说,这些“被误解”的命令( susshshsudobash等)在单独运行(不带参数)时将启动一个交互式 shell,而在交互式会话中,这显然很好; but when run from a script, that's very often not what you want.但是从脚本运行时,这通常不是您想要的。

All of these commands have ways to accept commands by ways other than in an interactive terminal session.所有这些命令都可以通过交互式终端会话以外的方式接受命令。 Typically, each command supports a way to pass it commands as options or arguments:通常,每个命令都支持一种将命令作为选项或参数传递的方式:

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

Many of these commands will also accept commands on standard input:其中许多命令也将接受标准输入上的命令:

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

which also conveniently allows you to use here documents:这也方便您使用此处的文档:

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

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

For commands which accept a single command argument, that command can be sh or bash with multiple commands:对于接受单个命令参数的命令,该命令可以是带有多个命令的shbash

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

As an aside, you generally don't need an explicit exit because the command will terminate anyway when it has executed the script (sequence of commands) you passed in for execution.顺便说一句,您通常不需要显式exit ,因为该命令在执行您传入执行的脚本(命令序列)时无论如何都会终止。

If you want a generic solution which will work for any kind of program, you can use the expect command.如果您想要一个适用于任何类型程序的通用解决方案,您可以使用expect命令。

Extract from the manual page:从手册页中提取:

Expect is a program that "talks" to other interactive programs according to a script. Expect是一个根据脚本与其他交互式程序“对话”的程序。 Following the script, Expect knows what can be expected from a program and what the correct response should be.根据脚本, Expect知道程序可以预期什么以及正确的响应应该是什么。 An interpreted language provides branching and high-level control structures to direct the dialogue.解释语言提供分支和高级控制结构来指导对话。 In addition, the user can take control and interact directly when desired, afterward returning control to the script.此外,用户可以在需要时直接进行控制和交互,然后将控制权返回给脚本。

Here is a working example using 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

The script can then be launched with a simple command:然后可以使用一个简单的命令启动该脚本:

$ expect -f custom.script

You can view a full example in the following page: http://www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands您可以在以下页面中查看完整示例:http: //www.journaldev.com/1405/expect-script-example-for-ssh-and-su-login-and-running-commands

Note: The answer proposed by @tripleee would only work if standard input could be read once at the start of the command, or if a tty had been allocated, and won't work for any interactive program.注意: @tripleee 提出的答案只有在命令开始时可以读取一次标准输入,或者已经分配了 tty 时才有效,并且不适用于任何交互式程序。

Example of errors if you use a pipe使用管道时的错误示例

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

In SSH, you might force a TTY allocation with multiple -t parameters, but when sudo will ask for the password, it will fail.在 SSH 中,您可能会强制使用多个-t参数分配 TTY,但是当sudo要求输入密码时,它会失败。

Without the use of a program like expect any call to a function/program which might get information from stdin will make the next command fail:如果不使用像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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM