以下の内容はhttps://tech.guitarrapc.com/entry/2014/02/11/061627より取得しました。


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

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

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

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

まとめ

記事で紹介したコードは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スイッチで、ファイルがすでに存在した場合は上書きしない
  • 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ロックがかかっていないので読み込み専用で開ける

image

  • 書き込み途中の内容が見られる

image

  • また、書き込みにしたがって、ファイルサイズが大きくなっていっている様子も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ロックがかかっているので、読み込みすらできません

image

  • 書き込みにしたがってファイルサイズが大きくなっていっている様子は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は作っていないのがわかります。

image

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で、別ユーザーとしてコマンド実行可能に見える。*2
# 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有効中はトランザクション管理下に見えるが、ファイルシステムプロバイダはトランザクションをサポートしていない。*3
 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などで代行すればいいだけですが

*2:ファイルシステムプロバイダはCredentialをサポートしないので使えない

*3:レジストリプロバイダのみがTransactionを利用可能




以上の内容はhttps://tech.guitarrapc.com/entry/2014/02/11/061627より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14