2024/08/04

テキストファイルの読み書き(Openステートメント編)




VBAを使い「テキストファイルを読み書き」する手法について、以下のようなシリーズで紹介しています。
 ①テキストの文字コードについて
 ②Openステートメント法による読み書き   ←今回
 ③ADODB.Stream法による読み書き
 ④TextStream法による読み書き
 ⑤XMLHTTP法によるWebデータの読み取り   (作成中)
 ⑥Web上からダウンロードしての読み取り   (作成中)
今回は「Openステートメント」を使って、テキストファイルを読み書きする手法を紹介します。
読み書き手法対応文字コードファイルの場所
Shift-JISUTF-8UTF-16LAN上Web上
Openステートメント
ADODB.Stream
TextStream(FileSystemObject)
XMLHTTP+ADODB.Stream
図01

上表のようにOpenステートメント法では、基本的に「Shift-JISのみが操作対象」です。
なお工夫によりUTF-16にも対応可能ですが、これはVBAとの合わせ技的なもののようです。但しデータの並びとして、LE(Little Endian)はOKですが、BE(Big Endian)は対応できないようです。
また操作可能なのは、LAN上(ドライブ名やサーバー名で始まるパス名の場所)のファイルのみです。
手法全体の話ですが、図01のように手法ごとに「対応できる文字コードが異なる」原因の1つとして、手法が生まれた時期が関係ありそうです。下図が各手法の歴史です。今回紹介するOpenステートメントは1985年から存在したことが分かります。
テキストファイルの操作手法の歴史
図02

同様に、各文字コードの歴史をまとめたのが下図です。この表は「文字コード編」で紹介したのと同じ表です。
文字コードの歴史
図03

図02図03を見比べ、図01が何を物語っているのかを考えてみます。
今回紹介する「Openステートメント」が生まれた頃にはUnicodeは存在していないので、基本的に「Unicodeに対応していない(≒Shift-JISのみに対応)」のは仕方が無い事が分かります。但しVBA自体はUnicodeよりも新しく、VBA内部では文字をUnicode(UTF-16LEのようです)で処理しているため、VBAの機能を工夫することでUnicodeに一部対応できる事になります。
またADODB・FileSystemObject・XMLHTTPは次回以降での紹介となりますが、全てUnicode誕生後の手法です。それなのにFileSystemObjectは、UTF-8には対応していません。
この原因を勝手に推測してみると、Unicodeの将来性に気が付いていなかったとか、気が付いていたとしても「UTF-8よりはUTF-16の方が有力」と考えていたのかもしれません。
以下では、Openステートメントについて詳細を説明します。

1.Openステートメント法 

「Open ~」を使ったテキストファイルを操作する手法は、N88-Basicの頃から存在しました。その構文もOpen ~ For mode As ~ と、以下で説明するものとほぼ同じだったようです。
その流れを汲んでいるのか、VBAでテキストファイルを操作する手法の1つとして「Openステートメント」が使用できます。もちろんOpenはファイルを開くステートメントで、データを読み書きするのは「Line Input、Printステートメント」等ですが、これらの総称を何と呼べば良いのか分からなかったので、代表して「Openステートメント法」としました。

1-1.ファイルのOpen

「Openステートメント」は、ファイルを開くものです。構文は以下のようになります。角カッコ( [ ] )の中は省略可です。
 Open pathname For mode [Access access ][lock] As [#]filenumber [ Len = reclength ]
項目内容
pathname必須開くファイルの(パス名+)ファイル名
mode必須アクセスモード
access省略可許可される操作
lock省略可他プロセスの操作制限
filenumber必須ファイルを操作する際のファイル番号。「#」は任意
reclength省略可バッファ容量 or レコード長
図04

pathname」は、操作するファイルのパス+ファイル名を指定します。なお、パスを省略した時には「ドキュメント(C:¥Users¥ユーザー名¥Documents)」フォルダーにあるファイル名となるようです。
なおpathnameに指定できるファイルは、下図のように「LAN上」のみで、Web上に存在するファイルは指定不可です。
テキストファイルの存在する場所
図05

mode」は、アクセスモードの指定です。以下の中から指定します。
アクセスモード内容
Input入力モード
Output出力モード
Append追加モード
Randomランダムアクセスモード
Binaryバイナリモード
図06

このアクセスモードは、様々なサイトでも紹介されている通りです。
シーケンシャルモードと呼ばれる「Input・Output・Append」は、文字としてテキストファイルへの入出力をするものです。
ランダムアクセスモードは、固定長ファイル上のデータを「レコード位置を指定」して入出力できるものです。
バイナリモードは、文字通り1バイト単位のバイナリデータとして入出力するものです。
「Access」句に続けて指定する「access」は、開いたファイルに対し「許可される操作」のキーワードを以下の中から指定します。省略可能です。
許可される操作内容
Read読み取り
Write書き込み
Read Write読取+書込
図07

なおアクセスモードとの組み合わせで、設定が可能か否かを調べた結果が下記です。〇印が可能です。
accessInputOutputAppendRandomBinary
Read
Write
Read Write
図08

この表を見ると、例えば「Appendでも読み取りが可能?」と勘違いしてしまいそうですが、下の図13で説明する「アクセスモード~操作可能なステートメント」の表から、不可能な事が分かります。
そこで、各アクセスモードで access値 を変えて、操作の各ステートメントが実行できるかを試したのが以下です。言ってみれば、図08 × 図13 のマトリックスで確認したことになります。横軸の2行目(R・W・R&W)がaccess値です。
アクセスモードInputOutputAppendRandomBinary
 R  W  W R&W R  W R&W R  W R&W

Line Input------(〇)××(〇)
Input------(〇)×(〇)
Get----××

Print-------
Write-------
Put----××
 R=Read  W=Write  R&W=Read Write
 -:設定時エラー 〇:操作可 ×:操作時エラー ××:Excelハングアップ
図09

図09で分かる事は以下の2点です。
 ①accessを指定するとRead/Writeの制限がかかり、使用可能なステートメントが絞られる。
 ②Binaryモードで「Line Input / Inputステートメント」は、使用しない方が良い。
②は、Binaryモードと言えども「Read」または「Read Write」であれば「読み取り許可」を出している事になるので、Line Input / Inputステートメントが使えます。しかしLine InputやInputで書き込もうとしているデータは、基本的に文字(数値もある)なので、Binary処理をするには不適切です。
しかもBinaryモードの「Write」でLine Inputを実行すると「Excelがハングアップ」します(私のPCだけかもしれません)。
一方、Binaryモードの書き込み側は、PrintもWriteも「ファイルモード不適」でエラーを出してくれます。この事からも「Binaryモードで、Line Input とInputが使えてしまう事自体がおかしい」と言えそうです。もしかしたらMicrosoft側の設定ミスかもしれません。
なおBinaryモードの既定は「Read Write」のようなので、このaccess項目全体に言える事は「データを保護」などの目的を持って使用する以外は、既定のまま(=accessは設定しない)の方が良いかもしれません。
lock」は、開いたファイルに対し「他プロセスからの操作制限」を指定します。キーワードを以下から選択し、省略可能です。
なお一番下の「Lock Read Write」では、ReadとWriteを逆に書くと構文エラーとなります。
操作制限内容
Sharedファイルはロックされず(既定)
Lock Read別プロセスからの読み込み禁止。書き込みは可
Lock Write別プロセスからの書き込み禁止。読み込みは可
Lock Read Write別プロセスからの読み込みと書き込みを禁止
図10

Openステートメントで掛けられるLockは(既定のSharedを除き)3種類あります。以下では、どのLock(横軸)を掛けたら、どの操作(縦軸)が出来なくなるのかを示しています。縦軸は、アクセスモード毎に操作可能な「Openを含めたステートメント」を並べています。
なお、Lock側のアクセスモードの全組み合わせを試しましたが、モードによるLock動作の違いはありませんでした。またOpenステートメント実行時にエラーが出た場合には、操作ステートメントは実行しようが無いので、表内では「ー印」としています。
Lock側Input・Output・Append・Random・Binary
Lock ReadLock WriteLock Read Write
InputOpen××
Line Input-(〇)-
Input-(〇)-
OutputOpen××
Print--
Write--
AppendOpen××
Print--
Write--
RandomOpen×
Get×-
Put×-
BinaryOpen×
Get×-
Put×-
〇:実行可 ×:実行不可 (〇):Lock付きOutputモードでは、エラーの可能性有
図11

Openステートメント実行時にエラー(×印)が出る場合は「書き込みができません」とのメッセージが、またGet・Putステートメント実行時にエラーが出る場合は「パス名が無効です」とのメッセージが出ます。
またLine Input・Inputステートメントを「(〇)」としていますが、これはOutputモードでLock Writeを使う場合です。
Outputモードでは「Openした途端、ファイル内容がクリア」されるため、他プロセスが「読み取りはOK(≒Lock Write)」の状態でファイルを開いた時には空っぽの状態です。
ですので、その「空のファイル」に対してLine Input・Inputステートメントを実行しても、「ファイルにこれ以上データがありません」とのエラーコメントが出てしまうのは当然です。
なお他プロセスで開く前に、Lockを掛けた側のOutputモード内でPrintステートメント等でデータを書き込んでも、Closeでファイルを確定していないため空ファイルであることに変わりはなく、やはりエラーとなります。
Lockを掛けることで、データ不整合を防ぐことが可能になりますが、一方でロックを掛けられた方では「実行時エラー」が発生しますので、エラー処理も同時に盛り込むことが必要です。
filenumber」は、操作対象となるファイルを表す番号です。
番号の範囲は「1~511」の整数で、重複しない事が分かっていれば固定番号(例えば、#1)でも問題はありません。しかしもし番号が重複した場合にはエラーが出ますので、安全のために下記で紹介する「FreeFile関数」を使用し、「使用可能なファイル番号」を取得した上で設定するのが良いと思います。
Openステートメントの構文では filenumberの直前に「#」が角カッコ付きで表しているように「#を付けなくてもOK」です。他サイトでは「分かり易い様に#印を付ける」「#印が必須の場合にのみ付ける」と意見が割れていますが、コード例を見ると、ほぼ全てに#を付けているようです。
「Len=」句に続けて指定する「reclength」は、以下の様にアクセスモードによって意味が異なります。
なお、値としては 1 ~ 32,767の整数値を指定します。省略可能項目です。
アクセスモードreclengthの意味
Input、Output、Append内部バッファー容量(既定値=512バイト)
Randomレコード長(既定値=128バイト)
Binary無視
図12

シーケンシャルモード(Input、Output、Append)での「内部バッファー容量」は、読み書きする量よりも小さなバイト数を指定しても、結果に影響が出ることはなさそうです。但し処理速度には影響があるようで、大きな値を指定すれば処理は高速になりますが、メモリ消費大のデメリットもあります。
Randomモードは固定長ファイルを扱うモードですが、「固定長ファイルの1レコード長さ」をこのreclength値に指定します。省略するとレコード長は128バイトとなります。
なおGet・Putステートメントで値を授受する変数のサイズは、レコードサイズではありません。しかし授受変数のサイズをレコードサイズと合わせて置いた方が良さそうです。

1-2.ファイルの操作

Openステートメントで開いたファイルに対し、操作を加えるのが下記のステートメントです。
アクセスモードにより「使用できる/できない」が異なりますので注意が必要です。
ステートメント構文InputOutputAppendRandomBinary

Line Input1行分を読込みLine Input #filenumber, varname×××(×)
Inputカンマor改行まで
を読込み
Input #filenumber, varlist×××(×)
Get指定データ読込みGet [#]filenumber, [ recnumber ], varname×××

Print1行分を書込みPrint #fileNumber, [outputlist]×××
Writeカンマ区切りで
データ書込み
Write #fileNumber [,outputlist]×××
Put指定データ書込みPut [#]fileNumber ,[recordNumber] ,variableName×××

Seek現在位置を設定Seek [#]fileNumber ,position

Lock他プロセスをロックLock [#]fileNumber [,recordNumber | [start] To end ]
Unlockロックを解除Unlock [#]fileNumber [,recordNumber | [start] To end ]
Width出力行幅を設定Width #fileNumber ,width

Close全ファイルを閉じるClose [[#]fileNumber [,[#]fileNumber ]...]
(Reset)入出力を終了Reset
 〇:使用できる ×:使用できない -:無視される (×):設定できるが使用しない方が良い
図13

上表のRandomモードの(×)印は図09の所で説明した通り、Access句も同時に設定すると「Excelがハングアップ」する場合もありますので、使用しない方が良いです。
以降では、ファイル操作のステートメントについて1つ1つ説明していきます。
寄り道(ファイル番号に#を付けるか付けないか)
Openステートメントでファイルを開いた後は、Open時に指定した「ファイル番号」を元にファイル操作をします。
しかし、操作をするステートメントや関数によっては、ファイル番号の先頭に「#印」を付ける事が必須だったり、付けてはいけなかったりします。
下記では、どのステートメント・関数に「#印」を付けるか否かを整理しました。
filenumberに「#」を
必ず付けるどちらでも良い必ず付けない
ステートメント Line Input, Print, Input, Write, Width Open, Get, put, Close, Seek, Lock, UnLock -
関数-Input, InputBEOF, FileAttr, Loc, LOF, Seek
図14

何かルールがあるようにも見えませんが、必ずつける時につけなかったり、またはその逆だったりした場合、VBE上ではコードタイピングが完了した時点で「構文エラー」となって教えてくれます。
ですので、それほど一生懸命に覚える必要は無いと思いますが、「ステートメントには#をつける」「関数には#をつけない」としても良さそうです。

1-2-1.読み込みと書き込み

テキストファイルの仕様(単なる文章やカンマ区切り、固定長データなど)に対して使われるステートメントは、読み込みと書き込みがセットで使われる場合がほとんどです。そこで、ここでは「ファイルの仕様」ごとに読み込み・書き込みステートメントをセットで説明します。
なおサンプルコードは「サンプルファイル」のModule1にも記載しています。特に実行ボタンは設けていませんので、VBE上から各プロシージャを実行させて下さい。その際のテキストファイルは、既定の「C:¥Users¥[ユーザー名]¥Documents¥」内に存在することになります。
なお、説明では読み込みステートメントを先に行っていますが、サンプルコードを試すのであれば「書き込みを先」に実行すれば読み込みがうまく動くと思います(テキスト量の違いでうまくいかない場合もあります。適当にメモ帳で操作して下さい)。
1-2-1-1.1行ごとの処理
「1行分を読み込む」のがLine Input ステートメントです。構文は以下のようになります。
 Line Input #filenumber, varname
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は必須
varname必須1行分の文字列を代入する変数(Variant型 or String型での宣言要)
図15

例えば、以下のコードのようにします。
  1. '========== ⇩(1) 1行分のデータ取得 ============
  2. Private Sub test01()
  3.  Dim buf As String
  4.  Open "Test_SJIS.txt" For Input As #1   '←ファイルを読み取りモードで開く
  5.   Do Until EOF(1)      '←ファイル末尾に達するまで繰り返す
  6.    Line Input #1, buf   '←1行分読み取る
  7.    Debug.Print buf    '←読み取ったデータを処理する
  8.   Loop
  9.  Close #1   '←ファイルを閉じる
  10. End Sub
図16

04行目「Open "Test_SJIS.txt" For Input As #1」では、Inputモードでファイル(ここではTest_SJIS.txt)を開きます。もしファイルが存在しない場合は、エラーとなります。
06行目「Do Until EOF(1)」は、09行目「Loop」で囲われた07~08行目を「ファイル終端(EOF=True)」になるまで繰り返します。もし開いたファイル内にデータが無い(Open直後にEOF関数がTrue)場合は、Do~Loop内を1回も実行せずに抜けます。
07行目「Line Input #1, buf」では、Line Inputステートメントを使って1行ずつ読み込み、変数bufに代入しています。
08行目「Debug.Print buf」は、取得した行データの処理の一例で、イミディエイトWindowに出力しています。
なお、Openステートメントでファイルを開いた直後であれば「現在位置はファイルの先頭」に居ますので、初回の読み込み範囲は「ファイル先頭から行末まで」となります。
しかし、下で説明する「Seekステートメント」で現在位置を動かした場合は、読み込む範囲は「現在の位置から、その行の行末まで」となります。しかも動かす単位は「バイト単位」ですので、全角文字(=2バイト)を分断する位置に移動してしまうと、文字化けが発生する事になるので注意が必要です。
一行単位で、ファイルにデータを書き込むのがPrintステートメントです。構文は以下になります。
 Print #fileNumber, [outputlist]
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は必須
outputlist省略可1行分の文字列、又はString型変数。省略すると空白行を書き込む
図17

Outputモードの時にPrintステートメントを使用すると「全データを上書き(元のデータは残らない)」となり、Appendモード時には「元のデータの後ろに、Printステートメントで指定したデータが追加」されることになります。
データをOutputモードで書き込むコード例としては以下になります。
  1. '========== ⇩(2) 1行ごとの書き込み ============
  2. Private Sub test02()
  3.  Open "Test_SJIS.txt" For Output As #1   '←ファイルを書き込みモードで開く
  4.   Print #1, "はじめまして、"   '←1行分書き込む
  5.   Print #1, "よろしくお願いいたします。"
  6.  Close #1   '←ファイルを閉じる
  7. End Sub
図18

22行目「Open "Test_SJIS.txt" For Output As #1」では、Outputモードでファイルを開いています。
24行目「Print #1, "はじめまして、"」で1行目のデータをファイルに書き込み、25行目「Print #1, "よろしくお願いいたします。"」で2行目を書き込んでいます。
このコードで出力される姿は、以下のようになります。
Printステートメントでの出力例
図19

Printステートメントで書き込む文字列は、メモ帳に人間が記入するような形になります。
(下で説明するWriteステートメントでは、書き込む文字列は「""(ダブルクォーテーション)」で囲まれます。)
そしてPrintで書き込んだ行の行末には「改行(vbCrLf)」が入ります。今回は2行分の出力をしており、2行目の後ろにも改行が付いていますので、カーソルとしては3行目まで進めます。
また、書き込むテキストの文字コードは「ANSI(Shift-JIS)」です。既存のファイルに書き込んだ場合、元のテキストが何の文字コードで保存されていてもOutputモードでは「全消去後、書き込み」となるので、「ANSI(Shift-JIS)」のみとなります。
Outputモードでは「テキストファイルを全消去してから書き込み」を行いますが、「元のテキストの後ろに追記」するにはAppendモードを使用します。コード例としては以下になります。
  1. '========== ⇩(3) 追記での書き込み ============
  2. Private Sub test03()
  3.  Open "Test_SJIS.txt" For Output As #1   '←ファイルを書き込みモードで開く
  4.   Print #1, "はじめまして"
  5.  Close #1
  6.  Open "Test_SJIS.txt" For Append As #1   '←ファイルを追加モードで開く
  7.   Print #1, "今後とも、"
  8.   Print #1, "よろしくお願いいたします。"
  9.  Close #1
  10. End Sub
図20

43~45行目は図18とほぼ同じで、Outputモードで新たにテキストを書き込んでいます。
47~50行目ではAppendモードでファイルを開くことで、既存のテキスト(44行目で書き込んだテキスト)の末尾に文字列を追記できます。
48行目「Print #1, "本年も、"」で1行を追記し、49行目でもう一行を追記しています。出力は以下の様になります。
Appendモードで追記
図21

しかし、Appendモード(追加モード)でPrintステートメントを使用した場合は注意が必要です。
下図は、元のテキストがUTF-16LEで保存されているファイルに対し、AppendモードでPrintステートメントを使って文を追加したものです。
Appendモードで追加する時は、元のファイルの文字コードが重要
図22

Appendモードの場合は、元の文字コードのままのところに「Shift-JISで文を追加」してしまいます。しかしメモ帳で開くと「先頭側の文字列」で使われている文字コードで判別しますので、追加した文字列部分に対しては文字化けが発生します。
また行の終わりには、「改行」の印として正しく「0x0D 0x0A」が書き込まれるのですが、その前に来るコードの影響を受けて他の文字になってしまう可能性があります。図22の場合も、改行とはなりませんでした。
ですのでAppendモードでは文字コードが混在するのを防ぐため、元のテキストの文字コードは「Shift-JIS以外は不可」とする方が良いと思います。
1-2-1-2.カンマ区切りでの処理
「カンマ、または改行までを読み込む」のがInputステートメントです。これは主に「CSV(Comma Separated Values:カンマ区切り)」データを読み込む時に使用されます。構文は以下です。
 Input #filenumber, varlist
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は必須
varlist必須データを代入する為の カンマで区切られた変数のリスト
図23

なおvarlistの変数のデータ型は、ファイルのデータ型と一致している必要があります。またデータ列の数(=1行がカンマで区切られている数)と受け取る変数の数を合わせる必要があります。(数が合っていないと、どんどんズレて代入され、最後に数が合わなくなってエラーとなる場合があります。)
また、受け取り用に配列(例:配列buf)を用意するのはOKですが、「Input #1, buf」の様に「配列そのもの」でデータを受け取る事はできません。その場合は、下記コードのように「配列の要素」を使って受け取ります。
  1. '========== ⇩(4) カンマ区切りデータ取得 ============
  2. Private Sub test04()
  3.  Dim buf(1 To 4) As Variant
  4.  Open "Test_SJIS.txt" For Input As #1   '←ファイルを読み取りモードで開く
  5.   Do Until EOF(1)
  6.    Input #1, buf(1), buf(2), buf(3), buf(4)
  7.    Debug.Print buf(1), buf(2), buf(3), buf(4)
  8.   Loop
  9.  Close #1
  10. End Sub
図24

ここでは、カンマ区切りのデータは1行当たり4個としており、そのデータは配列の各要素で受け取る事とします。
62行目「Dim buf(1 To 4) As Variant」では、配列を4要素で宣言し、様々なデータ型が入ってくる事を考慮しVariant型としています。
64行目「Open "Test_SJIS.txt" For Input As #1」では、Inputモードでファイルを開いています。なおファイルの拡張子は通常は「.CSV」にするとは思いますが、「.txt」でもファイルを操作する上では問題はありません。
67行目「Input #1, buf(1), buf(2), buf(3), buf(4)」では、Inputステートメントを使い、「カンマ、または改行まで」を1データとして、ここでは4つのデータを4つの変数(配列の要素)で受け取っています。
もちろん受け取る変数を配列の要素とせずに、「1つ1つ異なる変数」を指定してもOKです。もしかしたら、その方がデータ型もしっかり設定できるので良いかもしれません。
「カンマでデータを区切って書き込む」のがWriteステートメントです。これは主に「カンマ区切りのCSVデータ」を書き込む時に使用されます。構文は以下です。
 Write #fileNumber ,[outputlist]
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は必須
outputlist省略可データを書き込む為の カンマで区切られた値・数式や変数のリスト
図25

Outputモードの時にWriteステートメントを使用すると「全データを上書き(元のデータは残らない)」となり、Appendモード時には「元のデータの後ろに、Writeステートメントで指定したデータが追加」されることになります。
なおoutputlistを省略すると、空の行が書き込まれます。
Outputモードでのコード例は以下です。
  1. '========== ⇩(5) カンマ区切りデータ書き出し ============
  2. Private Sub test05()
  3.  Dim buf(1 To 2) As Variant
  4.  Dim i As Integer
  5.  buf(1) = Array("あいう", "abc", 123, #3/20/2022#)
  6.  buf(2) = Array("えお", "def", 456, False)
  7.  Open "Test_SJIS.txt" For Output As #1    '←ファイルを書き込みモードで開く
  8.  For i = 1 To 2
  9.   Write #1, buf(i)(0), buf(i)(1), buf(i)(2), buf(i)(3)
  10.  Next i
  11.  Close #1
  12. End Sub
図26

85~86行目では、ファイルに入力するデータ(2行分)を作成しています。
88行目「Open "Test_SJIS.txt" For Output As #1」では、Outputモードでファイルを開いています。
91行目「Write #1, buf(i)(0), buf(i)(1), buf(i)(2), buf(i)(3)」では、4つのデータをカンマ区切りで書き出しています。4つのデータを書き出した後には、改行(vbCrLf)が入ります。
出力されたファイルは以下の様になります。
Writeステートメントでの書き出し
図27

Writeステートメントでは、1つ1つのデータはカンマで区切られ、且つデータが文字列の場合は「""(ダブルクォーテーション)」で囲まれ、日付値やBoolean型の場合は「#(ハッシュ)」で囲まれます。
1-2-1-3.固定長ファイルでの処理
固定長データやバイナリデータを読み取り・書き込みするには、GetとPutを使用します。しかしRandomモード時とBinaryモード時とでは使い方に違いがありますので、分けて説明します。まずはRandomモード(固定長ファイル)です。
固定長ファイルは、1つのデータ(レコード)の長さが一定のバイト数で、データの数だけ連続して繋がっているファイルで、以下のようなイメージです。データの区切り部に特に特徴はありません。1データ長さが決まっているため、データのレコード番号さえ分かれば、データの読み込み・書き込みがピンポイントで出来ます。
固定長ファイルのイメージ
図28

固定長ファイルから、レコード位置を指定してデータを取り出にはGetステートメントを使用します。構文は以下です。
 Get [#] filenumber, [ recnumber ], varname
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
recnumber省略可読み取りをするレコード番号(ファイルの先頭レコードは1、2番目は2、・・・)
省略時は、現在位置(≒前回取得したレコードの次のレコード位置)
varname必須レコードサイズと等しい長さの変数
図29

固定長ファイルのデータを取得する際は、Openステートメントで「Len = reclength(レコード長)」を付けてファイルを開くのが通常です。Len句を省略すると、既定の128バイトがレコード長とみなされるようです。
なお「Len句でのレコード長 ≧ Getで受け取る変数のサイズ」であれば、レコードデータの末尾が切れて格納されるだけですが、逆の「Len句でのレコード長 < Getで受け取る変数のサイズ」となると、レコード長が一致しません とのエラーが発生しますので注意が必要です。
例えば、以下のようなコードにします。
  1. '========== ⇩(6) 固定長のデータ型宣言 ============
  2. Private Type recData
  3.  num As Integer     '←整数(2バイト分)
  4.  str As String * 4    '←文字列(4バイト分)
  5.  yn As Boolean     '←Boolean(2バイト分)
  6. End Type
  7. '========== ⇩(7) 固定長データ取得 ============
  8. Private Sub test06()
  9.  Dim rec As recData     '←固定長データの変数を宣言
  10.  Open "Test_SJIS.txt" For Random As #1 Len = Len(rec)
  11.  Get #1, 1, rec
  12.  Debug.Print rec.num, rec.str, rec.yn
  13.  Close #1
  14. End Sub
図30

101~105行目は、Typeステートメントを使っての固定長のユーザー定義型の定義です。recData型という事になります。
ここでは1レコードに3列分のデータを、整数・文字列・Boolean型で宣言しています。データ型によって使用するバイト数は図31のように決まっています。(図31では、固定長データとして登録できそうなデータ型のみを示しています。)
  
データ型バイト数
Boolean2
Byte1
currency8
date8
double8
データ型バイト数
integer2
long4
longlong8
single4
データ型文字数
string * △
図31

この中でString型は、そのまま宣言すると可変長文字列となります。そのため「As String * 4 」等と、アスタリスクの後ろに文字数を指定することで固定長文字列にします。
固定長ファイルに於いては、この「文字数」には、半角文字=1、全角文字=2という数え方で指定します(詳細は「固定長ファイルのテキストデータ呼び込み時処理」を参照下さい)。
固定長ファイル読み込みプロシージャ(図30の108~117行目)では、まず109行目「Dim rec As recData」で、101~105行目で定義した固定長データ型(ここではrecData)での変数宣言をします。
111行目「Open "Test_SJIS.txt" For Random As #1 Len = Len(rec)」では、固定長ファイルを開いています。その時に「1レコード当たりの長さ」を「Len = ・・・」で指定します。今回のレコードは、101~105行目で宣言したデータ型なので、Len関数を使ってその長さを指定します。
ファイルを開いたら、113行目「Get #1, 1, rec」でデータを読み出します。読み出すファイルは「#1(=111行目で開いたファイル)」、レコード番号は「1」、データの格納先は「変数rec」と言う意味になります。なお読み出すレコード番号は省略可能で、省略時は「現在のレコード位置」となります。図30では、ファイルのOpenとGetステートメントの間では「位置を変更する操作をしていない」ため、現在位置は「先頭レコード位置(= 1レコード目)」のままなので、省略してもOKです。
1レコード分が格納された変数recからデータを取り出すには、変数名に続けて要素名(図30の場合は、num・str・yn)を指定します。データの処理例として114行目「Debug.Print rec.num, rec.str, rec.yn」で、各値をイミディエイトウィンドウに出力しています。
固定長ファイルにレコード位置を指定してデータを書き込むには、Putステートメントを使用します。構文は以下です。
 Put [#] filenumber, [ recnumber ], varname
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
recnumber省略可書き込みをするレコード番号(ファイルの先頭レコードは1、2番目は2、・・・)
省略時は、現在位置(≒前回取得したレコードの次のレコード位置)
varname必須レコードサイズと等しい長さの変数
図32

各項目の意味はGetステートメントとほぼ同じです。読み出すか書き込むかの違いです。
コード例としては、以下のようになります。
  1. '========== ⇩(8) 固定長データの書き出し ============
  2. Private Sub test07()
  3.  Dim rec(1 To 2) As recData     '←固定長データの変数を宣言
  4.  Dim i As Integer     '←カウンタ変数(レコード数)
  5.  Open "Test_SJIS.txt" For Random As #1 Len = Len(rec(1))
  6.  rec(1).num = 123:    rec(1).str = "abc":    rec(1).yn = True
  7.  rec(2).num = 999:    rec(2).str = "def":    rec(2).yn = False
  8.  For i = 1 To 2
  9.   Put #1, i, rec(i)
  10.  Next i
  11.  Close #1
  12. End Sub
図33

132行目「Dim rec(1 To 2) As recData」では、図30の上部で定義した固定長データ型(recData型)での配列変数宣言をしています。
135行目「Open "Test_SJIS.txt" For Random As #1 Len = Len(rec(1))」では、固定長ファイルをRandomモードで開いています。1レコードの長さは、固定長データ型の長さと等しいため「Len(rec(1))」とします。Len(rec(2))を使ってもOKです。
137~138行目では書き出すデータを作成しています。固定長データ型の第1要素(num)は整数、第2要素(str)は文字列、第3要素(yn)はBooleanです。各要素の長さ(≒桁数)は固定ですので、指定長さを超えた値を設定(例えば rec(1).str = "abcdefg" )したとしても、データは途中で切られます。
140~142行目のFor~Next内で「Put #1, i, rec(i)」を実行してデータを書き出します。書き出しのレコード位置(第2項目の「i」)を省略したとしても、Putにより現在位置は移動してくれますので、第1レコードから順番に書き出されます。
書き込まれた固定長データの内容は以下のようになります。
固定長データの書き込み内容
図34

固定長ファイルをメモ帳で開いてみると、上図メモ帳のように文字化けしています。このような文字になるのは、メモ帳がデータのバイト値を見て「UTF-16LEを使っているようだ」と判断したからですが、もしShift-JISとして見たとしてもバイナリエディタの右端のように「アルファベットのみが確認できる状態」にしかなりません。
しかしこのデータをバイナリ―エディタで確認してみると、図34の下側のように「2バイト+4バイト+2バイト」のレコードデータが2レコード出来ていることが分かります。
先頭2バイトは数値で、表示の16進数を10進数に換算すると入力した値が確認できます。なお数値はLE(Little Endian)で記録されるようで、下位バイトが先に来ています。そのため2レコード目の「999」は、上位バイト「0x03」× 28 と下位バイト「0xE7」の足し算となります。
また2番目のカラムの文字列は4バイト分で、Shift-JISコードで記録されています。余っている分はスペースとなります。
最後のカラムのBoolean型は2バイトで、全ビットにフラグが立つとTrue、フラグが立たないとFalseとなるようです。
寄り道(日付値の記録)
図34の例では日付値の説明を省いています。もちろん図33のコード内に日付値を入れて試してみたのですが、どのような形で日付値が保存されているかが明確にできなかった為、省略した次第です。分かっている分だけでも紹介しておきます。
Date型は8バイトで、バイト順はLE(Little Endian)で記録されるようです。例えば「2000/1/1 18:00」は「00 00 00 00 D8 D5 E1 40」と記録されます。分かり易いように上位から並べると以下のようになります。
Date型の固定長データのビット情報
図35

色々な日付値を入れてみた結果、上位4バイト目の上位3ビットより上で「日付(Date型の整数部)」を表し、それより下で時分秒を表しているようです。(18:00は1日の0.75なので、小数点部の先頭に11と入っている)
しかし日付の起点(=ゼロ値)がどこなのかが良く分かりません。上位2バイトまでを使っているとすると起点は「8世紀」となってしまい、ちょっと違うなぁ という感じです。1つずつビットを下げていくと、1451年・1811年と少しもっともらしい年代になってきますが、起点日が6月とか2月になってしまいキリが良くありません。
最上位バイトの7ビット目のフラグはDate型のマークなのか? も含めて、まだまだ分からない事だらけです。しかし書き込んだ日付は、読み込み時にキチンと戻ってくるので、何らかの決まりがあるのは確かです。

1-2-1-4.バイナリーファイルでの処理
Binaryモードでバイナリーデータを取得するには、Getステートメントを使用します。構文は以下です。
 Get [#] filenumber, [ recnumber ], varname
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
recnumber省略可読み取りをするバイト位置(ファイルの先頭バイトは1、2番目は2、・・・)
省略時は、現在位置(≒前回取得したバイトの次のバイト位置)
varname必須Byte型の変数・配列変数
図36

ファイルから読み出すバイト数は、varnameに指定した変数の大きさにより決まります。つまり単なるByte型の変数の場合であれば1バイトを、Byte型の配列の場合はその要素数のバイト数を読み出します。
またBinaryモード時は、Openステートメントの最後に「Len = ・・・」を指定しても無視されます。
例えば、ファイルの全バイトを読み取る場合は、以下のようなコードにします。
  1. '========== ⇩(9) バイナリーデータの読み取り ============
  2. Private Sub test08()
  3.  Dim buf() As Byte   '←バイナリデータを格納する配列変数
  4.  Dim i As Integer
  5.  Open "Test_SJIS.txt" For Binary As #1
  6.   ReDim buf(1 To LOF(1))
  7.   Get #1, 1, buf
  8.   For i = LBound(buf) To UBound(buf)
  9.    Debug.Print buf(i);
  10.    Debug.Print Hex(buf(i)) & " ";
  11.   Next i
  12.  Close #1
  13. End Sub
図37

162行目「Dim buf() As Byte」では、バイナリーデータを格納する配列bufを動的に宣言しています。これは「ファイルを開いてみないと、何バイトあるのか分からない」ためです。
165行目「Open "Test_SJIS.txt" For Binary As #1」では、ファイルをBinaryモードで開いています。
166行目「ReDim buf(1 To LOF(1))」では、下で説明するLOF関数を使って「ファイルのサイズ(バイト単位)」を取得し、そのバイト数分だけの要素数にサイズを広げています。
168行目「Get #1, 1, buf」では、Getステートメントを使い、「1番目のバイト位置」からデータを読み取り、「配列buf」の要素が満杯(=全ファイル内容)になるまでデータを格納しています。
なお、もし配列のサイズの方が大きい場合には、データが入らなかった要素の値は0x00(数値型の既定値)で埋まる事になります。
170~173行目は、読み取ったデータの処理例で、171行目「Debug.Print buf(i);」では「バイトの値(10進数)」を出力しています。行末の「;(セミコロン)」は、改行をせずに後ろに続けて出力 を意味します。
なお16進数とする場合は、見え消しの172行目「Debug.Print Hex(buf(i)) & " ";」のようにHex関数を使います。その際戻り値は「文字列」となり、値同士が連なって出力されるため、1バイトずつ離して表示する様に「& " "」を追加しています。
イミディエイトWindowに出力した形は以下のようになります。図40でファイルに書き出したものを読み取っています。上段が10進数、下段が16進数です。
Binaryデータの出力例
図38

次にBinaryモードでバイナリーデータを書き出すには、Putステートメントを使用します。構文は以下です。
 Put [#] filenumber, [ recnumber ], varname
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
recnumber省略可書き込みをするバイト位置(ファイルの先頭バイトは1、2番目は2、・・・)
省略時は、現在位置(≒前回書き出したバイトの次のバイト位置)
varname必須書き込むデータ(Byte型の変数・配列変数)
図39

書き出すサイズはvarnameのサイズで決まります。単純なByte型変数であれば1バイトを、配列であれば要素数のバイト数分を書き出します。
例えば、ファイルに「Windows11 Homeデス」の文字をバイナリーとして書き出すには、以下の様なコードにします。
(文字列をいちいちバイナリーで書き込む事はしないと思いますが、ご容赦下さい。)
  1. '========== ⇩(10) バイナリーデータの書き出し ============
  2. Private Sub test09()
  3.  Dim buf() As Byte   '←バイナリデータを格納する配列変数
  4.  Const str As String = "Windows11 Homeデス"   '←書き込む値
  5.  Dim i As Integer
  6.  Open "Test_SJIS.txt" For Binary As #1
  7.   ReDim buf(1 To Len(str))
  8.   For i = 1 To UBound(buf)
  9.    buf(i) = Asc(Mid(str, i, 1))
  10.   Next i
  11.   Put #1, 1, buf
  12.  Close #1
  13. End Sub
図40

192行目「Dim buf() As Byte」では、変数bufをByte型の動的配列として宣言しています。配列のサイズは198行目のReDimで指定しています。
193行目「Const str As String = "Windows11 Homeデス"」では、ファイルに書き込むバイトの元データを定数宣言しています。ここでは半角文字(半角アルファベット+数字+半角カナ)のみとしています。
196行目「Open "Test_SJIS.txt" For Binary As #1」では、ファイルをBinaryモードで開いています。
198行目「ReDim buf(1 To Len(str))」では、動的配列bufのサイズを書き込むサイズ(=文字列のサイズ)にしています。
199~201行目では、For~Nextを使って配列bufの各要素に「文字コード」を格納しています。
200行目「buf(i) = Asc(Mid(str, i, 1))」は、193行目で設定した書き込む文字列を1文字ずつMid関数で切り出し、その文字コードを配列bufに格納しています。Asc関数で変換したコードは「Shift-JIS」となります。
配列への文字コードの格納が完了したら203行目「Put #1, 1, buf」で、ファイルに対して書き出しを行います。書き出した結果は以下のようになります。
Binaryで書き出した文字列
図41

なお、193行目で設定した文字列の最後の「デス(半角カナ)」が無いと、メモ帳の文字コードは「UTF-8」と表示されます。これは「アルファベット+数字の文字コードは、Shift-JISとUTF-8とで同じ」ために、メモ帳としてはとりあえず「メモ帳の既定のUTF-8」を表示するようです。
またこのPutステートメントは、ファイルの「指定した位置(recnumber項目)」と「指定した長さ(varnameのバイトサイズ)」に対して「バイト値を埋める」機能ですので、ファイルの元の文字列が残ってしまうことがあります。例えば下のような事にもなります。
Binaryモードでは、部分上書きされる
図42

上図では、後から「半角17文字(=17バイト)」を書き込みましたので、先に書き込まれていた全角(=2バイト文字)の半分が残骸として残った形になっています。
もし元のデータを残したくないのであれば、事前に「Outputモード」で一旦ファイルを開き、空にしておく必要があります。
なお図40の193行目は、バイナリーコードの代用として「半角文字のみ」を指定しました。
もしBinaryモードを使って「全角文字(≒2バイト文字)」を含んだ文字列を書き込む場合は、図40の200行目で示したような単純なロジックでは無く、「半角か全角かの判断」+「上位バイト・下位バイトに分割」したのちに変換するような工夫が必要になります。
しかし文字列の入力には、Outputモード+Printステートメントを使う方が圧倒的に楽なはずです。
寄り道(UTF-16LEテキストの読み書き)
Openステートメントで読み書き可能なのは、文字コードが「Shift-JIS」に限られます。
但し「Binaryモード」+「Byte型配列 ⇔ String型変数の変換」を使うことで、「UTF-16LE」に対応することが可能です。これはVBA内の文字管理をUTF-16LEで行っているためと考えられます。
まず、UTF-16LEを読み取るコード例が以下です。
  1. '========== ⇩(11) UTF-16データの読み取り ============
  2. Private Sub test10()
  3.  Dim buf() As Byte   '←バイナリデータを格納する配列変数
  4.  Dim str As String   '←文字列型の変数
  5.  Open "Test_UTF16.txt" For Binary As #1   '←UTF-16をバイナリで開く
  6.   ReDim buf(1 To LOF(1))
  7.   Get #1, 1, buf
  8.   str = buf   '←バイナリ配列をString型に格納
  9.   MsgBox str
  10.  Close #1
  11. End Sub
図43

225行目「Open "Test_UTF16.txt" For Binary As #1」では、「UTF-16」のテキストファイルを開きます。
228行目「Get #1, 1, buf」では、配列bufに文字列をバイト単位で格納します。
230行目「str = buf」では、あらかじめString型で宣言しておいた変数strに、Byte型配列を代入します。これにより「UTF-16のバイトコードが、VBAの文字列に変換」されます。
231行目「MsgBox str」では、その文字列をメッセージとして出力しています。
なおメモ帳のUTF-16LEは「BOM付き」ですので、先頭の2バイトにはBOMが付いています。ですのでBOM付きの場合は、以下の様に「先頭の2バイトを除いて処理」をするような修正が必要です。
227行目を「ReDim buf(3 To LOF(1))」
228行目を「Get #1, 3, buf」
また、UTF-16LEでの書き込みのコード例が以下です。
  1. '========== ⇩(12) UTF-16データの書き出し ============
  2. Private Sub test11()
  3.  Dim buf() As Byte
  4.  Const str As String = "Windows11 Homeこんにちは"
  5.  Open "Test_UTF16.txt" For Binary As #1
  6.   buf = str
  7.   Put #1, 1, buf
  8.  Close #1
  9. End Sub
図44

252行目「Dim buf() As Byte」で、動的配列としてByte型の変数bufを宣言します。
253行目「Const str As String = "Windows11 Homeこんにちは"」は、UTF-16で書き込む文字列です。全角が入っても大丈夫です。
255行目でBinaryモードでファイルを開きます。
257行目「buf = str」で、文字列をByte型配列に変換します。今回の文字列の場合だと38要素の配列(19文字 × 2バイト/文字)になります。これは、UTF-16は「半角でも全角でも、2バイト/文字」の文字コードのためです(一部4バイトの文字も存在しますが、VBA上でもうまく表示されませんので、2バイトのみと考えても良いかもしれません)。
なお、事前にReDimで動的配列のサイズを決めておくのが通常ですが、勝手に必要サイズに変更されます。
258行目「Put #1, 1, buf」で、Byte型配列をファイルに書き込みます。
書き込まれたファイル側は、以下のようになります。
BinaryモードでUTF-16LEのテキストを書き出す
図45

このファイルをバイナリーエディタで見たのが以下です。半角も全角も、2バイトずつ使われているのが分かります。
UTF-16ファイルをバイナリ―エディタで確認
図46

なお、図44ではUTF-16LEのBOM(先頭の2バイト)は書き込んでいません。但し図45のメモ帳を「上書き保存」すると、「メモ帳のUTF-16LE」の既定であるBOMが自動的に付加されます。

図40(Shift-JIS)では2バイト文字に対応させるには複雑なロジックが必要なのですが、図44(UTF-16LE)では結構簡単なロジックで実現できます。これはShift-JISは「1バイト 又は 2バイト」なのに対し、VBA内でも使われているUnicode(UTL-16)が「半角も全角も2バイト」とVBA内での文字との相性が良く、コードが単純化できるのではないかと考えています。但しUTF-16には4バイト文字(例えば、ホッケと言う漢字(さかな編に花))も存在するので、注意が必要です。

1-2-2.ファイルポインタの位置

Seekステートメントは、読取・書込のための現在位置(ファイルポインタ位置)を設定するものです。
ファイルポインタ位置は、Openステートメントでファイルを読み込んだ直後は「ファイルの先頭(値=1バイト目・1レコード目)」に居ます。
操作ステートメントの内、Get・Putステートメントは、読取・書込のための現在位置を「recnumber」として指定できます。しかし、その他のステートメント(Line Input, Input, Print, Write)は、ステートメント実行によりその分だけファイルポインタ位置は移動しますが、ユーザー側が意図を持って動かすためにはSeekステートメントを使う必要があります。
構文は以下になります。
Seek [#]fileNumber , position
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
position必須移動先の位置(1~2,147,483,647 の範囲)
図47

Seekに指定する値の意味合いは、アクセスモードにより以下の様に少し異なります。
なおGet・Putを使う場合、recnumberに値を指定した場合は、Seek設定をしても無視されます。
モードInput, Output, (Append)BinaryRandom
Line Input, PrintInput, WriteGet ,Put
Seek値1バイト単位1レコード単位
図48

例えば図16で、Line Inputの実行前に「Seek #1, 3」と「読み取り位置を移動」させた場合は、以下のように2バイト(2バイト文字の場合は1文字分)進んだ場所から読み込むことになります。
Seekで位置を移動してからLine Input
図49

また図18で、Printの実行前に「Seek #1, 3」と「書き込み位置を移動」させた場合は、以下のように2バイト進んだ場所から書き込むことになります。
Seekで位置を移動してからPrint
図50

寄り道(AppendモードでのSeekステートメント)
図48ではAppendモードをカッコ付きで示しました。Appendモードは「ファイルの末尾にデータを追加」する機能ですので、文の末尾基準でファイルポインタが動くと勘違いしそうですが、実際はファイル先頭基準で動きます。
勘違いする原因は、Appendモードでファイルを開いた直後に、「現在のファイルポインタ位置(Seek関数参照)」を取得すると、「1(=先頭位置)」となるためです。
「Open直後の位置が「1」なのだから、『Seek #1, 1 』を実行しても書き込み位置は変わらないだろう」と思って実行してしまうと、元のテキストの先頭から上書きをしてしまいます。
もしAppend標準の機能(文末に追記する)に戻すのであれば、LOF関数を使って「Seek #1, LOF(1)」とします。
なお、Appendモードで積極的にSeekステートメントを使う事も考えられます。下図は図20のAppendモードでOpenした直後(47行目と48行目の間)に「Seek #1, 5 」を実行したものです。
Appenモードで位置を移動してからPrint
図51

但し、うまく上書きするためには、いくつかハードルがありそうです。
1つは「バイト単位でSeek移動」するために、既存データの全角半角を踏まえた上での位置を割り出す必要があります。全角文字を分断する位置から上書きすると文字化けが発生します。上書きするテキストよりも元のテキストの方が長い場合も同様で、上書きしたテキストの最後が文字化けする場合があります。
またPrintやWriteでは、行データの最後に「改行マーク(vbCrLf)」が入りますので、その分も考慮する必要があります。

1-2-3.排他制御

Openで開いたファイルに対し、「他のプロセスによる同じファイルへのアクセスを制限」するのがLockステートメントです。制限解除するのはUnlockステートメントになります。構文は以下です。
Lock [#]fileNumber [ , recordNumber | [ start ] To end ]
Unlock [#]fileNumber [ , recordNumber | [ start ] To end ]
対象となるファイルを示す[#]fileNumberは必須ですが、第2項は以下の様にアクセスモードにより異なります。
項目InputOutputAppendRandomBinary
1#filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
2-1recordNumber省略可無視ロックする
レコード番号
ロックする
バイト位置
2-2[start] To end
(start省略時=1)
省略可ロックする
レコード番号範囲
ロックする
バイト位置範囲
図52

コードの書き方は、以下の様な3種類です。第2項は、単一指定(第2-1項)か範囲指定(第2-2項)のどちらか一方です。
 ファイル全体にロックを掛ける・・・・・「Lock #1」など(第2項を省略)
 レコード番号、バイト番号を指定・・・・「Lock #1, 1」など(第2-1項で、単一設定)
 レコード範囲、バイト範囲を指定・・・・「Lock #1, 2 To 3」など(第2-2項で、範囲設定)
なお第2-2項で範囲を指定する時、[start]は省略可です。省略した場合は、現在のファイルポインタからでは無く、ファイル先頭(値=1)から指定した事となります(例えば「Lock #1, To 3 」は「Lock #1, 1 To 3」と同じ)。
また複数の範囲にロックを掛ける場合は、範囲ごとのLockステートメントを複数回発行すれば良さそうです。
第2項(recordNumber(2-1項)または start to end(2-2項))を省略すると、ファイル全体をロックすることになりますし、2項は無視されるInput、Output、Appendモードはファイル全体がロックされる事になるはずですが、調べてみると「うまくロックされない場合」もあることが分かりました。
下表は、各アクセスモードでの確認結果です。一方でロックを掛け、別プロセス(縦軸)から操作をした場合、どこでエラーがでるか(×印の場所でエラー発生)を調べています。
Lock側Input・Output・Append・Random・Binary
Lockステートメント
InputOpen
Line Input×
Input×
Close-
OutputOpen
Print
Write
Close×
AppendOpen
Print
Write
Close×
RandomOpen
Get×
Put×
Close-
BinaryOpen
Get×
Put×
Close-
〇:実行可 ×:実行不可
図53

図53内の記号の意味ですが、例えば先にOpenしたプロセスがLockステートメントでロックを掛けても、他プロセスが同じファイルをOutputモードで開く(〇印)事ができ、Printステートメント等で上書き(〇印)が出来てしまう という意味です。
作業後にCloseステートメントを実行する時にはエラー(×印)が出てくれますが、時は既に遅く「Outputモードでファイルが開けたので、ファイルは空」になってしまっています。
ロックをした側では「データは守られているはず」と思っているのに、作業しようとするとデータは空 という状態です。
テキストファイルを使ってデータ操作をするシステムでは、作業が重複するほど使用頻度は高く無いのかもしれませんが、Lockステートメントは完璧では無い事は理解した上で使うべきと考えます。
なお図53を作るに当たり、全てのアクセスモードで入ってからLockを掛けて、他プロセスではどの様な操作がエラーとなるかを確かめてみました。その結果「各モード内での挙動は同じ」だったため、使用ステートメントは共通のような表としました。
なおロック外し(Unlockステートメント)は、Lockと同じ範囲を指定しないとエラーとなります。(一部だけロック解除、包括範囲を解除 などは不可)
寄り道(Open時のRockとRockステートメントとの比較)
Openステートメントの中で紹介したRock機能は、「Lock Read」「Lock Write」「Lock Read Write」を設定するものでした。これは別プロセスからの「ファイルの読み込み・書き込みを制限」する機能です。
一方、ここで説明している「Lockステートメント」は、主にレコードやバイトの範囲の使用を制限するものです。
制限する種類が異なるので、どちらが良いとかはありませんが、「他プロセスから完全にブロックする」とすれば、以下の方法となります。各ステートメントの動作表のリンクも載せます。
 ・Openステートメントでの「Lock Read Write」・・・図11の右端列
 ・Lockステートメントの第2項目省略・・・・・・・・図53
2つのステートメントの動作表を見比べてみると、Lockステートメントの方は「Output・Appendモード」の時に不充分な気がします。一方OpenステートメントのLock機能の方は、別プロセスが「同じファイルを開くことすらできない」状態になります。
もし「絶対に他のプロセスには触らせたくない」という事であれば、Openステートメント内のLock機能を使った方が確実なような気がします。
また、ロックを掛けると「他プロセス側でエラーが発生」しますので、エラー対策はもちろん必須です。

1-2-4.出力の幅

Widthステートメントは、出力行の幅を割り当てます。構文は以下の様になります。
Width #fileNumber , width
項目内容
#filenumber必須ファイルOpen時に指定したfilenumber。「#」は必須
width必須出力の幅(文字数)。0~255の範囲。ゼロを指定すると1行の幅は無制限。
図54

なお、Widthステートメントを実行しない場合は「1行の幅は無制限」となります。
出力のイメージが浮かびにくいので、コード例を示します。
  1. '========== ⇩(13) 出力幅の指定 ============
  2. Private Sub test12()
  3.  Dim i As Long
  4.  Open "Test_SJIS.txt" For Output As #1
  5.   Width #1, 5    '←1行を5文字に設定
  6.   For i = 65 To 77
  7.    Print #1, Chr(i);    '←1バイト文字を出力
  8.   Next i
  9.   For i = 33440 To 33460 Step 2
  10.    Print #1, Chr(i);    '←2バイト文字を出力
  11.   Next i
  12.  Close #1
  13. End Sub
図55

上のコードでは277~279行目のFor~Next内のPrintステートメントで、アルファベット(=1バイト文字)のA~Mのコードを出力しています。その際「Chr(i);」と、出力の最後に「;(セミコロン)」を付けています。これは「改行をしない」という意味ですので、このままだと「ABCD・・・KLM」と1行で出力することになります。
また、そのアルファベットに続けて、281~283行目では2バイト文字(あいう・・・)を出力しています。
しかしここでは、Printステートメントの前に275行目「Width #1, 5」を実行しているため「1行の幅は5文字」となります。出力した形は以下の様になります。
Widthステートメントで出力幅を指定
図56

結果としては「Widthステートメントで指定した文字数ごとに改行が入る」イメージです。なお半角文字でも全角文字でも1文字と数えますので、1バイト文字と2バイト文字が混在する環境では「右端が揃うわけではない」事に注意が必要です。
このWidthステートメントは、Printステートメント使用時のみに有効で、Writeステートメント時には無視されます。またOutputモード・Appendモードのみで有効で、その他のモード時は無視されます。(当然と言えば当然です)
また複数のWidth指定をした場合は、最新の設定値が有効となります。

1-2-5.終了処理

処理が終了したら、Closeでファイルを閉じます。構文は以下のようになります。
 Close [[#]fileNumber [,[#]fileNumber ]...]
項目内容
#filenumber任意ファイルOpen時に指定したfilenumber。「#」は任意
図57

Closeに続けて、閉じたいファイル番号を1つ、または連記します。
なお#filenumberを省略して「Close」のみを実行すると、開いている全てのファイルを閉じる事になります。
また#filenumberには「使用中のファイル番号」を指定するのが通常ですが、使用していないファイル番号を指定しても特にエラーは発生しません。
一方Closeに似たようなステートメントとして「Resetステートメント」があります。構文は以下です。
 Reset
Closeのように閉じるファイル番号を指定することはできず、開いている全ファイルに対する処理となります。
Microsoftのサイトでは「開いているファイルのデータを書き込み、全てのファイルを閉じる」と説明していますが、Closeと何が異なるのか良く分かりません。「閉じる前に、内部バッファの内容をディスクに書き込む」とも説明しているので、書き込み処理を強制的に行うのかもしれません。
但しどちらを使っても、Openで指定したファイル番号はちゃんと解放されますし、書き込み内容もファイルに反映されるので、どちらでも大丈夫とは思いますが、Resetを使ったコード例は見たことがありません。ですので、Closeステートメントを使った方が、後からコードを見る人のためにも安全だと思われます。

1-3.関数類

ファイル番号を引数とする「ファイルの状態を取得」等の関数が下表です(ファイル番号を使わない関数も含む)。
操作ステートメントと同様に、アクセスモードにより「使用できる/できない」がありますので注意が必要です。
関数内容構文InputOutputAppendRandomBinary
EOFファイル末尾か否かEOF(filenumber) As Boolean
fileAttrファイルモードを取得FileAttr(filenumber, returntype) As Long
Inputファイルから文字列を取得(文字数単位)Input(number, [#]filenumber) As String×××
InputBファイルから文字列を取得(バイト単位)InputB(number, [#]filenumber) As String×××
FreeFile使用可能なファイル番号FreeFile As Integer-----
LOFファイルサイズを取得(バイト単位)LOF(filenumber) As Long
Seek読み書きしようとする位置を取得Seek(filenumber) As Long
Loc読み書きした位置を取得Loc(filenumber) As Long(〇)(〇)(〇)
Spc半角スペースを挿入Spc(n)×××
Tab次の文字列の出力位置を指定Tab[(n)]×××
 〇:使用できる ×:使用できない
図58

以下では、この関数について1つ1つ説明していきます。

1-3-1.EOF関数

一般的には、現在のファイルポインタ位置が「ファイルの終端」か否かを調べる関数ですが、図60のようにアクセスモードによっては別な意味となります。図16の06行目のように、Do~Loopで「ファイル終端までを連続処理」する場合に多く使われます。構文は以下です。
 EOF ( filenumber ) As Boolean
項目内容
filenumber必須ファイルOpen時に指定したfilenumber。「#」は付けない
図59

戻り値はBoolean型です。アクセスモードにより、その内容は変わります。
戻り値InputOutputAppendRandomBinary
Trueファイル終端に達している既定Getでレコード全体が読み込めずGetで全バイト数が読み込めず
Falseファイル終端に達していないレコード全体が正しく読み込めた全バイト数が正しく読み込めた
図60

Inputモードで使われるEOFは、「ファイル終端か否か」そのものです。またOutputモード・Appendモードでは、常にEOF=Trueとなるのですが、これはEOF関数が「ファイル読み取り時に使用」する関数だからと考えられます。
一方、Random・BinaryモードのEOF関数の戻り値は少し分かり難いので下図で説明します。Binaryモードで4バイト単位でデータが並んでいるのをイメージしています。
Binaryモード等でのEOF関数でTrueが戻る条件
図61

図61の左側は、ファイルの状態も読み取り方法も正常な状態です。Getステートメントで先頭方向からデータを読み取る時、最後のデータセットを読み取った時点では「EOFの戻り値はFalse」となります。そしてデータが存在しない部分を読み取った後のEOF値はTrueとなります。
一方図61の右側は、ファイルのデータが異常だったり、読み取りの起点がズレていたりした場合です。Getで正しく4バイトが取得できる内はEOF値はFalseになりますが、ファイル終端を跨いでのデータ取得(=取得バイト数が足りない)した後はEOF値がTrueとなります。
ですので、データを読み取った後のEOF値を調べることで、取得したデータが「予定のバイト数か否か」が分かる事になります。但し読み取り開始時の起点ズレ等があると、EOF値がFalseであったとしても期待したデータが取得できていない場合もあるのでEOFだけに頼るのは危険です。
なお図61はBinaryモードでの説明図ですが、レコード長が固定のRandomモードでも同じです。
図61で示したような特性をEOF関数は持つため、ファイルを開いたばかりの状態でのEOF値はアクセスモードにより下記の様に異なることになります。なおこの時のSeek値は、ファイルの先頭にいるのでAppendモードも含めて「1」となっています。
InputOutputAppendRandomBinary
Open時の
EOF値
データ有FalseTrueTrueFalseFalse
データ無TrueTrueTrueFalseFalse
図62

RandomモードとBinaryは、データの有無に関わらずにFalseです。これは、まだ「読み取る前段階」という意味と思われます。
一方Inputモードでは、データがあれば「先頭位置≠ファイル終端」なのでFalse、データがなければ「先頭位置=ファイル終端」でTrueとなります。
Outputモード・Appendモードは、図60の通り常にTrueです。

1-3-2.fileAttr関数

Openステートメントで開いたファイルのアクセスモードやファイルハンドルを戻します。構文は以下です。
FileAttr(filenumber [, returntype]) As Long
項目内容
1filenumber必須ファイルOpen時に指定したfilenumber。「#」は付けない
2returntype省略可
情報の種類(1 or 2)
省略時は1
1アクセスモード
2O/Sのファイルハンドル
図63

第2引数のreturntypeに「1」を指定すると、ファイルへのアクセスモードが数値として得られます。対照表は以下です。
ビット演算できるような値(2^n 値)になってはいますが、組み合わせ値が出力される条件は無いはずです。
戻り値内容
1Input
2Output
4Random
8Append
32Binary
図64

なお、第2引数に「2」を指定するとファイルのハンドルが得られるはずですが、64ビットO/Sでは「引数が不正」との実行時エラーが発生します。戻り値のデータ型がLong型なのでエラーとなるのは当然ですが、エラー内容から推察すると、64ビットO/Sには元々対応していないのかもしれません。

1-3-3.Input関数

Input関数は、ファイルから「指定した文字数の文字列を取得」します。
このInput関数はInputステートメントとは異なり、コンマ・引用符(文字列を囲んでいる""(ダブルクォーテーション)やBoolean型を囲んでいる#(ハッシュ)など)・先頭スペースなどを含めた、読み取った全ての文字列を返す関数です。また読み出しの開始ポイントは、現在のファイルポインタ位置からになります。構文は以下です。
Input(number, [#]filenumber) As String
項目内容
1number必須読み込み文字数。1バイト文字・記号・2バイト文字とも「1」と数える
2filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
図65

第1引数のnumberには、読み込む文字数として「0~」を指定します。ゼロを指定すると「""(長さゼロの文字列)」を戻してきます。なお、ファイル終端を超えて文字列を取得しようとするとエラーとなります。
文字数の数え方は、全角・半角・記号を問わず1文字と数え、改行は「CrLf」であれば2文字と数えます。
このInput関数は、下記のようにInputモード・Binaryモードでファイルを開いた時のみに使用できます。×印のついたアクセスモードで使用すると、「ファイルモードが不正」との実行時エラーが発生します。
InputOutputAppendRandomBinary
Input関数使用可×××
図66

コード例は以下になります。
  1. '========== ⇩(14) Input関数での文字列の取得 ============
  2. Private Sub test13()
  3.  Dim str As String    '←Input関数の戻り値を代入する変数
  4.  Open "Test_SJIS.txt" For Input As #1
  5.   str = Input(6, #1)
  6.   MsgBox str
  7.  Close #1
  8.  Open "Test_SJIS.txt" For Binary As #1
  9.   str = Input(6, #1)
  10.   MsgBox str
  11.  Close #1
  12. End Sub
図67

304~307行目が「Inputモード」、309~312行目が「Binaryモード」でファイルを開いています。
ファイルを開いた後、305行目「str = Input(6, #1)」ではInput関数を使って6文字分を読み出し、変数strに代入します。その後306行目でメッセージとして出力しています。310~311行目も内容は全く同じです。
Input関数で読み込むファイルの文字コードは「Shift-JIS」である必要があります。しかしInput関数は、Shift-JISを「VBA内で使っているUnicodeの文字列に変換」した後String型変数に格納するため、Binaryモードで読み込んでもVBA内で文字列として使うことが可能です。

1-3-4.InputB関数

上のInput関数では「文字数単位」での指定でしたが、InputB関数では「バイト数単位」で取得することが出来ます。
但し、Input関数では「Unicodeの文字列に変換してからString変数に格納」していたのに対し、InputB関数では「バイト値をそのままString変数に格納」する機能となっています。構文は以下です。
InputB(number, [#]filenumber) As String
項目内容
1number必須読み込みバイト数。
2filenumber必須ファイルOpen時に指定したfilenumber。「#」は任意
図68

バイト値がそのままString型変数に入るため、そのままでは文字列としては認識出来ません。ですのでVBA内で使える文字列にするには「StrConv関数でUnicodeに変換」する必要があります。コードとしては以下のようになります。
  1. '========== ⇩(15) InputB関数での文字列の取得 ============
  2. Private Sub test14()
  3.  Dim str As String    '←InputB関数の戻り値を代入する変数
  4.  Open "Test_SJIS.txt" For Input As #1
  5.   str = InputB(LOF(1), #1)
  6.   str = StrConv(str, vbUnicode)
  7.   MsgBox str
  8.  Close #1
  9.  Open "Test_SJIS.txt" For Binary As #1
  10.   str = InputB(LOF(1), #1)
  11.   str = StrConv(str, vbUnicode)
  12.   MsgBox str
  13.  Close #1
  14. End Sub
図69

324~328行目が「Inputモード」、330~334行目が「Binaryモード」でファイルを開いています。
ファイルを開いた後、325行目「str = InputB(LOF(1), #1)」ではInputB関数を使って、ここでは全バイト数を読み出し、変数strに代入しています。
尚ここで使っているLOF関数は、ファイルのバイトサイズを求める関数で、ファイルの「全文字列」を取り出すという意味になります。文字数指定のInput関数には出来ない技がInputB関数では使えます。
しかしこのままでは「バイト値」のため、326行目「str = StrConv(str, vbUnicode)」でVBA内でも文字として扱える形に変換(Unicode化)した後、327行目でメッセージとして出力しています。
Inputモードのコードで説明してきましたが、Binaryモードでも全く同じです。
なおInput関数と同様、図66のようにInputモードとBinaryモード以外では使用できず、またテキストファイルの「文字コードはShift-JIS」である必要があります。

1-3-5.FreeFile関数

Openステートメントでファイルを開く際には「ファイル番号」が必須です。そのファイル番号は1~511の範囲から「使われていない番号」を選択する必要があります。FreeFile関数は、使われている番号を除いた「現在使用可能なファイル番号」を取得するものです。構文は以下です。
 FreeFile [(rangenumber)]
項目内容
1rangenumber省略可
(既定=0)
0= 1~255を戻す
1 =256~511を戻す
図70

コード例は以下です。Test_SJIS.txtから1行目のデータを読み取り、Test2_SJIS.txtに書き込んでいます。
  1. '========== ⇩(16) FreeFile関数でのファイル番号の取得 ============
  2. Private Sub test15()
  3.  Dim buf As String
  4.  Dim FileNumber1 As Integer
  5.  Dim FileNumber2 As Integer
  6.  FileNumber1 = FreeFile    '←Inputモード用のファイル番号取得
  7.  Open "Test_SJIS.txt" For Input As #FileNumber1
  8.   Line Input #FileNumber1, buf
  9.   FileNumber2 = FreeFile    '←Outputモード用のファイル番号取得
  10.   Open "Test2_SJIS.txt" For Output As #FileNumber2
  11.    Print #FileNumber2, buf
  12.   Close #FileNumber2
  13.  Close #FileNumber1
  14. End Sub
図71

356行目「FileNumber1 = FreeFile」で、使用可能なファイル番号を取得します。その番号を元に357行目「Open "Test_SJIS.txt" For Input As #FileNumber1」でファイルを開き、358行目「Line Input #FileNumber1, buf」で1行目のデータを変数bufに代入しています。
FileNumber1のファイルは開いたまま、361行目「Open "Test2_SJIS.txt" For Output As #FileNumber2」で別なファイルを開いています。もちろんFileNumber1の番号は使えませんので、360行目「FileNumber2 = FreeFile」で再び使用可能なファイル番号を取得し、Openステートメントに使用しています。そして362行目「Print #FileNumber2, buf」で最初に読み取った1行目のデータを書き込んでいます。
通常は固定のファイル番号でも大丈夫ですが、図71のように複数ファイルを同時に作業する場合にはFreeFile関数を使うことでミスを防げますし、またファイルの閉じ忘れ等があった場合にも安全です。
なお図71の場合は、Test_SJIS.txtのデータを変数bufに格納した後すぐに閉じ、次にTest2_SJIS.txtに書き込めば、同じファイル番号を使えます。
寄り道(ファイル番号の重複とは)
図71のように、同じプロシージャ内で「同じファイル番号」を使った場合は重複エラーが出るのは当然と思いますが、どの範囲まで重複エラーが出るのかが気になり少し調べてみました。それは同様に、FreeFile関数がどの範囲を調べて「使っていない番号を戻す」のかという事になります。
他サイトでは「VBA内」という意見が多そうでした。確かにFreeFile関数はVBAの関数ですので頷けます。またAIに訊いてみると「裏で動いているアプリ(Windowsやセキュリティソフト等)」とも重複してはいけない という意見も出てきます。
まず「違うプロセス」でExcelを同時起動させ「ファイル番号=1」で同時にOpen状態にしましたが、問題無く作業できます。
次に、Excelでファイル番号「1」でOpen状態にし、同時に起動したWordで同じファイル番号「1」でOpenを実行してみました。これも特に問題も無く実行できます。FreeFile関数も、Excelと同じ「1」を戻してきます。
どうも「ファイル番号が重複できない」というのは、VBA内では無く「同じOfficeアプリの、同じプロセスの中」での事のようです。これの正しい情報は、どこにも見当たりません。
しかし内心では「どこまで影響しているのかが分からない」ので、出来るだけ異なるファイル番号を使いたい気がします。しかし、FreeFile関数で得られる空いている値は「同じプロセスの同じOffice内」での値なので、どうする事もできません。
せいぜいプログラムミスでのトラブルを防ぐために、FreeFile関数を使うくらいしか無さそうです。

1-3-6.LOF関数

LOF関数は、開いているファイルの長さ(≒実ファイルサイズ)をバイト単位で戻します。構文は以下です。
 LOF (fileNumber) As Long
項目内容
1filenumber必須ファイルOpen時に指定したfilenumber。「#」は付けない
図72

この関数を使用した例としては、図37図43図69があります。
なおLOF関数は、当然ながらファイルをOpenステートメントで開いている間のみで使用できます。もしファイルを開いていない間にバイトサイズを調べるには「FileLen(fileName)」を使用します。

1-3-7.Seek関数とLoc関数

ファイル内のポインタ位置などを取得するのがSeek関数・Loc関数です。構文は以下です。
 Seek(filenumber) As Long
 Loc(filenumber) As Long
項目内容
1filenumber必須ファイルOpen時に指定したfilenumber。「#」は付けない
図73

SeekとLocは共にポインタ位置を戻す関数ですが、その内容は下記のようにアクセスモードによっても異なります。
InputOutputAppendRandomBinary
Seek関数現在のポインタ位置(バイト単位)
(=次の読み書きの先頭位置)
現在のポインタ位置(レコード単位)
(=次のレコード番号の先頭)
現在のポインタ位置(バイト単位)
(=次のバイトの先頭位置)
Loc関数(意味は無さそう)直前に読み書きしたレコード番号直前に読み書きしたバイト位置
SeekとLoc
の関係
切り上げ((Seek値-1)/128) = Loc値Seek値 - 1 = Loc値
図74

2つの関数の使い分けは、以下のようになるようです。
 ・Seek関数:これから読み書きをするデータの位置を確認する
 ・Loc関数:直前に読み書きをしたデータの位置を取得する
しかしシーケンシャルモード(Input・Output・Append)ではLoc関数は意味のある値を示さないため、Seek関数で「これから読み書きする位置」としてポインタ位置を取得し、そこを起点として計算するのが良さそうです。
なお、N88-Basicには既にLoc関数はあった(Seekは無かった様子)ようです。その時のLoc関数の機能は、Randomモードでは「最後に読み書きしたレコード番号」と今のVBAと同じですが、シーケンシャルモード(Input・Output・Append)では「Openしてから、ファイルを読み書きした回数」となっています。
当時の1行が128バイトに固定されていた訳では無さそうですが、その辺りの名残なのかもしれません。また、Randomモードでの固定長ファイルの既定長さが128バイトの様なので、こちらの関係もあるのかもしれません。

1-3-8.Spc関数とTab関数

Printステートメント(Outputモード・Appendモード)では、書き出す文字列の中にSpc関数とTab関数を含めることが出来ます。 これらの機能は、出力幅(Widthステートメント)の指定有無等により、以下のように異なります。
構文Spc(n)Tab[(n)]
nの範囲 0~32767 (0以下は0とみなす)1~32767 (1以下は1とみなす) 既定=15
Width
ステートメント
による
出力幅
指定無し n個の半角スペースを挿入Tab関数の右側の文字列をSeek(n)の位置に出力。
もしSeek(n)の位置を既に越している場合は、
改行をし次の行のSeek(n)の位置に出力。
指定有り
スペース挿入後の後端がWidth内の時はスペースとして出力
後端が超える時は改行し次行で残りスペースを挿入
(前行のスペースは出力されない)
「n>Width」の場合は「n mod Width」
出力した文字列の後端がWidthを超える時は
改行し次行の行頭に出力。
図75

まず「Widthを指定しない場合」です。
Spc関数は「指定した数(n)の半角スペースを挿入」します。nの範囲は0~32767で、ゼロ以下を指定するとゼロ(スペース無し)と見なされます。
Tab関数は、Tab関数の次に指定した文字列を「先頭から数えて、nバイト目の位置に表示」します。行の先頭位置を「1」と数える数え方なので、1以上の指定となります。nを省略すると「Tab(15)」としたことになります。
コード例としては以下になります。データ "abc"と"def" の間に「Spc(10)」を、またデータ "ghi"と"jkl" の間に「Tab(14)」を入れた状態でファイルに書き込んでいます。
  1. '========== ⇩(17) Spc関数・Tab関数でのデータ整列 ============
  2. Private Sub test16()
  3.  Open "Test_SJIS.txt" For Output As #1
  4. '  Width #1, 15
  5.   Print #1, "abc"; Spc(10); "def"
  6.   Print #1, "ghi"; Tab(14); "jkl"
  7.  Close #1
  8. End Sub
図76

385行目「Print #1, "abc"; Spc(10); "def"」では、1番目のデータ「"abc"」と2番目のデータ「"def"」との間に「Spc(10)」を入れていますので、データ間に「半角スペース10個」を入れるという意味になります。
386行目「Print #1, "ghi"; Tab(14); "jkl"」では、3番目のデータ「"ghi"」と4番目のデータ「"jkl"」との間に「Tab(14)」を入れていますので、Tab関数の次にある4番目のデータ("jkl")を「14番目の位置から書き出す」という意味になります(行の先頭位置は1と数える)。
その出力結果が下図です。
出力幅指定が無い場合のSpc・Tab関数
図77

また、図76の384行目で見え消しにしている「Width #1, 15」を有効にすると、書き込み側の1行の幅が15文字に制限されます。その状態で図76のコードを実行したものが下図になります。
出力幅指定が有る場合のSpc・Tab関数
図78

出力幅制限により各行のデータが「出力幅内に収まらない」場合は、Spc関数・Tab関数ともに「改行され、次行の先頭に書き込み」されます。
なおWidthステートメントで指定する出力幅は「文字数」単位ですので、行の右端が揃うとは限りません。

アプリ実例・関連する項目

固定長ファイルのテキストデータ呼び込み時処理

サンプルファイル

今回、説明の中で紹介したコードは、以下のサンプルファイルの標準モジュール(Module1)に記載しています。実行するにはVBEから直接実行してください。またOpenステートメントの指定固定長ファイルにはPathを指定していませんので、
「C:¥Users¥[ユーザー名]¥Documents¥」にファイルが作られます。
テキストファイルの読み書き_Openステートメント編(its-049.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。