New-Item cmdlet can be used to create an empty file. However, it gives an error if the file already exists. Sometimes, you just want to ensure the file exists – create it if it doesn't, do nothing if it does. Of course, one could do this with an if conditional but having just a switch to do it would make things a lot easier.
(This is different from -Force – -Force replaces the existing file with an empty file, -IfNotExists would leave the existing file unmodified.)
Sometimes it is suggested that New-Item is equivalent to Unix touch. However, touch leaves existing files intact, New-Item obliterates them. If New-Item had an -IfNotExists option, it would be closer to touch.
I'd name the parameter -TouchFile. "Touch" says about expected behavior, "File" says that the parameter is a dynamic parameter for FileSystem provider.
Update: -Touch \
/cc @mklement0
It's a useful thing to be able to do.
Currently you can do if (-not (test-path $path)) {new-item $Path}so this would make the syntax neater, rather than making impossible things possible. .
It needs to have the right behaviour for different item types. -TouchFile for files only is OK , but something more generic for directories etc. might be better.
I commonly find I write
md $path -erroraction SilentlyContinue
really I _should_ use try {} catch{}and ignore "exists" errors , but stop for "failed to add" ones. Some sort of "Ensure-there-is " which errors if it can't make one would make for nicer code
Making a simple powerShell function which is the equivalent of unix's touch on Windows would be the natural next thing to do.
It needs to have the right behaviour for different item types. -TouchFile for files only is OK , but something more generic for directories etc. might be better.
Makes sense.
Also I found that touch utility can accept optional timestamp.
If it will be a dynamic parameter for FileSystem provider the parameter could be -Touch <optional datetime>.
tl;dr:
Good idea, but the switch should be named -NoClobber for consistency with existing switches with essentially the same purpose - this switch should _not_ modify the timestamp of an existing item.
I suggest leaving timestamp-related functionality out of New-Item.
There are two independent aspects:
ensuring a file's _existence_
modifying a file's _timestamps_
Even though the Unix touch utility combines the two aspects (in its argument-less form, it ensures the existence of the file _and_ updates the last-write timestamp if the file already exists), I don't think that's appropriate for New-Item, given its name and purpose.
The focus of touch is _timestamp functionality_, whereas New-Item's is _existence_.
touch has numerous useful options, and fundamentally the ability to target _multiple_ items, via wildcards; the best we could do with a dynamic -Touch parameter is to provide a limited, single-item subset of touch's functionality - and I don't think that's worth it.
Food for thought for a fully featured potential PowerShell implementation of the Unix touch utility is in the Touch-Item implementation from this Stack Overflow answer; note that I deliberately chose to go with non-standard verb Touch, because I couldn't come up with a succinct yet meaningful name using an approved verb.
md $path -erroraction SilentlyContinue
As @skissane notes, -Force _truncates_ the item if it is a _file_, but if it is a _directory_, it leaves an existing one alone (including its time stamps), hence:
# Create the directory, unless it already exists.
# Note: on Unix-like platforms 'md' and 'mkdir' refer to the Unix mkdir utility,
# not New-Item.
New-Item -Type Directory -Force $path
This asymmetry of the -Force behavior is unfortunate, but were's stuck with it.
Therefore, for directories the new -NoClobber switch would have to have the same effect as -Force, and would arguably be the better switch to use going forward, to better signal the intent. - see below.
What about adding an Update-Item that works like touch?
Update sounds like a good choice for the verb, but Item is too vague, I think: the cmdlet wouldn't be updating the _item itself_, but _a piece of associated metadata_.
Perhaps Update-ItemTimestamp? Or just Update-Timestamp?
A compliant alias name for it would be udts (ud being the official alias name for the Update verb).
Re the term "Item": It suggests support for all providers, which I don't think we need or perhaps even can aim for; unfortunately, there is no better umbrella term for "file or folder" I can think of.
Update-Path
Perhaps
Update-ItemTimestamp? Or justUpdate-Timestamp?
That makes most sense.
Back to the original question, I'm not sure how often we need to create an empty file, one can do
$null | Add-Content $path which does nothing to the file if it exists (doesn't change the date stamp) but creates a zero byte file if there is none.
- Good idea, but the switch should be named
-NoClobberfor consistency with existing switches with essentially the same purpose - this switch should _not_ modify the timestamp of an existing item
New-item already seems to have -NoClobber by default -Force to overcome that deletes the file and creates a new one. Doing append rather than create seems to be the answer for files but for directories or other containers that doesn't make sense.
What's best depends on whether the aim is to fix general cases and have a switch which does "Ignore 'already exists' errors" or give something very similar to touch for files.
Updatesounds like a good choice for the verb, butItemis too vague, I think: the cmdlet wouldn't be updating the _item itself_, but _a piece of associated metadata_.
(...)
Re the term "Item": It suggests support for all providers, which I don't think we need or perhaps even can aim for; unfortunately, there is no better umbrella term for "file or folder" I can think of.
Well, I think it would be up to the provider to determine what updating an item means. For example, lets say you have a cache provider (I think UD has one?), update item could refresh the cache expiration. Registry also has a lastwritetime iirc (I know one is recorded somewhere, can't remember if it's surfaced in the dotnet API).
Think of it like Invoke-Item. Plenty of providers do not implement an Invoke, but the cmdlet still makes sense.
Well, I think it would be up to the provider to determine what updating an item means.
Good point; what I was thinking of is updating an item's _content_, for which we do have the noun _Content_, such as in Set-Content.
If we can come up with a generic Update-Item implementation that meaningfully works for multiple providers, I won't complain, but I wonder if that would become too generic / hard to discover, because the term _timestamp_ would then be in the _parameter_ names.
Also, such a cross-provider cmdlet would have to have to implement parts of the touch-specific functionality via dynamic, filesystem-provider-specific parameters (last-access vs. last-write timestamp, treatment of symbolic links).
But it sounds like at this point we should create a new issue focused on implementing touch functionality in a separate cmdlet, and keep this issue focused on the -NoClobber aspect.
$null | Add-Content $pathwhich does nothing to the file if it exists (doesn't change the date stamp)
That's a neat _workaround_, but far from obvious; I do think there is value in implementing _desired-state logic_ in this case (and in general): "Do what is necessary to make sure that this file exists - whether it's already there or whether you have to create it".
However, one case I hadn't considered before: if -Value is _also_ specified, -NoClobber should cause a statement-terminating error, as the two parameters conflict conceptually, given that New-Item - by design - when given a -Value - either truncates and fills or creates and fills, leaving only the specified -Value in the file (it shouldn't _append_; for that, we have Add-Content, which also creates the file on demand).
As an aside: specifying a -Value when creating a _directory_ - which arguably shouldn't be prevented _syntactically_ - is currently quietly ignored.
deletes the file and creates a new one.
Actually, it _truncates the existing file_ instead, which is not the same, because truncating preserves the file creation date, permissions, ownership, ...
You could argue that -Force should indeed delete and re-create, but truncating is the current behavior.
As for -NoClobber vs. -Force:
I hadn't consider that -Force has _another_ implication:
It creates _parent directories on demand_, so that you can use New-Item -Force noSuchSubDirYet/newFile and New-Item -Force noSuchSubDirYet/another/newDir.
I we say that -NoClobber means _only_: "leave an existing item alone, (do not also create parent directories on demand)", then adding -Force would be needed to ask for the latter, so that we get the following combinations:
switch(es) → / item type ↓ | -NoClobber | -Force | -NoClobber -Force
--------- | ------------ | ------- | ---------
File | if file exists, leave it alone, except fail if -Value is also specified; if parent path doesn't exist, fail | (current behavior) create parent path on demand; truncate file, if it exists | if file exists, leave it alone (except if -Value is also specified); create parent path on demand
Directory | if directory exists, leave it alone; if parent path doesn't exist, fail | (current behavior) if directory exists, leave it alone; if parent path doesn't exist, create it | same as -Force alone
Note that a slight inconsistency with respect to parameter name -NoClobber is that in the existing uses it usually results in an _error_ when the target file already exists, but, given the commonality of leaving an existing target untouched, I think it's still the right name.
P.S., @iSazonov:
Update-Path
In PowerShell, a _path_ is an _address_ of sorts that locates a provider _item_, as reflected in parameters -Path and -LiteralPath, so I don't think that's the way to go; it also has echoes of $env:PATH. "FileSystemItem" instead of "Item" would capture "file or directory", but that's too wordy.
I don't think a new cmdlet is needed to update the modified date time. Set-ItemProperty cmdlet can update LastWriteTime, LastAccessTime, CreationTime properties of a file.
C:\> Get-ItemProperty C:\temp\abc.psd1 -Name lastwritetime
lastwritetime : 1/6/2020 11:22:09 PM
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\temp\abc.psd1
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName : abc.psd1
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
C:\> Set-ItemProperty C:\temp\abc.psd1 -Name lastwritetime -Value (Get-Date)
C:\> Get-ItemProperty C:\temp\abc.psd1 -Name lastwritetime
lastwritetime : 1/26/2020 2:32:33 PM
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\temp\abc.psd1
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName : abc.psd1
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
C:\> Set-ItemProperty C:\temp\abc.psd1 -Name lastwritetime -Value (Get-Date).AddDays(-10)
C:\> Get-ItemProperty C:\temp\abc.psd1 -Name lastwritetime
lastwritetime : 1/16/2020 2:33:05 PM
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\temp\abc.psd1
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\temp
PSChildName : abc.psd1
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
Excellent point, @ThomasNieto - I completely forgot about Set-ItemProperty, because I rarely use it - but it effectively is the embodiment of what we discussed as an all-providers Update-Item.
This gives us most of what touch can do, except:
file creation on demand
being able to distinguish between modifying the properties of a symlink's _target_ and the _link itself_.
Arguably:
the former can be covered simply by having to call New-Item [-NoClobber] first.
the latter should be added as a dynamic filesystem-provider parameter to Set-ItemProperty, such as -ModifyLink (default would be to modify the link's _target_).
Given Set-ItemProperty's support for positional arguments and using its built-in alias sp, concise commands are possible in interactive use; e.g., the following sets the last-write date of all *.txt files in the current directory back by 1 day:
sp *.txt LastWriteTime (Get-Date).AddDays(-1)
In short: I'm no longer convinced we need a separate touch-like cmdlet.
Let me offer a pragmatic solution, which is easy to implement _as a custom solution_ now, also in Windows PowerShell, using either an available touch utility or an emulation of it (with the same basis syntax).
On Unix, the touch utility is automatically available, so no action is required - just call it directly.
On Windows, there are two options, which you could place in your $PROFILE:
On machines that have WSL installed, you can wrap a Unix distro's touch utility as follows:
function touch { wsl touch $args }On other machines, you can use the function below, which emulates the _core_ functionality of the touch utility (no options supported).
# Define the `touch` function only if a `touch` utility isn't present (it always is on Unix).
if (-not (Get-Command -Type Application touch -ea Ignore)) {
function touch {
<#
.SYNOPSIS
Emulates the Unix touch utility.
.DESCRIPTION
On Windows, emulates the core functionality (only) of
the Unix touch utility; examples:
touch new1.txt new2.txt
Creates said files as empty files in the current dir., if
files by that name don't yet exist yet; otherwise sets their
LastWriteTime to now. Note the use of spaces (only) to
separate multiple file names.
touch *.txt
Sets the LastWriteTime of all matching files, if any, to now.
Note:
* No parameter (names) other than -? are supported; all other
arguments are interpreted as file names.
* If you have WSL installed, you can alternatively call
`wsl touch ...`, which provides access to all features of the
Unix utility.
#>
# As a courtesy, interpret -h, --help or the absence of arguments the same as -?
if (-not $args -or $args[0] -in '-h', '--help') { return touch -? }
# Get any existing files...
$item = Get-Item -ea SilentlyContinue -ErrorVariable errs -Path $args
if ($item) {
# ... and update their timestamps.
Write-Verbose "Setting LastWriteTime timestamps to now for: $item"
$item.ForEach('LastWriteTime', [datetime]::now)
}
# Create any files that couldn't be found.
$errs | ForEach-Object {
if ($_.Exception -is [System.Management.Automation.ItemNotFoundException]) {
Write-Verbose "Creating file: $_.TargetObject"
$null = New-Item -Type File -Path $_.TargetObject
}
else {
# Unexpected error, pass it through.
$_ | Write-Error
}
}
}
}
Most helpful comment
tl;dr:
Good idea, but the switch should be named
-NoClobberfor consistency with existing switches with essentially the same purpose - this switch should _not_ modify the timestamp of an existing item.I suggest leaving timestamp-related functionality out of
New-Item.There are two independent aspects:
ensuring a file's _existence_
modifying a file's _timestamps_
Even though the Unix
touchutility combines the two aspects (in its argument-less form, it ensures the existence of the file _and_ updates the last-write timestamp if the file already exists), I don't think that's appropriate forNew-Item, given its name and purpose.The focus of
touchis _timestamp functionality_, whereasNew-Item's is _existence_.touchhas numerous useful options, and fundamentally the ability to target _multiple_ items, via wildcards; the best we could do with a dynamic-Touchparameter is to provide a limited, single-item subset oftouch's functionality - and I don't think that's worth it.Food for thought for a fully featured potential PowerShell implementation of the Unix
touchutility is in theTouch-Itemimplementation from this Stack Overflow answer; note that I deliberately chose to go with non-standard verbTouch, because I couldn't come up with a succinct yet meaningful name using an approved verb.As @skissane notes,
-Force_truncates_ the item if it is a _file_, but if it is a _directory_, it leaves an existing one alone (including its time stamps), hence:This asymmetry of the
-Forcebehavior is unfortunate, but were's stuck with it.Therefore, for directories the new
-NoClobberswitch would have to have the same effect as-Force,and would arguably be the better switch to use going forward, to better signal the intent.- see below.