tech.guitarrapc.cóm

Technical updates

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

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

私が書くコードでは Set-Content/Add-Content を使わないです。が、なぜなのかを振り返るのもいいでしょう。

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

tl;dr;

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

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

機能 Content Out-File
上書き Set-Content Out-File
追記 Add-Content Out-File -Append
NoClobberスイッチ X O
Write Lock O O
Read Lock O X
PowerShell 5.1 デフォルトEncoding ASCII UCS-2 Little Endian
PowerShell 6以降 デフォルトEncoding utf-8 utf-8
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 が欲しくなる機会は少なく、ップすればできるので問題ではありません。

上書きと追記

対応する機能は次の通りです。(リンクは 7.2 を示します)

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-ContentAdd-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

Out-File -NoClobber -Force の併用

  • -Force を使うと、Out-File -ForceSet-Content -ForceAdd-Content -Force いずれも読み込み専用ファイルに書き込みできます
  • PowerShell 5.1 では、Out-File -NoClobber -Force とスイッチを併用すると-Forceのみが有効になります
  • PowerShell 7.2.5 では、Out-File -NoClobber -Force とスイッチを併用すると-NoClobberのみが有効になります。上書き保存は拒否されます
# PowerShell 5.1 では上書き保存 (ファイルがない場合は作成/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となる。
# PowerShell 7.2.5 では読み込み専用ファイルへの書き込みとみなされて上書きできません。
$ 1..10 | Out-File outNoClobberForce.log -Encoding UTF8 -NoClobber -Force
Out-File: The file 'D:\outNoClobberForce.log' already exists.

追記

  • Out-File -Append で追記します
  • Add-Content は、追記用のCmdletです

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

# 追記 (ファイルがない場合は作成)
1..10 | Out-File outAppend.log -Encoding UTF8 -Append
1..10 | Add-Content contentAdd.log -Encoding UTF8

Out-File -NoClobber -Append の併用

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

Write/Read ロック

  • Out-File は、実行中は Write Lock がかかります
  • Set-ContentAdd-Content は、実行中は Write Lock / Read Lock がかかります

私が Out-File しか使わないのはログ出力として利用しているからです。ログは 実行中の内容を読めないと困るので、Out-File の読み込みロックがかからない性質はもってこいです。

書き込みを一時停止させつつ、その間に作成しているログを開いて挙動を確認してみましょう。

Out-File

# 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
  • 複数のホストから同時にファイルへ書きこもうとしても、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にも違いがあります。

  • PowerShell 5.1 では、Out-file と Set-Content/Add-Content を Encoding 指定せずに混ぜると、ファイルエンコーディングが壊れます。(混ぜるな危険)
  • PowerShell 6 以降は、デフォルトエンコーディングが utf-8 に統一されたので問題が起こりません。(PowerShell 5.1 で作ったファイルに PowerShell 6 以降で読み書き時は注意がいります)
# 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
  • ハッシュ値を比べてみてもわかりますね
Algorithm Hash                                                             Path                          
--------- ----                                                             ----                          
SHA256    BD45EFBC002BB183C495E8AFFFF54DEDA45915C9AFC2F0CA0CDF1130D0466B8A D:\content\outEncoding.log    
SHA256    C6011831661EAA30565BF87A2793DE08BEC53FF0E8F29C4404869C049925066B D:\content\contentEncoding.log

もちろん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

一緒ですね。

Algorithm Hash                                                             Path                          
--------- ----                                                             ----                          
SHA256    F7F5796614E196FF5893D06D826BCA4DC7C40A6D5403C624B706B9C8A029F17A D:\content\outEncoding.log    
SHA256    F7F5796614E196FF5893D06D826BCA4DC7C40A6D5403C624B706B9C8A029F17A D:\content\contentEncoding.log

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

  • Out-File はパイプライン上流から渡されたコマンド実行結果が空でも、ファイル作成/書き込みを行います
  • Set-ContentAdd-Content は、パイプライン上流から渡されたコマンド実行結果が空の場合、ファイル作成/書き込みを行いません
# 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                   # これだとつくっちゃうけどね

Out-File はファイルを作成していますが、Set-Content は作っていないのがわかります。

f:id:guitarrapc_tech:20140211054818p:plain

PassThru の可否

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

-PassThru を付ければ実行状態が見えます。

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

1
2
3
4
5
6
7
8
9
10

Credential の可否

  • Out-File-Credential スイッチがないため、別ユーザーとしてコマンド実行ができません。*1
  • Set-Content -CredentialAdd-Content -Credential で、別ユーザーとしてコマンド実行可能に見えます。が、ファイルシステムプロバイダは Credentialをサポートしないので使えません。New-PSDrive や New-SmbMapping でご利用ください
# Out-File は-Credential パラメータを持たず、別ユーザーとしての実施は不可 (Invoke-Command 使えばいい)
# Set-Content は、-PassThru スイッチにより書き込み中のオブジェクトを標準出力しながら処理可能
1..10 | Set-Content contentPassThru.log -Credential (Get-Credential)

Include/Exclude の可否

私は使わないです。

  • Out-File は、-Include-Exclude` スイッチがないため、対象フォルダにあるファイル指定などができません
  • Set-Content -IncludeAdd-Content -Include あるいは -Exclude スイッチが利用できます
# Out-Fileは、 -Include/-Exclude スイッチがない
Get-Childitem hoge | Set-Content contentInclude -Include "*.log"

Filter の可否

私は使わないです。

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

Transaction の可否

私は使わないです。

  • Out-File は、-Transaction スイッチがなく、Transaction管理外です
  • Set-Content -TransactionAdd-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.

詳細: about_transactions

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