◆ コマンドライン引数に改行を含む文字列を渡すのが難しい
◆ Linux(bash) は " の途中で直接改行入れたり $'text' のなかで \n を使ったりできる
◆ Windows (コマンドプロンプト) は特殊なことをしないとできないし それも bat ファイル限定
◆ Windows (PowerShell) だと `n で改行になるので楽だった

コマンドラインツールで引数に渡す文字列に改行を含めたいです
Linux (bash) ならできるのですがコマンドプロンプトだとやり方がわかりません

dump ツール

ちゃんと渡せてることを楽に確認するために引数をダンプするだけのツールを用意しました
今回は 1 つの exe にしたかったのでランタイムが別になってスクリプト指定が必要になる動的言語は使いません

コマンドプロンプトでデスクトップとかでこれを入力します

mkdir dumparg
cd dumparg
dotnet new console -lang f#

作られる Program.fs ファイルを⇩のコードに書き換えます

// Learn more about F# at http://fsharp.org

open System

[<EntryPoint>]
let main argv =
printfn "##dump-start##"
for arg in argv do
printfn "* %s" arg
printfn "##dump-end####"
0 // return an integer exit code

ビルドして実行します

dotnet publish -c Release -r win10-x64
cd bin\Release\netcoreapp2.2\win10-x64\publish
dumparg --message foo bar

スペース区切りの引数がそれぞれ表示されます

##dump-start##
* --message
* foo
* bar
##dump-end####

ところで F# にしたのは C# だとネームスペース・クラス・メソッドといっぱい書かないといけないのが多くて やりたいのがほんのちょっとの処理だと書くのが面倒だから という理由だったのですがテンプレートのファイルが自動生成されたので結局どっちでもよかったです

Linux (bash) の場合

先に bash の場合を試します

Linux でも動くように dotnet core にしたつもりだったのですが msys2 の bash で試せば十分だったのでそのまま Windows 上で試しました

単純にエンターを入力することができます

$ ./dumparg.exe --message "foo
> bar"
##dump-start##
* --message
* foo
bar
##dump-end####

ダブルクオートの途中なので実行はされず続きの入力ができます
一行で書くならこういう方法があります

$ ./dumparg.exe --message $'foo\nbar'
##dump-start##
* --message
* foo
bar
##dump-end####

だめな例

$ ./dumparg.exe --message foo\nbar
##dump-start##
* --message
* foonbar
##dump-end####

$ ./dumparg.exe --message "foo\nbar"
##dump-start##
* --message
* foo\nbar
##dump-end####

$ ./dumparg.exe --message $"foo\nbar"
##dump-start##
* --message
* foo\nbar
##dump-end####

普通に \n と書いても n になります
"" で囲むと \n になりますが文字列として \n なので改行ではありません
$'' を $"" にすると \n のままです

Windows (コマンドプロンプト) の場合

もちろん \n は駄目です

> dumparg --message foo\nbar
##dump-start##
* --message
* foo\nbar
##dump-end####

コマンドプロンプトのエスケープ文字は ^ なので ^n とかしてみます

> dumparg --message foo^nbar
##dump-start##
* --message
* foonbar
##dump-end####

bash のときのように ^ が消えました
他も試してみます

> dumparg --message "foo^nbar"
##dump-start##
* --message
* foo^nbar
##dump-end####

> dumparg --message 'foo^nbar'
##dump-start##
* --message
* 'foonbar'
##dump-end####

> dumparg --message $'foo^nbar'
##dump-start##
* --message
* $'foonbar'
##dump-end####

> dumparg --message "foo
##dump-start##
* --message
* foo
##dump-end####

ダブルクオートで囲めば ^n という文字列が表示できます
シングルクオートで囲めばシングルクオートがそのまま表示されます
$ をつけてみても特別な意味を持たないのでそのまま出力されます
ダブルクオートではじめて直接改行を入れるとそこまでで実行されます
ダブルクオートが終わってないなんて気にしません

できる気がしませんね

プログラムから

そもそも引数に改行があれば行けるのかと思ってプログラムから実行してみました
Node.js の REPL でやってます

> child_process.spawn("dumparg", ["--message", "foo\nbar"]).stdout.on("data", d => console.log(d.toString())), null
null
> ##dump-start##
* --message
* foo
bar
##dump-end####

ちゃんとできてます
ところで exec を使って実行すると

> child_process.exec("dumparg --message foo\nbar", (err, sout, serr) => console.log(sout)), null
null
> ##dump-start##
* --message
* foo
##dump-end####

となって bar は消えました
exec だと複数コマンドが入力可能なので bar は別コマンド扱いされてしまうようです

bat ならできた

プログラムからの実行ならできることはわかりましたが不便な点も多いです
動的言語ならランタイムが入ってないとダメですし 静的言語コンパイルが必要です
どの環境でもすぐ使えるものではありません

ソースを変えることない方法だと 改行したいところを <LF> と書いておけば改行に変換して実行されるとかできますが 特別な書き方を覚える必要がありますし 文字列として渡したい部分と重複することを考えると改行に置換する文字を別のものにできたほうが良かったりと考え始めるとどんどん複雑になってきます

やっぱりコマンドプロンプトだけでできたほうがいいなと思って調べてみると bat ファイル限定ですができる方法がありました

@echo off
setlocal EnableDelayedExpansion
set LF=^




dumparg --message foo!LF!bar

> lf.bat
##dump-start##
* --message
* foo
bar
##dump-end####

できてます
LF 変数に改行文字を入れてしまって foo!LF!bar で展開します
遅延展開が必要らしく通常の %LF% だとダメでした
foo だけしか表示されません
遅延展開を行うために setlocal で EnableDelayedExpansion を指定する必要があります
これがないと !LF! はそのまま !LF! と表示されます
遅延展開についての補足

bat ファイル中でないコマンドプロンプトだと遅延展開ができないみたいで直接打っていってもダメでした

注意するのは LF に改行を代入するところです
2 つの空行が必要です
コマンドプロンプトで実行してみればわかりますが 2 回 More? と聞かれます

騙されたやつ

!LF! を使わず %LF% でできるものがあったので使ってみたものがあります

>set LF=^& echo.
>dumparg --message foo%LF%bar
##dump-start##
* --message
* foo
##dump-end####
bar

bar の位置が違ってますね
最初これでなんで行けるんだろうと思ってたのですが & を使って複数のコマンドを続行することになります
展開された結果

dumparg --message foo& echo.bar

になって & の前後の 2 つが実行されます
1 つめのコマンドがそのまま表示するだけであれば 2 つの出力がつながると改行区切りになるので良さそうに見えるのですが コマンドに渡されるのは %LF% の直前までとなるので出力する以外にプログラム中で使う場合には使われず全く意味がないものになります
その結果今回のだと dump-end のあとに bar が出力されてるわけです

Windows (PowerShell) の場合

PowerShell 嫌いで Windows10 の PowerShell で開くをコマンドプロンプトで開くに変えるほどの私ですが さすがに今回は辛いし bat ファイルもあまり気がすすまないので PowerShell に頼ることにしました

> .\dumparg --message foo`nbar
##dump-start##
* --message
* foo
bar
##dump-end####

わぁ簡単ですねー
bash よりも楽です
\n じゃなくて `n っていうのが覚えづらいですけど

コマンドプロンプトから実行する場合はこうなります

>powershell -Command .\dumparg --message foo`nbar
##dump-start##
* --message
* foo
bar
##dump-end####