git diff で行末に ^M が表示されるのは改行コードが CRLF になっているから
git diff
で変更箇所の行末に ^M
が表示される現象に遭遇しました。
原因
改行コードが CRLF
になっていたのが原因でした。
git には空白文字の扱いを決める core.whitespace
という設定があり,そこで blank-at-eol
が有効になっていると,git diff
した際に改行コードの前にある空白文字をエラーとして報告してくれます。
デフォルトでは CR
は改行コードの一部ではなく空白文字として扱われるので,改行コードが CRLF
になっていると CR
の部分がエラーとなり,キャレット記法 ^M
で反転表示されます。
解決方法
まず考えられるのは,改行コードをすべて LF
にすることです。
改行コードを変更したくない・変更できない事情がある場合は,core.whitespace
に cr-at-eol
を有効にして CR
を改行コードの一部として認識させる方法もあるようです。
$ git config --global core.whitespace cr-at-eol
[core] whitespace = cr-at-eol
core.autocrlf
を true
で運用している場合も,この設定を併用するとよいのではないでしょうか。
参考資料
Python の内包表記で if ~ else を使った場合分けをする
内包表記の中で場合分けしたい
Python の内包表記には if 節をつけることができます。
>>> [x for x in range(10) if x % 2 == 0] [0, 2, 4, 6, 8]
しかし,これでできるのはデータのフィルタリングであって,else 節をつけて条件に応じて値を変えるということはできません。 条件に合致しないデータはすべて破棄されてしまいます。 試しに if 節の後に else 節をくっつけてみると Syntax Error になります。
>>> [x for x in range(10) if x % 2 == 0 else -x] File "<stdin>", line 1 [x for x in range(10) if x % 2 == 0 else -x] ^ SyntaxError: invalid syntax
素直に if 文を使った形に書き下せばよいのですけれど,内包表記のまま条件分岐できないものかなと思って調べたら,ちゃんと解決方法がありました。
条件演算式を利用すればできる
条件演算式(三項演算子)を使えば内包表記で else 節を使うことができるようです。 条件演算式はその名前の通り式なので,for 節の後ろではなく前に,iterable から取り出した要素を評価する式のところに書きます。 先ほどとは if の位置が変わっていることに注意してください。
>>> [x if x % 2 == 0 else -x for x in range(10)] [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
FizzBuzz ワンライナー
条件演算式は入れ子にできるので,FizzBuzz もワンライナーで書けてしまいます。
>>> ["fizzbuzz" if x % 15 == 0 else "fizz" if x % 3 == 0 else "buzz" if x % 5 == 0 else x for x in range(1, 16)] [1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz']
参考資料
ls コマンドのタイムスタンプの書式を指定する
--time-style
オプションで ls -l
のタイムスタンプの書式を指定できることを知りました。
これまでずっと更新日時の情報がわかりづらいと不満だったのですが,このオプションの存在を知ってストレスが大幅に軽減されました。
そりゃ指定できますよね……。
タイムスタンプの書式
デフォルトでは ls -l
で表示されるタイムスタンプは以下のようになります。
直近 6 ヵ月以内のファイルは月日時分表示,それ以外は過去のファイルも未来のファイルも年月日表示です。
$ ls -l 合計 3 -rw-r--r-- 1 amano41 amano41 5 1月 2 2000 hoge.txt -rw-r--r-- 1 amano41 amano41 5 1月 14 2016 mage.txt -rw-r--r-- 1 amano41 amano41 5 7月 15 16:16 piyo.txt
GNU coreutils の ls
には --time-style
オプションがあり,タイムスタンプの書式を指定することができます。
指定できるのは以下の値です。
値 | 意味 |
---|---|
full-iso | ナノ秒単位まで表示 |
long-iso | 分単位まで表示 |
iso | 直近 6 ヵ月以内は月日時分,それ以外は年月日 |
locale | ロケール依存(ls コマンドのデフォルトと同じ) |
+FORMAT | date コマンドの書式指定子で指定する |
タイムスタンプの書式は date
コマンドと同じやり方で自由に指定することができます。
たとえば 2016-07-15 01:23:45
というような書式で表示したい場合には,以下のように指定します。
$ ls -l --time-style="+%Y-%m-%d %H:%M:%S" 合計 3 -rw-r--r-- 1 amano41 amano41 5 2000-01-02 03:45:06 hoge.txt -rw-r--r-- 1 amano41 amano41 5 2016-01-14 16:16:35 mage.txt -rw-r--r-- 1 amano41 amano41 5 2016-07-15 16:16:39 piyo.txt
出力例
full-iso
$ ls -l --time-style=full-iso 合計 3 -rw-r--r-- 1 amano41 amano41 5 2000-01-02 03:45:06.000000000 +0900 hoge.txt -rw-r--r-- 1 amano41 amano41 5 2016-01-14 16:16:35.000000000 +0900 mage.txt -rw-r--r-- 1 amano41 amano41 5 2016-07-15 16:16:39.849838900 +0900 piyo.txt
long-iso
$ ls -l --time-style=long-iso 合計 3 -rw-r--r-- 1 amano41 amano41 5 2000-01-02 03:45 hoge.txt -rw-r--r-- 1 amano41 amano41 5 2016-01-14 16:16 mage.txt -rw-r--r-- 1 amano41 amano41 5 2016-07-15 16:16 piyo.txt
iso
$ ls -l --time-style=iso 合計 3 -rw-r--r-- 1 amano41 amano41 5 2000-01-02 hoge.txt -rw-r--r-- 1 amano41 amano41 5 2016-01-14 mage.txt -rw-r--r-- 1 amano41 amano41 5 07-15 16:16 piyo.txt
locale
$ ls -l --time-style=locale 合計 3 -rw-r--r-- 1 amano41 amano41 5 1月 2 2000 hoge.txt -rw-r--r-- 1 amano41 amano41 5 1月 14 2016 mage.txt -rw-r--r-- 1 amano41 amano41 5 7月 15 16:16 piyo.txt
--full-time オプション
ls -l --time-style=full-iso
と同じ出力をする --full-time
というオプションもあります。
$ ls --full-time 合計 3 -rw-r--r-- 1 amano41 amano41 5 2000-01-02 03:45:06.000000000 +0900 hoge.txt -rw-r--r-- 1 amano41 amano41 5 2016-01-14 16:16:35.000000000 +0900 mage.txt -rw-r--r-- 1 amano41 amano41 5 2016-07-15 16:16:39.849838900 +0900 piyo.txt
bash は変数や配列に += 演算子で値が追加できる
ここのところ Cygwin 環境の再構築をしており,シェル周りについてもいろいろ調べ直しています。
今まで知らなかったのですが,bash で変数に値を連結したり,配列に値を追加したりするときに +=
演算子が使えるそうです。
man page にもちゃんと書いてありました。
代入文でシェル変数や配列のインデックスに値を代入する場面では、 += 演算子を使って変数の直前の値に追加したり加算したりできます。 (中略) インデックスによる配列の場合は、新しい値が最大のインデックス より一つ大きいインデックスから配列に追加されます。
変数に値を連結する
これまで以下のように書いていたものが
$ var="foo" $ var="$var bar" $ echo $var foo bar
こう書ける
$ var="foo" $ var+=" bar" $ echo $var foo bar
配列に値を追加する
旧式の書き方
$ array=("foo" "bar") $ array=(${array[@]} "baz") $ echo ${array[@]} foo bar baz
見やすくスッキリ
$ array=("foo" "bar") $ array+=("baz") $ echo ${array[@]} foo bar baz
参考
Cygwin 2.4.1 以降でコマンド置換が働かないのは MacType が原因でした
Cygwin 64bit 版を 2.4.1 にアップデートしたら,特定の条件下でコマンド置換がうまく働かなくなりました。 半月ほど悩まされましたが,ついに MacType が原因であることがわかり解決しました。 同じ症状で悩む人がいるかもしれませんのでメモを残しておきます。
この記事を投稿した時点で Cygwin の最新バージョンは 2.5.0 になっていますが,同じ問題が発生することを確認済みです。
なお,この問題は 64bit 版だけで見られるもののようです。 32bit 版では確認できませんでした。
症状
特定の条件下でコマンド置換がうまく働かなくなりました。 たとえば,ファイルのハッシュ値を変数に代入し,すぐに出力するコマンドを実行してみます。 コマンド置換でエラーが発生しているのか,変数に空文字列が代入されてしまうため,何も表示されません。
$ for f in *; > do > HASH=`md5sum $f | cut -d " " -f 1` > echo $HASH > done ...
変数に代入せず直接 echo
すればちゃんと出力されます。
$ for f in * > do > echo `md5sum $f | cut -d " " -f 1` > done b1946ac92492d2347c6235b4d2611184 591785b794601e212b260e25925636fd c59548c3c576228486a1f0037eb16a1b ...
いろいろパターンを変えて試した結果,コマンド置換にパイプが含まれている場合のみ,直後に別のコマンドを連結して実行すると構文解析でコケる(らしい)ことがわかりました。
for
文は do ~ done
ブロックの内容を ;
で連結した形で実行されるのでエラーになっていると思われます。
;
だけでなく,||
や &&
といった他の連結記号でも同じ症状が出ます。
原因と解決策
どうやら原因は MacType のようです。 MSYS2 の Discussion にそれらしい情報を見つけて,試してみたら当たりでした。 MacType を停止した状態では症状が再現されません。
MacType は Cygwin と競合する可能性があるプログラムとして FAQ のリストにもちゃんと載っていました。 このリストは BLODA(Big List Of Dodgy Apps)と呼ばれているそうです。 セキュリティソフトなど常駐型のプログラムが多く名を連ねています。
MacType が原因とわかりましたので,解決策としては Cygwin を MacType の処理対象から除外してやればよいことになります。
MacType のプロセスマネージャーで bash.exe
を選択し,コンテキストメニューから「このプロセスを除外」にチェックすれば OK です。
もちろん,*.ini
ファイルを直接編集してもよいと思います。
2016-04-22 追記
bash.exe
を除外するだけでは不十分でした。
シェルスクリプトは /bin/sh
で実行されることが多いため,このままではシェルスクリプト内でコマンド置換を使用している場合に問題が起こる可能性があります。
たとえば,git rebase --interactive
を実行すると,以下のようなエラーが発生しました。
$ git rebase -i e563d21 /usr/libexec/git-core/git-rebase--interactive: 行 125: + : 構文エラー: オペランドが予期されます (エラーのあるトークンは "+ ")
該当箇所では rebase の進捗を表示するためにコマンド置換を利用してカウントしています。
コマンド置換で失敗して二項演算子 +
のオペランドが空になっているので,シンタックスエラーが発生しているようです。
new_count=$(git stripspace --strip-comments <"$done" | wc -l) echo $new_count >"$msgnum" total=$(($new_count + $(git stripspace --strip-comments <"$todo" | wc -l)))
MacType の除外リストに sh.exe
を追加してやるとこのエラーは解消されました。
しばらくこのままで様子を見ようと思いますが,git 関連でエラーが出るのは正直怖いので,MacType の使用自体を中止することも考えています。
Dropbox で Git のワークツリーのみを同期する
Dropbox に置いたディレクトリを Git でバージョン管理しつつ,リポジトリを同期対象から外し,ワークツリーのみを同期する方法を考えてみました。 Git と Dropbox の連携というと,Dropbox をプライベートなリモートリポジトリとして利用する方法が知られていますが,この記事ではそれとは別の連携方法を試しています。
Git と Dropbox の連携
やっていること
Dropbox 内のディレクトリをリポジトリとして初期化する際に,リポジトリの管理データが格納される Git ディレクトリの場所を Dropbox の外に指定します。
git 1.7.5 で追加された --separate-git-dir
オプションを使うと Git ディレクトリの場所を指定することができます。
作業を中断して別の端末で続きをするときには,Dropbox で同期した作業ディレクトリの内容を維持するために git pull
ではなく git fetch
+ git reset
を使用します。
できるようになること
現在の作業を中断して別の端末で続きをしなくてはならない状況で,一時的なコミットを作らずにワークツリーの状態を別のリポジトリにコピーすることができます。 また,移動中や出先など Git が使えない環境にいる場合やコミットしていない更新がある場合でも最新の作業内容にアクセスすることができるようになります。 GitHub はブラウザから編集する機能がありますが,当然コミット&プッシュしていないと編集できません。
Dropbox 内のディレクトリを単純に Git で管理した場合に生じるいくつかの問題が解消されます。 たとえば,同期中にコミットしてリポジトリを破壊してしまう危険性はありませんし,Git で何か操作をするたびに余計な更新が発生することもありません。 Dropbox の無料ユーザーであるわたしにとっては,リポジトリのデータでストレージ容量が圧迫されるのを防ぐことができるのがもっとも重要かもしれません。
Git ディレクトリの場所を指定する
ローカルリポジトリの初期化
Dropbox 内のディレクトリで --separate-git-dir
オプションに Dropbox 外のディレクトリを指定して git init
を実行します。
すでに Git 管理下にあるディレクトリで同じことをするとリポジトリの再初期化となり,Git ディレクトリの位置を変更することができます。
$ cd ~/Dropbox/project $ git init --separate-git-dir /path/to/project.git Initialized empty Git repository in /path/to/project.git/
指定した場所に Git ディレクトリが作成され,プロジェクトが Git の管理下に置かれました。
$ ls /path/to/project.git config description HEAD hooks/ info/ objects/ refs/
リポジトリとワークツリー
何もオプションをつけずに git init
を実行すると,カレントディレクトリに .git という名前で Git ディレクトリが作成されます。
--separate-git-dir
オプションで Git ディレクトリの場所を指定すると,カレントディレクトリには Git ディレクトリの絶対パスを記述した .git ファイルが代わりに作成されます。
$ ls -lA total 1 -rw-r--r-- 1 amano41 amano41 35 Nov 6 00:00 .git $ cat .git gitdir: /path/to/project.git
また,リポジトリの config に core.worktree
という項目が追加され,このリポジトリに対応するワークツリー(作業ディレクトリ)の絶対パスが保存されます。
$ git config --local core.worktree /home/amano41/Dropbox/project
普通に操作することが可能
リポジトリの作成が終われば,あとは普通の Git プロジェクトとして作業することができます。
$ echo "hello world" > hello.txt $ git add hello.txt $ git commit -m "First commit"
リモートリポジトリとのやり取りもまったく問題ありません。
$ git remote add origin git@github.com:amano41/project.git $ git push origin master $ git log --oneline --decorate --all 582972b (HEAD -> master, origin/master) First commit
別の端末でもコミットできるようにする
Dropbox でディレクトリを同期しながら,Git でバージョン管理ができるようになりました。 別の端末や移動中であっても,ネット環境があって Dropbox さえ使えれば最新の作業内容にアクセスできることになります。
しかし,別の端末から作業の続きをすることはできますが,このままではコミットの履歴を残すことができません。 Git で管理しているプロジェクトですからリモートリポジトリからクローンしてもよいのですが,そうすると Dropbox を使っている意味がなくなってしまいます。 別の端末でもディレクトリを Git の管理下に置き,Dropbox で同期した内容はそのままにして,リポジトリだけを最新の状態にする方法を考える必要があります。
状況の確認
別の端末でも Dropbox によってプロジェクトのディレクトリが同期されています。 この中にはリポジトリの実体(Git ディレクトリ)への参照である .git ファイルも含まれています。
$ cd ~/Dropbox/project $ ls -A .git hello.txt
しかし,この端末には .git ファイルに書かれている場所に Git ディレクトリがないため,git
コマンドを実行することはできません。
$ git status fatal: Not a git repository: /path/to/project.git
ローカルリポジトリの(再)初期化
まずはローカルリポジトリを作成して,このマシンでもプロジェクトを Git の管理下に置きます。
そのまま git init
を実行してもエラーとなるため,.git ファイルを削除 or リネームした上で git init
を実行します。
--separate-git-dir
オプションに先ほど指定したものと同じパスを指定することに注意してください。
.git ファイルの内容が変わってしまうと,先ほどの端末で Git がリポジトリを見つけられなくなります。
.git ファイルにはリポジトリの場所が絶対パスで書き込まれますが,作業ディレクトリからの相対パスに書き換えても動作するようです。
リポジトリの場所を端末間で一致させられない場合でも,作業ディレクトリからの相対的な位置関係を同じにしておけば対応できます。
--separate-git-dir
オプションで相対パスを指定しても .git ファイルには絶対パスで書き込まれるので,いちいち手動で編集しなくてはならないのが面倒ですが……。
$ rm .git $ git init --separate-git-dir /path/to/project.git Initialized empty Git repository in /path/to/project.git/
リポジトリが作成され,この端末でも Git によるバージョン管理ができるようになりました。 Dropbox のおかげでワークツリーは最新の状態になっていますが,リポジトリを作ったばかりなのでコミットの履歴はまだありませんし,どのファイルも追跡されていない状態です。
$ git log fatal: your current branch 'master' does not have any commits yet $ git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) hello.txt nothing added to commit but untracked files present (use "git add" to track)
リモートリポジトリの情報にあわせる
これまでのコミット履歴を参照できるようにするため,git fetch
でリモートリポジトリから情報を取り込みます。
最初の端末で行った作業の記録が追跡ブランチ origin/master にコピーされます。
ワークツリーの内容は Dropbox によって同期されていますので,これで手元に前回の作業終了時の情報がすべて揃うことになります。
コミットがない状態 and/or ステージングされていない変更がある状態ではマージできませんので,git pull
ではエラーが出ます。
仮にマージできたとしても,Dropbox で同期したワークツリーの内容が上書きされてしまうので望ましくありません。
$ git remote add origin git@github.com:amano41/project.git $ git fetch origin master $ git log --oneline --decorate --all 582972b (origin/master) First commit
Dropbox で同期したワークツリーの内容を保持したまま HEAD とインデックスの状態を追跡ブランチ origin/master に一致させるために git reset
を実行します。
先ほど git pull
を使わなかったのと同じ理由で git checkout
は使い(え)ません。
リポジトリの情報が更新され,先ほどは未追跡の状態だったファイルもきちんと Git の管理するファイルとして認識されるようになります。
$ git reset origin/master $ git log --oneline --decorate --all 582972b (HEAD -> master, origin/master) First commit $ git status On branch master nothing to commit, working directory clean
最初の端末で作業を終えたときの状態が完全に再現されました。
リモートリポジトリからクローンしてくるのと違う点は,コミットしていなかった変更点も Dropbox のおかげで手元に反映されているということです。
コミットしていない変更点は git stash
で一時保存できますが,それをリポジトリ間でコピーすることはできません。
中断した作業を引き継ぐには,(一時的なブランチを切って)リモートリポジトリ経由でマージするか,Git を使わずに直接コピーするしかなかったのではないかと思います。
今回は後者のやり方を Dropbox に代わりにやってもらったことになります。
中断した作業を別の端末で再開する
Dropbox で作業ディレクトリを同期しながら,それぞれの端末で Git によるバージョン管理を行う環境が構築できました。 コミットの履歴はリモートリポジトリで共有していますので,いわば作業ログも同期していることになります。
以下では,ある端末で中断した作業の続きを,別の端末で再開する流れについて例示したいと思います。 とはいっても,やり方は基本的にローカルリポジトリの再初期化をしたときと同じです。 作業終了時にそれまでのコミットをプッシュしておき,作業する端末が変わったらフェッチして最新のコミットにリセットするだけです。
端末 A で作業する
いつも通りに作業を進めます。 今日は新しいファイルを作りました。
$ echo "good bye" > bye.txt $ git add bye.txt $ git commit -m "Add bye.txt"
その日の作業が終わったので,別の端末からもコミット履歴がたどれるようにリモートリポジトリにプッシュしておきます。 ある程度まとまった作業が終わってからプッシュするのが一般的だと思いますが,たとえば自宅に帰ってから続きをするつもりであれば今日コミットした分は必ずプッシュしておきます。
$ git push origin master $ git log --oneline --decorate 927bcd8 (HEAD -> master, origin/master) Add bye.txt 582972b First commit
端末 B で作業の続きをする
先ほど端末 A で作ったファイルが Dropbox によって同期されています。 この端末の Git にとっては作業ディレクトリに新しく追加されたファイルですので,これを追跡されていないファイルとして検知します。 もし,追跡対象となっている既存のファイルを変更していた場合には,ステージングされていない変更として検知されることになります。
$ ls bye.txt hello.txt $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) bye.txt nothing added to commit but untracked files present (use "git add" to track)
コミット履歴はリモートリポジトリがローカルリポジトリよりも先行している状態です。 リモートリポジトリが先行している分は端末 A でコミットしたものであり,現在のワークツリーはこのコミット(+α)の状態であることがわかっています。 コミットせずに中断していたり,出先や移動中にファイルを変更していたりすることもあるため,必ずしもワークツリー=最新のコミットではなく,+αの可能性があることに注意してください。
$ git fetch $ git log --oneline --decorate --all 927bcd8 (origin/master) Add bye.txt 582972b (HEAD -> master) First commit
ローカルリポジトリを再初期化したときと同じように,git reset
で HEAD とインデックスの状態を追跡ブランチ origin/master に強制的にあわせてやります。
$ git reset origin/master $ git log --oneline --decorate 927bcd8 (HEAD -> master, origin/master) Add bye.txt 582972b First commit $ git status On branch master nothing to commit, working directory clean
端末 A で作業を終えた状態(+α)が再現できましたので,あとはいつも通り作業の続きをします。 作業を終えるときには,それまでコミットした分をプッシュしておくのを忘れないでください。
端末での作業を終えるときの留意点
その日の作業を終えるときには,それまでにコミットした分をリモートリポジトリに反映させるのを忘れないようにします。 リモートリポジトリ経由でコミット履歴を取り込めない状態で Dropbox がワークツリーを同期すると,一度もコミットをしないまま多くの作業をしてしまったのと同じ状態になります。
プッシュするのを忘れていたとしても作業の続きをすることはできますが,別の端末での作業内容を部分的に修正することが難しくなりますし,あとでマージなりリベースなりする必要があって面倒です。
参考資料
Excel で UTF-8 の CSV ファイルを出力する VBA プログラム
Excel 標準の機能だけでは UTF-8 の CSV ファイルを出力することができません。 メニューから[名前をつけて保存]すれば CSV 形式で保存することはできるのですが,残念ながらエンコーディングを指定することができません。 ワークシートを CSV 形式で保存した場合,エンコーディングは強制的に Shift_JIS になってしまいます。
Excel で UTF-8 の CSV ファイルを出力するには VBA を使います。 ADO(ActiveX Data Objects)の ADODB.Stream オブジェクトを利用すれば,エンコーディングを指定してデータを読み書きすることができます。 ただし,この方法で UTF-8 のデータを出力すると BOM が付加されるので,気になるようであればこれを削除する処理も考えてやらなくてはなりません。
ここでは,Excel VBA で ADODB.Stream オブジェクトを利用し,ワークシートの内容を UTF-8 エンコーディングで BOM のついていない CSV ファイルとして出力する方法を紹介します。
ライブラリの参照設定
デフォルトでは ADO のライブラリは読み込まれていないため,New でオブジェクトを生成することができず,Visual Basic Editor のコード補完機能なども働きません。 オブジェクトの生成は CreateObject 関数で可能なのですが,クラスや定数にアクセスできないのは不便なので,まずはライブラリの参照設定をしましょう。
Visual Basic Editor のメニューから[ツール]→[参照設定]を選び,[参照可能なライブラリファイル]の中から "Microsoft ActiveX Data Objects x.x Library" にチェックを入れます。
いろいろバージョンがありますが,ADODB.Stream はバージョン 2.5 で導入されたので,それ以降のバージョンであれば OK です。 ファイルを配付する予定がなければ最新のバージョンにチェックを入れておけばよいと思います。
なお,アドインなどとしてファイルを配布する場合には注意が必要です。 配布先の環境に同じバージョンのライブラリが入っていないと実行時エラーが発生するからです。 ファイルを配付する際には参照設定を外しておくのが安全です。 この場合,オブジェクトは CreateObject 関数で動的に生成することになり,定数は MSDN などで調べて自分で定義する必要があります。
ADODB.Stream オブジェクト
まずは ADODB.Stream オブジェクトを生成します。
Dim outStream As ADODB.Stream Set outStream = New ADODB.Stream
ADODB.Stream の Type プロパティでストリームの種類をテキストに設定し,Charset プロパティでエンコーディングを指定します。 MSDN によれば,Charset には一般的なエンコーディングをあらわす文字列が指定できるようです。 今回は "UTF-8" を指定します。 改行コードを指定したければ LineSeparator プロパティを設定します。
With outStream .Type = adTypeText .Charset = "UTF-8" .LineSeparator = adLF End With
ストリームを開いたらデータ出力の準備は完了です。
outStream.Open
ワークシートの内容を出力
二重の For ループでワークシートを走査し,セルの値にカンマをつけて 1 行ずつ ADODB.Stream に流し込みます。
以下の例では Cells(1, 1)
から Cells(maxRow, maxCol)
の範囲にデータがあるとしています。
maxRow と maxCol の値は Sheet オブジェクトの UsedRange プロパティを使えば簡単に求めることができます。
Dim r As Long Dim c As Long Dim line As String '1 行ずつ処理 For r = 1 To maxRow '1 列目はカンマなし line = ActiveSheet.Cells(r, 1) '2 列目以降 For c = 2 To maxCol line = line & "," & ActiveSheet.Cells(r, c) Next 'r 行目のデータを Stream に出力 outStream.WriteText line, adWriteLine Next
BOM の削除
あとはストリームの内容をファイルに保存するだけなのですが,このまま出力すると BOM 付きのファイルになってしまいます。 BOM なしの UTF-8 にしたい場合には,もう一手間必要です。
BOM の削除は,ストリーム内の位置を調整して BOM の部分をスキップしてから出力することで実現します。 Position プロパティを 0 にして位置をストリームの先頭に戻し,Type プロパティでバイナリモードに変更してから,位置を 3 バイトに設定します。 これでストリームの先頭にある BOM の分 3 バイトをスキップした状態になります。
outStream.Position = 0 outStream.Type = adTypeBinary outStream.Position = 3
ADODB.Stream にはストリームの途中からファイルに書き出す機能はないので,この状態で一度新しいオブジェクトにコピーし,BOM を含まないストリームデータを用意します。
Dim csvStream As ADODB.Stream Set csvStream = new ADODB.Stream 'バイナリモードで開く csvStream.Type = adTypeBinary csvStream.Open 'BOM の後からデータをコピー outStream.CopyTo csvStream
ファイルへの書き出しはこのコピーの方で行います。
ファイルへの出力と後始末
SaveToFile メソッドでファイルに出力します。
MSDN の記述がわかりづらい(というより間違っているらしい)のですが,第二引数でファイルが存在した場合に上書きするかどうかを指定できます。
ここでは adSaveCreateOverWrite
で上書きを許可しています。
規定値の adSaveCreateNotExist
は「ファイルが存在しない場合のみ作成する」という意味(=上書きしない)のようです。
Dim fileName As String fileNme = ActiveSheet.Name & ".csv" csvStream.SaveToFile fileName, adSaveCreateOverWrite
Open したストリームは必ず Close しなくてはなりません。
csvStream.Close outStream.Close
出力された CSV ファイルをエディタなどで開いてみれば,きちんと UTF-8 で出力されていることがわかると思います。 ちなみに,こうしてできた UTF-8 の CSV ファイルを再度 Excel で開くには,また少し工夫が必要だったりします。
参照設定をしない場合
ライブラリの参照設定をしない場合,ADODB.Stream が未定義の状態となるので,New によるインスタンスの生成ができません。 この場合,以下のように CreateObject 関数を使って動的に生成することになります。 ADODB.Stream という型が使えないので,変数の型も Object 型になっていることに注意してください。
Dim outStream As Object Set outStream = CreateObject("ADODB.Stream")
また,同様にライブラリで定義されている定数も利用できません。 MSDN などで値を調べて自分で定義する必要があります。 以下はこのサンプルで使用した定数の値です。
'StreamTypeEnum Const adTypeBinary = 1 Const adTypeText = 2 'LineSeparatorsEnum Const adCR = 13 Const adCRLF = -1 Const adLF = 10 'StreamWriteEnum Const adWriteChar = 0 Const adWriteLine = 1 'SaveOptionsEnum Const adSaveCreateNotExist = 1 Const adSaveCreateOverWrite = 2
ソースコード
この記事で紹介したやり方をまとめたものを掲載します。 Excel VBA でワークシートの内容を UTF-8 エンコーディングで BOM のついていない CSV ファイルとして保存するサンプルです。