Пакетный файл Windows для отображения определенного номера строки - PullRequest
6 голосов
/ 24 апреля 2010

Итак, для второй части моей текущей дилеммы у меня есть список папок в c:\file_list.txt. Мне нужно иметь возможность извлечь их (ну, повторить их с некоторыми модами) на основе номера строки, потому что этот пакетный скрипт вызывается итеративным процессом макроса. Я передаю номер строки в качестве параметра.

@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. Doh! Итак, я попытался повторить !a! (результат: ECHO is off.); Я попытался повторить %a (результат: a)

Я подумал, что проще всего было бы изменить код head.bat, найденный здесь: Пакетная команда Windows для чтения первой строки из текстового файла
кроме того, чтобы повторять каждую строку - я бы просто повторил последнюю найденную строку. Не так просто, как можно подумать. Я заметил, что мой счетчик почему-то стоит на нуле; Мне интересно, если set /a counter+=1 делает то, что, я думаю, делает.

Ответы [ 4 ]

12 голосов
/ 26 октября 2012

Я знаю, что это старый вопрос, но здесь есть некоторая дополнительная информация для тех, у кого похожая проблема ...

Ли, ваши рассуждения о том, почему "%% 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) .to ('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 =:" , чтобы разбить номер строки и содержимое.

  • ' tokens = 1 *' устанавливает первый токен (сохраненный в %% a ) для всего перед разделителем и второй токен (сохраненный в %%) b ) ко всему после него.
  • ' delims =: ' устанавливает ": " в качестве символа разделителя, используемого для разбиения строки.

Теперь, когда мы перебираем файл, %% a вернет номер текущей строки, а %% b вернет содержимое этой строки.

Осталось только сравнить параметр % 1 с %% a (вместо переменной счетчика) и использовать %% b для сохранения содержимое текущей строки: , если "%% a" == "% 1" установить строку = %% b .

Дополнительным бонусом является то, что enabledelayedexpansion 'больше не требуется, поскольку приведенный выше код исключает чтение переменной счетчика в середине цикла for.

Редактировать: изменено ' эхо% line% ' на ' эхо.% 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
  )
)
3 голосов
/ 24 апреля 2010

Бах, он съел мое форматирование.

@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 голосов
/ 25 ноября 2014

Вы можете использовать пакетную функцию следующим образом:

@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: () <> %! ^| "&

OUTPUT

Строка, содержащая специальные символы оболочки: () <>%! ^ | «&

Неверные номера строк

Вышеуказанная функция также может печатать пустые строки или строки, содержащие специальные символы, и этого достаточно для большинства случаев. Однако для обработки недопустимых номеров строк, предоставленных этой функции, добавьте код проверки ошибок в функцию, например:

: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

  • специальные символы - напечатано

  • пустая строка - напечатано

  • несуществующая строка - отображается сообщение об ошибке

1 голос
/ 26 января 2017

Существует хитрость для извлечения строк строк без префикса номеров строк (или с, если вы хотите), и без необходимости использовать пакетные итерации ("для / F" плюс подсчет) по всем строкам файла. *

Для этого вы должны всегда использовать findstr.exe с флагом / N в конвейере и строк обратного фильтра вторым findstr.exe в конвейере через аргументы /B /C:"<N1>:" /C:"<N2>:" ... /C:"<NX>:".

Вот скрипт 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" по всем строкам в файле.
  • Работает со специальными символами, такими как & | Символ% "` '? и даже! (проверено на реальных ресурсах DLL).
  • Обрабатывает строки ресурсов из PE-файлов, таких как dll и exe (скачайте strings.exe из https://technet.microsoft.com/en-us/sysinternals/strings.aspx и поместите его рядом со сценарием). Например, вы можете извлечь строку версии из строк, встроенных в файл exe / dll.

Известные проблемы:

  • Если использовался фильтр по строкам (строкам) или был установлен флаг -f1, то: символы (повторные) будут обрезаны с начала строки.
  • findstr имеет ограничение на внутренний строковый буфер - 8191 символ (включая символ конца строки). Все строки больше этого числа в большинстве случаев будут обрезаны до нулевой длины.

Примеры:

  1. вызовите print_file_string.bat -n. example.txt 1: 20: 10: 30 / R /C:".*"

    Печать 1, 10, 20, 30 строк файла example.txt, отсортированных по номеру строки и печатает их с префиксом номера строки:

  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. вызовите print_file_string.bat -pe c: \ Application res.dll 1: 20: 10: 30 / R /C:".*"

    Печатает 1, 10, 20, 30 строк строковых ресурсов из двоичный файл c: \ Application \ res.dll, где строки начинаются с Строка "VERSION =" и печатает их без префикса номера строки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...