Aruba AP に telnet で接続して連続コマンド実行をする

ちょっと必要になったのでメモ。

# Cisco 機器の場合
Cisco のルータなどに纏めてコマンドを投げるときには適宜 sleep を挟みながら echo で投げれば良いのですが

```sh:for Cisco
#!/bin/sh

for CISCO_Cisco in 100.64.20.1 100.64.21.1 100.64.22.1 ; do
(
	sleep 1;\
	echo 'admin';\
	sleep 1;\
	echo 'Cisco123';\
	sleep 1;\
	echo 'enable';\
	echo 'Cisco123';\
	sleep 1;\
	echo 'ter len 0';\
	echo 'ter wid 0';\
	echo 'show run';\
	sleep 3;\
	echo 'show version';\
	sleep 1;\
	echo 'show clock';\
	sleep 1;\
	echo 'show tech';\
	sleep 60;\
	echo '!!! ALL DONE';\
	sleep 1;\
	echo 'exit';\
	sleep 1;\
) | (telnet $CISCO_Cisco | tee ${CISCO_Cisco}.log )&
done
```
コマンドを投げる部分を関数にしてバックグランドで実行、出力ログの最後に「ALL DONE」が入っていなければ再実行。とかしたら (for ループに適宜 sleep を挟まないと、えらいことになります) 100 台 show tech とかも休憩に行ってる間に終わらせることが出来ます。

# Aruba の AP の場合
ところが同じ方法で Aruba AP の情報を収集しようとしたら改行の所に ^M と表示され、コマンドが実行されません。
ということで、Aruba の AP は改行コードが違うらしいので、printf で書いてあげましょう。
```sh:for Aruba IAP
#!/bin/sh

(
	sleep 1;\
	printf 'show_user\r';\
	sleep 3;\
	printf 'abc123\r';\
	sleep 1;\
	printf 'show running-config\r';\
	sleep 3;\
	printf 'show version\r';\
	sleep 1;\
	printf 'show clock\r';\
	sleep 1;\
	printf 'show ip interface brief\r';\
	sleep 1;\
	printf 'exit\r';\
	sleep 1;\
) | telnet 192.168.0.200
```
tr で \n と \r を変換しても良いんですけどね。
まぁ、最近は ssh なのでこの手が使いにくいのですが。
というメモでした。

# Cisco 機器の場合 perl バージョン
```perl:for Cisco
#!/usr/bin/perl
use strict;
use warnings;
use feature qw(try);
no warnings "experimental::try"; # 警告の抑制

use POSIX qw(strftime);
use Net::Telnet;

my $host;					# ホスト or IP
my $user = 'admin';			# ユーザ名
my $pass = 'Cisco123';		# パスワード
my $prompt = '/[>#]$/';		# プロンプト(正規表現)

my @HOSTs = ('100.64.20.1', '100.64.21.1', '100.64.22.1');

foreach my $host ( @HOSTs ){
	&command_exec($host)
}

sub command_exec(){
	my $host = shift;
	my $log_file = strftime("${host}_%Y%m%d_%H%M%S.log", localtime);
	my @result;

	# 失敗しても ３回までリトライする
	for ( 1..3 ) {
		print "--- 再実行します ---\n" unless ($_ eq 1);
		try {
			my $result;
			my $telnet = new Net::Telnet(
				Timeout => 5,
				Errmode => 'return',				# エラーメッセージを追加するため
				Prompt => $prompt,
				Input_log => $log_file,
				Cmd_remove_mode => 0,
				Max_buffer_length => 10485760,		# 10MB
			);

			print "\n*** $host へ接続します\n";

			# ホストに接続してログインする
			die "connect failer\n"
				unless $telnet->open($host);
			die "login failer. check username or password"
				unless $telnet->login( Name => $user, Password => $pass);
			die "enable command failer. check log please."
				unless $telnet->cmd(String => "enable" ,Prompt  => '/Password:/');
			die "enable failer. check enable password."
				unless $telnet->cmd($pass);

			# ここからエラーになることは考えにくいので die にする
			$telnet->errmode("die");

			$telnet->cmd('ter len 0');
			$telnet->cmd('ter wid 0');

			# コマンドの実行
			@result = ($telnet->last_prompt(), $telnet->cmd("show run"));
			print @result;

			@result = ($telnet->last_prompt(), $telnet->cmd("show version"));
			print @result;

			@result = ($telnet->last_prompt(), $telnet->cmd("show clock"));
			print @result;
	
			@result = ($telnet->last_prompt(), $telnet->cmd(String => "show tech", Timeout => 60));
			print @result;

			# コマンドの実行結果にたまたまプロンプトに合致する物が含まれていた場合
			# 途中で切れてしまうので、絶対に実行結果に含まれないような文字列で
			# 最後まで出力していることを確認する。
			@result = ($telnet->last_prompt(), $telnet->cmd(String => "!!! ALL DONE !!!", Prompt  => '/!!! ALL DONE !!!/', Timeout => 60));
			print @result;
			print $telnet->last_prompt();

			# 接続の切断
			$telnet->close;
			print "\n*** $host から切断しました\n\n";
		} catch ($e) {
			# エラーが発生した場合は、もう一度
			# redo だと無限ループになる
			print "\n$e";
			next;
		}
		# 成功した場合は for ループを抜ける
		last;
	}
}
```

# Aruba AP 機器の場合 perl バージョン
接続先毎に、ユーザ名、パスワード、ログファイル名の先頭部分を指定できるように変更しました。

```perl:for Aruba AP
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw(strftime);
use feature qw(try);
no warnings "experimental::try"; # 警告の抑制

use Net::Telnet;

my @HOSTs = (
	# 
	# ['<接続先>', '<log_prefix>', '<user>', '<pass>'],
	#
	# 接続先     : IP アドレスや、ホスト名
	# log_prefix : ログファイル名の区別用 (ホスト名など)
	# user       : ログインの user 名
	# pass       : パスワード

	['100.64.10.33', 'AP-001', 'show', 'abc123'],
	['100.64.20.61', 'AP-101', 'show2','Passw0rd'],
);
my $prompt = '/# $/';		# プロンプト(正規表現)


foreach my $host ( @HOSTs ){
	&command_exec($host)
}

sub command_exec(){
	my ($host, $log_prefix, $user, $pass) = @{$_[0]};
	my $log_file = strftime("${log_prefix}_%Y%m%d_%H%M%S.log", localtime);

	# 失敗しても ３回までリトライする
	for ( 1..3 ) {
		print "--- 再実行します ---\n" unless ($_ eq 1);
		try {
			my @result;
			my $telnet = new Net::Telnet(
				Timeout => 5,
				Errmode => 'return',				# エラーメッセージを追加するため
				Prompt => $prompt,
				Input_log => $log_file,
				Cmd_remove_mode => 0,
				Max_buffer_length => 10485760,		# 10MB
				Output_record_separator => "\r",	# コマンド送信時の改行コードは \r
			);

			&print_banner("$host へ接続します");

			# ホストに接続してログインする
			# パターンが違うので、login() は使えない
			die "connect failer\n"
				unless $telnet->open($host);
			die "login failer. no response."
				unless $telnet->cmd(String => '', Prompt  => '/User: $/');
			die "login failer. no/illigal response."
				unless $telnet->cmd(String => $user, Prompt  => '/Password: $/');
			die "login failer. check username or password"
				unless $telnet->cmd($pass);

			# ここからエラーになることは考えにくいので die にする
			$telnet->errmode("die");

			# コマンドの実行
			@result = ($telnet->last_prompt(), $telnet->cmd("show running-config"));
			print @result;

			@result = ($telnet->last_prompt(), $telnet->cmd("show version"));
			print @result;

			@result = ($telnet->last_prompt(), $telnet->cmd("show clock"));
			print @result;
	
			@result = ($telnet->last_prompt(), $telnet->cmd(String => "show tech-support", Timeout => 60));
			print @result;

			# コマンドの実行結果にたまたまプロンプトに合致する物が含まれていた場合
			# 途中で切れてしまうので、絶対に実行結果に含まれないような文字列で
			# 最後まで出力していることを確認する。
			@result = ($telnet->last_prompt(), $telnet->cmd(String => "!!! ALL DONE !!!", Prompt  => '/% Parse error/', Timeout => 60));
			print @result;
			print $telnet->last_prompt();

			# 接続の切断
			$telnet->close;
			&print_banner("$host から切断しました");
		} catch ($e) {
			# エラーが発生した場合は、もう一度
			# redo だと無限ループになる
			print "\n$e";
			next;
		}
		# 成功した場合は for ループを抜ける
		last;
	}
}

sub print_banner(){
	my $str = shift;
	my $line = '*' x (length($str) + 8);
	print "\n";
	print "$line\n";
	print "*** $str ***\n";
	print "$line\n";
	print "\n";
}
```

:::note Info
perl モジュール、Net-Telnet のマニュアルは cpan を参照
[Net::Telnet](https://metacpan.org/pod/Net::Telnet)
:::

:::note Info
sub print_banner 部分、きちんと並べることが出来ませんでしたが、きちんと並べようとすると追加でモジュールを読み込まないといけない雰囲気だったので諦めました。
[Perlでマルチバイト文字の文字幅をAmbiguousWidthも考慮して取りたいときのTips](https://songmu.jp/riji/entry/2013-08-05-visual-width.html)
:::

### Teraterm のログのタイムスタンプを外す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306041/26ba244e-606c-bb42-4e2a-2f5e1597c9c1.png)
タイムスタンプでログを取得したけども、結果を diff しようとしたらタイムスタンプが邪魔。ということも。そういうときには、さらっとタイムスタンプを外してしまいましょう。
```sh:teraterm のタイムスタンプを外す
for i in *.log ; do
sed -ne 's/^\[[-0-9]*\] [:\.0-9]*\] //p;' < "$i" > "$i.タイムスタンプなし.log"
done
```

外部リンク
[telnet操作をコンピュータにやらせて処理(show account)を自動化する方法はありませんか？(telnet用チャットスクリプト) ](http://www.rtpro.yamaha.co.jp/RT/FAQ/Config/telnet-script.html) (YAMAHA)