2021年6月6日日曜日

Deno.runはダブルクォーテーションを渡す際に¥マークが入ってしまう

例えば「dir "C:¥ab c"」のようなコマンドを実行したい場合、「Deno.run({cmd:['cmd', '/C dir "C:¥ab c"']})」とすれば良い…かと思いきや、それを実行するとコマンドラインに渡る文字列は「cmd /C dir ¥"C:¥ab c¥"」となってしまいます。
これがどうにか回避できないと非常に困るので色々試した結果が以下。



結論から言うと、v1.10.2ではバッチファイルを一時的に作成・実行することで回避できます。

具体的な手順は以下。
  1. カレントディレクトリに存在しないファイル名を探す。
  2. 上記で見つけた名前でバッチファイルを作成する。
  3. バッチファイルの中に、実行したいコマンドを書き込む。
  4. Deno.runで上記バッチファイルを実行して出力を読み取る。
  5. バッチファイルを削除する。

カレントディレクトリに対して書き込み権限がないと使えませんが、それさえクリアすれば、とりあえずこれで何とかなりそう。
上記の手順を反映したソースが以下。

async function 一時ファイルを作成(value:string){
const path作成 = function(中間:string){return 'tmp_'+(new Date()).getTime()+中間+'.bat'}
let path = path作成('')
let cnt = 0
while(await fileExists('./'+path)){
path = path作成('-' + (cnt++))
console.log(path)
}
await Deno.writeTextFile(path, value)
return path
}
async function fileExists(path: string): Promise<boolean> {
console.log(path)
try {
await Deno.lstat(path)
return true
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return false
}
throw err
}
}
const path = await 一時ファイルを作成('chcp 65001 & dir "D:\\abc\\de f"')
console.log(path)
const p = Deno.run({cmd:['cmd', '/C', path], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
Deno.remove(path)
view raw dir.ts hosted with ❤ by GitHub
上記を実行した結果の画面が以下。



以降は上記のソースに辿り着くまでに試したサンプルです。

スペースを含まないPathなら問題なく動作する。
const p = Deno.run({cmd:['cmd', '/C', 'chcp 65001 &', 'dir', 'D:\\abc'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw dir1.ts hosted with ❤ by GitHub


スペースを含むPathだと意図した結果にならない。
const p = Deno.run({cmd:['cmd', '/C', 'chcp 65001 &', 'dir', 'D:\\abc\\de f'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw dir2.ts hosted with ❤ by GitHub


バッチファイルのPathの先頭に「./」がついているとcmdがエラーになっちゃう。
(しかしバッチファイル出力時に上書きを防ぐためにfileExistsにpathを渡す時は「./」が無いとエラーになっちゃう)
async function 一時ファイルを作成(value:string){
const path = './deno_tmp.bat'
await Deno.writeTextFile(path, value)
return path
}
const path = await 一時ファイルを作成('chcp 65001 & dir "D:\\abc\\de f"')
console.log(path)
const p = Deno.run({cmd:['cmd', '/C', path], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw dir3.ts hosted with ❤ by GitHub


以下のサンプルで、バッチファイル経由でのdir実行が可能なことが確認できた。
あとはバッチファイル出力時に既存のファイルを上書きしないようにPathを調整する機能を加えて、冒頭の完成サンプルとした。
async function 一時ファイルを作成(value:string){
const path = 'deno_tmp.bat'
await Deno.writeTextFile(path, value)
return path
}
const path = await 一時ファイルを作成('chcp 65001 & dir "D:\\abc\\de f"')
console.log(path)
const p = Deno.run({cmd:['cmd', '/C', path], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
Deno.remove(path)
view raw dir4.ts hosted with ❤ by GitHub



以降は、バッチファイルを使わずにDeno.runに渡すパラメータを工夫して回避する、というのをechoで試してみた結果です。
結論から言うと、その方法では無理そうでした。

基本形
スペースを含まない文字なら問題ない。
const p = Deno.run({cmd:['cmd', '/C', 'echo 123'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 1.ts hosted with ❤ by GitHub


以下は色々試したけど全部だめだった。
const p = Deno.run({cmd:['cmd', '/C echo 123'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 2.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C echo "123"'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 3.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C echo "12 3"'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 4.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C echo "12 3" '], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 5.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C echo', 123], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 6.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C echo', '123'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 7.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C', 'echo', '123'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 8.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C', 'echo', '"123"'], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 9.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C', 'echo', "'123'"], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 10.ts hosted with ❤ by GitHub


const p = Deno.run({cmd:['cmd', '/C', 'echo', "`123`"], stdout:'piped'})
const o = await p.output()
const text = new TextDecoder().decode(o);
console.log(text)
view raw 11.ts hosted with ❤ by GitHub



ちなみに、スペースを含む文字列をコマンドに渡す際、ダブルクォーテーション以外の物で囲ってみたりスペースをエスケープできないかと色々試してみましたが、ダメでした。仕様的にもそうなっている筈なので、当然ですが…。

0 件のコメント:

コメントを投稿