【问题标题】:Pass commands as input to another command (su, ssh, sh, etc)将命令作为输入传递给另一个命令(su、ssh、sh 等)
【发布时间】:2021-04-19 17:47:52
【问题描述】:

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

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。

【问题讨论】:

  • 你为什么不创建一个shell-script 来使用你想以root 身份运行的命令呢?然后你所要做的就是运行sudo sh yourshellscript.sh。
  • @JoseSerodio 这不适用于ssh 场景,例如(或者您必须先将脚本scp 发送到远程主机),并且在命令执行时使简单的情况变得没有吸引力只是一些琐碎的(然后您必须管理两个脚本文件,并确保调用脚本知道另一个脚本的路径)。当然,它不容易扩展到某些命令是动态的场景(例如,以调用脚本中执行的计算为条件)。

标签: bash shell unix ssh sh


【解决方案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 等良好的通用脚本语言的可用性使得学习的必要性降低了)。
    • 你的问题很简单,但解决方案不是只要你希望它是通用的。您提出的解决方案没有考虑到您不能同时在&lt;stdin&gt; 上推送所有内容。但我同意你的看法,大多数时候我们都期望使用expect。
    猜你喜欢
    • 2016-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-14
    • 1970-01-01
    相关资源
    最近更新 更多