Golang Escape Символ вопросительного знака для Cisco Regex

Итак, регулярное выражение Cisco допускает символ вопросительного знака. Но загвоздка в том, что перед вводом вопросительного знака нужно ввести Ctrl-Shift-v, чтобы он интерпретировался как вопросительный знак, а не как команда справки... Ссылка на рекомендации Cisco по регулярному выражению

У меня есть программа Go, которая входит в набор устройств и запускает набор команд на каждом устройстве. Однако при попытке использовать регулярное выражение, содержащее вопросительный знак, устройство Cisco всегда интерпретирует вопросительный знак как команду справки. Использование строковых литералов в Go не решает проблему, равно как и отправка команды в виде фрагмента байтов.

Например, если я попытаюсь отправить команду show boot | include (c|cat)[0-9]+[a-zA-Z]?, CLI Cisco вернет

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

switch-1#

вместо того, чтобы интерпретировать вопросительный знак как соответствие регулярному выражению 0 или 1 для группы [a-zA-Z].

Однако использование команды ssh user@switch-1 'show boot | include (c|cat)[0-9]+[a-zA-Z]?' работает должным образом и правильно интерпретирует шаблон регулярного выражения.

Как воспроизвести поведение команды ssh? Есть ли способ отправить Ctrl-Shift-v перед каждым вопросительным знаком или экранировать каждый символ вопросительного знака?

Мой код по запросу:

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+"\n"); 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\n", 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\n", res.Host, res.Err)
            continue
        }
        fmt.Printf("Host %s\n%s\n%s\n", res.Host, res.Output, strings.Repeat("-", 50))
    }
}

person mwalto7    schedule 21.06.2018    source источник
comment
Попробуйте решение из Any Way To Escape Golang Строка в регулярном выражении?   -  person Wiktor Stribiżew    schedule 21.06.2018
comment
@WiktorStribiżew Я не сопоставляю буквальный вопросительный знак для команд. Кроме того, регулярное выражение — это строка, включенная в команду, а не объект регулярного выражения Go.   -  person mwalto7    schedule 21.06.2018
comment
@ThunderCat Я добавил свой код   -  person mwalto7    schedule 21.06.2018
comment
Случайное предположение: не создавайте файл pty. Оболочка может иметь различное поведение в зависимости от интерактивного и неинтерактивного сеанса.   -  person Cerise Limón    schedule 22.06.2018
comment
@ThunderCat Я действительно пробовал это перед публикацией, но результат тот же.   -  person mwalto7    schedule 22.06.2018


Ответы (1)


Попробуйте принудительно перевести терминальный сервер IOS в линейный режим (в отличие от символьного режима). Отправьте эти последовательности согласования telnet:

IAC DONT ECHO
IAC WONT ECHO
IAC DONT SUPPRESS-GO-AHEAD
IAC WONT SUPPRESS-GO-AHEAD

См.: https://tools.ietf.org/html/rfc858.

person Everton    schedule 22.06.2018