Пакетный скрипт Windows для разбора файла CSV и вывода текстового файла - PullRequest
6 голосов
/ 15 декабря 2011

Я видел ответ на другой странице ( Помощь в написании пакетного скрипта для анализа CSV-файла и вывода текстового файла ) - блестящий код BTW:

@ECHO OFF
IF "%~1"=="" GOTO :EOF
SET "filename=%~1"
SET fcount=0
SET linenum=0
FOR /F "usebackq tokens=1-10 delims=," %%a IN ("%filename%") DO ^
CALL :process "%%a" "%%b" "%%c" "%%d" "%%e" "%%f" "%%g" "%%h" "%%i" "%%j"
GOTO :EOF

:trim
SET "tmp=%~1"
:trimlead
IF NOT "%tmp:~0,1%"==" " GOTO :EOF
SET "tmp=%tmp:~1%"
GOTO trimlead

:process
SET /A linenum+=1
IF "%linenum%"=="1" GOTO picknames

SET ind=0
:display
IF "%fcount%"=="%ind%" (ECHO.&GOTO :EOF)
SET /A ind+=1
CALL :trim %1
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !f%ind%!!tmp!
ENDLOCAL
SHIFT
GOTO display

:picknames
IF %1=="" GOTO :EOF
CALL :trim %1
SET /a fcount+=1
SET "f%fcount%=%tmp%"
SHIFT
GOTO picknames

Это прекрасно работает для примера CSV-файла, который я сделал в формате:

Header,Name,Place
one,two,three
four,five,six

Однако фактический файл, который я хочу изменить, состоит из 64 полей - поэтому я изменил tokens=1-10 на tokens=1-64 и увеличил %%a и т. Д. До 64 переменных (например, последняя называется %%BL) , Однако теперь, когда я запускаю пакет на моем «большом» CSV-файле (с 64 токенами), ничего не происходит. Нет ошибок (хорошо), но нет вывода! (плохой). Если кто-то может помочь, это было бы здорово ... я так близок к тому, чтобы заставить работать все приложение, если бы я мог просто прибить этот последний кусочек! Или, если у кого-то есть пример кода, который будет делать подобное для неопределенного числа токенов ... В конечном итоге я хочу создать строку, которая будет выглядеть примерно так:

field7,field12,field15,field18

Ответы [ 4 ]

16 голосов
/ 15 декабря 2011

Важное обновление - Я не думаю, что пакет Windows является хорошим вариантом для ваших нужд, потому что один FOR / F не может анализировать более 31 токена. См. Пояснение в нижней части приложения ниже.

Однако с пакетом можно делать все, что вы хотите. Этот уродливый код даст вам доступ ко всем 64 токенам.

for /f "usebackq tokens=1-29* delims=," %%A in ("%filename%") do (
  for /f "tokens=1-26* delims=," %%a in ("%%^") do (
    for /f "tokens=1-9 delims=," %%1 in ("%%{") do (
      rem Tokens 1-26 are in variables %%A - %%Z
      rem Token  27 is in %%[
      rem Token  28 is in %%\
      rem Token  29 is in %%]
      rem Tokens 30-55 are in %%a - %%z
      rem Tokens 56-64 are in %%1 - %%9
    )
  )
)

В приложении содержится важная информация о том, как работает вышеперечисленное.

Если вам нужно всего лишь несколько токенов, распределенных среди 64 строк, то решение будет несколько проще, поскольку вы сможете избежать использования сумасшедших символов в качестве переменных FOR. Но предстоит еще тщательная бухгалтерия.

Например, следующее даст вам доступ к токенам 5, 27, 46 и 64

for /f "usebackq tokens=5,27,30* delims=," %%A in ("%filename%") do (
  for /f "tokens=16,30* delims=," %%E in ("%%D") do (
    for /f "tokens=4 delims=," %%H in ("%%G") do (
      rem Token  5 is in %%A
      rem Token 27 is in %%B
      rem Token 46 is in %%E
      rem Token 64 is in %%H
    )
  )
)

Апрель 2016 г. Обновление - На основе исследовательской работы пользователей DosTips Aacini, penpen и aGerman я разработал относительно простой метод одновременного доступа к тысячам токенов с помощью FOR / F. Работа является частью этой ветки DosTips . Фактический код можно найти в этих 3 сообщениях:

Оригинальный ответ Переменные FOR ограничены одним символом, поэтому ваша стратегия %% BL не может работать. Переменные чувствительны к регистру. Согласно Microsoft, вы можете ограничиться получением 26 токенов в одном операторе FOR, но вы можете получить больше, если будете использовать больше, чем просто альфа. Это боль, потому что вам нужна таблица ASCII, чтобы выяснить, какие символы куда идут. Однако FOR не допускает использования каких-либо символов, а максимальное количество токенов, которое может назначить один FOR / F, составляет 31 +1. Как вы обнаружили, любая попытка разобрать и назначить больше 31 потерпит неудачу.

К счастью, я не думаю, что вам нужно столько токенов. Вы просто указываете, какие токены вы хотите с опцией TOKENS.

for /f "usebackq tokens=7,12,15,18 delims=," %%A in ("%filename%") do echo %%A,%%B,%%C,%%D

даст вам 7-й, 12-й, 15-й и 18-й жетоны.

Добавление

Апрель 2016 г. Обновление Пару недель назад я узнал, что следующие правила (написанные 6 лет назад) зависят от кодовой страницы. Приведенные ниже данные были проверены для кодовых страниц 437 и 850. Что более важно, последовательность переменных FOR расширенных символов ASCII 128-254 не соответствует байту кодовое значение, и сильно зависит от кодовой страницы. Оказывается, отображение переменных FOR / F основано на базовой кодовой точке UTF- (16?). Поэтому расширенные символы ASCII имеют ограниченное использование при использовании с FOR / F. См. Нить в http://www.dostips.com/forum/viewtopic.php?f=3&t=7703 для получения дополнительной информации.

Я провел несколько тестов и могу сообщить следующее (обновлено в ответ на комментарий Джеба) :

Большинство символов можно использовать в качестве переменной FOR, включая расширенный ASCII 128-254. Но некоторые символы нельзя использовать для определения переменной в первой части инструкции FOR, а можно использовать в предложении DO. Некоторые не могут быть использованы ни для одного. Некоторые не имеют ограничений, но требуют специального синтаксиса.

Ниже приводится краткое описание символов, которые имеют ограничения или требуют специального синтаксиса. Обратите внимание, что текст в угловых скобках, например <space>, представляет собой один символ.

Dec  Hex   Character   Define     Access
  0  0x00  <nul>       No         No
 09  0x09  <tab>       No         %%^<tab>  or  "%%<tab>"
 10  0x0A  <LF>        No         %%^<CR><LF><CR><LF>  or  %%^<LF><LF>
 11  0x0B  <VT>        No         %%<VT>
 12  0x0C  <FF>        No         %%<FF>
 13  0x0D  <CR>        No         No
 26  0x1A  <SUB>       %%%VAR%    %%%VAR% (%VAR% must be defined as <SUB>)
 32  0x20  <space>     No         %%^<space>  or  "%%<space>"
 34  0x22  "           %%^"       %%"  or  %%^"
 36  0x24  $           %%$        %%$ works, but %%~$ does not
 37  0x25  %           %%%%       %%~%%
 38  0x26  &           %%^&       %%^&  or  "%%&"
 41  0x29  )           %%^)       %%^)  or  "%%)"
 44  0x2C  ,           No         %%^,  or  "%%,"
 59  0x3B  ;           No         %%^;  or  "%%;"
 60  0x3C  <           %%^<       %%^<  or  "%%<"
 61  0x3D  =           No         %%^=  or  "%%="
 62  0x3E  >           %%^>       %%^>  or  "%%>"
 94  0x5E  ^           %%^^       %%^^  or  "%%^"
124  0x7C  |           %%^|       %%^|  or  "%%|"
126  0x7E  ~           %%~        %%~~ (%%~ may crash CMD.EXE if at end of line)
255  0xFF  <NB space>  No         No

Специальные символы, такие как ^ < > | &, должны быть либо экранированы, либо заключены в кавычки. Например, следующие работы:

for /f %%^< in ("OK") do echo "%%<" %%^<

Некоторые символы нельзя использовать для определения переменной FOR. Например, следующее дает синтаксическую ошибку:

for /f %%^= in ("No can do") do echo anything

Но %%= может быть неявно определено с помощью опции TOKENS, а значение, доступное в предложении DO, выглядит следующим образом:

for /f "tokens=1-3" %%^< in ("A B C") do echo %%^< %%^= %%^>

% нечетно - Вы можете определить переменную FOR, используя %%%%. Но значение не может быть доступно, если вы не используете модификатор ~. Это означает, что заключенные в кавычки не могут быть сохранены.

for /f "usebackq tokens=1,2" %%%% in ('"A"') do echo %%%% %%~%%

Вышеуказанные выходы %% A

~ является потенциально опасной переменной FOR. Если вы попытаетесь получить доступ к переменной, используя %%~ в конце строки, вы можете получить непредсказуемые результаты и даже может привести к сбою CMD.EXE! Единственный надежный способ получить к нему доступ без ограничений - использовать %%~~, что, конечно, удаляет любые заключающие в кавычки.

for /f %%~ in ("A") do echo This can crash because its the end of line: %%~

for /f %%~ in ("A") do echo But this (%%~) should be safe

for /f %%~ in ("A") do echo This works even at end of line: %%~~

Символ <SUB> (0x1A) является специальным, поскольку литералы <SUB>, встроенные в пакетные сценарии, читаются как перевод строки (<LF>). Чтобы использовать <SUB> в качестве переменной FOR, значение должно быть каким-то образом сохранено в переменной среды, и тогда %%%VAR% будет работать как для определения, так и для доступа.

Как уже говорилось, один FOR / F может анализировать и назначать максимум 31 токен. Например:

@echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-31" %%A in ("!str!") do echo A=%%A _=%%_

Выше приведено A=1 _=31 Примечание - токены 2-30 работают отлично, я просто хотел небольшой пример

Любая попытка разобрать и назначить более 31 токена молча провалится без установки ERRORLEVEL.

@echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-32" %%A in ("!str!") do echo this example fails entirely

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

@echo off
setlocal enableDelayedExpansion
set "str="
for /l %%0 in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1-31*" %%@ in ("!str!") do echo @=%%A  ^^=%%^^  _=%%_

Вышеуказанные выходы @=1 ^=31 _=32 33 34 35

А теперь о действительно плохих новостях. Один FOR / F никогда не может анализировать более 31 токена, как я узнал, когда посмотрел на Ограничение числа токенов в команде FOR в Пакетный скрипт Windows

@echo off
setlocal enableDelayedExpansion
set "str="
for /l %%n in (1 1 35) do set "str=!str! %%n"
for /f "tokens=1,31,32" %%A in ("!str!") do echo A=%%A  B=%%B  C=%%C

Очень неудачный вывод A=1 B=31 C=%C

4 голосов
/ 15 декабря 2011

Мой ответ состоит из двух частей. Первый - это новый ответ, который я написал в вопросе «Помогает в написании пакета сценария для анализа CSV-файла-и-вывода-текста-файла», который не имеет ограничений в количестве полей. .

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

@echo off
setlocal EnableDelayedExpansion

rem Create heading array:
set /P headingRow=< %1
set i=0
for %%h in (%headingRow%) do (
    set /A i+=1
    set heading[!i!]=%%~h
)


REM SAVE FILE NAME AND CREATE TARGET ELEMENTS ARRAY:
SET FILENAME=%1
IF "%2" == "" (FOR /L %%J IN (1,1,%i%) DO SET TARGET[%%J]=%%J) & GOTO CONTINUE
SET J=0
:NEXTTARGET
    SHIFT
    IF "%1" == "" GOTO CONTINUE
    SET /A J+=1
    SET TARGET[%J%]=%1
GOTO NEXTTARGET
:CONTINUE


rem Process the file:
call :ProcessFile < %FILENAME%
exit /B

:ProcessFile
set /P line=
:nextLine
    set line=:EOF
    set /P line=
    if "!line!" == ":EOF" goto :EOF
    set i=0
    SET J=1
    for %%e in (%line%) do (
        set /A i+=1
        FOR %%J IN (!J!) DO SET TARGET=!TARGET[%%J]!
        IF !i! == !TARGET! (
            for %%i in (!i!) do echo !heading[%%i]!%%~e
            SET /A J+=1
        )
    )
goto nextLine
exit /B

Например:

EXTRACTCSVFIELDS THEFILE.CSV 7 12 15 18

РЕДАКТИРОВАТЬ Более простой метод

Ниже приведена новая версия, которая является одновременно более простой и понятной, поскольку она использует список целевых элементов вместо массива:

@echo off
setlocal EnableDelayedExpansion

rem Create heading array:
set /P headingRow=< %1
set i=0
for %%h in (%headingRow%) do (
    set /A i+=1
    set heading[!i!]=%%~h
)

REM CREATE TARGET ELEMENTS LIST:
IF "%2" == "" (
    SET TARGETLIST=
    FOR /L %%J IN (1,1,%i%) DO SET TARGETLIST=!TARGETLIST! %%J
) ELSE (
    SET TARGETLIST=%*
    SET TARGETLIST=!TARGETLIST:* =!
)

rem Process the file:
call :ProcessFile < %1
exit /B

:ProcessFile
set /P line=
:nextLine
    set line=:EOF
    set /P line=
    if "!line!" == ":EOF" goto :EOF
    set i=0
    for %%e in (%line%) do (
        set /A i+=1
        for %%i IN (!i!) DO (
            IF "!TARGETLIST:%%i=!" NEQ "!TARGETLIST!" (
                echo !heading[%%i]!%%~e
            )
        )
    )
goto nextLine
exit /B

Кроме того, эта версия не требует, чтобы требуемые поля указывались по порядку.

EDIT

Oops! Материал для параметров отвлек мое внимание, поэтому я не знал о вашем последнем запросе:

"Ultimately I want to make a string which will be something like:

field7,field12,field15,field18"

Просто измените последнюю часть программы, чтобы сделать это:

:ProcessFile
set /P line=
:nextLine
    set line=:EOF
    set /P line=
    if "!line!" == ":EOF" goto :EOF
    set i=0
    set resultString=
    for %%e in (%line%) do (
        set /A i+=1
        for %%i IN (!i!) DO (
            IF "!TARGETLIST:%%i=!" NEQ "!TARGETLIST!" (
                set resultString=!resultString!%%~e,
            )
        )
    )
    set resultString=%resultString:~0,-1%
    echo Process here the "%resultString%"
goto nextLine
exit /B

Вы также можете удалить создание массива заголовков, потому что вы НЕ хотите заголовков! ;)

1 голос
/ 15 февраля 2013

Используя %% @ и %% `(здесь не документировано) в качестве стартовых переменных, вы можете получить максимум не более 71:

@echo off
for /f "tokens=1-31* delims=," %%@ in ("%filename%") do (
    echo:
    echo  1=%%@
    echo  2=%%A
    echo  3=%%B
    echo  4=%%C
    echo  5=%%D
    echo  6=%%E
    echo  7=%%F
    echo  8=%%G
    echo  9=%%H
    echo 10=%%I
    echo 11=%%J
    echo 12=%%K
    echo 13=%%L
    echo 14=%%M
    echo 15=%%N
    echo 16=%%O
    echo 17=%%P
    echo 18=%%Q
    echo 19=%%R
    echo 20=%%S
    echo 21=%%T
    echo 22=%%U
    echo 23=%%V
    echo 24=%%W
    echo 25=%%X
    echo 26=%%Y
    echo 27=%%Z
    echo 28=%%[
    echo 29=%%\
    echo 30=%%]
    echo 31=%%^^
    for /F "tokens=1-30* delims=," %%` in ("%%_") do (
        echo 32=%%`
        echo 33=%%a
        echo 34=%%b
        echo 35=%%c
        echo 36=%%d
        echo 37=%%e
        echo 38=%%f
        echo 39=%%g
        echo 40=%%h
        echo 41=%%i
        echo 42=%%j
        echo 43=%%k
        echo 44=%%l
        echo 45=%%m
        echo 46=%%n
        echo 47=%%o
        echo 48=%%p
        echo 49=%%q
        echo 50=%%r
        echo 51=%%s
        echo 52=%%t
        echo 53=%%u
        echo 54=%%v
        echo 55=%%w
        echo 56=%%x
        echo 57=%%y
        echo 58=%%z
        echo 59=%%{
        echo 60=%%^|
        echo 61=%%}
        for /F "tokens=1-9* delims=," %%0 in ("%%~") do (
            echo 62=%%0
            echo 63=%%1
            echo 64=%%2
            echo 65=%%3
            echo 66=%%4
            echo 67=%%5
            echo 68=%%6
            echo 69=%%7
            echo 70=%%8
            echo 71=%%9
        )
    )
)
0 голосов
/ 29 марта 2017

Когда я снова прочитал эту проблему и предложил решение, предложенное в ответе с наибольшим количеством голосов, я подумал, что можно разработать гораздо более простой способ для эффективного использования серии вложенных команд FOR / F.Я начал писать такой метод, который позволял бы использовать 127 дополнительных токенов, помещая их в диапазон символов ASCII 128-254.Однако, когда моя программа была завершена, я обнаружил, что символы ASCII в «естественном» порядке 128..254 не могут быть использованы для этой цели ...

Затем группа людей заинтересовалась этой проблемойи они сделали ряд открытий и разработок, кульминацией которых стал метод, который позволяет использовать много токенов (более 43 000!) в серии вложенных команд FOR / F.Вы можете прочитать подробное описание исследований и разработок, связанных с этим открытием, по адресу в этой теме DosTips .

Наконец, я использовал новый метод для изменения моей программы, поэтому теперь он позволяет обрабатыватьдо 4094 одновременных токенов (из текстового файла с длинными строками), но простым способом .Мое приложение состоит из пакетного файла с именем MakeForTokens.bat , который вы можете запустить с количеством нужных токенов в параметре.Например:

MakeForTokens.bat 64

Программа генерирует пакетный файл с именем ForTokens.bat , который содержит весь код, необходимый для управления таким количеством одновременных токенов, включая примеры того, какобработать файл.Таким образом, пользователям просто нужно вставить свои собственные имена файлов и нужные токены, чтобы получить работающую программу.

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

@echo off & setlocal EnableDelayedExpansion & set "$numTokens=65"

Rem/For  Step 1: Define the series of auxiliary variables that will be used as FOR tokens.
call :DefineForTokens

Rem/For  Step 2:  Define an auxiliary variable that will contain the desired tokens when it is %expanded%.
call :ExpandTokensString "tokens=7,12,15,18"

Rem/For  Step 3:  Define the variable with the "delims" value that will be used in the nested FOR's.
set "delims=delims=,"

Rem/For  Step 4:  Create the macro that contain the nested FOR's.
call :CreateNestedFors

Rem/For  Step 5:  This is the main FOR /F command that process the file.
for /F "usebackq tokens=1-31* %delims%" %%%$1% in ("filename.txt") do %NestedFors% (

   Rem/For  Step 6: Process the tokens.

   Rem/For  To just show they, use the "tokens" variable defined above:
   echo %tokens%

   Rem/For  You may also process individual tokens via another FOR /F command:
   for /F "tokens=1-%tokens.len%" %%a in ("%tokens%") do (
      echo Field  #7: %%a
      echo Field #12: %%b
      echo Field #15: %%c
      echo Field #18: %%d
   )

)

goto :EOF


Support subroutines. You must not modify any code below this line.


:DefineForTokens

for /F "tokens=2 delims=:." %%p in ('chcp') do set /A "_cp=%%p, _pages=($numTokens/256+1)*2"
set "_hex= 0 1 2 3 4 5 6 7 8 9 A B C D E F"
call set "_pages=%%_hex:~0,%_pages%%%"
if %$numTokens% gtr 2048 echo Creating FOR tokens variables, please wait . . .
(
   echo FF FE
   for %%P in (%_pages%) do for %%A in (%_hex%) do for %%B in (%_hex%) do echo %%A%%B 3%%P 0D 00 0A 00
) > "%temp%\forTokens.hex.txt"
certutil.exe -decodehex -f "%temp%\forTokens.hex.txt" "%temp%\forTokens.utf-16le.bom.txt" >NUL
chcp 65001 >NUL
type "%temp%\forTokens.utf-16le.bom.txt" > "%temp%\forTokens.utf8.txt"
(for /L %%N in (0,1,%$numTokens%) do set /P "$%%N=")  < "%temp%\forTokens.utf8.txt" 
chcp %_cp% >NUL
del "%temp%\forTokens.*.txt"
for %%v in (_cp _hex _pages) do set "%%v="
exit /B


:CreateNestedFors

setlocal EnableDelayedExpansion
set /A "numTokens=$numTokens-1, mod=numTokens%%31, i=numTokens/31, lim=31"
if %mod% equ 0 set "mod=31"
set "NestedFors="
for /L %%i in (32,31,%numTokens%) do (
   if !i! equ 1 set "lim=!mod!"
   set "NestedFors=!NestedFors! for /F "tokens=1-!lim!* %delims%" %%!$%%i! in ("%%!$%%i!") do"
   set /A "i-=1"
)
for /F "delims=" %%a in ("!NestedFors!") do endlocal & set "NestedFors=%%a"
exit /B


:ExpandTokensString variable=tokens definitions ...

setlocal EnableDelayedExpansion
set "var=" & set "tokens=" & set "len=0"
if "%~2" equ "" (set "params=%~1") else set "params=%*"
for %%a in (!params!) do (
   if not defined var (
      set "var=%%a"
   ) else for /F "tokens=1-3 delims=-+" %%i in ("%%a") do (
      if "%%j" equ "" (
         if %%i lss %$numTokens% set "tokens=!tokens! %%!$%%i!" & set /A len+=1
      ) else (
         if "%%k" equ "" (set "k=1") else set "k=%%k"
         if %%i leq %%j (
            for /L %%n in (%%i,!k!,%%j) do if %%n lss %$numTokens% set "tokens=!tokens! %%!$%%n!" & set /A len+=1
         ) else (
            for /L %%n in (%%i,-!k!,%%j) do if %%n lss %$numTokens% set "tokens=!tokens! %%!$%%n!" & set /A len+=1
         )
      )
   )
)
endlocal & set "%var%=%tokens%" & set "%var%.len=%len%"
exit /B

Вы можете загрузить приложение MakeForTokens.bat с с этого сайта .

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