Ctrl-Shift-v

I have a Go program that logs into a set of devices and runs a set of commands on each device. When trying to use a regex containing a question mark, though, the Cisco device always interprets the question mark as a help command. Using string literals in Go does not fix the problem nor does sending the command as a slice of bytes.

show boot | include (c|cat)[0-9]+[a-zA-Z]?
switch-1#show boot | include (c|cat)[0-9]+[a-zA-Z]?
LINE    <cr>

switch-1#

instead of interpreting the question mark as a regex match of 0 or 1 for the [a-zA-Z] group.

ssh user@switch-1 'show boot | include (c|cat)[0-9]+[a-zA-Z]?'
Ctrl-Shift-v

My code as requested:

package main

import (
    "golang.org/x/crypto/ssh"
    "net"
    "fmt"
    "os"
    "bufio"
    "time"
    "golang.org/x/crypto/ssh/terminal"
    "io"
    "io/ioutil"
    "sync"
    "strings"
)

// ReadLines reads a file line-by-line and returns a slice of the lines.
func ReadLines(filename string) ([]string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, fmt.Errorf("failed to open file: %v", err)
    }
    defer f.Close()

    var lines []string
    s := bufio.NewScanner(f)
    for s.Scan() {
        lines = append(lines, s.Text())
    }
    if err := s.Err(); err != nil {
        return nil, err
    }
    return lines, nil
}

// Type Result represents the result of running the Configure method.
type Result struct {
    Host   string // Hostname of device
    Output []byte // Remote shell's stdin and stderr output
    Err    error  // Remote shell errors
}

// Configure logs into a device, starts a remote shell, runs the set of
// commands, and waits for the remote shell to return or timeout.
func Configure(host string, config *ssh.ClientConfig, cmds []string, results chan<- *Result, wg *sync.WaitGroup) {
    defer wg.Done()

    res := &Result{
        Host:   host,
        Output: nil,
        Err:    nil,
    }

    // Create client connection
    client, err := ssh.Dial("tcp", net.JoinHostPort(host, "22"), config)
    if err != nil {
        res.Err = fmt.Errorf("failed to dial: %v", err)
        results <- res
        return
    }
    defer client.Close()

    // Create new session
    session, err := client.NewSession()
    if err != nil {
        res.Err = fmt.Errorf("failed to create session: %v", err)
        results <- res
        return
    }
    defer session.Close()

    // Set session IO
    stdin, err := session.StdinPipe()
    if err != nil {
        res.Err = fmt.Errorf("failed to create pipe to stdin: %v", err)
        results <- res
        return
    }
    defer stdin.Close()

    stdout, err := session.StdoutPipe()
    if err != nil {
        res.Err = fmt.Errorf("failed to create pipe to stdout: %v", err)
        results <- res
        return
    }
    stderr, err := session.StderrPipe()
    if err != nil {
        res.Err = fmt.Errorf("failed to create pipe to stderr: %v", err)
        results <- res
        return
    }

    // Start remote shell
    if err := session.RequestPty("vt100", 0, 0, ssh.TerminalModes{
        ssh.ECHO:          0,
        ssh.TTY_OP_ISPEED: 14400,
        ssh.TTY_OP_OSPEED: 14400,
    }); err != nil {
        res.Err = fmt.Errorf("failed to request pseudoterminal: %v", err)
        results <- res
        return
    }
    if err := session.Shell(); err != nil {
        res.Err = fmt.Errorf("failed to start remote shell: %v", err)
        results <- res
        return
    }

    // Run commands
    for _, cmd := range cmds {
        if _, err := io.WriteString(stdin, cmd+"
"); err != nil {
            res.Err = fmt.Errorf("failed to run: %v", err)
            results <- res
            return
        }
    }

    // Wait for remote commands to return or timeout
    exit := make(chan error, 1)
    go func(exit chan<- error) {
        exit <- session.Wait()
    }(exit)
    timeout := time.After(1 * time.Minute)
    select {
    case <-exit:
        output, err := ioutil.ReadAll(io.MultiReader(stdout, stderr))
        if err != nil {
            res.Err = fmt.Errorf("failed to read output: %v", err)
            results <- res
            return
        }
        res.Output = output
        results <- res
        return
    case <-timeout:
        res.Err = fmt.Errorf("session timed out")
        results <- res
        return
    }
}

func main() {
    hosts, err := ReadLines(os.Args[1])
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    cmds, err := ReadLines(os.Args[2])
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    fmt.Fprint(os.Stderr, "Password: ")
    secret, err := terminal.ReadPassword(int(os.Stdin.Fd()))
    if err != nil {
        fmt.Fprintf(os.Stderr, "failed to read password: %v
", err)
        os.Exit(1)
    }
    fmt.Fprintln(os.Stderr)

    config := &ssh.ClientConfig{
        User:            "user",
        Auth:            []ssh.AuthMethod{ssh.Password(string(secret))},
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        Timeout:         30 * time.Second,
    }
    config.SetDefaults()
    config.Ciphers = append(config.Ciphers, "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc")

    results := make(chan *Result, len(hosts))

    var wg sync.WaitGroup
    wg.Add(len(hosts))
    for _, host := range hosts {
        go Configure(host, config, cmds, results, &wg)
    }
    wg.Wait()
    close(results)

    for res := range results {
        if res.Err != nil {
            fmt.Fprintf(os.Stderr, "Error %s: %v
", res.Host, res.Err)
            continue
        }
        fmt.Printf("Host %s
%s
%s
", res.Host, res.Output, strings.Repeat("-", 50))
    }
}