I have this kind of tree folders structure:
c:\Loc_2636\08\20220801.csv
c:\Loc_2636\06\20220607.csv
c:\Loc_2538\07\20220701.csv
i would like to add to any files suffix of upper "Loc_*" folder. At the end they should be like:
c:\Loc_2636\08\Loc_2636_20220801.csv
c:\Loc_2538\07\Loc_2538_20220701.csv
I thought it was simple but i got in trouble because if I do this:
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName $_.FullName.Split("\")[5] "_" $_.Name -WhatIf}
This error is raised: A positional parameter cannot be found that accepts argument ' _ 20220701.csv.Name'.
Instead if I try to do that through a function
function rena(){
param ([string]$name, [string]$fullpath)
$arr=$fullpath.Split("\")
foreach($tmp in $arr)
{
if($tmp.ToString().Contains("Loc_")){
$found=$tmp;
}
}
return $found "_" $name
}
Get-ChildItem -File -Recurse | % { Rename-Item -Path $_.PSPath -NewName {rena($_.Name,$_.FullName) -WhatIf}}
another error is raised
Cannot evaluate parameter 'NewName' because its argument is specified as a script block and there is no input. A script block cannot be evaluated without input.
I'm quite confused since i already used function inside Get-ChildItem command but I never got this error.
Thank you in advance for any help.
CodePudding user response:
You can do this like below:
Get-ChildItem -File -Recurse | Where-Object { $_.DirectoryName -match '\\(Loc_\d )' } |
Rename-Item -NewName { '{0}_{1}' -f $matches[1], $_.Name } -WhatIf
Remove the -WhatIf
switch if what is displayed in the console is OK. Then run again to actually start renaming the files.
To prevent renaming files that have already been renamed (when running more than once), extend the Where-Object clause to Where-Object { $_.DirectoryName -match '\\(Loc_\d )' -and $_.Name -notlike 'Loc_*' }
CodePudding user response:
Theo's helpful answer offers a better solution to your task, which bypasses your syntax problems.
As for the problems with your attempts:
In order to pass expressions (e.g., $_.FullName.Split("\")[5] "_" $_.Name
) or command calls (e.g., rena $_.Name $_.FullName
) as arguments to commands, you must enclose them in (...)
, the grouping operator.
While simple expressions in isolation (e.g.,
$foo
,$foo.Bar
,$foo.ToUpper()
) do not require(...)
, anything more complex does.- For the full rules of how tokens that are unquoted / not enclosed in
(...)
are parsed as arguments, see this answer.
- For the full rules of how tokens that are unquoted / not enclosed in
Enclosing an argument in
{ ... }
creates a script block - a reusable piece of code for on-demand invocation; that code is not instantly executed, so it is not an alternative to(...)
(if you pass a script blocck to a[string]
-typed parameter, you'll get its verbatim content, sans{
and}
). However, script blocks as arguments are useful for dynamically calculating parameter values based on pipeline input, using a technique called delay-bind script blocks, as shown in Theo's answer.
Finally, note that I've reformulated your function call from rena($_.Name,$_.FullName)
to
rena $_.Name $_.FullName
, because only the latter works as intended:
- PowerShell commands - functions, cmdlets, scripts, and external programs - must be invoked like shell commands -
foo arg1 arg2
- not like C# methods -foo('arg1', 'arg2')
. - That is, no
(...)
around the list of arguments, and whitespace rather than,
to separate the arguments.- If you use
,
to separate arguments, you'll construct an array that a command sees as a single argument.
- If you use
- See this answer for more information.