DOS批处理FOR循环与FIND.exe会删除空行吗?

9
这个DOS批处理脚本正在去除空行并且不显示文件中的空行,即使我使用TYPE.exe命令将文件转换为ASCII格式,以确保FIND命令与文件兼容。有没有人可以告诉我如何使这个脚本包括空行?
@ECHO off
FOR /F "USEBACKQ tokens=*" %%A IN (`TYPE.exe "build.properties" ^| FIND.exe /V ""`) DO (
  ECHO --%%A--
)
pause
5个回答

19

这是FOR /F的设计行为 - 它永远不会返回空行。解决方法是使用 FIND 或 FINDSTR 在行前加上行号。如果您可以保证没有行以行号分隔符开头,那么只需设置适当的分隔符并保留 tokens 1 *,但仅使用第二个 token。

::preserve blank lines using FIND, assume no line starts with ]
::long lines are truncated
for /f "tokens=1* delims=]" %%A in ('type "file.txt" ^| find /n /v ""') do echo %%B

::preserve blank lines using FINDSTR, assume no line starts with :
::long lines > 8191 bytes are lost
for /f "tokens=1* delims=:" %%A in ('type "file.txt" ^| findstr /n "^"') do echo %%B

::FINDSTR variant that preserves long lines
type "file.txt" > "file.txt.tmp"
for /f "tokens=1* delims=:" %%A in ('findstr /n "^" "file.txt.tmp"') do echo %%B
del "file.txt.tmp"

我更喜欢使用FINDSTR - 它更加可靠。例如,FIND可以截断长行 - 只要直接从文件中读取,FINDSTR就不会这样做。但是,通过管道或重定向从stdin读取时,FINDSTR会放弃长行。

如果文件可能包含以分隔符开头的行,则需要保留带有行号前缀的整行,然后使用搜索和替换删除行前缀。当将%%A传输到环境变量中时,您可能希望关闭延迟扩展,否则任何!都会受损。但是,在循环内部进行搜索和替换时,您需要启用延迟扩展。

::preserve blank lines using FIND, even if a line may start with ]
::long lines are truncated
for /f "delims=" %%A in ('type "file.txt" ^| find /n /v ""') do (
  set "ln=%%A"
  setlocal enableDelayedExpansion
  set "ln=!ln:*]=!"
  echo(!ln!
  endlocal
)

::preserve blank lines using FINDSTR, even if a line may start with :
::long lines >8191 bytes are truncated
for /f "delims=*" %%A in ('type "file.txt" ^| findstr /n "^"') do (
  set "ln=%%A"
  setlocal enableDelayedExpansion
  set "ln=!ln:*:=!"
  echo(!ln!
  endlocal
)

::FINDSTR variant that preserves long lines
type "file.txt" >"file.txt.tmp"
for /f "delims=*" %%A in ('findstr /n "^" "file.txt.tmp"') do (
  set "ln=%%A"
  setlocal enableDelayedExpansion
  set "ln=!ln:*:=!"
  echo(!ln!
  endlocal
)
del "file.txt.tmp"
如果您不需要将文件转换为ASCII,则更有效的方法是放弃管道,让FIND或FINDSTR打开指定为参数或通过重定向指定的文件。还有另一种解决方法,完全绕过了FOR / F读取过程。它看起来很奇怪,但效率更高。使用延迟扩展没有限制,但不幸的是它有其他限制。
1)行必须以<CR><LF>结尾(如果进行TYPE文件转换,则不会出现问题)
2)每行长度必须<= 1021个字节(忽略<CR><LF>)
3)从每行中删除任何尾随控制字符。
4)它必须从文件中读取,不能使用管道。因此,在您的情况下,您需要使用临时文件进行ASCII转换。
setlocal enableDelayedExpansion
type "file.txt">"file.txt.tmp"
for /f %%N in ('find /c /v "" ^<"file.txt.tmp"') do set cnt=%%N
<"file.txt.tmp" (
  for /l %%N in (1 1 %cnt%) do(
    set "ln="
    set /p "ln="
    echo(!ln!
  )
)
del "file.txt.tmp"

1
没有问题,但我同意它看起来很糟糕。它比ECHO.更可靠。有些模糊的情况下,ECHO.可能会失败,而ECHO(从不失败。任何一种语法的重点都是允许输出空行。 - dbenham
1
你能解释一下这行代码 "echo(!ln!" 吗?这里不应该有一个多余的右括号吗?另外,上面那行代码缺少一个右感叹号。 - djangofan
@Didzis/Tom/X.L.Ant - 除非你知道为什么我看到的结果表明这个用户的说法“{在所有版本的Windows中}跳过空行是一个设计特性”是错误的,否则我不确定为什么我的建议编辑被拒绝了? - user66001
@JamesK - 我使用TYPE来确保内容是ASCII。请重新阅读OP的问题,然后阅读我的答案中的第四段(不考虑代码)。我在那里解决了您的担忧。 - dbenham
1
@JCarlos - 不,SET /P 技术有一个硬性的1021字节限制。 - dbenham
显示剩余4条评论

2

我写了一个非常简单的程序,可以替代 FINDFINDSTR 命令在这种情况下使用。我的程序叫做 PIPE.COM,它只会在空行中插入一个空格,以便所有行都可以直接通过 FOR 命令进行处理,无需进一步调整(只要不在意插入的空格)。以下是代码:

@ECHO off
if not exist pipe.com call :DefinePipe
FOR /F "USEBACKQ delims=" %%A IN (`pipe ^< "build.properties"`) DO (
  ECHO(--%%A--
)
pause
goto :EOF

:DefinePipe
setlocal DisableDelayedExpansion
set pipe=´)€ì!Í!ŠÐŠà€Ä!€ü.t2€ü+u!:æu8²A€ê!´#€ì!Í!².€ê!´#€ì!Í!²+€ê!´#€ì!Í!Šò€Æ!´,€ì!Í!"Àu°´LÍ!ëÒ
setlocal EnableDelayedExpansion
echo !pipe!>pipe.com
exit /B

编辑: 针对新评论的补充作为答案

在:DefinePipe子程序中的代码创建了一个名为pipe.com的88字节程序,基本上执行与此伪批处理代码等效的过程:

set "space= "
set line=
:nextChar
   rem Read just ONE character
   set /PC char=
   if %char% neq %NewLine% (
      rem Join new char to current line
      set line=%line%%char%
   ) else (
      rem End of line detected
      if defined line (
         rem Show current line
         echo %line%
         set line=
      ) else (
         rem Empty line: change it by one space
         echo %space%
      )
   )
goto nextChar

这样,输入文件中的空行将被替换为一行带有一个空格的行,因此FOR /F命令不再忽略它们。正如我在回答中所说的,“只要插入的空格不重要”,这种方法是有效的。

请注意,pipe.com程序在64位Windows版本中无法使用。

安东尼奥


感谢来自 DOS 技巧的帮助。 - djangofan
对不起。请问一下,我的PIPE.COM程序是否解决了你的问题? - Aacini
@Aacini - 你能详细描述一下这个解决方案是如何工作的吗? - user66001
@Aacini - 谢谢。虽然我不是很理解全部内容(我现在相信我意识到了%pipe%包含可执行程序的编译代码),但如果您不介意,能否解释一下:set /PC char=中的C是什么意思,以及这行代码如何只获取一个字符;%NewLine%的内容在哪里定义;echo命令为什么不将字符/空格和尾随换行符连接到%line%上;最后,在每个第二次迭代中,所有单个字符都加入到哪个变量中?此外,是否无法创建64位版本? - user66001
@user66001:正如我在答案中所说,这是伪批处理代码,不是真正的代码。这只是一种解释pipe.com的方式,而不是:“读取下一个字符,如果不是NL,则将其附加到当前行,否则显示当前行,但如果为空,则插入一个空格”。由于您提到的细节,批处理文件无法精确地实现此过程。原始的pipe.asm程序是用汇编语言编写的。 - Aacini
@Aacini - 好的;抱歉;谢谢。 - user66001

1

输出包括空行的文本行

这里是我为自己开发的一个方法。

将代码保存为批处理文件,比如SHOWALL.BAT,并将源文件作为命令行参数传递。

输出可以重定向或管道传输。

@echo off

for /f "tokens=1,* delims=]" %%a in ('find /n /v "" ^< "%~1"') do echo.%%ba

exit /b

示例:

showall source.txt

showall source.txt >destination.txt

showall source.txt | FIND "string"

一个奇怪的地方是包含 '^<'(重定向),而不是仅执行以下操作:

for /f "tokens=1,* delims=]" %%a in ('find /n /v "" "%~1"') do echo.%%ba

如果省略重定向,则会输出一个前导空行。


1
不错,但它会从行首删除 ] 字符,如果存在像 \..\..\Windows\system32\calc.exe 这样的行,则 echo. 将失败。 - jeb
1
@jeb ,实际上,至少在Windows 7上,它不会去掉所有的]字符。只有第一个或遇到*为止。 - James K

0
感谢dbenham,这个方法可行,尽管它与他的建议略有不同:
::preserve blank lines using FIND, no limitations
for /f "USEBACKQ delims=" %%A in (`type "file.properties" ^| find /V /N ""`) do (
  set "ln=%%A"
  setlocal enableDelayedExpansion
  set "ln=!ln:*]=!"
  echo(!ln!
  endlocal
)

1
什么没有起作用?你看到了什么?我看到一个问题。如果该行为空白,ECHO !ln!将打印ECHO is off!这就是为什么我使用了ECHO(!ln!的原因。 - dbenham
好的,我已经更新了。现在它可以工作了。非常感谢!现在我只需要将其集成到我的项目中。 - djangofan

0
正如在上面问题的this答案中提到的,使用for /f (至少在Windows XP上)默认似乎不会跳过行。(社区 - 请通过测试以下批处理命令来更新此答案以适用于您的Windows版本和服务包)。


   编辑: 根据Jeb在下面的评论中所述,在至少Windows XP中,似乎ping命令会导致for /f生成<CR>而不是空行(如果有人知道为什么,请更新此答案或评论)。

作为一种解决方法,似乎第二个默认的分隔符标记(例如<space> / %%b
    返回blank,这对于通过“父级”方式消除空行的情况非常有效
    在for /f开头的第二个标记上设置“if”条件,就像这样:

   for /f "tokens=1,2*" %%a in ('ping -n 1 google.com') do (
      if not "x%%b"=="x" (
         {do things with non-blank lines}
      )
   )

使用以下代码:
@echo off
systeminfo | findstr /b /c:"OS Name" /c:"OS Version" 
echo.&echo.
ping -n 1 google.com
echo.&echo.
for /f %%a in ('ping -n 1 google.com') do ( echo "%%a" )
echo.&echo.&echo --------------&echo.&echo.
find /?
echo.&echo.
for /f %%a in ('find /?') do ( echo "%%a" )
echo.&echo.
pause

以下是我在Windows XP、Windows 7和Windows 2008上看到的,它们是我能够轻松访问的三个版本和服务包:

Windows XP Pro SP3

Windows 7 Enterprise SP1

Windows Server 2008 R2 Enterprise SP1


就像dbenham所说: 对于/f来说,总是剥离空白行。在你的情况下,似乎XP不会剥离空白行,但实际上你会在“Pinging”和“Reply”之间得到一行只有一个单引号的内容。但这并不意味着FOR / F获取了一个空行。该行包含一个<CR>字符,因此光标跳回到行开头,并且第一个引号被最后一个引号覆盖,仅此而已。在XP下,ping命令只是有点问题。 - jeb
感谢@jeb。我来到这个页面是因为我当时认为有额外的空格问题,但现在意识到(由您纠正)它们只是在XP上输出的回车符。您能否也建议如何在“if not“x%%a” =“”语句中测试<cr>? - user66001
要删除 CR,您可以调用类似于 :trimmer <LF> set "trim=%1" 的函数,这个方法有效,因为在百分号扩展阶段后所有的 CR 都会被删除。但是如果有更多问题,您应该开一个新的问题。 - jeb
你没有正确使用'~'符号。 它会为您删除字符串外的引号。 理解这一点可能是问题的根源。 您应该使用'%%〜a'代替'%%a'。 - djangofan
@djangofan 一开始没明白,我根本没有使用 ~,更不用说正确使用了。但是,在像这个截图中所示的位置插入它之后,我肯定是做错了。问题是,正如jeb在上面正确地指出的那样,for /f并没有完全忽略空行,我猜它是在Win XP上删除了lf,但没有删除cr。我只是将其放在引号中以查看输出内容。 - user66001

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