如何在PowerShell中将“多个文件”或“单个目录”接受为单个参数?

huangapple go评论70阅读模式
英文:

How to accept 'multiple files' or 'single directory' to single parameter on PowerShell?

问题

我试图编写一个用于处理文件的FFmpeg的PowerShell脚本。

由于我试图在附带Windows的PowerShell v5.1中使用它,所以我尝试使用PowerShell v5.1编写它。如果无法在PowerShell v5.1中实现,也可以使用最新版本的PowerShell。

由于Windows不允许从“运行”或命令提示符执行PowerShell脚本,我将使用附加的批处理脚本,如下所示,用于与PowerShell脚本一起使用:

@echo off
powershell -File .\ff.ps1 %*

有了这个,我可以从命令提示符将所有参数和参数传递给PowerShell脚本。

命令语法将如下所示(在命令提示符中):

> ff mp3 [一些可选参数] <多个文件>|<单个目录>

如果提供多个用空格分隔的文件,脚本将处理它们。如果提供单个目录,脚本将处理该目录中的所有文件,而不是其子目录(及其文件)。

由于我需要指定“子命令”,我在脚本顶部定义了参数,如下所示:

[CmdletBinding()]
param(
  [Parameter()][Boolean]$loop = $true,
  [Parameter()][Boolean]$twoPass = $true,
  [Parameter()][Int]$height = -1,
  [Parameter()][Int]$width = 1280,
  [Parameter()][Int]$quality = 80,
  [Parameter()][Int]$samplerate = 48000,
  [Parameter()][Int]$compression = 6,
  [Parameter()][Int]$giType = 1,
  [Parameter()][Int]$fps = 24,
  [Parameter()][Int]$bitrate = 320,
  [Parameter()][String]$preset = "picture",
  [Parameter()][Switch]$lossless = $false,
  [Parameter(Position = 0, Mandatory = $true)][String]$subcmd = ""
)

有了这个,我可以设置我的脚本的子命令。

问题是,我不知道如何为那些<多个文件>|<单个目录>设置另一个参数。

这些是该参数的“可接受”值:

// 当前目录中的单个文件
01.mp4
..mp4
// 当前目录中的多个文件
01.jpg 02.jpg 03.jpg 04.jpg
..jpg ..jpg 03.jpg 04.jpg
// 子目录中的单个文件
subdir.png
.\subdir.png
// 子目录中的多个文件
subdir.png subdir.jpg subdir.webp
.\subdir.png .\subdir.jpg .\subdir.webp
// 通配符
*.png
.\*.png
subdir2\*.mp3
.\subdir2\*.mp3
// 多个通配符(带子目录)
*.png *.jpg
.\*.png .\*.jpg
subdir2\image_*.png subdir2\image_*.jpg
.\subdir2\image_*.png .\subdir2\image_*.jpg
*.png subdir2\*.png
*.png .\subdir2\*.png

// 当前目录中的单个子目录
subdir3
.\subdir3
// 子目录中的单个目录
.\subdir3\subdir4
subdir3\subdir4
// 单个绝对路径
C:\another_dir

所有通配符和相对路径应该被“解析”为绝对路径,这意味着我需要一个要在FFmpeg处理之前处理的完整文件列表。

(在处理它们之前,我将不得不筛选列表,因为我们不能将PNG“转换”为MP3,但这将是另一个问题)

以下是脚本的一些实际用法示例(在PowerShell中):

PS> ff.ps1 mp3 -bitrate 320 01.mp4 subdir\*.avi 00.webp // '00.webp' 将在处理之前被过滤掉
PS> ff.ps1 webpa -loop 0 subdir // 获取“subdir”中的所有文件并进行过滤,然后处理

因此,这里是一个简要的问题。

如何接受提供的<多个文件>|<单个目录>,如上所示,作为文件列表的单个参数?

英文:

I'm trying to write a PowerShell script for processing files with FFmpeg.


Since I'm trying to use it with PowerShell v5.1 which is shipped with Windows, I'm trying to code it with PowerShell v5.1. If it isn't possible with PowerShell v5.1, it is OK to use with latest PowerShell.

Because Windows doesn't allow PowerShell script to be executed from 'Run' or Command Prompt, I'll use additional batch script like this to be used with PowerShell script:

@echo off
powershell -File .\ff.ps1 %*

With this, I can pass every parameter and arguments to PowerShell script from Command Prompt.


The command syntax would be like this (in Command Prompt):

&gt; ff mp3 [Some optional parameters] &lt;multiple files&gt;|&lt;single directory&gt;

If you provide multiple files separated with space, the script will process them.
If you provide single directory, the script will process all files inside of that directory, not its subdirectories (and their files).

Since I need to specify 'subcommand', I defined parameters like this, at the top of the script.

[CmdletBinding()]
param(
  [Parameter()][Boolean]$loop = $true,
  [Parameter()][Boolean]$twoPass = $true,
  [Parameter()][Int]$height = -1,
  [Parameter()][Int]$width = 1280,
  [Parameter()][Int]$quality = 80,
  [Parameter()][Int]$samplerate = 48000,
  [Parameter()][Int]$compression = 6,
  [Parameter()][Int]$giType = 1,
  [Parameter()][Int]$fps = 24,
  [Parameter()][Int]$bitrate = 320,
  [Parameter()][String]$preset = &quot;picture&quot;,
  [Parameter()][Switch]$lossless = $false,
  [Parameter(Position = 0, Mandatory = $true)][String]$subcmd = &quot;&quot;
)

With this, I can set subcommand of my script.

The problem is, I don't know how to set another parameter for those &lt;multiple files&gt;|&lt;single directory&gt;.

These are 'acceptable' values for that parameter:

// single file in current directory
01.mp4
..mp4
// multiple files in current directory
01.jpg 02.jpg 03.jpg 04.jpg
..jpg ..jpg 03.jpg 04.jpg
// single file in subdirectory
subdir.png
.\subdir.png
// multiple files in subdirectory
subdir.png subdir.jpg subdir.webp
.\subdir.png .\subdir.jpg .\subdir.webp
// wildcard
*.png
.\*.png
subdir2\*.mp3
.\subdir2\*.mp3
// multiple wildcard (with subdirectory)
*.png *.jpg
.\*.png .\*.jpg
subdir2\image_*.png subdir2\image_*.jpg
.\subdir2\image_*.png .\subdir2\image_*.jpg
*.png subdir2\*.png
*.png .\subdir2\*.png

// single subdirectory in current directory
subdir3
.\subdir3
// single directory in subdirectory
.\subdir3\subdir4
subdir3\subdir4
// single absolute path
C:\another_dir

All wildcards and relative paths should be 'resolved' to absolute paths, which means I need a complete files list to be processed with FFmpeg.

(I will have to filter the list before processing them with FFmpeg because we can't 'convert' PNG to MP3, but that would be an another problem)

Here are some possible actual usage of the script (in PowerShell):

PS&gt; ff.ps1 mp3 -bitrate 320 01.mp4 subdir\*.avi 00.webp // &#39;00.webp&#39; will be filtered out before processing
PS&gt; ff.ps1 webpa -loop 0 subdir // Get all files inside of &#39;subdir&#39; and filter them, and process

So, here is brief question.

How can I accept &lt;multiple files&gt;|&lt;single directory&gt; provided as above, as a file list, in a single parameter?

答案1

得分: 1

我建议将要处理的对象,即包含文件的文件和文件夹,通过管道发送,并将参数用作描述要对到达管道的每个对象执行的操作的说明。

以下是代码的解释:

  1. 对于“myfunc”函数的第一个参数使用ValueFromPipeline
  2. 在“myfunc”的begin部分定义了名为“PipeOneFileAtTime”的子函数,用于逐个处理文件以及文件夹中的所有子文件(使用-Recurse),然后将每个文件逐个传递到管道。
  3. 使用switch语句,因为它会自动循环遍历数组中的每个项目,并允许检查每个项目的类型,这种情况下只测试它是否是[string]
  4. 如果项目不是[string],则由switch语句的default部分处理。
  5. 如果项目是[string],则会测试它是文件、文件夹还是无法匹配任何内容。如果是文件,则将其传递到管道,如果是文件夹,则将文件夹中的文件放入管道,如果无法匹配任何内容,则会向主机发送红色警告(参见下面的“Unknown: vkea”示例输出)。
  6. 使用$MyInvocation.ExpectingInput来测试管道上是否有项目。在所有3个部分(beginprocessend)中都有使用示例,但只有process部分中的示例实际执行操作。
  7. 代码不尝试执行ffmpeg.exe,只是使用Write-Host来构建一个发送到主机屏幕的伪命令。
  8. 它尝试构建一个名为$twoPass的参数,作为构建传递给ffmpeg.exe的参数的示例。

这是一个示例用法,将一个数组、一个整数、一个指向不存在文件或文件夹的字符串以及一个指向文件夹的字符串发送到管道中。在数组中包含一个文件夹、一个文件,然后是另一个文件夹。

上面的示例在我的计算机上生成了以下输出。首先处理数组,包括其“C:\Temp”文件夹,一个文件,然后是文件夹“D:\Temp\TestCL”。然后是传递给default部分的整数,生成整数的GetType()输出。接下来是一个没有意义的字符串,生成“Unknown: vkea”文本(在主机控制台中以红色显示),然后是最后一个文件夹。

这是一个非常通用的答案,但希望您可以找出如何将其中一些功能整合到您的项目中。

英文:

I would recommend sending the objects to be worked on, in this case the files and folders containing files, through the pipeline and use the parameters as a description of what is going to be done to each of those objects that arrive on the pipeline.

This code:

  1. Uses ValueFromPipeline for the first parameter of the "myfunc" function.
  2. Uses a sub-function called "PipeOneFileAtTime" defined in the begin section of "myfunc" to break out each file, and all sub files in folders (-Recurse), and pass each one down the pipeline one at a time.
  3. Uses switch because it will automatically loop through each item in an arrays while allowing for a check the type of the each item, in this case it only test if it is a [string].
  4. If an item isn't an [string], then it is handled by the default section of switch statement.
  5. If an item is a [string], then it is tested to see if it is a file, a folder, or fails to match anything. If a file, then it is sent down the pipe, if a folder, then the files in the folder are placed on the pipeline, and if it fails to match anything, then a red warning is sent to the host (see the "Unknown: vkea" example output below).
  6. Uses $MyInvocation.ExpectingInput for testing if there are items on the pipeline. Examples of use is in all 3 sections - begin, process, and end, but only the one in process actually does anything.
  7. The code does NOT try to execute ffmpeg.exe, but just uses Write-Host to build a sudo command sent to the host screen.
  8. It does try to build one parameter, $twoPass, as an example of how you might build the parameters that you pass to ffmpeg.exe.
#$ffmpeg = &#39;D:\Temp\Downloads\ffmpeg-2023-02-27-git-891ed24f77-full_build\bin\ffmpeg.exe&#39;
$ffmpeg = &#39;ffmpeg.exe&#39;
function myfunc {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true)]$InputObject,
        [Parameter()][Boolean]$loop = $true,
        [Parameter()][Boolean]$twoPass = $true,
        [Parameter()][Int]$height = -1,
        [Parameter()][Int]$width = 1280,
        [Parameter()][Int]$quality = 80,
        [Parameter()][Int]$samplerate = 48000,
        [Parameter()][Int]$compression = 6,
        [Parameter()][Int]$giType = 1,
        [Parameter()][Int]$fps = 24,
        [Parameter()][Int]$bitrate = 320,
        [Parameter()][String]$preset = &quot;picture&quot;,
        [Parameter()][Switch]$lossless = $false,
        [Parameter(Position = 0, Mandatory = $true)][String]$subcmd = &quot;&quot;
    )
    begin {
        function PipeOneFileAtTime {
            param(
                [Parameter(ValueFromPipeline = $true)]$FileInfo
            )
            process {
                switch ($FileInfo) {
                    {$_ -is [string]} {
                        if(Test-Path -Path $_ -PathType Leaf) { $_ }
                        elseif(Test-Path -Path $_ -PathType Container) {
                            Get-ChildItem -Path $_ -Recurse | Where-Object {$_.PSIsContainer -eq $false} |
                                ForEach-Object {$_.FullName}
                        }
                        else { Write-Host &quot;Unknown: $_&quot; -ForegroundColor Red }
                    }
                    default {
                        $_.GetType() | Out-Host
                    }
                }
            }
        }
        $_twoPass = if($twoPass) {&#39; -pass 2 &#39;} else {&#39;&#39;}
        if ($MyInvocation.ExpectingInput) {
        }
    }
    process {
        if ($MyInvocation.ExpectingInput) {
            $InputObject | PipeOneFileAtTime | ForEach-Object {
                Write-Host &quot;$ffmpeg &quot; -ForegroundColor White -NoNewline
                Write-Host &quot; $_ &quot; -ForegroundColor Cyan -NoNewline
                Write-Host &quot; $_twoPass&quot; -ForegroundColor Yellow
            }
        }
    }
    end {
        if ($MyInvocation.ExpectingInput) {
        }
    }
}

This is an example use, on the pipeline is sent an array, and int, a string that points to no file or folder, and a string that points to a folder. In the array is a folder, a file, and then another folder.

,@(&quot;C:\Temp&quot;, &quot;D:\Downloads\VSCodeSetup-x64-1.41.1.exe&quot;, &quot;D:\Temp\TestCL&quot;), [int]22, &quot;vkea&quot;, &quot;D:\Temp\Test&quot;| myfunc -subcmd &quot;abc&quot; -twoPass $true

The above example use, on my computer, produced the following output. The array is processed first, with it's "C:\Temp" folder, a file, and then the folder "D:\Temp\TestCL". Followed by the int which is passed to the default section produces the GetType() output for the int. Followed by a string that has no meaning producing the "Unknown: vkea" text (which appears red in the host console), and then the last folder.

ffmpeg.exe  C:\Temp\a.csv   -pass 2
ffmpeg.exe  C:\Temp\b.csv   -pass 2
ffmpeg.exe  C:\Temp\dif.lst   -pass 2
ffmpeg.exe  C:\Temp\Data\ffmpeg.txt   -pass 2
ffmpeg.exe  C:\Temp\Data\MyInvoc.TXT   -pass 2
ffmpeg.exe  C:\Temp\Data\names.txt   -pass 2
ffmpeg.exe  D:\Downloads\VSCodeSetup-x64-1.41.1.exe   -pass 2
ffmpeg.exe  D:\Temp\TestCL\GPFSVI.txt   -pass 2
ffmpeg.exe  D:\Temp\TestCL\GPFSVI2.txt   -pass 2
ffmpeg.exe  D:\Temp\TestCL\TestStrAry.CMD   -pass 2

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType


Unknown: vkea
ffmpeg.exe  D:\Temp\Test\KicDocs.zip   -pass 2
ffmpeg.exe  D:\Temp\Test\Nums.srt   -pass 2
ffmpeg.exe  D:\Temp\Test\Nums.txt   -pass 2
ffmpeg.exe  D:\Temp\Test\Resized952023012395195324.jpg   -pass 2
ffmpeg.exe  D:\Temp\Test\Resized952023012395195343.bmp   -pass 2
ffmpeg.exe  D:\Temp\Test\Resized952023012395195343.jpg   -pass 2
ffmpeg.exe  D:\Temp\Test\Resized9558eda8b8-5520-406e-8f35-4e6e2ea7d464.jpg   -pass 2
ffmpeg.exe  D:\Temp\Test\test.ps1   -pass 2

This is a very generalized answer, but hopefully you can figure out how to incorporate some of it's functionality into your project.

huangapple
  • 本文由 发表于 2023年2月27日 16:28:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/75578226.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定