冬眠期間

クマの冬眠期間中にみんなでワイワイする(したい)ブログ

【Tech】Windowsバッチで改行なし出力したいときの小技

技術屋がブログ始めて1年経とうとしているのに技術関連ネタ上げないのもどうかと思い現場でやったことでも備忘を兼ねてアウトプットします。

クライアントからとある要望が来ました。

内容としては既存のbatに「改行コードをLFにしてファイル出力してくれ」というオーダーでした。

今回はそれに対するアクションをまとめてみます。

そもそもなんでわざわざLFに?

まずLFってなんだと言うとLineFeedの略称です。簡単に言うと改行コードというやつです。

で、Windowsで使用される改行コードはCR + LFです。LFはUnix系の改行コードになります。詳しいことは、「改行コード」でググればその道の大先輩方が優しく教えてくれるのでこのあたりでやめておきましょう。

話はもどしてWindows環境でなぜわざわざ改行コードをUnix系のやつで出力したいかと言うと、細かい背景は伏せたいので言えませんが、Windows側で作成したファイルをLinux環境に転送して使いたいからだそうです。転送したあとに使う側でCR + LFで区切る処理にするとか、業務的に大丈夫か確認すればと逃げようとしましたが、どうやらそのシステムはリリース済み。かつ、転送後の処理はクライアントさんのシステムじゃないからいじれないし、確認依頼出すのもコストがかかるから、転送前にLFにしておきたいとのこと。

なら仕方ないととりあえず着手。

アクション①:標準出力の改行コード変えられないかな?

いわゆる「echo hoge > ファイル」っていうやつですね。なんか新人のころ、なんでこれでファイルに出力されんのかまるでわからなくて悩んでた記憶が蘇りました。

上のコマンドを実行すると、ファイルに「hoge↩(CR + LF)」こんな感じでechoでプロンプト上に出力した文字が出力されますね。いい具合の記号がなかったのでそれっぽいのをつけましたが、標準出力すると改行コードがくっついてくるんですね。

なんでかまでは調べていませんが、まぁついてきちゃうもんはしょうがないです。

というわけでせっかくつけてくれるならこれを環境変数とかで変えられないか?という観点で調査開始。したけどありませんでした・・・。

まぁそりゃWindowsではCR + LFだって言ってるのにわざわざ他の改行コードなんざ使いませんよね。

というわけでこの方法は断念。

 

アクション②:改行コードを置換してみよう

最初にこれを思いつけって話ですが、めんどくさがりなので、コーディングしないでできるならやりたくなるのが性なのです。

でも出来なかったので、CR + LFをLFに置換してみようと。

batで処理しようとすると、↓みたいな感じですかね。

=================================

for /f "delims=" %%a in (置換したいファイル) do (
  echo %%a:\r\n=\n>>出力ファイル
)
=================================
※イメージなのでこれをコピペっても動きません!

for文で1行ずつ読んで、\r\nを\nに置換して別ファイルに出力していく感じですね。

ただここで問題。別ファイルに出すのにアクション①でダメだと判明したechoを使用しているんです。困ったことに置換して出力しても改行コードは\r\nのままになってしまうのです。原因は謎です笑

どうやらbatのみでは不可能ということが判明したので、更に調査。

その結果PowerShellを使えば1行で置換できることがわかりました。

参考にしたサイトはこちら。

qiita.com

Get-ChildItem -Recurse -File | ForEach-Object {((Get-Content $_.FullName -Raw) -replace "`r`n","`n") | Set-Content $_.FullName}

これだけで置換ができるわけです。素晴らしいですね。普段PowerShellなんか使う機会が無いというか、ほぼ初見なので新鮮でした。

というわけで、早速batに組み込んで実行してみました。結果を確認すると・・・

f:id:beowolf5963:20200404220616p:plain

おかわりいただけるだろうか。確かに置換はされている。されているがなぜか最終行にCRLFが挿入されているのを。

うーん。。。要件としては満たしはしたけど結局最終行にCRLF入ってるからNGなんだよなぁ・・・。

これも深くまでは追いませんでしたが、おそらく内部で1行読んで置換をしてecho的なもので出力しているんだろうと推測。で、元ファイルは3行目で改行されてるからEOFは4行目にある。だから置換後ファイルは4行目を出力して、かつWindowsだからCRLFなんだろうなぁと勝手に納得しました。

というわけでこの方法も断念。

 

アクション③(解):改行コードを置換しつつ改行されないように標準出力

なにか言ってるかわからないでしょうが。筆者も他者に説明するのに苦労しました笑

内容的には書いたとおりですが、先に改行されない標準出力の解答を出します。

=============================

SET /P X=文字列<NUL >>ファイル

=============================

これです!

参考にしたサイトはこちらです。

qiita.com

仕組みは、

・SET /Pで入力を受け付けるようにする

・入力受付は文字は「文字列」の場所

・入力元はNUL→入力元なしの状態

・SETの結果が表示されるので、それをファイルに標準出力

という感じです。

いや仕組みは理解できたんですけど、なんでこれでそうなるのか不思議ですね。

あとは簡単で、ループしつつ改行コードを変換していくだけです。完成したコードは、

====================================

SETLOCAL ENABLEDELAYEDEXPANSION
SET LF=^


FOR /f "delims=" %%a IN (置換前ファイル) DO (
  SET LINE=%%a!LF!
  SET /P X=!LINE!<NUL >>置換後ファイル
)
ENDLOCAL

====================================

こうなりました。

 

所感

正直やってることは単純ですが、解にたどり着くまでに半日かかってます・・・。

もうちょっと知識があれば早めに出来たんじゃないかという自分への戒めと、

そもそも改行コード変えるだけでならやはり使う側で編集したほうが良いんじゃないかと思います。別にWindows側で無理やりやるくらいだったらLinux側でやったほうが自然ですし。まぁ冒頭でも言いましたがコスト的な問題も有るので一概には言えませんがなんとも言えない要望でした。

別記事でまとめようと思いますが、この処理、だいたい30万行くらいで50分かかります笑

なので次は性能を気にした実装を紹介しようと思います。

 

最後までお読みいただきありがとうございました!