コマンドプロンプトに憧れて...
はじめに
WindowsやLinux、Macはそれぞれコマンドを実行できる環境がデフォルトで入っている。CUIは、慣れると操作がとても速くなるし使いやすい。何よりコンピュータを操作している感じに浸れるのが嬉しい。C言語やJavaやRubyなどはコンソールアプリケーションを作ることができる。それに対してVBAはイミディエイトウィンドウに出力できるもののコンソールアプリケーションとは少し違うような...。そんなこんなでVBAを使ってコマンドを実行できるものを作ってみた。役に立つかはわかりないが、あくまで私の「憧れ」を形にしたものなのであしからず。まずは以下の動画をご覧あれ。
少し作業して、コンソールっぽいものからコマンドで実行できるようなもの作ってみました。本当はオプション付けられて処理いろいろ変えられるんですけど、まだプログラムが全部書きあがってないのでめっちゃ簡単なやつだけ。私は面白いと思うけど、需要ないだろうな...(´;ω;`)ウゥゥ#VBA#Excel pic.twitter.com/kPG765TMkZ
— あっさん (@Kabura_net14831) 2020年4月24日
コンソールを作る
Excelでは(私の知る限りでは)そのままコンソール入出力を扱うことができない。例外としてコマンドプロンプトを呼び出してコマンドを入力させることができるが、Excel上で処理をさせようと考えた場合、少し分が悪い。そこでまずは入出力可能なコンソールから作るとする。以下のようなフォームを作成してほしい。表記してあるのはプロパティを変える部分だけで、後は変更の必要はない。
まずは起動時の初期処理から書いていく。コマンドプロンプトに似せるためにそれっぽく書いてみる。ユーザーフォームの起動時のイベントに書くとしよう。
Private Sub UserForm_Initialize() 'バージョン情報の表示 Dim VerInfo As String VerInfo = "Microsoft Excel [Version " + Application.Version + "]" + vbCrLf + _ "(c) 2019 Microsoft Corporation. All rights reserved." + vbCrLf + vbCrLf Console.Text = VerInfo End Sub
なお、Excelのバージョン情報は以下のコードで取得できる。
Application.Version
コマンドの受け付けは「Console_Input」が受け持つ。入力された文字列はそのままコマンド文字列として使うので何もいじらず、そのままコンソールに表示する。Enterで実行したいので入力されたキーを判別する必要がある。そこでKeyDownイベントにコードを記述する。
Private Sub Console_Input_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer) If KeyCode = vbKeyReturn And Console_Input.Text <> "" Then 'もしexitと入力された場合は終了する If LCase(Console_Input.Text) = "exit" Then Unload Me Exit Sub End If 'Enterキーが押された場合、処理をCommandクラスに投げる(コマンドクラスを後で作る) Console_Write_Command (Console_Input.Text) '(この後で実装する) 'コマンドの実行 Run_Command (Console_Input.Text) '(この後で実装する) '入力されたコマンドをクリア Console_Input.Text = "" 'スクロール対策 Console.SetFocus 'フォーカスをコンソールにあてる Console.SelStart = Len(Console.Text) '最後に移動 Console_Input.SetFocus '入力用のテキストボックスにフォーカスを戻す End If End Sub
ここで大事なのが、スクロール対策のところで、Excelで用意されているテキストボックスはなぜかホイールでスクロールできない。一度フォーカスを当ててキャレットを移動した後、再び入力用のテキストボックスにフォーカスを戻してやる必要がある。 次に、コマンドを表示するところを書く。処理自体は一行で終わるほど簡単なもの。
'入力されたコマンドを表示する Private Sub Console_Write_Command(ByVal CommandStr As String) Console.Text = Console.Text + ">>> " + CommandStr + vbCrLf End Sub
今度はコマンドを実行するクラスを呼び出す処理を書く。ログ出力もできるようにしたほうが利便性高いかな?
'ログ出力用 Private Sub Console_Write_Log(ByVal LogMessage As String) Console.Text = Console.Text + LogMessage + vbCrLf + vbCrLf End Sub
'コマンドを実行するクラスを呼び出す Private Sub Run_Command(ByVal CommandStr As String) Dim Command As New Command Dim Result As String 'Commandクラスに処理を投げる Result = Command.Run(CommandStr) 'ログ出力用 Console_Write_Log (Result) End Sub
フォームの処理は全部書き終わったので本命のコマンド解析&実行するクラスを書いていくよ。
コマンド解析&実行するクラスを作る
初めに言っておくが、これがすごく面倒くさい。簡単そうに見えるけど、実はそう簡単なものでもなかった。実装してみて初めて気が付いた。 まずは、コマンドを解析する処理から書いていく。新しくクラスを追加し、名前を「Command」に変える。 今回考慮することは以下の通り。
- 空白で引数の区切りとする
- ダブルクォーテーションの中に空白があった場合は無視
実装すると以下のようになる。結構複雑なところもある(わかる人はすぐわかると思う)のでわからなければTwitterでDMくれて聞いてもらって構わない(回答に困らない質問の仕方にしてね)。 コマンドが空白じゃない場合は処理を継続することにする。本当はもっとちゃんとエラーチェックすべきだろうけど、そこは気にしたら負けだ。うん。
Public Function GetCommandLineArgs(ByVal CommandStr As String) As String() '---------------------------------------------------------------- '@Name ' GetCommandLineArgs ' '@Param ' CommandStr : コマンド文字列。 ' '@Return ' CommandLineArgs : コマンドを分割して順に入れた配列。 ' '@Description ' コマンドを分割して配列で返す。 ' '@Note ' なし。 ' '---------------------------------------------------------------- '戻り値 Dim CommandLineArgs() As String If CommandStr = "" Or CommandStr = " " Then '文字列が空白だった場合 ReDim CommandLineArgs(0) As String CommandLineArgs(0) = "" Else '文字列が空白でなかった場合 '入力された文字列を分割する。 Dim Counter As Long: Counter = 0 'ダブルクォーテーションの数をカウントする。 Dim Char As String '一文字切り出して入れておく Dim ArrayCounter As Long: ArrayCounter = 0 '配列用のカウンタ Dim StartPos As Long: StartPos = 1 '分割の始まりの位置 Dim i As Long: For i = 1 To Len(CommandStr) + 1 '一文字切り出す。 Char = Mid(CommandStr, i, 1) '切り出した文字がダブルクォーテーションの場合。 If Char = Chr(34) Then Counter = Counter + 1 'ダブルクォーテーションの数を1増やす。 End If 'ダブルクォーテーションの数が偶数の場合のみ空白で分割する。 If (Counter Mod 2 = 0 And Char = " ") Or (i = Len(CommandStr) + 1) Then Dim tmp As String: tmp = Mid(CommandStr, StartPos, i - StartPos) 'オプションの「/」のみのものは追加しない。 If tmp <> "/" Then ReDim Preserve CommandLineArgs(ArrayCounter) As String CommandLineArgs(ArrayCounter) = tmp ArrayCounter = ArrayCounter + 1 End If StartPos = i + 1 End If Next i End If '置換処理 Dim j As Long: For j = 0 To UBound(CommandLineArgs) 'ダブルクォーテーションを一括削除 CommandLineArgs(j) = Replace(CommandLineArgs(j), Chr(34), "") Next j GetCommandLineArgs = CommandLineArgs End Function
次はコマンドを実行する関数を作っていくよ。VBAには関数を文字列として指定できる「CallByName」関数があるのでそれを有効活用させてもらおう。なお、上で作った、コマンド解析の関数を呼び出してる。それから、これが一番大事なことで、「コマンド名」=「関数名」という関係(大文字と小文字は区別しない)。これが成り立たないと呼び出せない!!
Public Function Run(ByVal CommandStr As Variant) As String '---------------------------------------------------------------- '@Name ' Run ' '@Param ' CommandStr : コマンド文字列。 ' '@Return ' Result : 処理結果を返す文字列。 ' '@Description ' コマンドを実行する。 ' '@Note ' CallByNameを使用。 ' '---------------------------------------------------------------- On Error GoTo ERROR Dim Result As Variant '処理結果 Dim Args() As String: Args = GetCommandLineArgs(CommandStr) '引数を取得 '配列の最初にコマンド名が入っている。配列としては渡せないのでコマンドとして渡してコマンドプロセスで引数判断をしてもらう。 Result = CallByName(Me, Args(0), VbMethod, CommandStr) Run = Result Exit Function ERROR: Run = "'" + CommandStr + "'" + "は、有効なコマンドではありません。" End Function
コマンドを作る
よっしゃ!!ようやくここまで来たぜ。コマンドを作ってくぞ!今回は最も簡単な例として一番最初の動画にも使った、文字列反転のコマンドにする。引き続きCommandクラスに書いていくぞ。こちらもオプション解析が非常に面倒くさい。ここでの実装は私が何も見ずに思いつきで書いたものなので実際のコマンドプロンプトにおけるコマンド処理とは全然違うかもしれない。興味ある人は自分で調べてみてもいいだろう。解説するとすごく長い記事になっちゃうのでソースコードだけ載せておくので、これも不明な点があればTwitterのDMで聞いてくださいな(回答に困らない質問の仕方にしてね)。
Public Function StringReverse(ByVal CommandStr As String) As Variant '---------------------------------------------------------------- '@Name ' StringReverse ' '@Param ' CommandStr : コマンド文字列。 ' '@Return ' Result : 戻り値。 ' '@Description ' 任意の文字列を反転して返す。 ' '@Note ' オプション一覧 ' /a 選択範囲に限定。 ' /l すべて大文字にして反転。 ' /s すべて小文字にして反転 ' ' 文法 ' StringReverse {/aまたは反転する文字列} (/l) (/s) '---------------------------------------------------------------- Dim Result As String Dim Args() As String: Args = GetCommandLineArgs(CommandStr) '引数の確認 If UBound(Args) = 0 Then Result = "コマンドの構文が間違っています。" Else '------------------------------------------オプションのためのスイッチ---------------------------------------------- Dim OPTION_SELECTION As Boolean: OPTION_SELECTION = False Dim OPTION_LARGE As Boolean: OPTION_LARGE = False Dim OPTION_SMALL As Boolean: OPTION_SMALL = False '------------------------------------------------------------------------------------------------------------------ 'オプションの確認 Dim i As Long: For i = 1 To UBound(Args) '一番最初が「/」で始まっているものはオプションとみなす If Args(i) Like "/*" = True Then If Args(i) Like "/a" = True Then OPTION_SELECTION = True ElseIf Args(i) = "/l" Then OPTION_LARGE = True ElseIf Args(i) = "/s" Then OPTION_SMALL = True End If End If Next i '反対のオプションがあった場合はエラーとして処理 If OPTION_LARGE = True And OPTION_SMALL = True Then GoTo ERROR End If Dim tmp As String '読み込み範囲を選択範囲に限定するかどうか If OPTION_SELECTION = False Then '範囲読み込みでない場合は第一引数に反転させる文字を持ってくる tmp = Args(1) '大文字にするかどうか If OPTION_LARGE = True Then tmp = UCase(tmp) End If '小文字にするかどうか If OPTION_SMALL = True Then tmp = LCase(tmp) End If Result = StrReverse(tmp) Else Dim r As Range: Set r = Selection Dim val As Range '出力先範囲が指定されていない場合 For Each val In r tmp = CStr(val.Value) '大文字にするかどうか If OPTION_LARGE = True Then tmp = UCase(tmp) End If '小文字にするかどうか If OPTION_SMALL = True Then tmp = LCase(tmp) End If val.Value = StrReverse(tmp) Next Result = "指定された範囲の文字列の反転を開始しています...完了" End If End If '戻り値 StringReverse = Result Exit Function ERROR: StringReverse = "不正なオプションを使用しているか、コマンドの文法が間違っています。" End Function
以上、Excel上で動くコマンドプロンプトもどきでした!コマンドプロンプトに憧れて...ってタイトル付けたけど実際私が憧れてるのはbashなどのLinuxシェルの方です(笑)。何かの役に立てば私は嬉しいかな。自分でいろいろコマンドを追加して遊んでみてくださいね!!
はじめに
はじめまして。
私は小学生の頃からExcel VBAを触ってきました。なぜほかのプログラミング言語じゃないのか、というツッコミはなしにしてくださいね(笑)。まだ周りには興味を持ってくれる友達もいない中、寂しくも一生懸命にプログラミングに励んでいました。
時がたつこと早6年、Twitterを始めた私は軽い気持ちで作ったソフトの動画を公開しました。すると予想以上に好評でした。しかしながら、裏にはたくさんの苦労があります。一つのツールやソフトを作るために様々なサイトを調べましたが、変わった使い方を紹介しているサイトは少なく困ってしまいました。今後私と似たような境遇の人が少しでも多く情報を手に入れることができるように情報を公開したいと思いブログを開設しました。
現時点(2019 / 9 / 16)において、私は高校生ですから更新頻度は不定期です。更新したらぜひ見てくださいね。
これからよろしくお願いします!