Home > other >  How do you move more than a single item with PowerShell at once?
How do you move more than a single item with PowerShell at once?

Time:04-15

When I try to run:

mv test.txt test2.txt test-files

Returns the error:

Move-Item : A positional parameter cannot be found that accepts argument '.\test-files\'.
At line:1 char:1
  mv .\test.txt .\test2.txt .\test-files\
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      CategoryInfo          : InvalidArgument: (:) [Move-Item], ParameterBindingException
      FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.MoveItemCommand

However, if I simply move the files one at a time with

mv test.txt test-files
mv test2.txt test-files

Then it moves the files, but this seems tedious if I had more than two files.

Is there any possible way to move multiple files at once to a specific location?

I've tried mv t*.txt test-files which works, but the files I want to actually move are index.html and style.css

CodePudding user response:

Note: As your error message reveals, mv is a built-in alias of PowerShell's Move-Item cmdlet (albeit only on Windows) - see the bottom section for why this is problematic.

Mathias R. Jessen provided the crucial pointer in a comment:

Multiple source paths must be passed as an array to Move-Item's -Path or -LiteralPath parameter, which with directly specified paths requires separating them with ,:

# Note the "," separating the source file names.
Move-Item -LiteralPath test.txt, test2.txt -Destination test-files

I've used named arguments above, i.e. I've preceded the arguments with the names of their target parameters.

PowerShell also supports positional parameter binding:

Move-Item test.txt, test2.txt test-files

This works, but note that it isn't fully equivalent to the above, because the first positional argument - test.txt, test2.txt binds to the -Path parameter, not -LiteralPath:

  • It doesn't make a difference here, because the file names at hand contain no wildcard characters.
  • However, if your literal file names happen to contain [ (e.g., test[2].txt), binding to -Path wouldn't work as expected.

Discovery of whether a cmdlet's parameters support arrays:

You can discover whether a given parameter supports arrays by looking at its syntax diagrams, which are part of the brief help that is displayed when you pass the standard -? switch; you can also display them in isolation with Get-Command -Syntax Move-Item, for instance.

Using Get-Help, you can even ask for details about specific parameters, using -Path as an example:

PS> Get-Help Move-Item -Parameter Path

-Path <System.String[]>

    Specifies the path to the current location of the items. The default is the current directory. Wildcard characters are permitted.
    
    Required?                    true
    Position?                    0
    Default value                Current directory
    Accept pipeline input?       True (ByPropertyName, ByValue)
    Accept wildcard characters?  true

<System.String[]> denotes the parameter's type, which in this case is an array ([] of System.String ([string]) instances. In other words: [] following a parameter type implies that arrays are supported.

However, a given cmdlet can choose to also support passing multiple arguments individually to an array-typed parameter (of necessity only one parameter can support this per cmdlet), declared with a [Parameter()] attribute whose ValueFromRemainingArguments property is set to $true:

An example is Write-Output, whose (one and only positional) -InputObject parameter is typed System.Management.Automation.PSObject[], but also has ValueFromRemainingArguments set, so that the following two calls are equivalent:

# Single array argument that directly binds to -InputObject
Write-Output 1, 2, 3

# Multiple arguments that implicitly bind collected in an array to -InputObject
Write-Output 1 2 3

Note:

  • Unfortunately, the help does not reveal the presence of ValueFromRemainingArguments - neither in the parameter's detailed view with Get-Help ... -Parameter ..., nor, typically, in a parameter's description.

  • While programmatic discovery is possible, it is nontrivial:

    # -> 'InputObject'
    (Get-Command Write-Output).Parameters.Values.
      Where({ $_.Attributes.ValueFromRemainingArguments -contains $true }).Name
    

Another example is Write-Host, whose -Object parameter behaves the same way. However, what is unusual about this parameter is that its type is -Object <System.Object>, i.e. it doesn't explicitly reveal that it accepts arrays; however, it does; technically, this possible, due to System.Object being capable of storing any data type, including arrays (collections). It is only the plural in the parameter's description that hints at the fact that -Object accepts arrays: "Objects to display in the host."


Alias-naming considerations:

That mv is a built-in alias for Move-Item (on Windows only)[1] is problematic. More broadly, all aliases named for internal commands of a different shell (e.g., cmd.exe's dir) or, as in this case, for external programs (possibly a different platform's) are problematic, because the syntax of these commands / programs usually differs fundamentally from PowerShell's syntax.

  • Case in point: the mv command in your question would work with the Unix-platform /bin/mv utility.

It is better to use PowerShell own aliases, whose names are formed methodically from the approved verb part of the command being aliased, with each approved verb having its official abbreviation, e.g., m for Move-. The abbreviation for the noun part isn't standardized, but using the initial letter stands to reason, e.g. i for Item.

  • In PowerShell (Core) 7 , you can discover the verb abbreviation via Get-Verb (output column AliasPrefix), e.g., Get-Verb Move; in Windows PowerShell you'll need to consult the "approved verb" link above.

And, indeed, mi is another built-in alias for Move-Item, as the following discovery command based on Get-Alias reveals:

PS> Get-Alias -Definition Move-Item

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Alias           mi -> Move-Item                                               
Alias           move -> Move-Item                                             
Alias           mv -> Move-Item      # WINDOWS ONLY                                       
  • The big advantage of such aliases - aside from not pretending to be something they're not - is that their methodic naming makes it easier to remember and possibly even guess their names; e.g., gc for Get-Content and, as Get-Alias -Definition Get-Alias reveals, gal for Get-Alias.

  • Name conflicts with external programs can still occur, but are less likely. E.g. sc for Set-Content shadows the native sc.exe utility (for controlling services), which is the reason it was - somewhat controversially - removed from PowerShell (Core).


[1] On Unix-like platforms, the mv alias isn't defined, so as not to shadow the external, platform-native /bin/mv program.

  • Related