Windows 批处理文件以回显特定行号
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2701910/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Windows Batch file to echo a specific line number
提问by Lee
So for the second part of my current dilemma, I have a list of folders in c:\file_list.txt
. I need to be able to extract them (well, echo them with some mods) based on the line number because this batch script is being called by an iterative macro process. I'm passing the line number as a parameter.
因此,对于我当前困境的第二部分,我在c:\file_list.txt
. 我需要能够根据行号提取它们(好吧,用一些 mods 回应它们),因为这个批处理脚本是由迭代宏过程调用的。我将行号作为参数传递。
@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
which gives me an output of %a
. Doh! So, I've tried echoing !a!
(result: ECHO is off.
); I've tried echoing %a
(result: a)
这给了我一个输出%a
。哦!所以,我试过 echo !a!
(result :) ECHO is off.
; 我试过回声%a
(结果:a)
I figured the easy thing to do would be to modify the head.bat
code found here:
Windows batch command(s) to read first line from text file
except rather than echoing every line - I'd just echo the last line found. Not as simple as one might think. I've noticed that my counter is staying at zero for some reason; I'm wondering if the set /a counter+=1
is doing what I think it's doing.
我认为最简单的方法是修改head.bat
此处找到的代码:
Windows 批处理命令从文本文件中读取第一行,
而不是回显每一行 - 我只是回显找到的最后一行。并不像人们想象的那么简单。我注意到我的计数器由于某种原因保持在零;我想知道它是否set /a counter+=1
正在做我认为它正在做的事情。
回答by Seth McCauley
I know this is an old question, but here is some additional info for anyone with a similar issue...
我知道这是一个老问题,但这里有一些额外的信息给有类似问题的人......
Lee, your reasoning on why "%%a" isn't working outside the for loop is correct. The %a-z and %A-Z variables (%%a-z inside a batch file) are a construct of the for loop, and do not exist outside of it.
Lee,你关于为什么“%%a”不在 for 循环之外工作的推理是正确的。%az 和 %AZ 变量(批处理文件中的 %%az)是 for 循环的构造,不存在于循环之外。
I would like to recommend an alternative solution to this problem that matches the correct line numbers (no empty lines skipped) and does not require delayed expansion, counters, or a goto statement. Take a look at the following code:
我想为这个问题推荐一个替代解决方案,它匹配正确的行号(没有跳过空行)并且不需要延迟扩展、计数器或 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%
Here is what led me to the above changes. Let's say you had the following file contents:
这就是导致我进行上述更改的原因。假设您有以下文件内容:
Some text on line 1
Blah blah blah
More text
The first thing I did was change (c:\file_list.txt).to ('findstr /n .* "c:\file_list.txt"').
我做的第一件事是更改(c:\file_list.txt).to ('findstr /n .* "c:\file_list.txt"')。
- 'findstr /n .* "PATH\FILENAME"' reads the file and adds a line number ('/n') to every line ('.*' is a regular expression matching "0 or more" of any character). Since every line will now have a line number at the beginning (even the empty ones), no lines will be skipped by the for loop.
- ' findstr /n .* "PATH\FILENAME"' 读取文件并在每一行中添加一个行号 (' /n')(' .*' 是匹配“0 或多个”任何字符的正则表达式)。由于现在每一行的开头都有一个行号(即使是空行),for 循环不会跳过任何行。
Each line will now look like this inside the for loop:
现在每行在 for 循环中看起来像这样:
1:Some text on line 1
2:Blah blah blah
3:More text
Next, we use "tokens=1* delims=:"to break up the line number and the content.
接下来,我们使用“tokens=1* delims=:”来拆分行号和内容。
- 'tokens=1*' sets the first token (stored in %%a) to everything before the delimiter, and the second token (stored in %%b) to everything after it.
- 'delims=:' sets ":" as the delimiter character used to break up the string.
- ' tokens=1*' 将第一个标记(存储在%%a 中)设置为分隔符之前的所有内容,将第二个标记(存储在%%b 中)设置为它之后的所有内容。
- ' delims=:' 将“ :”设置为用于拆分字符串的分隔符。
Now as we loop through the file, %%awill return the current line number and %%bwill return the content of that line.
现在,当我们遍历文件时,%%a将返回当前行号,%%b将返回该行的内容。
All that's left is to compare the %1parameter to %%a(instead of a counter variable) and use %%bto store the current line content: if "%%a" =="%1" set line=%%b.
剩下的就是将%1参数与%%a(而不是计数器变量)进行比较并使用%%b来存储当前行内容:if "%%a" =="%1" set line=%%乙。
An added bonus is that 'enabledelayedexpansion' is no longer necessary, since the above code eliminates reading a counter variable in the middle of a for loop.
一个额外的好处是不再需要' enabledelayedexpansion',因为上面的代码消除了在 for 循环中间读取计数器变量。
Edit:changed 'echo %line%' to 'echo.%line%'. This will correctly display blank lines now, instead of "ECHO is off.". Changed 'type c:\file_list.txt ^| findstr /n .*' to 'findstr /n .* "c:\file_list.txt"', since the findstrcommand can already read files directly.
编辑:将“echo %line%”更改为“echo.%line%”。这将正确显示空行,而不是“ECHO is off.”。已更改 '类型 c:\file_list.txt ^| findstr /n .*' to 'findstr /n .* "c:\file_list.txt"',因为findstr命令已经可以直接读取文件了。
Jeb, I think I've solved all the special character issues. Give this a shot:
Jeb,我想我已经解决了所有的特殊字符问题。试一试:
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
)
)
回答by Lee
Bah, it ate my formatting.
呸,它吃了我的格式。
@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%
回答by Amr Ali
You can use a batch function like this:
您可以使用这样的批处理功能:
@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
输出
A line containing special shell characters: () <> %! ^| "&
包含特殊 shell 字符的行:() <> %! ^| "&
Invalid Line Numbers
无效的行号
The above function can also print empty lines or lines containing special characters, and this is enough for most cases. However, in order to handle invalid line numbers supplied to this function, please add error checking code to the function like this:
上述函数还可以打印空行或包含特殊字符的行,这对于大多数情况来说已经足够了。但是,为了处理提供给此函数的无效行号,请向该函数添加错误检查代码,如下所示:
: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
读取第 2 行
special characters -- printed
empty line -- printed
non-existing line -- an error message shown
特殊字符——打印
空行——打印
不存在的行 - 显示错误消息
回答by Andry
There is a trick to extract line strings w/o line numbers prefix (or with if you want to) and w/o need to use batch iterations ("for /F" plus counting) over all file lines.
有一个技巧可以提取没有行号前缀(或者如果你愿意的话)的行字符串,并且不需要在所有文件行上使用批处理迭代(“for /F”加计数)。
To do so you must use findstr.exe always with /N flag in pipeline and backfilter lines by second findstr.exe in pipeline through the /B /C:"<N1>:" /C:"<N2>:" ... /C:"<NX>:"
arguments.
为此,您必须始终在管道中使用带有 /N 标志的 findstr.exe,并通过/B /C:"<N1>:" /C:"<N2>:" ... /C:"<NX>:"
参数在管道中通过第二个 findstr.exe 反向过滤行。
Here the print_file_string.bat script i am using to parse text and binary files:
这里是我用来解析文本和二进制文件的 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
Advantages:
好处:
- Faster than "for /F" iteration over all lines in the file.
- Works with special characters like & | % " ` ' ? and even ! character (tested on a real dll resources).
- Handles resource strings from PE files like dll and exe (download the strings.exe from https://technet.microsoft.com/en-us/sysinternals/strings.aspxand put it near the script). For example, you can extract version string from strings builtin in exe/dll file.
- 比文件中所有行的“for /F”迭代更快。
- 适用于特殊字符,如 & | % " ` ' ? 甚至 ! 字符(在真实的 dll 资源上测试)。
- 处理来自 dll 和 exe 等 PE 文件的资源字符串(从https://technet.microsoft.com/en-us/sysinternals/strings.aspx下载 strings.exe并将其放在脚本附近)。例如,您可以从 exe/dll 文件中内置的字符串中提取版本字符串。
Known Issues:
已知的问题:
- If a filter by line(s) has used or -f1 flag is set, then : characters (repeated) will be trimmed from the beginning of a string.
- findstr has a limit to internal string buffer - 8191 characters (including line return characters terminator). All strings greater than this number in most cases will be truncated to zero length.
- 如果使用了按行过滤或设置了 -f1 标志,则 : 字符(重复)将从字符串的开头修剪。
- findstr 对内部字符串缓冲区有限制 - 8191 个字符(包括行返回字符终止符)。在大多数情况下,所有大于此数字的字符串都将被截断为零长度。
Examples:
例子:
call print_file_string.bat -n . example.txt 1:20:10:30 /R /C:".*"
Prints 1, 10, 20, 30 lines of the example.txt file sorted by line number and prints them w/ line number prefix:
call print_file_string.bat . example.txt 100 /R /C:".*"
Prints 100'th string of example.txt file and prints it w/o line number prefix.
call print_file_string.bat -pe c:\Application res.dll "" /B /C:"VERSION="
Prints all strings from the c:\Application\res.dll binary file, where strings beginning by the "VERSION=" string and prints them w/o line number prefix.
call print_file_string.bat -pe c:\Application res.dll 1:20:10:30 /R /C:".*"
Prints 1, 10, 20, 30 lines of string resources from the c:\Application\res.dll binary file, where strings contains any character and prints them w/o line number prefix.
调用 print_file_string.bat -n 。示例.txt 1:20:10:30 /R /C:".*"
打印按行号排序的 example.txt 文件的 1、10、20、30 行,并使用行号前缀打印它们:
调用 print_file_string.bat 。示例.txt 100 /R /C:".*"
打印 example.txt 文件的第 100 个字符串并打印不带行号前缀。
调用 print_file_string.bat -pe c:\Application res.dll "" /B /C:"VERSION="
打印 c:\Application\res.dll 二进制文件中的所有字符串,其中以“VERSION=”字符串开头的字符串并打印不带行号前缀的字符串。
调用 print_file_string.bat -pe c:\Application res.dll 1:20:10:30 /R /C:".*"
从 c:\Application\res.dll 二进制文件打印 1、10、20、30 行字符串资源,其中字符串包含任何字符,并在不带行号前缀的情况下打印它们。