はじめに
以前投稿した以下の記事に載せた関数を使って別のスクリプトの中の処理を組み込んでいた際に想定外のエラーが発生した。
'"C:\Users\user01\Desktop\test\isou\C\c.txt"' is not File
発生場所 行:53 文字:8
+ throw "'${File_name}' is not File"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: ('"C:\Users\user...t"' is not File:String) [], RuntimeException
+ FullyQualifiedErrorId : '"C:\Users\user01\Desktop\test\isou\C\c.txt"' is not File
対象のパスがファイルであるかの判定で「ファイルじゃない」と返ってきている状態だ。
パスを見てみると...
'"C:\Users\user01\Desktop\test\isou\C\c.txt"'
「"c.txt"って思いっきりファイルじゃん!!」と思いきやよく見ると
'"C:\
「クォーテーションが2重にはいっている!!」
目標
- Read-Hostにより入力した文字列に対して、前後のシングル|ダブルクォーテーションを削除する。
Read-Hostで読み込んだ文字列の先頭と末尾のクォーテーションはそのまま文字列として渡される
ということでRead-Host
コマンドはこのような仕様になっていました。
てっきりエスケープシーケンスを使わない限り無視されると思っていましたが違ったようです。
まぁそもそもが文字列の入力を受け付けるものですから、この挙動自体納得感はあります。
見えないシングルクォーテーションで囲まれているようなイメージでしょうか?
#見かけ
Read-Host "input"
input:Hello!
#実体
Read-Host "input"
input:'Hello!'
なのでこの状態でダブルクォーテーションで囲まれた文字列を入れると
#入力文字列をダブルクォーテーションで囲む
Read-Host "input"
input:'"Hello!"'
前述のパスと同様に'"<文字列>"'
で囲まれた状態が出来上がる。
またシングルクォーテーションに囲まれている想定となると、
入力文字列内では変数の展開などはできないでしょう。
$moji="Hello"
#${}で展開
$var=Read-Host "input"
input: こんにちは:${moji}
Write-Host $var
こんにちは:${moji}
#$($)で展開
$var=Read-Host "input"
input: こんにちは:$($moji)
Write-Host $var
こんにちは:$($moji)
replace演算子で前後のクォーテーションを削除する
対象の関数は単体利用を想定というよりも、他のスクリプトの中で組み込むモジュールの一種です。
この関数を組み込もうとしたときに、このRead-Hostによるこの仕様が分からず正しいパスを渡しているのにエラーになるといったパターンに陥るケースが多発することが容易に想像つきます。
対象の関数がRead-Hostを経由してインプットする値は「パス情報」と決まっているので、
今回はクォーテーションが文字列の先頭と末尾にある場合は自動で除去する処理を組み込みます。
前後のクォーテーションを削除するコマンドライン
$Selected_Path = Read-Host "対象ディレクトリのパスを入力してください。"
$Selected_Path = $Selected_Path -replace '^"|"$' -replace "^'|'$"
-replace演算子を使用することで文字列の置換を行います。
replaceという名前の通り置換を行う演算子ですが、
マッチ条件だけを記述し置換文字列が未指定の場合は空文字列と置換されます。
→つまり、パターンにマッチした文字列を削除する用途にも使用できます。
この演算子では正規表現が使用できるので、
これを利用して文字列の先頭と末尾だけを対象に絞り、クォーテーションを除去します。
またこの演算子は複数指定できるので、シングルとダブルの計2つの条件を記述しておきます。
パターンマッチしなければ置換は行われないのでif文で条件分岐を書く必要もありません。
結果ワンライナーでクォーテーションの除去を実装することができました。
フォルダ名やファイル名にシングルクォーテーションが使える問題
Windowsにおいてフォルダ名やファイル名はダブルクォーテーションは使用不可文字として扱われておりますが、シングルクォーテーションは違います。
私自身今回の調査に伴い始めて知りました。
まだ文字と文字の間に含まれているならばいいのですが、普通に末尾にシングルクォートを置くことも可能です。
ディレクトリ: C:\Users\user01\Desktop\test\isou\C\W'
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/11/09 10:52 0 new'
シングルクォートが存在するディレクトリへはエスケープシーケンスを使用してアクセスします。
ディレクトリ: C:\Users\user01\Desktop\test\isou\C
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2023/11/09 10:52 W'
-a---- 2023/08/20 12:00 0 c.txt
cd '.\W''\'
pwd
Path
----
C:\Users\user01\Desktop\test\isou\C\W'
実用上最悪ですね...
この仕様に則るなら、末尾のシングルクォーテーションは場合によっては除去してはいけないということで、条件分岐させる必要がありますが、以下のASCIIの記事内でも推奨されていない文字列であるということが伺えます
(Onedriveなどではそもそも使用できないようです)
なので、今回シングルクォートが末尾に置かれているパス情報はそのままエラーとして扱うこととします。
追記
trimメソッドで削除する方法
パスに含まれる前後のクオーテーションを削除する方法としては-replace
の他にもtrim
メソッドを使用して削除する方法もあります。
$var=Read-Host "inpur"
inpur: "C:\Users\user01\Desktop\test\isou\C\"
$var
"C:\Users\user01\Desktop\test\isou\C\W'"
$var.trim('"',"'")
C:\Users\user01\Desktop\test\isou\C\W
こちらの方が書き方がシンプルでいいかもしれません。
またtrim
メソッドは文字列の先頭と末尾のみが対象とするため、
間に指定した文字が存在していたとしても、問題ないようです。
$var="abcabcabc"
$var.trim(a)
$var
bcdabcd #先頭の"a"しか削除されていない
正規表現を使わなくてもいい分こちらのほうが可読性が高くていいですね。
どちらかというと、このやり方の方が正攻法に見えますね。
参考