サクッと作るteratermマクロ入門

(2020.12.15追記)sendlnコマンドにsendコマンドとの違いを補足
(2020.12.02追記)ログイン処理関係追記しました
月に数回の頻度でストックやLGTM頂けて感謝です、更新の励みになります。
(2020.07.02追記)getdateについて追加しました

---

# はじめに
teratermで各サーバやNW機器の情報取得するとき、マクロでサクッと取りたいときってあると思います。
そんなときネットで検索しても凝ったものが多くて
**「いやそこまではいいよ！　もっとシンプルでいい！！」**と思うことがあったり。
そして調べて作ったのはいいけど、そんな作る頻度多いわけではないのですぐ忘れちゃうのも悲しいところ。

なので、必要最低限の知識で作れるマクロの紹介です。

# どんな人向け？

- 面倒な作業をサクッとマクロで書きたいけど触ったことない
- そこまで凝ったものは作る必要ない
- 試験、検証用にログを残し、同じ処理を流したい
- ~~ログ保存をよく忘れる~~

# 最低限知っておきたい

以下に記載するTeratermマクロを利用する際に必要最低限知っておくべきことを記載。
そんな解説よりマクロ見たい！って方は飛ばしてもらって構いません。

## TTLファイル

マクロ動かすためには、処理を書くファイルが必要です。
それが**TTLファイル(xxx.ttl)**です。
ちなみに拡張子が**ttl**なだけで、形式はテキストファイルと変わりません。

## マクロの実行方法

以下の2パターンが主な実行方法です。

1. **TeratermにTTLファイルを読み込ませて実行**
2. **TTLファイルをダブルクリックして実行**

今回は「1.teratermにTTLファイルを読み込ませて実行」する方法を紹介します、作るのラクですし。
「2.TTLファイルをダブルクリックして実行」は気が向いたら作ります。

## どこからマクロを実行するか？

マクロ実行する環境として、以下の2パターンが主です。

1. **直接リモートログインして情報収集**
2. **踏み台を利用した情報収集**

イメージこんな感じです。
(さくらのナレッジさんのアイコン使いやすい・・・)

![teratermマクロについて.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392066/46721fa9-7f70-33ca-2853-09cc48a250fb.png)

今回は「2.踏み台を利用した情報収集」を紹介します。
基本は直接リモートログインして情報収集する事が多いですが、踏み台を使うことでのメリットもあります。

 - **踏み台でSSH接続することでウィンドウの閉じ開けが発生せず、裏側で処理動かすのに最適**
 - **初期のプロンプトや接続先が固定になるので、マクロの変更箇所を減らすことが出来る**

ただ、1台に対して定期的に情報収集する用途であれば、直接ログインしたほうが早いので、
用途次第でどっちにするか決めると良いです。

## sendlnでコマンド実行、waitでプロンプトを待つ！

各コマンドの詳細は後述しますが、基本的に上に書いている通りです。
**自分らがコマンド打つときもコマンドを打ち、プロンプトが返ってきたことを確認し、**
**次の処理に行く**ので、それをマクロでも同じようにやります。

```
; コマンド「show version」を実行
sendln 'show version'

;プロンプト「switch#」が返ってくるまで待つ
wait 'switch#'

;次のコマンド「show running-config」を実行
sendln 'show running-config'

```

めんどくさがってwaitコマンドを抜かして**sendlnコマンド連続でやると、**
**後続コマンドが効かず実行漏れする**ことがありますので、
漏れないようにするためにも、waitコマンドでプロンプトを待ってから後続処理に行くようにしましょう。

## コメントはコロン(;)

上の例でもある通り、コメント書きたい時は先頭にセミコロン(;)を入れます
コメント用の記号、なんだったかすぐ忘れがち。

## 知っておくべきコマンド

とりあえずここらへん知っておくとマクロ作るのに役立てるコマンドを簡単に紹介します。
とはいえ・・・気づいたら結構な量になり**「全然サクッとしてねぇ！」**と言われそう・・・。
右側の各タイトルを見て興味あるものを掻い摘んでみていただけたらと思います。

なお、ここで紹介するコマンドを組み合わせれば、この後に紹介するマクロを作ることができます。

詳しくは以下のURLのコマンドリファレンス参照。

- [TTLコマンドリファレンス](https://ttssh2.osdn.jp/manual/ja/macro/command/index.html)

### コマンド投入
```sendln <data1>```
```sendln <data1> <data2> ...```

- \<data1\>の文字列と**改行**をホストに送信するので、コマンド打鍵でよく使う
    - 類似コマンドである**```send```は改行含まれないので注意**
- ```sendln```のみだと空エンターと同じ
- \<data2\>以降入力すると、\<data1\>から連結してホストに送信
    - わざわざ連結コマンド使わなくても連結して送信してくれるので便利！
    - 例：```sendln "host" "name"```とすると```"hostname"```＋改行がホストに送られる

### 文字列待ち
```wait <string1> [<string2> ...]```

- \<string1～10\>の文字列の内、ホストからの送られてくるまで待機
- ```timeout``` か```mtimeout```でタイムアウト値設定可能、設定しなければタイムアウトなし
- コマンドの実行結果は```result```に格納
    - 0 : タイムアウト
    - 1 : ```<string1>```がヒット
    - ...

### 時間待機
```pause <time>```

- 数分毎に実行したい時はこれを使う
- ミリ秒指定したい時は```mpause```を使う
- 接続先のタイムアウト値以上指定するともちろんタイムアウト値ちゃうので、注意
    - 途中で```sendln```で空エンターを挟んだりすればタイムアウトしなくて済む

### 文字結合
```strconcat <strvar> <string>```

- コマンド文字列やログファイル名加工で使うシリーズその1
- ```<strvar>```の末尾に```<string>````を追加する
- 複数文字列を結合したい場合、これを繰り返す必要があり、ちょっと手間

### 文字分割
```strsplit <strval> <separator> [<count>]```

- コマンド文字列やログファイル名加工で使うシリーズその2
- ```<strval>```の文字列を```<separator>```の区切り文字(**1文字**)で区切り、```groupmatchstr1...9```に格納
- ```<count>```は分割した文字列の最大数、デフォルトが最大値9なので、入力しなくても問題なし

### 時間取得
```getdate <strvar> [<format> [<timezone>]]```

- ログ名に日付入れたい場合はコレを使います
- ```strvar```に現在時刻を代入します、```<format>```指定しなければ**yyyy-mm-dd**と出力されます
    - **yymmdd**じゃないとヤダ！って場合は```<format>```に```'%y%m%d'```指定するといい感じに(yyyymmddなら```'%Y%m%d'```)
    - 0を除いてほしい場合は```%#m```のように#挟むといい感じになる
    - ~~```strconcat```や```strcopy```で切り貼りする必要なんてなかったんや・・・~~
- ```<timezone>```はGMTなどタイムゾーンを合わせた時刻を出力、指定なければローカルタイム

```
;時刻をそのまま出力(yyyy-mm-dd)
getdate datestr
messagebox  datestr 'unuse format'

;フォーマット指定して時刻出力(yymmdd)
getdate datestr '%y%m%d'
messagebox  datestr 'use format'

;フォーマット指定して出力(yyyymmdd)
getdate datestr '%Y%m%d'
messagebox  datestr 'use format'

;フォーマット指定して出力(yyyy/m/d hh:nn:ss)
getdate datestr '%Y/%#m/%#d %H:%M:%S'
messagebox  datestr 'use format'
```

### ログイン関連
```connect <command line parameters>```

- ログイン処理をマクロで自動化してくれる
- **「毎回IPアドレス、ユーザ、パスワード入れるの面倒！　ついでにログ保存忘れる！」**という時に大活躍
- Telnet、SSH、コンソールそれぞれで```<command line parameters>```が変わるので、詳細は以下で説明
- 各ログインで共通する部分として、うまくつながれば```result = 2```、そのほかはうまく繋がってないのでエラー処理で活用できます

#### Telnetログイン
```connect 'IPアドレス:ポート番号(23) /nossh'```

- ユーザ、パスワードはプロンプトに表示されるので、```wait```や```sendln```を使っていい感じにログイン処理を作りましょう

```:Telnetログイン
; ログイン処理
connect 'IPアドレス:ポート番号(23) /nossh'

; ログインプロンプトの待機(接続先によって適宜変更)
wait 'login:'
sendln 'ユーザID'
wait 'password:'
sendln 'パスワード'
```

#### パスワード認証によるSSHログイン
```connect 'IPアドレス:ポート番号(22) /ssh /SSHバージョン(1 or 2) /auth=password /user=ユーザ名 /passwd=パスワード'```

- ポート番号、SSHバージョンは指定しなくてもだいたいいけます
- 毎回同じIPアドレス、ユーザ、パスワードであれば上をひとつ書けばいいです
- 使いまわしたり、毎回変わったりする場合は以下のように```strconcat```を良い感じに文字列加工する必要あり
- 公開鍵認証したい場合は```/auth=pagean```を利用

```:strconcatでログインコマンド生成してログイン処理
;ログイン情報
USERNAME = 'ユーザ名'
PASSWORD = 'パスワード'
HOSTADDR = 'IPアドレス'

;ログインコマンド生成
COMMAND = HOSTADDR
strconcat COMMAND ' /ssh /auth=password /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /passwd='
strconcat COMMAND PASSWORD

;ログイン
connect COMMAND
```

### ログ保存関連

```
;ログフォルダ格納先を指定
changedir path_log_folder

;ログ記録開始
logopen LOGFILE 0 0

;ログ記録終了
logclose
```

- 基本は上記3つのコマンドを使うことになる。
- 補足として、```logopen```の引数は以下の通り
    - LOGFILE：ログファイル名
    - 1つ目の0(binary flag)：0なら受信した漢字や改行文字を変換してファイルに書き込み
    - 2つ目の0(append flag)：0なら上書き、0以外なら追記
- 上記以外のパラメータたくさんあるのでマニュアル一読すると良いかもしれないです


### 無限ループ

夜中ずっとコマンド垂れ流したいときはdo ～ loopで無限ループが出来ます。

```
;ココらへんに初回処理

do

;ループさせたい処理

;時間間隔開けたければpauseを使う、以下は5分待機している
pause 300

loop

;ココらへんに終了処理
```

上のコマンド、垂れ流すのに非常に便利ですが、**ログインホストのタイムアウト値**には気をつけましょう。
(1時間おきに実行してたらタイムアウト値5分でタイムアウトしちゃう等、よくやっちゃいますね！！)

上のようなことを回避するため、**タイムアウト値未満の時間間隔で空エンター入れる**といい感じです。

```
do

;ループさせたい処理

pause 60

;59分間、1分毎に空エンターを実行

i = 59

while i

sendln ''

pause 60

i=i-1

endwhile

;空エンターの繰り返し処理末尾
loop

;ループさせたい処理の繰り返し処理末尾
loop
```

### リストファイルを読み込んで繰り返し処理する

```
;ファイルを開く
fileopen fh 'test.txt' 0

;繰り返し地点
:loop
; 一行読み込み
filereadln fh buf
if result goto breakloop

～※1行読み込んだbufを用いた処理※～

;loopに戻る
goto loop

;ループ抜け先
:breakloop
; ファイルクローズ
fileclose fh
```

基本は上のコードで事足ります。
ちなみにコロン(:)はサブルーチンで、処理内の特定の行に飛びたいときに使います。
**test.txt**を読み込み、一行ずつ```buf```へ格納した後、※印部分を繰り返してEOFまで来たら```:breakloop```に飛ぶ。
読み込み終わった後、最後にフィアルクローズすることを忘れずに！

### 所定時刻到来時に実行

夜中の特定時刻にマクロを動かしたいときに使うと便利です。
500ms間隔で時刻をチェックし、特定時刻と一致したら、当該処理を実行するようになっています。

```
;処理実行時刻
time_exec_str = "2019/12/24 14:00:00"

;時刻チェック
do

;現在時刻取得
gettime time_now_str "%Y/%m/%d %H:%M:%S"

;現在時刻と処理実行時刻と比較(一致したらresultに0を代入)
strcompare time_now_str time_exec_str

;前処理の比較が一致した場合、時刻到来時処理に移動
if result=0  goto method_exec

;500ms待機
mpause 500

;doへ戻る
loop


;時刻到来時処理
:method_exec

sendln 'echo ' time_now_str
```


# 早速サクッと作ってみる

直近の案件で使ったマクロの内、今後もどこかで使いそうなものの紹介となります。

## 複数機器へのPingコマンド結果を取得する

NW機器構築の際、複数台の機器に対してPing疎通確認を行うことがあると思います。
ただ単にPingコマンド打つだけなら簡単ですが、試験用証跡としてログを残す際は
ログフォーマットが同じ方がいいですよね。

手動だとだいたいログ設定が抜けたりコメントが抜けたり誤入力したりするので、マクロの出番です。
踏み台にログインしてからマクロを実行すると、リストに記載されているホストに対してPing疎通確認を行います。

![ping02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392066/1431ac57-9f69-ba46-d614-9b0141811e92.png)


マクロファイル以外にPingコマンドを送るリストファイルを作成する必要があります。
数台程度ならリストファイル作る必要ないですが、10台超えるとリスト化したほうが効率いいですね。

今回はファイル構成を以下のようにします。

- リストファイル　C:\work\Ping_host.list
- ログフォルダ　　C:\work/logs

![ping00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392066/69a2197d-8a41-0258-d0d9-c61b9d60aaf9.png)

サクッとしていない行数かもしれませんが、コメント除けば20行前後なのでサクッの許容範囲なはず！

```:サクッとPingコマンド用マクロを作る
;踏み台サーバまたはNW機器にログインしてから実行
;出力先は同フォルダの「log」フォルダ、作成しておくこと
;Pingを送る対象はPing_host.listを参照する
;Ping_host.listの入力規則は「ホスト名,IPアドレス」

;変数 - - - - - - - - - - -

;「Ping_host.list」の絶対パス
path_ping_host_list = 'C:\work\Ping_host.list'

;踏み台サーバのプロンプト(処理終了後のプロンプト確認用)
prompt = 'test00'

;ログフォルダの絶対パス
path_log_folder = 'C:\work\logs'


;本処理 - - - - - - - - - - -

;Ping_host.listを開く
fileopen fh path_ping_host_list 0

;ここから繰り返し
:pingloop

;Pingリストファイルを取得
filereadln fh buf

;Pingリスト読み終わったらloop抜け
if result goto breakloop

;「,」で文字列分割
;groupmatchstr1 : ホスト名
;groupmatchstr2 : IPアドレス
strsplit buf ','

;ログファイル名　※①
LOGFILE = ''
strconcat LOGFILE groupmatchstr1
strconcat LOGFILE '.log'

;ログフォルダ格納先を指定
changedir path_log_folder

;ログ記録開始
logopen LOGFILE 0 0

;Pingコマンド　※②
;デフォルトで無限Pingの場合、回数指定すること
PING = 'ping '
strconcat PING groupmatchstr2
strconcat PING ' -c 5'

;コマンド投入、踏み台サーバのプロンプトが返ってくるまで待機
sendln PING
wait prompt

;ログ記録終了
logclose

;上記「:pingloop」まで戻って繰り返し
goto pingloop

;ループ抜け先(何もせずに終了)
:breakloop

end
```

実行すると、logsフォルダにPing結果のログがホストごとに出力されます。

![ping01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392066/a5ac63a7-c215-afca-28f2-f5d2d797d52f.png)

上のマクロでも十分ですが、+αな処理を下に追加してます。

### ①ログファイル名に日付を入れたい

上のマクロだと、ログファイル名が**ホスト名.log**となります。
取得日付を入れたい場合、※①の次の処理``LOGFILE = ''``を以下に書き換えると、**yyyymmdd_ホスト名.log**となります。

```:ログファイル名に日付を入れる
;日付の取得し、YYYYMMDD形式で出力
;※時間出したければ%H%M%Sを追加する
getdate Str_Getdate "%Y%m%d"

;年月日をファイル名に連結
LOGFILE = Str_Getdate
strconcat LOGFILE '_'
```

### ②各ログファイル内にコメントを入れたい

ファイル名にどのホストに対するPing疎通確認か書いてますが、ログファイル内ではIPアドレスのみでホスト名が載っていません。
ホスト名を載せるためにコメントをコマンドとして入れておくのも有効です。

※②の前に以下を入れておくとコメントを残すことが出来ます。

```:コメントをログファイル内に入れたい

;コメント用コマンド
COMMENT = '########## Ping to '
strconcat COMMENT groupmatchstr1
strconcat COMMENT ' ##########'

;コマンド
sendln COMMENT
wait login_server
```

## 複数の機器に対して参照コマンド結果を取得したい

どちらかというとこちらが本題ですね。
数十台ある機器の情報取得となると絶対に漏れたりミスする自信がありますね、こんな時こそマクロです。
全機器のユーザ、パスワードが同じ前提とします。
機器によってユーザパスワードが違うパターンは・・・気が向いたら作ります。

Pingのときと同様、踏み台にログインして取得ホスト、IPアドレスを
まとめた「Show_host.list」を作成してからマクロを実行します。

```
;踏み台サーバまたはNW機器にログインしてから実行
;出力先は同フォルダの「log」フォルダ、作成しておくこと
;Pingを送る対象はShow_host.listを参照する
;Ping_host.listの入力規則は「ホスト名,IPアドレス」

;変数 - - - - - - - - - - -

;「Show_host.list」の絶対パス
path_show_host_list = 'C:\work\Show_host.list'

;踏み台サーバのプロンプト(処理終了後のプロンプト確認用)
prompt = 'test00'

;ログフォルダの絶対パス
path_log_folder = 'C:\work\logs'

;ログインユーザ、パスワード
login_user = 'vagrant'
login_password = 'vagrant'

;本処理 - - - - - - - - - - -

;Show_host.listを開く
fileopen fh 'C:\work\Show_host.list' 0

;ここから繰り返し
:showloop

;Pingリストファイルを取得
filereadln fh buf

;Pingリスト読み終わったらloop抜け
if result goto breakloop

;「,」で文字列分割
;groupmatchstr1 : ホスト名
;groupmatchstr2 : IPアドレス
strsplit buf ','

;ログファイル名
LOGFILE = ''
strconcat LOGFILE groupmatchstr1
strconcat LOGFILE '.log'

;ログフォルダ格納先を指定
changedir path_log_folder

;ログ記録開始
logopen LOGFILE 0 0

;リモートログイン(Linuxサーバ)
;ssh接続の場合、LOGIN = 'ssh '」に変更
LOGIN = 'ssh '
strconcat LOGIN login_user
strconcat LOGIN '@'
strconcat LOGIN groupmatchstr2

;コマンド投入、パスワード入力、SU切り替え
sendln LOGIN
wait 'password:'

sendln login_password
wait groupmatchstr1

sendln 'sudo su'
wait groupmatchstr1

;ログの直近10件出力
sendln 'tail -n 10 /var/log/messages'
wait groupmatchstr1

;SU切り替え、リモートログイン終了
sendln 'exit'
wait groupmatchstr1
sendln 'exit'
wait prompt

;ログ記録終了
logclose

;上記「:pingloop」まで戻って繰り返し
goto showloop

;ループ抜け先(何もせずに終了)
:breakloop

end

```

実行すると以下のように取得コマンド結果がホストごとに格納されます。

![ping03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392066/9658401f-08e4-74ba-0478-e1f38560f5bf.png)


# おわりに

インターネット上では有志による素晴らしいマクロがある一方で、
サクサクッと作れるマクロもあっていいと思うんですよね。
今後、仕事でサクッと出来たTeratermマクロがあったら追加していこうと思います。


# おまけ(vagrantfile)

ログインユーザやパスワードから察された方がいるかもしれませんが、検証環境はvagrantで構築しました。
そのときのvagrantfileを参考として載せておきます。
4台手動で立てるとだるく感じますが、vagrantだとサクッと立ってくれてホントいい子ですね。
boxは**centos/7**を利用してます。
ログインIDパスワードはデフォルトの**vagrant**です、鍵なしでパスワード認証でアクセス可能。
ブリッジ接続なので、IPはいい感じに直してください。

```ruby:検証環境構築用
# -*- mode: ruby -*-
# vi: set ft=ruby :

# コンフィグ関数
def ConfigCentos7Easy(config , hostname , ip)
  config.vm.define hostname do |node|
    # ホスト名、IPアドレス(ブリッジ接続)
    node.vm.hostname = hostname
    node.vm.network "public_network", ip: ip
    # 起動時実行コマンド
      # - タイムゾーンを日本に設定
      # - パスワード認証有効化(鍵不要)
      # - sshd_config変更に伴うsshd再起動
    node.vm.provision "shell", inline: <<-SHELL
      timedatectl set-timezone Asia/Tokyo
      sed -i "s/PasswordAuthentication no/PasswordAuthentication yes/" /etc/ssh/sshd_config
      systemctl restart sshd
      SHELL
  end

  config.vm.provider "virtualbox" do |vb|
    # メモリ512MB
    vb.memory = "512"
    # CPU数1台
    vb.cpus = "1"
  end
end

# 本処理
Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  # test00
  ConfigCentos7Easy(config, "test00" , "192.168.11.70")
  # test01
  ConfigCentos7Easy(config, "test01" , "192.168.11.71")
  # test02
  ConfigCentos7Easy(config, "test02" , "192.168.11.72")
  # test03
  ConfigCentos7Easy(config, "test03" , "192.168.11.73")
end
```



# 参考文献

- [さくらのアイコンセット](https://knowledge.sakura.ad.jp/4724/)
    - イラストに利用させていただきました、使いやすくて好きです。