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
-NoClobber
for 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.
Update
sounds like a good choice for the verb, butItem
is 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 $path
which 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
-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 forNew-Item
, given its name and purpose.The focus of
touch
is _timestamp functionality_, whereasNew-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 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
touch
utility is in theTouch-Item
implementation 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
-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.