我试图通过ssh运行多个命令,但似乎Session.Run每个会话只允许一个命令(除非我错了)。我想知道如何绕过这个限制并重用会话或发送一系列命令。
原因是我需要在下一个命令(sh /usr/bin/myscript.sh)的同一个会话中运行sudo su
答案 0 :(得分:9)
虽然针对您的具体问题,您可以轻松运行sudo /path/to/script.sh
,但令我震惊的是,在同一会话中运行多个命令的方法并不简单,所以我想出了一点{ {3}},YMMV:
func MuxShell(w io.Writer, r io.Reader) (chan<- string, <-chan string) {
in := make(chan string, 1)
out := make(chan string, 1)
var wg sync.WaitGroup
wg.Add(1) //for the shell itself
go func() {
for cmd := range in {
wg.Add(1)
w.Write([]byte(cmd + "\n"))
wg.Wait()
}
}()
go func() {
var (
buf [65 * 1024]byte
t int
)
for {
n, err := r.Read(buf[t:])
if err != nil {
close(in)
close(out)
return
}
t += n
if buf[t-2] == '$' { //assuming the $PS1 == 'sh-4.3$ '
out <- string(buf[:t])
t = 0
wg.Done()
}
}
}()
return in, out
}
func main() {
config := &ssh.ClientConfig{
User: "kf5",
Auth: []ssh.AuthMethod{
ssh.Password("kf5"),
},
}
client, err := ssh.Dial("tcp", "127.0.0.1:22", config)
if err != nil {
panic(err)
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
log.Fatalf("unable to create session: %s", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
log.Fatal(err)
}
w, err := session.StdinPipe()
if err != nil {
panic(err)
}
r, err := session.StdoutPipe()
if err != nil {
panic(err)
}
in, out := MuxShell(w, r)
if err := session.Start("/bin/sh"); err != nil {
log.Fatal(err)
}
<-out //ignore the shell output
in <- "ls -lhav"
fmt.Printf("ls output: %s\n", <-out)
in <- "whoami"
fmt.Printf("whoami: %s\n", <-out)
in <- "exit"
session.Wait()
}
如果你的shell提示没有以$
结尾($后跟一个空格),那么这将是僵局,因此它为什么是黑客攻击。
答案 1 :(得分:6)
NewSession是一种连接方法。您不需要每次都创建新连接。会话似乎是此库为客户端调用的通道,并且许多通道在单个连接中复用。因此:
func executeCmd(cmd []string, hostname string, config *ssh.ClientConfig) string {
conn, err := ssh.Dial("tcp", hostname+":22", config)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
var stdoutBuf bytes.Buffer
for _, command := range cmd {
session, err := conn.NewSession()
if err != nil {
log.Fatal(err)
}
defer session.Close()
session.Stdout = &stdoutBuf
session.Run(command)
}
return hostname + ": " + stdoutBuf.String()
}
所以你打开一个新的会话(频道),你在现有的ssh连接中运行命令,但每次都有一个新的会话(频道)。
答案 2 :(得分:4)
您可以使用小技巧:sh -c 'cmd1&&cmd2&&cmd3&&cmd4&&etc..'
这是一个命令,实际命令作为参数传递给将执行它们的shell。这就是Docker处理多个命令的方式。
答案 3 :(得分:1)
这对我有用。
firstClass = mapper.readValue(manifestJSON, FirstClass.class);
答案 4 :(得分:0)
真的很喜欢OneOfOne的答案,这个答案激发了我一个更通用的解决方案,即采用一个可以匹配读取字节尾部的变量并打破阻塞读取(也不需要分叉两个额外的线程来阻止读取和写入)。已知的限制是(如在原始解决方案中)如果匹配字符串在64 * 1024
字节之后,则this code将永远旋转。
package main
import (
"fmt"
"golang.org/x/crypto/ssh"
"io"
"log"
)
var escapePrompt = []byte{'$', ' '}
func main() {
config := &ssh.ClientConfig{
User: "dummy",
Auth: []ssh.AuthMethod{
ssh.Password("dummy"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", "127.0.0.1:22", config)
if err != nil {
panic(err)
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
log.Fatalf("unable to create session: %s", err)
}
defer session.Close()
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
log.Fatal(err)
}
w, err := session.StdinPipe()
if err != nil {
panic(err)
}
r, err := session.StdoutPipe()
if err != nil {
panic(err)
}
if err := session.Start("/bin/sh"); err != nil {
log.Fatal(err)
}
readUntil(r, escapePrompt) //ignore the shell output
write(w, "ls -lhav")
out, err := readUntil(r, escapePrompt)
fmt.Printf("ls output: %s\n", *out)
write(w, "whoami")
out, err = readUntil(r, escapePrompt)
fmt.Printf("whoami: %s\n", *out)
write(w, "exit")
session.Wait()
}
func write(w io.WriteCloser, command string) error {
_, err := w.Write([]byte(command + "\n"))
return err
}
func readUntil(r io.Reader, matchingByte []byte) (*string, error) {
var buf [64 * 1024]byte
var t int
for {
n, err := r.Read(buf[t:])
if err != nil {
return nil, err
}
t += n
if isMatch(buf[:t], t, matchingByte) {
stringResult := string(buf[:t])
return &stringResult, nil
}
}
}
func isMatch(bytes []byte, t int, matchingBytes []byte) bool {
if t >= len(matchingBytes) {
for i := 0; i < len(matchingBytes); i++ {
if bytes[t - len(matchingBytes) + i] != matchingBytes[i] {
return false
}
}
return true
}
return false
}
答案 5 :(得分:0)
我花了几天时间,这个答案激励我尝试使用sdtin来运行多个命令,最终成功。我想说我根本不懂golang,因此它可能是多余的,但代码可以工作。
Tuple2
相同
这是整个代码:
if _, err := w.Write([]byte("sys\r")); err != nil {
panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("wlan\r")); err != nil {
panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("ap-id 2099\r")); err != nil {
panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("ap-group xuebao-free\r")); err != nil {
panic("Failed to run: " + err.Error())
}
if _, err := w.Write([]byte("y\r")); err != nil {
panic("Failed to run: " + err.Error())
}
答案 6 :(得分:-3)
以下代码适用于我。
func main() {
key, err := ioutil.ReadFile("path to your key file")
if err != nil {
panic(err)
}
signer, err := ssh.ParsePrivateKey([]byte(key))
if err != nil {
panic(err)
}
config := &ssh.ClientConfig{
User: "ubuntu",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
client, err := ssh.Dial("tcp", "52.91.35.179:22", config)
if err != nil {
panic(err)
}
session, err := client.NewSession()
if err != nil {
panic(err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin
session.Shell()
session.Wait()
}