2010-04-23 12 views
6

Por lo tanto, para la segunda parte de mi dilema actual, tengo una lista de carpetas en c:\file_list.txt. Necesito poder extraerlos (bueno, repetirlos con algunos mods) basados ​​en el número de línea porque este script por lotes está siendo llamado por un macro proceso iterativo. Estoy pasando el número de línea como parámetro.Windows Archivo por lotes para hacer eco de un número de línea específico

@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 

que me da una salida de %a. Doh! Por lo tanto, he intentado hacer eco de !a! (resultado: ECHO is off.); He intentado eco %a (resultado: a)

pensé que la cosa fácil de hacer sería modificar el código head.bat encontrar aquí: Windows batch command(s) to read first line from text file
excepto en lugar de eco de cada línea - que acababa de hacerse eco de la última línea encontrada No es tan simple como uno podría pensar. Me di cuenta de que mi contador se queda en cero por alguna razón; Me pregunto si el set /a counter+=1 está haciendo lo que creo que está haciendo.

+1

Tenga en cuenta que 'for/f' omitirá las líneas vacías. Esto puede y probablemente hará que tu conteo disminuya en algunos casos. – Joey

Respuesta

2

Bah, se comió mi formateo.

@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% 
+0

Ah, lo tengo. para los iniciadores establecer/a contador = 1 luego establecer una variable de entorno igual a %% a y repetir eso - porque supongo que el %% a no existe fuera de la instrucción for. para/f "usebackq delims =" %% a en (c: \ file_list.txt) do (si "! Contador!" == "% 1" establece línea = %% a & goto: printme conjunto/a contador + = 1) : printme echo% línea% – Lee

12

Sé que esto es una vieja pregunta, pero aquí hay alguna información adicional para cualquier persona con un problema similar ...

Lee, su razonamiento sobre por qué "%% a" no está funcionando fuera el bucle for es correcto. Las variables% a-z y% A-Z (%% a-z dentro de un archivo por lotes) son una construcción del ciclo for, y no existen fuera de él.

Me gustaría recomendar una solución alternativa a este problema que coincida con los números de línea correctos (no se saltan líneas vacías) y no requiere expansión retrasada, contadores o una instrucción goto. Echar un vistazo al siguiente código:

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

Esto es lo que me llevó a los cambios anteriores. Vamos a decir que tenía los siguientes contenidos de archivo:

Some text on line 1 
Blah blah blah 
More text 

La primera cosa que hice fue el cambio . (C: \ file_list.txt) .para('findstr/n * "c: \ file_list.txt "').

  • 'findstr/n. * "Path \ filename"' lee el archivo y añade un número de línea ('/n ') para cada línea (' . *' es una expresión regular que coincida con "0 o más" de cualquier personaje). Como cada línea ahora tendrá un número de línea al principio (incluso las vacías), el bucle for no omitirá ninguna línea.

Cada línea ahora se verá así dentro del bucle:

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

A continuación, utilizamos "tokens = 1 * delims =:" para romper el número de línea y el contenido.

  • 'tokens = 1 *' establece el primer contador (almacenado en %% a) a todo antes de que el delimitador, y el segundo contador (almacenado en %% b) a todo después de que se .
  • 'delims =:' establece ":" como el carácter delimitador utilizado para separar la cadena.

Ahora, al recorrer el archivo, %% a devolverá el número de línea actual y %% b devolverá el contenido de esa línea.

Todo lo que queda es comparar la % 1 parámetro para %% a (en lugar de una variable de contador) y utilizar %% b para almacenar el contenido línea actual: si "%% a" == "% 1" set line = %% b.

Una ventaja adicional es que 'enabledelayedexpansion' ya no es necesario, ya que el código anterior elimina la lectura de una variable de contador en el medio de un ciclo for.

Editar: cambiado 'echo% línea%' a 'echo% línea%.'. Esto mostrará correctamente las líneas en blanco ahora, en lugar de "ECHO está apagado". Se ha cambiado 'tipo c: \ file_list.txt^| findstr/n. * 'a' findstr/n. * "c: \ file_list.txt" ', ya que el comando findstr ya puede leer archivos directamente.

Jeb, creo que he resuelto todos los problemas con los personajes especiales. Dar a este un tiro:

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 
) 
) 
+0

Lo probé, pero falla con mi archivo. Primero lo intenté con ':::: Colon' (todos los dos puntos se eliminan) y luego lo probé con' \ .. \ .. \ .. \ windows \ system32 \ calc.exe', pero no lo hace imprimir esto :-) – jeb

+0

Ahhh ... no estoy seguro de por qué el calc.exe no funcionó (parece que funciona para mí), pero definitivamente me consiguió en los dos puntos, todo se derrumbó. Tendré que replantearme ese método. Yo usaría 'find/n/v" "' en lugar de 'findstr', pero luego terminaría con el mismo problema:']]]] Bracket' y '[[[Bracket' aparecerían como 'Soporte '. No parece haber una manera muy simple de evitar que esos delimitadores se colapsen tampoco. –

+0

Estoy empezando a pensar que su método de usar "más + Número de línea" puede ser la única forma confiable de hacerlo ([link] (http://stackoverflow.com/questions/6409869/echo-the-nth-line -from-a-text-file-where-n-is-a-command-line-argument/13080820 # 13080820)). Es posible que tenga que editar algunas publicaciones ahora:/ –

1

Se puede utilizar una función de la hornada así:

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

SALIDA

Una línea que contiene caracteres especiales de shell:() <>%!^| "&

números no válidos de línea

La función anterior también pueden imprimir líneas en blanco o líneas que contienen caracteres especiales, y esto es suficiente para la mayoría de los casos.Sin embargo, con el fin de manejar los números de línea válido para el suministro a esta función, por favor agregar el código de comprobación de errores a la función como esta:

: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

  • caracteres especiales - impresa

  • línea vacía - impreso

  • línea no existente - se muestra un mensaje de error

0

Hay un truco para extraer cadenas de líneas sin prefijo de números de línea (o con si lo desea) y sin necesidad de utilizar iteraciones de lotes ("para/F" más recuento) en todas las líneas de archivo .

Para hacerlo, debe usar findstr.exe siempre con/N flag en tuberías y líneas de backfilter por second findstr.exe en canalización a través de los argumentos /B /C:"<N1>:" /C:"<N2>:" ... /C:"<NX>:".

Aquí el guión print_file_string.bat estoy usando para analizar el texto y archivos binarios:

@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 [email protected]"%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 

Ventajas:

  • Más rápido que la iteración "for/F" sobre todas las líneas en el archivo.
  • Funciona con caracteres especiales como & | % "` '' y even! character (probado en recursos dll reales)
  • Maneja cadenas de recursos de archivos PE como dll y exe (descargue strings.exe desde https://technet.microsoft.com/en-us/sysinternals/strings.aspx y póngalo cerca de la secuencia de comandos). Por ejemplo ., se puede extraer la cadena de versión de cadenas incorporadas en el archivo exe/dll

Problemas conocidos:

  • Si un filtro de línea (s) ha utilizado o si se establece la bandera de f1, a continuación, : los caracteres (repetidos) se recortarán desde el comienzo de una cadena.
  • findstr tiene un límite en el búfer de cadena interno: 8191 caracteres (incluido el terminador de caracteres de devolución de línea). Todas las cadenas mayores que este número en la mayoría de los casos serán truncadas a longitud cero.

Ejemplos:

  1. llamada print_file_string.bat -n. example.txt 1: 20: 10: 30/R /C:".* "

    Imprime 1, 10, 20, 30 líneas del archivo example.txt ordenadas por el número de línea y las imprime con el número de línea prefijo:

  2. call print_file_string.bat. example.txt 100/R /C:".* "

    Imprime la fila 100'del archivo example.txt y lo imprime sin el prefijo número de línea .

  3. llamada print_file_string.bat -pe c: \ res.dll aplicación ""/B/C: "version ="

    Imprime todas las cadenas del directorio C: \ archivo binario de programa \ res.dll, donde cadenas que comienzan por la cadena "VERSIÓN =" y las imprime sin el prefijo número de línea.

  4. llamada print_file_string.bat -PE c: \ res.dll Aplicación 1: 20: 10: 30/R /C:".*"

    Prints 1, 10, 20, 30 líneas de recursos de cadena desde el archivo binario c: \ Application \ res.dll , donde las cadenas comienzan por la cadena "VERSIÓN = = " y las imprime sin el prefijo de número de línea.

Cuestiones relacionadas