tech.guitarrapc.cóm

C#, PowerShell, Unity, Cloud, Serverless Technical Update and Features

PowerShellの Out-File と Set-Content あるいは Out-File -Append と Add-Content の違い

ファイルの連結について、いい記事があります。

私が書くコードでは Set-Content/Add-Content を使わにゃいのでする....ほげ

今回は、 Set-Content/Add-Content と Out-File/Out-File -Append の違いについてです。

目次

上書きと追記

まずは簡単に対応する機能を

Cmdlet 上書き 追記
Content Set-Content Add-Content
Out-File Out-File Out-File -Append

ここまでは問題ないですね?

両方とも -Encoding パラメータで、エンコードもセットできます。

では、違いを見てみましょう。末尾に、機能まとめを置いておきます。

書き込み方法の違い と NoClobber による上書き防止

単純に利用方法を見てみましょう。

上書き
  • Out-File は、パスのみ指定で上書き保存されます。
  • Set-Content は、上書き保存用の Cmdletです。

両方、書き込み先にファイルがなかった場合は、ファイルを作成します。また、書き込み先にすでにファイルがあった場合は、上書きします。

#region 1. 書き込み方法の違い と NoClobber による上書き防止
# 上書き保存 (ファイルがない場合は作成)
1..10 | Out-File out.log -Encoding UTF8
1..10 | Set-Content content.log -Encoding UTF8
上書き防止
  • Out-Fileは、-NoClobber スイッチで、ファイルがすでに存在した場合は上書きしません。つまり、上書き保存防止です。実際、Alias に NoOverwrite とついています。
  • 一方で、 Set-Content/Add-Content に、-NoClobber スイッチはなく、上書き防止することができません。
# 上書き保存禁止 (ファイルがない場合は作成)
1..10 | Out-File outNoClobber.log -Encoding UTF8 -NoClobber
# Set-Content/Add-Content には -NoClobber がなく、上書き禁止を制限できない
Out-File : The file 'D:\content\outNoClobber.log' already exists.
At line:1 char:9
+ 1..10 | Out-File outNoClobber.log -Encoding UTF8 -NoClobber
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceExists: (D:\content\outNoClobber.log:String) [Out-File], IOException
    + FullyQualifiedErrorId : NoClobber,Microsoft.PowerShell.Commands.OutFileCommand
NoClobberとForceの併用
  • Out-Fileも、Set-Content/Add-Content も -Forceスイッチで読み込み専用ファイルに書き込みできます。
  • また、Out-File は、 -NoClobber-Forceスイッチを併用すると、-Forceのみが有効になります。
# 上書き保存 (ファイルがない場合は作成/NoClobberとForceを併用するとForce(読み込み専用ファイルへの書き込み)優先)
1..10 | Out-File outNoClobberForce.log -Encoding UTF8 -NoClobber -Force
1..10 | Set-Content contentForce.log -Encoding UTF8 -Force
1..10 | Add-Content contentAddForce.log -Encoding UTF8 -Force
# Set-Content には -NoClobber がなく、上書き禁止を制御できない。つまり Forceとなる。
追記
  • Out-Fileは、-Appendスイッチで追記します。
  • Add-Contentは、追記用のCmdletですね。

両方、書き込み先にファイルがなかった場合は、ファイルを作成します。また、書き込み先にすでにファイルがあった場合は、上書きします。

# 追記 (ファイルがない場合は作成)
1..10 | Out-File outAppend.log -Encoding UTF8 -Append
1..10 | Add-Content contentAdd.log -Encoding UTF8
NoClobberとAppend の併用
  • Out-File は、 -NoClobber-Appendスイッチを併用すると、-Appendのみが有効になります。
# 追記 (ファイルがない場合は作成/NoClobberとAppendを併用するとAppend優先)
1..10 | Out-File outAppendNoClobber.log -Append -NoClobber
# Add-Content には -NoClobberがないため、上書き禁止を制御できない。
#endregion

Write/Read ロック

なぜ私が Out-File しか使わないのかというと、出力をもっぱらログとして利用しているからです。そしてログは 実行中の内容を読めないと話になりません。

Set-Content や Add-Content は、実行中は Read Lock がかかります。 ほげー

ためしに、以下のコードで書き込みを一時停止させつつ、その間に作成しているログを開いてみましょう。

Out-File
#region 2. Write/Read Lock について
# Out-File は書き込み中は Read/Write Locking のうち Write Lockingのみ
# つまり書き込み中に、その内容を読み取れる

1..10 |%{$_;sleep -Milliseconds 500} | Out-File out.log -Encoding UTF8
  • 複数のホストから同時にファイルへ書きこもうとしても、Write ロックがかかっていますね。
Out-File : The process cannot access the file 'D:\content\out.log' because it is being used by another process.
At line:1 char:40
+ 1..10 |%{$_;sleep -Milliseconds 500} | Out-File out.log -Encoding UTF8
+                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (:) [Out-File], IOException
    + FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.OutFileCommand
  • しかしReadロックがかかっていないので読み込み専用で開けます。

f:id:guitarrapc_tech:20140211051312p:plain

  • 書き込み途中の内容が見れました。

f:id:guitarrapc_tech:20140211051430p:plain

  • また、書き込みにしたがって、ファイルサイズが大きくなっていっている様子も Explorerから見れます。
Set-Content / Add-Content
# Set-Content/Add-Content は書き込み中は Read/Write Locking のうち Read/Write Locking両方がかかる
# つまり書き込み中、その内容は読み取れない
1..10 |%{$_;sleep -Milliseconds 500} | Set-Content content.log -Encoding UTF8
#endregion
  • 複数のホストから同時にファイルへ書きこもうとしても、Write ロックがかかっていますね。
Add-Content : The process cannot access the file 'D:\content\content.log' because it is being used by another process.
At line:1 char:33
+ 1..10 |%{$_;sleep -Seconds 4} | Add-Content content.log
+                                 ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (D:\content\content.log:String) [Add-Content], IOException
    + FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.AddContentCommand
  • Read ロックがかかっているので、読み込みすらできません。

f:id:guitarrapc_tech:20140211052244p:plain

  • 書き込みにしたがってファイルサイズが大きくなっていっている様子は Explorerから見えず 0KBのまま、完了後にファイルサイズが更新されます。

デフォルトEncoding の違い

  • -Encoding を指定しなかった場合の、Encodingにも違いがあります。

つまり、 Out-file と Set-Content/Add-Content を Encoding 指定せずに混ぜると、ファイルエンコーディングが壊れます。*1

#region 3. Encoding の違い
# Out-File のデフォルトエンコーディングは UCS-2 Little Endian
1..10 | Out-File outEncoding.log

# Set-Content/Add-Content のデフォルトエンコーディングは ASCII
1..10 | Set-Content contentEncoding.log

# ハッシュ値で確認
Get-FileHash outEncoding.log,contentEncoding.log
#endregion
  • ハッシュ値を比べてみてもわかりますね。
Algorithm Hash                                                             Path                          
--------- ----                                                             ----                          
SHA256    BD45EFBC002BB183C495E8AFFFF54DEDA45915C9AFC2F0CA0CDF1130D0466B8A D:\content\outEncoding.log    
SHA256    C6011831661EAA30565BF87A2793DE08BEC53FF0E8F29C4404869C049925066B D:\content\contentEncoding.log
  • もちろんEncodingを指定すれば問題ありません。
#region 3. Encoding の違い
# Out-File も Set-Content/Add-Content もエンコーディングを指定すれば一緒
1..10 | Out-File outEncoding.log -Encoding utf8
1..10 | Set-Content contentEncoding.log -Encoding UTF8

# ハッシュ値で確認するとデフォルトエンコーディングが異なることがわかる。
Get-FileHash outEncoding.log,contentEncoding.log
#endregion
  • 一緒ですね。
Algorithm Hash                                                             Path                          
--------- ----                                                             ----                          
SHA256    F7F5796614E196FF5893D06D826BCA4DC7C40A6D5403C624B706B9C8A029F17A D:\content\outEncoding.log    
SHA256    F7F5796614E196FF5893D06D826BCA4DC7C40A6D5403C624B706B9C8A029F17A D:\content\contentEncoding.log

InputObjectが空の場合のファイル作成

  • Out-Fileはパイプライン上流から渡されたコマンド実行結果が空でも、ファイル作成/書き込みを行います。
  • 一方で、Set-Content/Add-Content は、パイプライン上流から渡されたコマンド実行結果が空の場合、ファイル作成/書き込みを行いません。
#region 4. InputObjectが空の場合のファイル作成
# Out-Fileは、結果がNullでもまずファイルを作成する
Get-ChildItem d:\empty | Out-File outNull.log         # フォルダが空でも作る

# Set-Content/Add-Contentは、結果が空だとファイルを作成しない(エラーにもならない)
Get-ChildItem d:\empty | Set-Content contentNull.log  # フォルダが空だと作らない
$null | Set-Content contentNull.log                   # これだとつくっちゃうけどね
#endregion

Out-File はファイルを作成していますが、Set-Content は作っていませんね。

f:id:guitarrapc_tech:20140211054818p:plain

PassThru の可否

  • Out-File は、-PassThru スイッチがないため、パイプラインから渡された結果を標準出力に返せません。
  • Set-Content/Add-Contentは、-PassThru スイッチで、パイプラインから渡された結果を標準出力に返せます。
#region 5. PassThru の可否
# Out-File は、-PassThru スイッチを持たず、書き込み中のオブジェクトを標準出力しながら処理することができない
# Set-Content は、-PassThru スイッチにより書き込み中のオブジェクトを標準出力しながら処理可能
1..10 | Set-Content contentPassThru.log -PassThru
1..10 | Add-Content contentPassThruAdd.log -PassThru
#endregion

-PassThru を付ければ、見えますね。

PS > 1..10 | Set-Content contentPassThru.log -PassThru

1
2
3
4
5
6
7
8
9
10

Credential の可否

  • Out-File は、-Credential スイッチがないため、別ユーザーとしてコマンド実行ができません。*2
  • Set-Content/Add-Contentは、-Credential スイッチで、別ユーザーとしてコマンド実行可能、に見えますがファイルシステムプロバイダは Credentialをサポートしないので使えません。

New-PSDrive や New-SmbMapping でどうぞ。

#region 6. Credential の可否
# Out-File は-Credential パラメータを持たず、別ユーザーとしての実施は不可 (Invoke-Command 使えばいい)
# Set-Content は、-PassThru スイッチにより書き込み中のオブジェクトを標準出力しながら処理可能
1..10 | Set-Content contentPassThru.log -Credential (Get-Credential)
#endregion

Include/Exclude の可否

  • Out-File は、-Include, -Exclude スイッチがないため、対象フォルダにあるファイル指定などができません。
  • Set-Content/Add-Content は、-Include, -Exclude スイッチで指定可能です。

まぁ、使いません。いらね。

#region 7. Include/Exclude の可否
# Out-Fileは、 -Include/-Exclude スイッチがない
Get-Childitem hoge | Set-Content contentInclude -Include "*.log"
#endregion

Filter の可否

  • Out-File は、-Filter パラメータを持たず、対象のプロバイダに合わせたフィルタをかけることはできない。
  • Set-Content/Add-Content は、-Filter パラメータでフィルタ可能です。

まぁ、使いません。いらね。

#region 8. Filter の可否
# -Filter パラメータを持たず、対象のプロバイダに合わせたフィルタをかけることはできない。
#endregion

Transaction の可否

  • Out-File は、-Transaction スイッチがなく、Transaction管理外です。
  • Set-Content/Add-Content は、Transaction有効中に -Transaction スイッチを付けると、トランザクション管理に入るように見えて、ファイルシステムプロバイダはトランザクションをサポートしていないです。レジストリプロバイダのみがTransactionを利用可能です。しょぼーん
 However, the lock is a feature of the database. It is not related
      to transactions. If you are working in a transaction-enabled
      file system or other data store, the data can be changed while
      the transaction is in progress.

まとめ

紹介したコードは GitHubにおいておきます。

機能のまとめです。

機能 Content Out-File
上書き Set-Content Out-File
追記 Add-Content Out-File -Append
NoClobberスイッチ X -NoClobber
Write Lock O O
Read Lock O X
デフォルトEncoding ASCII UCS-2 Little Endian
Encoding指定 O O
InputObjectが空の場合のファイル作成 X O
PassThruスイッチ O X
Credentialスイッチ X
Includeスイッチ O X
Excludeスイッチ O X
Filterスイッチ O X
Transactionスイッチ X
  • △ = ファイルシステムプロバイダでは使えないので無意味

一見すると、Set-Content/Add-Content が良さそうですか?いいえ、Read Lock かかるのは困りますし、-PassThru もラップすればできるので問題ではありません。

私がSet-Content/Add-Content を使うことは、おそらく今後もないでしょう。

*1:混ぜるな危険

*2:Invoke-Command などで代行すればいいだけですが