kwatz
kwatz

Reputation: 67

Piping results from ls into command in Powershell, capturing with regex

I am attempting to batch edit metadata of some video files by piping the results from ls into a function that edits the video file's name in metadata. Example:

Files:

Desired title renames in metadata:

I am using the mkvpropedit function from MKVToolNix in PowerShell, which can be used to rename a single file's title in the following way:

C:\"Program Files"\MKVToolNix\mkvpropedit 'My Movie 1 - 1080p DTS 5.1.mkv' -e info -s title='My Movie 1'

This works perfectly, however I would like to be able to perform this action for an entire directory, removing everything from the dash on in the metadata title. Here is the code I tried below, piping the results of ls into the command (I should mention that every single file in the directory is a movie file I wish to edit) and capturing the desired portion of the filename with a regex.

ls | C:\"Program Files"\MKVToolNix\mkvpropedit {$_.name} -e info -s title={$_.name -replace('(.+)-.+', '$1')}

The regex is correct as far as I can tell, but I receive the following error:

mkvpropedit.exe : The command parameter was already specified. At line:1 char:6

ls | C:"Program Files"\MKVToolNix\mkvpropedit {$_.name} -e info -s t ... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

CategoryInfo : InvalidArgument: (:) [], ParameterBindingException

FullyQualifiedErrorId : ParameterSpecifiedAlready

As suggested in the comments, I tried a foreach loop as well:

ls | foreach{C:\"Program Files"\MKVToolNix\mkvpropedit {$_.name} -e info -s title={$_.name -replace('(.+)-.+', '$1')}} 

This produced the same error, once for each file in the directory. What am I doing wrong here? Is there a better way to approach my desired outcome? Thanks!

Upvotes: 3

Views: 2059

Answers (1)

mklement0
mklement0

Reputation: 439757

It looks like you're trying to use delay-bind script blocks ({ ... } blocks that reference $_ as the current pipeline-input variable), which, however, are not supported in direct calls to external executables such as mkvpropedit.

Instead, you must use a ForEach-Object call and use expressions based on $_ to formulate your arguments:

Get-ChildItem | ForEach-Object {
  C:\"Program Files"\MKVToolNix\mkvpropedit $_.name -e info -s title=$($_.name -replace '(.+)-.+', '$1')
}
  • $_.name passed the current pipeline-input object's ($_'s) .name property as an argument.

  • title=$($_.name -replace '(.+)-.+', '$1') uses $(), the subexpression operator, to incorporate the result of an expression into the single title=<value> argument.

    • Note: While using (), the grouping operator by itself is sufficient to pass the result of an expression as an argument, in the case of a compound argument such as title=<value> it does not work, because PowerShell's parameter binder considers the (...) expression a separate argument.
      Using $() instead does work as a compound token, because PowerShell then treats the entire token implicitly as if it were enclosed in "...", i.e. it treats it as an expandable string.

Note: While invocation via C:\"Program Files"\MKVToolNix\mkvpropedit does work, the quoting style is unusual; more typically, you'd enclose the whole path in quotes:
"C:\Program Files\MKVToolNix\mkvpropedit" (or, given that there are no variables to expand, with single quote:
'C:\Program Files\MKVToolNix\mkvpropedit'
However, if the path is quoted as a whole and/or contains a variable reference, you must invoke via &, the call operator, for syntactical reasons:
& 'C:\Program Files\MKVToolNix\mkvpropedit' ...

Upvotes: 4

Related Questions