【问题标题】:Pipe in for loop breaks double quoted variablesfor循环中的管道中断双引号变量
【发布时间】:2015-07-19 04:58:10
【问题描述】:

情况:使用批处理脚本从 JSON 中检索某些值。
我有以下批处理脚本:

@ECHO off

ECHO Enter npo.nl program-url :
SET url=
SET /P url=
:: http://www.npo.nl/buitenhof/03-05-2015/VPWON_1232766/POMS_VPRO_850040 for example
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "tokens=6 delims=/" %%A IN ("%url%") DO (
    FOR /F "delims=" %%B IN ('curl.exe -s http://e.omroep.nl/metadata/aflevering/%%A ^| jq.exe -R -r -s ".[1+index(\"^(\"): rindex(\"^)\")]"') DO (
        FOR /F "delims=" %%C IN ('ECHO %%B ^| jq.exe -r .start') DO SET ss=%%C
        FOR /F "delims=" %%C IN ('ECHO %%B ^| jq.exe -r .eind') DO SET to=%%C
        FOR /F "delims=" %%C IN ('ECHO %%B ^| jq.exe -r .tijdsduur') DO SET t=%%C
        ECHO Start:    !ss!
        ECHO End:      !to!
        ECHO Duration: !t!
    )
)
ENDLOCAL
PAUSE

它有什么作用?
输入 npo.nl 程序 URL 后,第一个 for 循环将 url 剥离到 prid:POMS_VPRO_850040。在第二个 for 循环 curl.exe 中检索 JSON...:

parseMetadata({"STATUS":"OK","VERSION":"1.11.12","prid":"VPWON_1232766","titel":"Schuim & As","aflevering_titel":"","info":"Schuim & As met Jelle Brandt Corstius","ratio":"16:9","medium":"tv","gidsdatum":"2015-05-03","tijdsduur":"00:05:27","start":"00:23:13","eind":"00:28:40","url":"","webcast":1,"images":[{"size":"640x480","ratio":"4:3","url":"http:\/\/images.poms.omroep.nl\/image\/sx480\/c640x480\/606030.jpg"},{"size":"720x405","ratio":"16:9","url":"http:\/\/images.poms.omroep.nl\/image\/sx405\/c720x405\/606030.jpg"}],"omroepen":[{"naam":"VPRO"}],"pubopties":["adaptive","h264_bb","h264_sb","h264_std"],"tt888":"ja","serie":{"srid":"VPWON_1232748","serie_titel":"Buitenhof","serie_url":null},"sitestat":{"baseurl":"http:\/\/b.scorecardresearch.com\/p?c1=2&c2=17827132&ns_site=po-totaal","programurl":"uitzendinggemist.publiekeomroep.ondemand.tv.buitenhof.20150503","programurlpost":"category=uitzendinggemist&thema=informatief&po_source=video","baseurl_subtitle":"http:\/\/nl.sitestat.com\/klo\/po\/s","subtitleurl":"uitzendinggemist.publiekeomroep.ondemand.tv.player.tt888.buitenhof","subtitleurlpost":"category=uitzendinggemist&po_source=video&po_sitetype=webonly"},"reclame":"http:\/\/pubads.g.doubleclick.net\/gampad\/ads?_cookie_&impl=s&gdfp_req=1&env=vp&output=xml_vast2&unviewed_position_start=1&sz=_sz_&correlator=_correlator_&iu=\/9233\/_site_\/buitenhof&url=_url_&cust_params=genre%3Dinformatief%2Cnieuws%2Factualiteiten%26dur%3D3284%26prid%3DVPWON_1232766%26srid%3DVPWON_1232748%26player%3D_player_","streamSense":{"episode":"buitenhof","program":"buitenhof","station":"nederland_1","sitestatname":"uitzendinggemist.publiekeomroep.ondemand.tv.buitenhof.20150503","sko":"TRUE","sko_dt":"20150503","sko_pr":"buitenhof","sko_stid":"1","sko_ty":"tv.seg","sko_prid":"vpwon1232766","sko_t":"1210","sko_cl":"3284"}})
//epc

...并通过管道将其发送到 jq.exe,该管道会删除非 JSON 数据 parseMetadata() //epc 并保持单行完整。这有两个原因:1) 存在非 JSON 数据时,我们无法处理任何内容,以及 2) for 循环一次只能处理 1 行。
后面的 jq.exe 对指定对象的检索值不带双引号。
只要 curl.exe 和 jq.exe 与批处理脚本位于同一目录中,或者在 %path% 变量中,就可以正常工作:

Start:     00:23:13
End:       00:28:40
Duration:  00:05:27

现在我想从另一个地图调用 curl.exe 和 jq.exe。一个有空格的:

SET curl="C:\map with spaces\curl.exe"
SET jq="C:\map with spaces\jq.exe"

@ECHO off

ECHO Enter npo.nl program-url :
SET url=
SET /P url=
:: http://www.npo.nl/buitenhof/03-05-2015/VPWON_1232766/POMS_VPRO_850040 for example
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "tokens=6 delims=/" %%A IN ("%url%") DO (
    FOR /F "delims=" %%B IN ('%curl% -s http://e.omroep.nl/metadata/aflevering/%%A ^| %jq% -R -r -s ".[1+index(\"^(\"): rindex(\"^)\")]"') DO (
        FOR /F "delims=" %%C IN ('ECHO %%B ^| %jq% -r .start') DO SET ss=%%C
        FOR /F "delims=" %%C IN ('ECHO %%B ^| %jq% -r .eind') DO SET to=%%C
        FOR /F "delims=" %%C IN ('ECHO %%B ^| %jq% -r .tijdsduur') DO SET t=%%C
        ECHO Start:    !ss!
        ECHO End:      !to!
        ECHO Duration: !t!
    )
)
ENDLOCAL
PAUSE

对于第二个 for 循环,这会导致问题:

'C:\map' is not recognized as an internal or external command,
operable program or batch file.

虽然'ECHO %%X ^| %jq%' 有效,但似乎'%curl% ^| %jq%' 无效。因此,由于某种原因,一旦解析管道中的 2 个变量,就会出现问题。

好吧,那就不用管了:

SET curl="C:\map with spaces\curl.exe"
SET jq="C:\map with spaces\jq.exe"

@ECHO off

ECHO Enter npo.nl program-url :
SET url=
SET /P url=
:: http://www.npo.nl/buitenhof/03-05-2015/VPWON_1232766/POMS_VPRO_850040 for example
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "tokens=6 delims=/" %%A IN ("%url%") DO (
    FOR /F "delims=" %%B IN ('%curl% -s http://e.omroep.nl/metadata/aflevering/%%A') DO (
        FOR /F "delims=" %%C IN ('ECHO %%B ^| %jq% -R -r -s ".[1+index(\"^(\"): rindex(\"^)\")]"') DO (
            FOR /F "delims=" %%D IN ('ECHO %%C ^| %jq% -r .start') DO SET ss=%%D
            FOR /F "delims=" %%D IN ('ECHO %%C ^| %jq% -r .eind') DO SET to=%%D
            FOR /F "delims=" %%D IN ('ECHO %%C ^| %jq% -r .tijdsduur') DO SET t=%%D
            ECHO Start:    !ss!
            ECHO End:      !to!
            ECHO Duration: !t!
        )
    )
)
ENDLOCAL
PAUSE

现在 curl.exe 和 jq.exe 都在一个 for 循环中。起初,这似乎工作正常。 3 个值被回显,但随后出现问题:

parse error: Invalid numeric literal at line 1, column 5

parse error: Invalid numeric literal at line 1, column 5
parse error: Invalid numeric literal at line 1, column 5
parse error: Invalid numeric literal at line 1, column 5
Start:    00:23:13
End:      00:28:40
Duration: 00:05:27

就像我之前说的; for 循环一次只解析和处理 1 行。第 2 行的非 JSON 数据 //epc 导致 for 循环重新开始,如您所见,这是非常错误的。这就是上面代码中 curl 和 jq 之间存在管道的原因。输出 1 单行进行处理。可悲的是,这也不起作用......叹气。

当然,当 curl 和 jq 仍在带有空格的映射中时,使用临时文件是最后的手段,但我更喜欢使用变量,所以我正在尝试解决管道问题。例如,我尝试在 for 循环中使用反引号而不是单引号在 for 循环中使用“usebackq”,但无济于事。
到目前为止,我还没有找到解决方案。有没有人对此行为有解释以及如何解决它?

【问题讨论】:

  • 这似乎与jqcurl毫无关系;相反,它似乎是关于在批处理脚本中正确转义 map with spaces 文件夹的名称。我认为删除这些标签是有意义的。
  • 您是否愿意考虑使用 PowerShell 或其他不需要这种令人费解的语法来正确解析路径的脚本语言?
  • 我认为其他脚本语言可以轻松解决这个问题,但在这种情况下我更喜欢使用批处理。

标签: batch-file for-loop pipe environment-variables double-quotes


【解决方案1】:

感谢Dave Benham's answer on a related issue 我找到了解决方案!
它似乎是 WinXP 中的一个特定 FOR /F 错误,你猜怎么着,我还在 WinXP 上。为了解决主要的问题,curl-pipe-jq-for-loop,我不得不将^" 放在 之前, 放在整个命令之后。因此,我还进一步改进了整个批处理脚本:

@ECHO off
CLS

:: NPO JSON-extractor geschreven door Reino Wijnsma, 2015 (reino@degeelebosch.nl)

SET batchname=NPO JSON-extractor
SET version=1.1
TITLE %batchname% %version%

SET curl="C:\map with spaces\curl.exe"
SET jq="C:\map with spaces\jq-1.5rc1.exe"

:Check
IF EXIST %curl% (
    IF EXIST %jq% (
        GOTO Input
    ) ELSE (
        ECHO 'jq.exe' niet gevonden.
        ECHO.
        PAUSE
        GOTO :eof
    )
    GOTO Input
) ELSE (
    ECHO 'curl.exe' niet gevonden.
    ECHO.
    PAUSE
    GOTO :eof
)

:Input
ECHO Voer npo.nl programmalink in :
SET url=
SET /P url=
:: http://www.npo.nl/buitenhof/03-05-2015/VPWON_1232766/POMS_VPRO_850040 bijvoorbeeld
IF "%url%"=="" GOTO :eof

SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%A IN ("%url%") DO (
    FOR /F "delims=" %%B IN ('^"%curl% -s http://e.omroep.nl/metadata/aflevering/%%~nxA ^| %jq% -R -r -s ".[1+index(\"(\"): rindex(\"^)\")]"^"') DO (
        ECHO.
        ECHO JSON:
        FOR /F "delims=" %%C IN ('ECHO %%B ^| %jq% .') DO ECHO %%C
        ECHO.
        FOR /F "tokens=1-3" %%C IN ('ECHO %%B ^| %jq% -r "[.tijdsduur,.start,.eind] | @tsv"') DO (
            ECHO Tijdsduur: %%C
            IF NOT "%%D"=="" (
                SET ss=%%D
                SET to=%%E
                SET /A "_ss=((1!ss:~0,2!-100)*3600)+((1!ss:~3,2!-100)*60)+(1!ss:~6,2!-100)"
                SET /A "_to=((1!to:~0,2!-100)*3600)+((1!to:~3,2!-100)*60)+(1!to:~6,2!-100)"
                ECHO Start:     %%D (!_ss!s^)
                ECHO Einde:     %%E (!_to!s^)
            )
        )
    )
)
ECHO.
ENDLOCAL
GOTO Input


供日后参考的重要说明:

jq-syntax:           jq -R -r -s  '.[1+index("("):   rindex(")")]'
cmd-shell:           jq -R -r -s  ".[1+index(\"(\"): rindex(\")\")]"
for-loop:           'jq -R -r -s  ".[1+index(\"(\"): rindex(\"^)\")]"'
for-loop (path): '^"%jq% -R -r -s ".[1+index(\"(\"): rindex(\"^)\")]"^"'

- 对于 cmd-shell,您必须使用换行符 \ 转义双引号。
- 虽然右括号中的 2 是 jq 语法的一部分,但双引号之间的 1 不是。因此,当在一个 for 循环中时,为了防止这个 for 循环关闭,您必须使用 ^ 转义这个循环。
- 当 jq 的可执行路径放在带双引号的变量中时,为了规避 WinXP 错误,您还必须将整个命令放在 ^" 之间,因为括号现在被认为是特殊字符!此解决方法与 Vista 及更高版本兼容。 (另见DosTips.com

【讨论】:

    【解决方案2】:

    在无法测试的情况下,我建议尝试您的批处理代码,如下所示:

    @ECHO off
    SET "curl=C:\map with spaces\curl.exe"
    SET "jq=C:\map with spaces\jq.exe"
    ECHO Enter npo.nl program-url :
    SET "url="
    SET /P "url="
    :: http://www.npo.nl/buitenhof/03-05-2015/VPWON_1232766/POMS_VPRO_850040 for example
    SETLOCAL ENABLEDELAYEDEXPANSION
    FOR /F "tokens=6 delims=/" %%A IN ("%url%") DO (
        FOR /F "delims=" %%B IN ('"%curl%" -s http://e.omroep.nl/metadata/aflevering/%%A') DO (
            FOR /F "delims=" %%C IN ('ECHO %%B ^| "%jq%" -R -r -s ".[1+index(\"^(\"): rindex(\"^)\")]"') DO (
                FOR /F "delims=" %%D IN ('ECHO %%C ^| "%jq%" -r .start') DO SET "ss=%%D"
                FOR /F "delims=" %%D IN ('ECHO %%C ^| "%jq%" -r .eind') DO SET "to=%%D"
                FOR /F "delims=" %%D IN ('ECHO %%C ^| "%jq%" -r .tijdsduur') DO SET "t=%%D"
                ECHO Start:    !ss!
                ECHO End:      !to!
                ECHO Duration: !t!
            )
        )
    )
    ENDLOCAL
    PAUSE
    

    与您的代码不同的是双引号在将字符串分配给环境变量时的位置。

    set 命令通常与参数variable=value 一起使用,其中整个字符串就是参数。

    命令

    SET "curl=C:\map with spaces\curl.exe"
    

    将命令set的整个参数放在双引号中。因此,环境变量curl 是用字符串C:\map with spaces\curl.exe 定义的,没有分配双引号。

    作为在命令set的整个参数字符串上使用双引号的额外好处,现在忽略命令行末尾通常不可见的尾随空格和制表符。

    但是使用

    SET curl="C:\map with spaces\curl.exe"
    

    导致完全不同的行为。命令 set 的整个参数 variable=value 现在不用双引号括起来。由于行中第一个双引号的位置不同,命令 set 现在创建环境变量 curl 并使用双引号将字符串 "C:\map with spaces\curl.exe" 分配给它,并且可能还包括命令行末尾的现有空格和制表符。

    下面的批处理代码复制到批处理文件中并执行,展示了不同之处:

    @echo off
    set "Var1=String with spaces and "double quotes""   
    set Var2="String with spaces and "double quotes""
    set Var3="String with spaces and "double quotes" and 3 trailing spaces"   
    echo Var1=#%Var1%#
    echo Var2=#%Var2%#
    echo Var3=#%Var3%#
    pause
    

    3 个变量的输出上的字符 # 用于显示分配的字符串真正开始和结束的位置。

    输出是:

    Var1=#String with spaces and "double quotes"#
    Var2=#"String with spaces and "double quotes""#
    Var3=#"String with spaces and "double quotes" and 3 trailing spaces"   #
    

    定义Var1的行尾有3个尾随空格,但它们被忽略,因为双引号将命令set的整个参数字符串括起来。

    定义Var2 的行没有尾随空格,但所有4 个双引号现在都是分配字符串的一部分,而不仅仅是字符串的double quotes 部分周围的两个双引号。

    最后一行定义 Var3 也有 3 个尾随空格,这些空格也分配给在引用环境变量值的命令行上通常不需要的变量。

    所以最好总是使用set "variable=value",即使变量名和值是否包含空格。这种表示法只是为了防止不可见的尾随空格或制表符也被分配给环境变量。

    由于路径包含空格的curl.exe 现在被分配给没有双引号的环境变量curl,因此有必要在包含%curl% 的完整字符串周围使用双引号,这里只是"%curl%"%jq% 也是如此,因为它不会在更长的字符串中使用,因此始终只能使用 "%jq%"

    最后一个提示:批处理文件的调试通常非常简单。将第一行从@ECHO off 更改为@ECHO ON 或删除/注释第一行会导致批处理文件的执行,同时显示命令行解释器真正执行的内容。现在通常可以通过查看已处理的命令行来快速发现代码中的错误。

    【讨论】:

    • 感谢您的回答,莫菲。我知道评论@ECHO off。我用过很多次!我理解您对变量中双引号位置的要求,但我始终确保我的变量没有尾随字符。这也不是解决方案,因为 for 循环仍会为 //epc 重新启动。这个问题实际上是 FOR /F 解析器中的一个错误。见下文。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    相关资源
    最近更新 更多