【问题标题】:How to process a multi-line command inside of a for /f loop?如何在 for /f 循环中处理多行命令?
【发布时间】:2022-01-07 01:34:04
【问题描述】:

上下文

我正在尝试开发一个脚本来统一我当前工作的脚本来解析一些视频文件。第一个非常简单,只需确保所有视频文件都在 mkv 容器上。接下来的几个分别调用mkvmerge -i 来检查文件中的字幕、标签、附件和其他不需要的额外内容(有时使用find,有时使用findstr,用于RegEx)。

mkvmerge -i 的输出如下所示:

File 'test.mkv': container: Matroska
Track ID 0: video (AVC/H.264/MPEG-4p10)
Track ID 1: audio (Opus)
Track ID 2: subtitles (SubRip/SRT)
Attachment ID 1: type 'image/jpeg', size 30184 bytes, file name 'test.jpg'
Attachment ID 2: type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'
Attachment ID 3: type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'
Chapters: 9 entries

目前,我只是将其通过管道传输到findfindstr 并搜索诸如“字幕”、“字节”或: [0-9] 之类的词。

我的目标是,对于每个已处理的文件,让 mkvmerge -i 每个文件只调用一次,而不是每个脚本调用一次。这是我重新开始的一个旧项目,我之前的尝试可以在this question 中看到。

问题

this answer 之后,我设法将mkvmerge -i 的输出附加到一个变量(%mkvmergeinfo%),现在我所要做的就是将该变量传递几次,并让其余代码相应地执行.这是目前的样子:

for /f "usebackq delims= eol=$" %%c in (`echo !mkvmergeinfo! ^| find /c /i "subtitles"`) do (
    if [%%c]==[0] (
        ...

如果我将echo !mkvmergeinfo! 更改为mkvmerge -i,其余代码可以正常工作,但现在我正在尝试传递多行命令,当我在do 之后添加echo %%c 时,它只显示echo !mkvmergeinfo! 的第一行(我已经检查过它确实包含所有行)。

我试图解决这个问题,将括号内的整个命令替换为对标签的调用,这将执行回显和管道,但这不起作用。

除了将输出写入文件并解析该文件之外,还有其他方法可以解决此问题吗?

【问题讨论】:

  • !mkvmergeinfo! 是否包含您要处理的多行字符串?如果是这样,请将for /F 循环更改为for /F "delims=" %%c in ('cmd /D /V /C echo(^^!mkvmergeinfo^^!^| find /C /I "subtitles"') do ( … )...
  • 是的,!mkvmergeinfo! 包含mkvmerge -i 的完整输出。通过使用 subtitles attachmentchatpers 通过 find 或 findstr 执行多个管道,我可以确定必要的操作。 @aschipfl 请注意,!mkvmergeinfo! 在其第一行包含单引号,所以您的建议不需要 usebackq 吗?
  • 不,因为!mkmergeinfo!是在for /F命令行被解析之后展开的……

标签: windows for-loop batch-file


【解决方案1】:

带有一组处理命令行输出的for /F 命令导致在后台启动另一个带有%SystemRoot%\System32\cmd.exe /c 的Windows 命令处理器,并且命令行作为附加参数附加。因此,对于在后台运行的此命令进程,不启用延迟变量扩展,因为 echo !mkvmergeinfo! 需要这样做。有必要使用选项/V:ON left 到选项/c 和要执行的命令行运行后台cmd.exe。但这是不可能的,除了运行两个额外的cmd.exe,第一个以/c 和一个以%ComSpec% /D /V:ON /C ... 开头的命令行,第二个以cmd.exe 开头,正如aschipfl 在上面的评论中所建议的那样。

我建议为每个*.mkv 文件运行一次mkvmerge.exe -i 并仅使用cmd.exe 的内部命令(如FORIF 不使用@ 987654338@ 和 findstr.exe

例子:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%G in (*.mkv) do (
    echo/
    echo Processing file "%%G" ...
    for /F "delims=" %%H in ('mkvmerge.exe -i "%%G"') do (
        echo/
        echo Processing info line: %%H
        for /F "tokens=1,3,4* delims=:( " %%I in ("%%H") do (
            if "%%I" == "Track" (
                for /F "delims=)" %%M in ("%%L") do (
                    if "%%K" == "video" (
                        echo ... Track %%J is a video track with video codec %%M
                    ) else if "%%K" == "audio" (
                        echo ... Track %%J is an audio track with audio codec %%M
                    ) else if "%%K" == "subtitles" (
                        echo ... Track %%J is a subtitles track with subtitles codec %%M
                    )
                )
            ) else if "%%I" == "Attachment" (
                echo ... Attachment %%J is %%K %%L
            )
        )
    )
)
endlocal

如何进一步处理mkvmerge.exe -i "%%G" 输出的每一行取决于您。这只是一个演示,它会导致文件test.mkv 的发布信息的以下输出。

Processing file "test.mkv" ...

Processing info line: File 'test.mkv': container: Matroska

Processing info line: Track ID 0: video (AVC/H.264/MPEG-4p10)
... Track 0 is a video track with video codec AVC/H.264/MPEG-4p10

Processing info line: Track ID 1: audio (Opus)
... Track 1 is an audio track with audio codec Opus

Processing info line: Track ID 2: subtitles (SubRip/SRT)
... Track 2 is a subtitles track with subtitles codec SubRip/SRT

Processing info line: Attachment ID 1: type 'image/jpeg', size 30184 bytes, file name 'test.jpg'
... Attachment 1 is type 'image/jpeg', size 30184 bytes, file name 'test.jpg'

Processing info line: Attachment ID 2: type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'
... Attachment 2 is type 'image/jpeg', size 30184 bytes, file name 'test2.jpg'

Processing info line: Attachment ID 3: type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'
... Attachment 3 is type 'image/jpeg', size 30184 bytes, file name 'test3.jpg'

Processing info line: Chapters: 9 entries

所以第一行和最后一行信息输出并没有真正处理,而对第一个子字符串(令牌)区分大小写的行进行进一步处理TrackAttachment

第二个 FOR 命令行导致在后台启动,Windows 安装到 C:\Windows 另一个 Windows 命令处理器,当前文件的以下命令行为 test.mkv

C:\Windows\System32\cmd.exe /c mkvmerge.exe -i "test.mkv"

Windows 命令处理器必须首先使用环境变量PATHEXTPATH 搜索文件mkvmerge.exe。因此,如果批处理文件必须处理 100 个.mkv 文件,则很可能总共需要执行数千次文件系统访问才能一次又一次地找到该可执行文件。使用完全限定的文件名,即 mkvmerge.exe 及其完整路径,将有助于将文件系统访问次数减少到恰好 100 次 .mkv 文件的 100 次 mkvmerge.exe 执行次数。

一旦找到mkvmerge.exe,在后台启动的第二个cmd.exe 会使用其完整路径调用此可执行文件并将两个参数传递给它。 mkvmerge.exe输出数据处理后台命令进程的STDOUT

处理后台命令进程的STDOUT的输出由cmd.exe处理批处理文件捕获,FOR在启动@987654360后逐行处理@在mkvmerge.exe完成后自行关闭。

FOR 带有选项/F 总是忽略空行。这里没问题。

默认情况下,非空行将被拆分为子字符串(标记),使用普通空格和水平制表符作为字符串分隔符,然后将检查第一个子字符串以分号开头,这是默认的行尾字符,其中如果该行也将被忽略以进行进一步处理,最后将第一个空格/制表符分隔的子字符串分配给指定的循环变量H。但是在这种情况下不需要默认的行处理行为。出于这个原因,delims= 用于定义一个空的分隔符列表,这导致将整个捕获的行分配给循环变量H,除了该行将以; 开头,因为@987654367 可以在此处排除@ 不输出以分号开头的行。

演示代码将整行输出到控制台窗口,以便进一步处理该行的样子。

第二个for /F 循环现在是处理行数据的真正工作。分配给循环变量 H 的行在圆括号内的双引号中指定,导致 FOR 将该行解释为字符串以像从后台执行的命令进程中捕获的行或读取的行一样处理来自文本文件。

这次字符串分隔符是用冒号、左圆括号和使用delims=:( 的普通空格来定义的。还用tokens=1,3,4* 指定了感兴趣的不仅是第一个子字符串,而且第一、第三、第四和该行的其余部分没有使用分隔符进一步拆分为子字符串。

例如,让我们看看现在发生了什么:

Track ID 0: video (AVC/H.264/MPEG-4p10)

这一行被分成:

  1. Track 被分配给指定的循环变量I
  2. ID 根本没有进一步处理。
  3. 0 根据ASCII table 分配给下一个循环变量J
    这就是循环变量区分大小写的原因。
  4. video 根据 ASCII 表分配给循环变量 K
  5. AVC/H.264/MPEG-4p10) 这是分配给循环变量 L 的行的其余部分。

分配给循环变量IL 的字符串使用IF 条件进一步处理,其中以Track 开头的行的编解码器字符串末尾的圆括号是使用带有delims=) 的另外一个for /F 命令删除。

也可以直接将捕获的行拆分为子字符串,如以下代码所示,该代码另外忽略所有以 Attachment 开头的行,而之前处理的行以 Track 开头。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%G in (*.mkv) do (
    set "Tracks="
    echo/
    echo Processing file "%%G" ...
    for /F "tokens=1,3,4* delims=:( " %%H in ('mkvmerge.exe -i "%%G"') do (
        if "%%H" == "Track" (
            set "Tracks=1"
            for /F "delims=)" %%L in ("%%K") do (
                if "%%J" == "video" (
                    echo ... Track %%I is a video track with video codec %%L
                ) else if "%%J" == "audio" (
                    echo ... Track %%I is an audio track with audio codec %%L
                ) else if "%%J" == "subtitles" (
                    echo ... Track %%I is a subtitles track with subtitles codec %%L
                )
            )
        ) else if not defined Tracks (
            if "%%H" == "Attachment" echo ... Attachment %%I is %%J %%K
        )
    )
)
endlocal

文件test.mkv的演示代码的输出是:

Processing file "test.mkv" ...
... Track 0 is a video track with video codec AVC/H.264/MPEG-4p10
... Track 1 is an audio track with audio codec Opus
... Track 2 is a subtitles track with subtitles codec SubRip/SRT

在处理mkvmerge.exe 输出的行之前,每个文件的环境变量Tracks 首先是未定义的set "Tracks="。如果有一行以Track 开头,则环境变量Tracks 定义为值1,因此该值无关紧要。

在所有其他行中,首先检查环境变量Tracks 是否仍未定义。如果由于之前至少处理了以Track 开头的一行而导致此条件不成立,则该行将被完全忽略。否则,之前没有处理任何跟踪信息,因此以Attachment 开头的行接下来会处理有关附件的打印信息。在任何情况下,第一行 File 和最后一行 Chapters 仍然会被完全忽略。

要了解所使用的命令及其工作原理,请打开command prompt 窗口,在其中执行以下命令,并仔细阅读每个命令显示的所有帮助页面。

  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • setlocal /?

另请参阅我在Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files 上的回答以了解为什么将两个字符串括在" 中进行比较是有意义的,而将它们括在[] 中是没有意义的,因为方括号对 Windows 命令处理器没有特殊意义。

【讨论】:

    猜你喜欢
    • 2011-01-16
    • 2023-03-20
    • 2018-01-09
    • 2022-12-04
    • 2012-06-26
    • 2018-04-02
    • 2021-08-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多