Windows批处理文件以回显特定的行号

时间:2010-04-23 20:51:26

标签: windows batch-file

因此,对于当前困境的第二部分,我在c:\file_list.txt中有一个文件夹列表。我需要能够根据行号提取它们(好吧,用一些mod回显它们),因为这个批处理脚本是由迭代宏进程调用的。我将行号作为参数传递。

@echo off
setlocal enabledelayedexpansion
set /a counter=0
set /a %%a = ""
for /f "usebackq delims=" %%a in (c:\file_list.txt) do (
   if "!counter!"=="%1" goto :printme & set /a counter+=1
)
:printme
echo %%a

给出了%a的输出。卫生署!所以,我尝试回应!a!(结果:ECHO is off.);我试过回复%a(结果:a)

我认为最简单的方法是修改此处的head.bat代码: Windows batch command(s) to read first line from text file
除了回应每一行 - 我只是回应找到的最后一行。并不像人们想象的那么简单。我注意到我的柜台由于某种原因停留在零;我想知道set /a counter+=1是否正在做我认为它正在做的事情。

4 个答案:

答案 0 :(得分:12)

我知道这是一个老问题,但对于有类似问题的人来说,这里有一些额外的信息......

Lee,你为什么“%% a”在for循环之外工作的原因是正确的。 %a-z和%A-Z变量(批处理文件中的%% a-z)是for循环的构造,并且不存在于其外部。

我想推荐一个解决此问题的替代解决方案,该解决方案匹配正确的行号(没有跳过空行),并且不需要延迟扩展,计数器或goto语句。看看下面的代码:

@echo off
for /f "tokens=1* delims=:" %%a in ('findstr /n .* "c:\file_list.txt"') do if "%%a"=="%1" set line=%%b
echo.%line%

这是导致我进行上述修改的原因。假设您有以下文件内容:

Some text on line 1
Blah blah blah
More text

我做的第一件事就是改变(c:\ file_list.txt)。到('findstr / n。*“c:\ file_list.txt”')

  • ' findstr / n。*“PATH \ FILENAME”'读取文件并在每行添加行号(' / n ')('。* '是与任何字符的“0或更多”匹配的正则表达式。由于每一行现在都有一个行号(即使是空行),for循环也不会跳过任何行。

在for循环中,每一行现在都是这样的:

1:Some text on line 1
2:Blah blah blah
3:More text

接下来,我们使用“tokens = 1 * delims =:”来细分行号和内容。

  • '令牌= 1 * '将第一个令牌(存储在 %% a 中)设置为分隔符之前的所有内容,以及第二个令牌(存储在中) %% b )之后的一切。
  • ' delims =:'将“”设置为用于分解字符串的分隔符。

现在,当我们循环浏览文件时, %% a 将返回当前行号,而 %% b 将返回该行的内容。

剩下的就是将%1 参数与 %% a (而不是计数器变量)进行比较并使用 %% b 存储当前行内容:如果“%% a”==“%1”设置行= %% b

额外的好处是不再需要' enabledelayedexpansion ',因为上面的代码消除了在for循环中间读取计数器变量。

修改 将' echo%line%'更改为' echo。%line%'。这将正确显示空行,而不是“ECHO关闭”。更改了'键入c:\ file_list.txt ^ | findstr / n。* '到' findstr / n。*“c:\ file_list.txt”',因为 findstr 命令已经可以直接读取文件

杰布,我想我已经解决了所有特殊问题。试一试:

for /f "tokens=*" %%a in ('findstr /n .* "c:\file_list.txt"') do (
  set "FullLine=%%a"
  for /f "tokens=1* delims=:" %%b in ("%%a") do (
    setlocal enabledelayedexpansion
    set "LineData=!FullLine:*:=!"
    if "%%b" equ "%1" echo(!LineData!
    endlocal
  )
)

答案 1 :(得分:2)

呸,它吃了我的格式。

@echo off

setlocal enabledelayedexpansion

set /a counter=0
set %%a = ""

for /f "usebackq delims=" %%a in (c:\file_list.txt) do (if "!counter!"=="%1" goto :printme & set /a counter+=1)

:printme

echo %%a%

答案 2 :(得分:1)

您可以使用这样的批处理功能:

@ECHO OFF
CALL :ReadNthLine "%~nx0" 10
PAUSE >NUL
GOTO :EOF

:ReadNthLine File nLine
FOR /F "tokens=1* delims=]" %%A IN ('^<"%~1" FIND /N /V "" ^| FINDSTR /B /C:"[%2]"') DO ECHO.%%B
GOTO :EOF

A line containing special shell characters: () <> %! ^| "&

<强>输出

包含特殊shell字符的行:()&lt;&gt; %! ^ | “&安培;

无效的行号

上述功能还可以打印空行或包含特殊字符的行,这对大多数情况来说已经足够了。但是,为了处理提供给此函数的无效行号,请将错误检查代码添加到函数中,如下所示:

:ReadNthLine File nLine
FOR /F %%A IN ('^<"%~1" FIND /C /V ""') DO IF %2 GTR %%A (ECHO Error: No such line %2. 1>&2 & EXIT /b 1)
FOR /F "tokens=1* delims=]" %%A IN ('^<"%~1" FIND /N /V "" ^| FINDSTR /B /C:"[%2]"') DO ECHO.%%B
EXIT /b

ReadNthLine2

  • 特殊字符 - 已打印

  • 空行 - 打印

  • 不存在的行 - 显示错误消息

答案 3 :(得分:0)

有一个技巧可以提取没有行号前缀的行字符串(或者如果你想要的话),并且不需要在所有文件行上使用批量迭代(“for / F”加上计数)。

要这样做,必须使用findstr.exe始终在管道和反向过滤行中使用/ N标志,通过/B /C:"<N1>:" /C:"<N2>:" ... /C:"<NX>:"参数在管道中使用第二个findstr.exe。

这里是我用来解析文本和二进制文件的print_file_string.bat脚本:

@echo off

rem Description:
rem   Script for string lines extraction from a text/binary file by findstr
rem   utility pattern and/or line number.

rem Command arguments:
rem %1 - Optional flags:
rem      -n - prints line number prefix "<N>:" for each found string from file.
rem           By default, the line number prefix does not print.
rem      -f1 - filter by line numbers for strings after %4..%N filter pattern.
rem           By default, filters by line numbers from the file.
rem      -pe - treats input file as a Portable Executable file
rem           (the strings.exe must exist).
rem           By default, the file treated as a text file.
rem %1 - Path to a directory with a file to extract.
rem %2 - Relative path to a text/binary file with strings.
rem %3 - Set of line numbers separated by : character to print strings of.
rem      These line numbers by default are line numbers of strings from the
rem      file, not from filtered output. If you want to point line numbers
rem      after %4..%N filter pattern, then you must use -f1 flag.
rem      If empty, then treated as "all strings".
rem %4..%N - Arguments for findstr command line in first filter.
rem      If empty, then treated as /R /C:".*", which means "any string".

rem CAUTION:
rem   DO NOT use /N flag in %4..%N arguments, instead use script -n flag to
rem   print strings w/ line number prefix.

rem Examples:
rem 1. call print_file_string.bat -n . example.txt 1:20:10:30 /R /C:".*"
rem Prints 1, 10, 20, 30 lines of the example.txt file sorted by line number
rem and prints them w/ line number prefix:
rem
rem 2. call print_file_string.bat . example.txt 100 /R /C:".*"
rem Prints 100'th string of example.txt file and prints it w/o line number
rem prefix.
rem
rem 3. call print_file_string.bat -pe c:\Application res.dll "" /B /C:"VERSION="
rem Prints all strings from the c:\Application\res.dll binary file, where
rem strings beginning by the "VERSION=" string and prints them w/o line number
rem prefix.
rem
rem 4. call print_file_string.bat -pe c:\Application res.dll 1:20:10:30 /R /C:".*"
rem Prints 1, 10, 20, 30 lines of string resources from the
rem c:\Application\res.dll binary file, where strings beginning by the
rem "VERSION=" string and prints them w/o line number prefix.

setlocal EnableDelayedExpansion

set "?~dp0=%~dp0"
set "?~nx0=%~nx0"

rem script flags
set FLAG_PRINT_LINE_NUMBER_PREFIX=0
set FLAG_F1_LINE_NUMBER_FILTER=0
set FLAG_FILE_FORMAT_PE=0

rem flags
set "FLAGS="

:FLAGS_LOOP

rem flags always at first
set "FLAG=%~1"

if not "%FLAG%" == "" ^
if not "%FLAG:~0,1%" == "-" set "FLAG="

if not "%FLAG%" == "" (
  if "%FLAG%" == "-n" set FLAG_PRINT_LINE_NUMBER_PREFIX=1
  if "%FLAG%" == "-f1" set FLAG_F1_LINE_NUMBER_FILTER=1
  if "%FLAG%" == "-pe" set FLAG_FILE_FORMAT_PE=1
  shift

  rem read until no flags
  goto FLAGS_LOOP
)

set "DIR_PATH=%~dpf1"
set "FILE_PATH=%~2"

set "FILE_PATH_PREFIX="
if not "%DIR_PATH%" == "" set "FILE_PATH_PREFIX=%DIR_PATH%\"

if not "%FILE_PATH_PREFIX%" == "" ^
if not exist "%FILE_PATH_PREFIX%" (
  echo.%?~nx0%: error: Directory path does not exist: "%FILE_PATH_PREFIX%"
  exit /b 1
) >&2

if "%FILE_PATH%" == "" (
  echo.%?~nx0%: error: File path does not set.
  exit /b 2
) >&2

if not exist "%FILE_PATH_PREFIX%%FILE_PATH%" (
  echo.%?~nx0%: error: File path does not exist: "%FILE_PATH_PREFIX%%FILE_PATH%"
  exit /b 3
) >&2

set "LINE_NUMBERS=%~3"

set "FINDSTR_LINES_FILTER_CMD_LINE="
if "%LINE_NUMBERS%" == "" goto FINDSTR_LINES_FILTER_END

set LINE_NUMBER_INDEX=1
:FINDSTR_LINES_FILTER_LOOP
set "LINE_NUMBER="
for /F "tokens=%LINE_NUMBER_INDEX% delims=:" %%i in ("%LINE_NUMBERS%") do set "LINE_NUMBER=%%i"
if "%LINE_NUMBER%" == "" goto FINDSTR_LINES_FILTER_END

set FINDSTR_LINES_FILTER_CMD_LINE=!FINDSTR_LINES_FILTER_CMD_LINE! /C:"!LINE_NUMBER!:"
set /A LINE_NUMBER_INDEX+=1
goto FINDSTR_LINES_FILTER_LOOP

:FINDSTR_LINES_FILTER_END

shift
shift
shift

set "FINDSTR_FIRST_FILTER_CMD_LINE="

:FINDSTR_FIRST_FILTER_LOOP
set ARG=%1

if not "!ARG!" == "" (
  set FINDSTR_FIRST_FILTER_CMD_LINE=!FINDSTR_FIRST_FILTER_CMD_LINE! !ARG!
  shift
  goto FINDSTR_FIRST_FILTER_LOOP
)

if "!FINDSTR_FIRST_FILTER_CMD_LINE!" == "" set FINDSTR_FIRST_FILTER_CMD_LINE=/R /C:".*"

set OUTPUT_HAS_NUMBER_PREFIX=0

rem in case if /N at the end
set "FINDSTR_FIRST_FILTER_CMD_LINE=!FINDSTR_FIRST_FILTER_CMD_LINE! "

rem 1. add /N parameter to first filter if must print line prefixes and -f1 flag is not set.
rem 2. flags prefixed output if must print line prefixes.
if %FLAG_PRINT_LINE_NUMBER_PREFIX% NEQ 0 (
  if %FLAG_F1_LINE_NUMBER_FILTER% EQU 0 (
    if "!FINDSTR_FIRST_FILTER_CMD_LINE:/N =!" == "!FINDSTR_FIRST_FILTER_CMD_LINE!" (
      set "FINDSTR_FIRST_FILTER_CMD_LINE=/N !FINDSTR_FIRST_FILTER_CMD_LINE!"
    )
  )
  set OUTPUT_HAS_NUMBER_PREFIX=1
)

rem 1. add /N parameter to first filter and flags prefixed output if lines filter is not empty and -f1 flag is not set.
rem 2. add /B parameter to lines filter if lines filter is not empty
if not "!FINDSTR_LINES_FILTER_CMD_LINE!" == "" (
  if %FLAG_F1_LINE_NUMBER_FILTER% EQU 0 (
    if "!FINDSTR_FIRST_FILTER_CMD_LINE:/N =!" == "!FINDSTR_FIRST_FILTER_CMD_LINE!" (
      set "FINDSTR_FIRST_FILTER_CMD_LINE=/N !FINDSTR_FIRST_FILTER_CMD_LINE!"
      set OUTPUT_HAS_NUMBER_PREFIX=1
    )
  )
  if "!FINDSTR_LINES_FILTER_CMD_LINE:/B =!" == "!FINDSTR_LINES_FILTER_CMD_LINE!" (
    set "FINDSTR_LINES_FILTER_CMD_LINE=/B !FINDSTR_LINES_FILTER_CMD_LINE!"
  )
)

rem 1. remove /N parameter from first filter if -f1 flag is set.
rem 2. flags prefixed output if -f1 flag is set.
if %FLAG_F1_LINE_NUMBER_FILTER% NEQ 0 (
  if not "!FINDSTR_FIRST_FILTER_CMD_LINE:/N =!" == "!FINDSTR_FIRST_FILTER_CMD_LINE!" (
    set "FINDSTR_FIRST_FILTER_CMD_LINE=!FINDSTR_FIRST_FILTER_CMD_LINE:/N =!"
  )
  set OUTPUT_HAS_NUMBER_PREFIX=1
)

if "%TOOLS_PATH%" == "" set "TOOLS_PATH=%?~dp0%"
rem set "TOOLS_PATH=%TOOLS_PATH:\=/%"
if "%TOOLS_PATH:~-1%" == "\" set "TOOLS_PATH=%TOOLS_PATH:~0,-1%"

if %FLAG_FILE_FORMAT_PE% EQU 0 (
  set CMD_LINE=type "%FILE_PATH_PREFIX%%FILE_PATH%" ^| findstr !FINDSTR_FIRST_FILTER_CMD_LINE!
) else (
  rem add EULA acception into registry to avoid EULA acception GUI dialog
  reg add HKCU\Software\Sysinternals\Strings /v EulaAccepted /t REG_DWORD /d 0x00000001 /f >nul 2>nul

  rem @ for bug case workaround
  set CMD_LINE=@"%TOOLS_PATH%\strings.exe" -q "%FILE_PATH_PREFIX%%FILE_PATH%" ^| findstr !FINDSTR_FIRST_FILTER_CMD_LINE!
)

if %FLAG_F1_LINE_NUMBER_FILTER% NEQ 0 set CMD_LINE=!CMD_LINE! ^| findstr /N /R /C:".*"
if not "!FINDSTR_LINES_FILTER_CMD_LINE!" == "" set CMD_LINE=!CMD_LINE! ^| findstr !FINDSTR_LINES_FILTER_CMD_LINE!

rem echo !CMD_LINE! >&2
(
  endlocal
  rem to avoid ! character truncation
  setlocal DisableDelayedExpansion
  if %OUTPUT_HAS_NUMBER_PREFIX% NEQ 0 (
    if %FLAG_PRINT_LINE_NUMBER_PREFIX% NEQ 0 (
      %CMD_LINE% 2>nul
    ) else ( 
      for /F "usebackq eol= tokens=1,* delims=:" %%i in (`^(%CMD_LINE: | findstr = ^| findstr %^) 2^>nul`) do echo.%%j
    )
  ) else (
    %CMD_LINE% 2>nul
  )
)

exit /b 0

<强>优点:

  • 比文件中所有行的“for / F”迭代更快。
  • 使用&amp;等特殊字符| %“`'?甚至!字符(在真正的dll资源上测试)。
  • 处理来自PE文件(如dll和exe)的资源字符串(从https://technet.microsoft.com/en-us/sysinternals/strings.aspx下载strings.exe并将其放在脚本附近)。 例如,您可以从exe / dll文件中内置的字符串中提取版本字符串。

已知问题:

  • 如果设置了使用过滤器或-f1标志的过滤器,则:将从字符串的开头修剪字符(重复)。
  • findstr对内部字符串缓冲区有限制 - 8191个字符(包括行返回字符终结符)。在大多数情况下,所有大于此数字的字符串都将被截断为零长度。

<强>示例:

  1. 调用print_file_string.bat -n。 example.txt 1:20:10:3​​0 / R / C:“。*”

    打印按行号排序的example.txt文件的1,10,20,30行 并使用行号前缀打印它们:

  2. 调用print_file_string.bat。 example.txt 100 / R / C:“。*”

    打印第100个example.txt文件的字符串,并打印没有行号 前缀。

  3. 调用print_file_string.bat -pe c:\ Application res.dll“”/ B / C:“VERSION =”

    从c:\ Application \ res.dll二进制文件中打印所有字符串,其中 字符串以“VERSION =”字符串开头,并打印出没有行号的字符串 前缀。

  4. call print_file_string.bat -pe c:\ Application res.dll 1:20:10:3​​0 / R / C:“。*”

    从中打印1,10,20,30行字符串资源 c:\ Application \ res.dll二进制文件,其中字符串以 “VERSION =”字符串并打印它们没有行号前缀。