Ansibleのtelnetモジュールでコマンド出力に#が含まれる場合のtips
(諸事情あり)Ansibleのnetworkモジュールではなく、telnetモジュールを使いネットワーク機器の情報を収集しようとした時に、ある行以降の情報が取得できなくて困った。調べてみると、実行したコマンド出力中の #
が、プロンプトを識別するための文字指定 [>#]
にマッチしてしまっていた。そこで、プロンプトの指定を \n[^\n ]*[>#]
に変更したら解決した。
以下、少し詳細。
問題
具体的には、以下のようなタスクを実行した際に、インターフェースのDescriptionに # が使われていると、それをプロンプトとして認識してしまい、出力の読み取りがそこで中断されてしまう。最初は、この後のタスクのTextFSMのパースを疑ったけど、前段の問題だった。今回の話とは関係ないけど、TextFSMすごい。まるで魔法(
- name: show interfaces telnet: user: user password: hogehoge login_prompt: "Username: " prompts: - "[>#]" command: - terminal length 0 - show interfaces register: interfaces
問題の起きる出力の例:
FastEthernet0/1 is up, line protocol is up (connected) Hardware is Lance, address is 0000.0000.0000 (bia 0000.0000.0000) Description: hoge-str C#1 P#0 MTU: 1500bytes, BW 100000 Kbit, DLY 1000 usec, reliability 255/255, txload 1/255, rxload 1/255 Encapsulation ARPA, loopback not set (略)
対応
promptsの正規表現を以下のように修正したら、うまくいった。
prompts: - "\n[^\n ]*[>#]"
#
が出てくるのはDescription行ぐらいだろうという前提で、#
の前にはスペースと改行以外の文字が0個以上、という事を表した。なお、行頭を表す ^
はshow interfacesの一番最初の行の行頭にマッチしてしまうようで、改行記号を使った。
余談
Descriptionに使われている #
を無視したいのだから、最初は「#
より前かつ同じ行に Description:.*
がない #
」を表す正規表現を考えた。後読み否定を使って (?<!Description:).*[>#]
もしくは (?<!Description:.*)[>#]
と表せるかと思ったが、使えなかった。
前者は Description:
の直後を示しているだけなので、その次の文字からは否定の効果がなく、意図した動作にはならない。後者は、正規表現としてはよさそうな気がするけど、Pythonの仕様では、後読みでは文字数が可変な指定はできないとドキュメントにあり、ダメだった。
re — Regular expression operations — Python 3.7.6rc1 documentation
The contained pattern must only match strings of some fixed length, meaning that abc or a|b are allowed, but a* and a{3,4} are not.
少し調べると、後読みで可変文字数を使える言語は限られている模様。へぇー。
Regex Tutorial - Lookahead and Lookbehind Zero-Length Assertions
Many regex flavors, including those used by Perl, Python, and Boost only allow fixed-length strings.
関係ないけど、先読み、後読みと聞くと、それぞれ逆の意味を想像してしまう。先読みの場合、先「を」読む、ではなく先「に」読むだと捉えて、左にあるパターンだと考えてしまう。英語のlookaheadとlookbackだと分かりやすいのに(ネイティブの人だと同様にまぎらわしかったりするのだろうか..?)。