func (s *SSHSession) PipeExec(cmd string, printFn func(r io.Reader, w io.Writer)) error {
	err := s.requestPty(int(os.Stdin.Fd()))
	if err != nil {
		return err
	}
	s.updateTerminalSize()
	pr, pw := io.Pipe()
	defer func() {
		_ = pw.Close()
		_ = pr.Close()
	}()
	s.se.Stdout = pw
	s.se.Stderr = pw
	s.Stdout = pr
	s.Stderr = pr
	go func() { printFn(s.Stdout, os.Stdout) }()
	return s.se.Run(cmd)
}
PipeExec exec remote commands and provides pipeline output reading
package sshutils
import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
	"github.com/mritd/mmh/common"
	"golang.org/x/crypto/ssh/terminal"
	"golang.org/x/crypto/ssh"
)
type SSHSession struct {
	se *ssh.Session
	exitMsg    string
	hookCmd    string
	hookStdout bool
	Stdin  io.Writer
	Stdout io.Reader
	Stderr io.Reader
}
func (s *SSHSession) Close() error {
	pw, ok := s.se.Stdout.(*io.PipeWriter)
	if ok {
		if err := pw.Close(); err != nil {
			fmt.Println(err)
		}
	}
	pr, ok := s.se.Stdin.(*io.PipeReader)
	if ok {
		if err := pr.Close(); err != nil {
			fmt.Println(err)
		}
	}
	return s.se.Close()
}
func (s *SSHSession) updateTerminalSize() {
	go func() {
		sigs := make(chan os.Signal)
		signal.Notify(sigs, syscall.SIGWINCH)
		fd := int(os.Stdin.Fd())
		termWidth, termHeight, err := terminal.GetSize(fd)
		if !common.CheckErr(err) {
			return
		}
		for range sigs {
			currTermWidth, currTermHeight, err := terminal.GetSize(fd)
			if !common.CheckErr(err) {
				continue
			}
			if currTermHeight == termHeight && currTermWidth == termWidth {
				continue
			}
			err = s.se.WindowChange(currTermHeight, currTermWidth)
			if err != nil {
				fmt.Printf("Unable to send window-change reqest: %s", err)
				continue
			}
			termWidth, termHeight = currTermWidth, currTermHeight
		}
	}()
}
func (s *SSHSession) Terminal() error {
	return s.TerminalWithKeepAlive(5 * time.Second)
}
func (s *SSHSession) TerminalWithKeepAlive(interval time.Duration) error {
	if interval < 3*time.Second {
		return errors.New("the interval must be >= 3s")
	}
	fd := int(os.Stdin.Fd())
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		return err
	}
	defer func() { _ = terminal.Restore(fd, state) }()
	err = s.requestPty(fd)
	if err != nil {
		return err
	}
	s.updateTerminalSize()
	s.Stdin, err = s.se.StdinPipe()
	if err != nil {
		return err
	}
	s.Stdout, err = s.se.StdoutPipe()
	if err != nil {
		return err
	}
	s.Stderr, err = s.se.StderrPipe()
	go func() { _, _ = io.Copy(os.Stderr, s.Stderr) }()
	go func() { _, _ = io.Copy(os.Stdout, s.Stdout) }()
	go func() { _, _ = io.Copy(s.Stdin, os.Stdin) }()
	go func() {
		tick := time.Tick(interval)
		for range tick {
			_, err := s.se.SendRequest("ssh-keepalive@linux.com", true, nil)
			common.PrintErr(err)
		}
	}()
	if s.hookCmd != "" {
		go func() {
			cs, args := common.ParseCommand(s.hookCmd)
			cmd := exec.Command(cs, args...)
			if s.hookStdout {
				cmd.Stdin = s.Stdout
			}
			cmd.Stdout = s.Stdin
			common.PrintErr(cmd.Run())
		}()
	}
	if err := s.se.Shell(); err != nil {
		return err
	}
	return s.se.Wait()
}
func (s *SSHSession) requestPty(fd int) error {
	termWidth, termHeight, err := terminal.GetSize(fd)
	if err != nil {
		return err
	}
	termType := os.Getenv("TERM")
	if termType == "" {
		termType = "xterm-256color"
	}
	return s.se.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{})
}