为什么在管道后面使用 "set -P" 命令不起作用?

10
C:\>type c:\output.txt
abcd
C:\>type c:\output.txt | set /p V1=

C:\>set
... A bunch of junk, NOT seeing "V1"

发生了什么?根据我看过的所有SET文档,%V1%应该从上面被分配一个值为"abcd",是吗?

如果有影响的话,我在Windows XP Pro SP3上。

4个回答

14

管道似乎会创建一个新的CMD实例来执行接收管道数据的下一条命令。因此,当管道结束时,该CMD实例退出并且变量将被丢失。


巧合的是,我需要从一个程序的stdout中获取一个数字,对其进行一些数学运算,然后将其管道传输到另一个程序中以供使用。这似乎可以解决问题:echo 5 | (set /P TmpVar= && set /a TmpVar/2) > C:\test.txt - TonyM
1
+1;实际上,这个问题是由Windows如何实现管道造成的。请参阅为什么延迟扩展在代码块内部时失败?以获取机制的完整解释,以及许多有趣的副作用。 - dbenham

5
我不知道你看过哪个文档来了解set命令,但是set /?的输出清楚地说明:

/P开关允许你将变量的值设置为由用户输入的一行输入。

(我加了斜体)。我认为set /p无论你尝试通过标准输入管道传入什么内容,它都会从控制台获取输入。我不确定为什么它没有等待。 echo xxx | set /p xx=也无法设置变量。

但是,如果你想从单行文件中设置一个变量,你可以使用以下任意一种方法:

for /f "delims=" %%i in (c:\output.txt) do set V1=%%i
set /p V1=<c:\output.txt

第二种方法是最简单的,但如果你想获取任意命令的输出,它并没有太大帮助,但你可能需要先将其重定向到文件中。

第一种方法允许你执行任意命令,而无需使用临时文件:

for /f "delims=" %%i in ('echo AAA') do set xx=%%i

这个页面上有一段有趣的片段,它暗示了它与上下文有关:
“好的,我自己发现了。这是因为|创建了一个新的上下文,所以变量永远不会到达当前上下文的其余部分。证明:
> set bar=
> echo aaa | (set /p bar= && set bar)
bar=aaa
> set bar
环境变量bar未定义

虽然我不愿意评论这个结论的真实性,但在完整性的考虑下我提醒您此事。我不知道在这个意义上上下文是什么,只是想让您知道。

抱歉,我已经有超过10年没有进行批处理编码了,因此我的头脑被Unix的概念所污染,即STDIN就是STDIN...我猜你是对的,“由用户”在DOS世界中默认情况下不等同于“STDIN”(是的,我知道一些Unix程序可以执行花哨的TTY检测,但那是一种罕见而难以实现的例外情况:) 我需要阅读更多关于这些上下文的内容(实际上昨晚看到了那个网页,但太累了,无法完全解析它)。 - DVK
2
嗯,我认为你是对的,@DVK,尽管我在上面的答案中最初的段落是这样写的。显然它是某种STDIN,因为set /p V1=<c:\output.txt可以正常工作。只是管道类型似乎不起作用。 - paxdiablo
1
这个问题与SET命令无关,而与Windows如何实现管道有关。请参阅TonyM的答案以及我在那里发表的评论中提供的链接。 - dbenham

2
这不是对原问题“为什么[...]不能运行?”的直接回答,但对于寻求实现类似逻辑结果的人来说,这个答案可能很有用:
echo:somevalue|set /p somevar=

通常的解决方法,例如上面的代码,是:
echo:somevalue>tempfile.txt&set /p somevar=<tempfile.txt

它的功能非常完美,但是它会创建一个新文件,必须在之后处理(除了我们需要采取额外步骤来确保这个新文件不覆盖我们没有创建或从未打算替换的现有文件)。

虽然对于环境变量而言,在使用管道符 | 操作符跨越两侧之间,确实绕不过使用文件流,只要处于 Windows NT 环境下,并且提供的脚本所在的卷正在使用 NTFS 文件系统,则可以使用比临时文件更优雅的东西:备用数据流

让我们直接进入一个示例,说明如何使用它们:

echo:somevalue>".\%~nx0":temp&set /p somevar=<".\%~nx0":temp

这里的%~nx0代表并会被扩展为我们批处理文件的文件名(包括基本名称和扩展名),如果文件名中包含空格,则用引号括起来。

注意:虽然可以直接使用%0(不带引号)来引用当前脚本,但如果您需要在调试脚本时查看命令的扩展值,特别是如果您的脚本的完整路径非常长,则.\%~nx0更易于管理。

因此,".\%~nx0":temp指的是我们自己脚本的一个名为temp备用数据流(ADS),例如myscript.cmd:temp,我们使用echo命令的输出来创建(或覆盖)它。由于ADS的行为与文件的默认流相同,因此echoset命令没有任何区别。

一个完整的使用该方法的脚本可能如下所示:

@echo off
set __self=".\%~nx0"
set __adsname=test.dat

:: Using some input...
set l_input=@FirewallAPI.dll,-23000
echo:Input: %l_input%
echo:

:: ...we process it...
expand_str_res_id.exe %l_input%>%__self%:%__adsname%&set /p l_output=< %__self%:%__adsname%

:: ...and show the output, e.g. "File and Printer Sharing"
echo:Output: %l_output%
echo:

:cleanup
set l_input=
set l_output=
type nul> %__self%:%__adsname%
set __self=
set __adsname=
pause

在脚本的末尾,使用了一行代码type nul>%__self%:%__adsname,它可以扩展为类似于type nul>“.\myscript.cmd”:test.dat的内容,用于清除我们刚才使用的备用数据流的内容。尽管这将备用数据流的大小设置为零,但它并没有抹掉它(请参见下面的注释)。
一些最终的注释:
  1. 大多数命令不能理解或处理备用数据流,当它们被提供为源文件时。这意味着以下命令不能使用:

    • type,例如:type myscript.cmd:data > myscript_data.txt
    • copy,例如:copy myscript.cmd:data myscript_data.txt
    • move,例如:move myscript.cmd:data myscript_data.txt
    • del/erase,例如:del myscript.cmd:data
    • ren/rename,例如:ren myscript.cmd:data myscript.cmd:data.txt
    • 等等

    实际上,只有文件重定向支持备用数据流,有一个例外(我所知道的): start 命令。

  2. 虽然我们可以很容易地清除/擦除备用数据流的内容,但是删除一个则要困难得多,因为复制文件或重命名文件不涉及任何流(好吧,只有 :$DATA),而编辑/更改文件的内容只会影响默认数据流。尽管如此,根据我们想要实现的目标,我们仍有一些选择:
    • 如果我们只有一个备用数据流 不介意一次性删除所有备用数据流,且文件的内容只是文本,则可以使用以下命令通过仅保留原始文件的默认数据流来创建一个新文件: type myscript.cmd > myscript_clean.cmd,然后可以删除原始文件。
    • 如果我们在卷上拥有管理员权限,并在 Windows Vista 或更高版本的操作系统下操作,则可以使用 MKLINK 创建指向一个备用数据流的符号链接,这将为我们提供可与通常的文件管理命令一起使用的标准文件名。
    • 或者,可以使用众多可用于操作备用数据流的工具之一,例如 StreamsLADSAlternateStreamView
  3. 从命令行列出包括备用数据流在内的常规文件的有用命令是 dir /a-d /r。然后可以使用任何程序访问文件的名称(备用)数据流,例如:notepad.exe myscript.cmd:data
我希望这有所帮助!

0
为了简要介绍如何确认其他答案,您可以立即使用另一个set命令检查管道另一侧的变量内容,例如:
ECHO HELLO | (set /P hi= && set hi)
将输出:
hi=HELLO
但在执行后变量会被销毁,所以进一步的'set hi'会显示:
环境变量 hi 未定义

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接