Используя пакетный скрипт, как я могу использовать регулярные выражения для разделения данных в CSV-файле? - PullRequest
0 голосов
/ 23 мая 2019

У меня есть файл .csv (созданный при экспорте электронной таблицы googleDoc), из которого мне нужно извлечь информацию. Информация НЕ содержит согласованного разделителя.

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

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

Ячейкам с 2 частями информации вводятся двойные кавычки (") в начале и в конце. Предоставляются такие данные, как 1,"2,3",4

Мой сплиттер не может распознать разницу между 1,2,3,4 и 1, «2,3», 4, поэтому третье значение возвращает 3 для первого набора и 3" для второго набора, когда оно должен вернуть 4 для второго набора (ожидается 3 для первого набора)

Ниже приведен фрагмент файла .csv, который я использую.

A,SCONE,Shen ring,SHEN_RING,"FLOUR, BUTTER","BRONZE,GOLD",BLANK,Blank,,BLANK,
A,STRAWBERRIES_AND_CREAM,Cat1,CAT1,"STRAWBERRY, CREAM","OBSIDIAN,GOLD2",FS,FreeSpin,,FREE_SPIN,
A,WALNUT_TOFFEE,Pyramid,PYRAMID,"BUTTER, SUGAR, WALNUT","GOLD,EMERALD,PERIDOT",1,Champagne,Garnet,GARNET,
A,RASPBERRY_AND_LIME_JELLY,Cuff bracelet,CUFF_BRACELET,"RASPBERRY, JELLY, LIME","ZIRCON,BRONZE2,TOPAZ",2,Cocoa,Lapis lazuli,LAPIS_LAZULI,Blue
A,CHOCOLATE_CHIP_COOKIES,Nekhbet,NEKHBET,"SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT","EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER",3,GoldLeaf,gold3,GOLD3,yellow
A,BUTTER_CREAM_CUP_CAKE,Sobek,SOBEK,"ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM","JADE,BRONZE,GOLD,GARNET2",4,Sugar,emerald,EMERALD,green
A,PEANUT_BUTTER_COOKIE,Sekhmet,SEKHMET,"PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER","GARNET1,BRONZE,AMAZONITE,EMERALD",5,IcingSugar,JADE,JADE,green
A,CHOCOLATE_MARSHMALLOWS,Osiris,OSIRIS,"MARSHMALLOW, CHOCOLATE_CHIPS","PLATINUM,ALEXANDRITE",6,Flour,Bronze,BRONZE,yellow
,,,,,,7,Butter,Gold,GOLD,yellow
B,BLUEBERRY_PIE,Ankh,ANKH,"BLUEBERRY, SUGAR, FLOUR, BUTTER","JADEITE,EMERALD,BRONZE,GOLD",8,ChocolateChips,Alexandrite,ALEXANDRITE,

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

SET originalCol=8
SET newCol=10
SET startRow=2
SET lastRow=45
SET rowsToSkip=1
SET /a i=0
SET /a totalValues=0
SET /a maxLines=%lastRow%-%startRow%
FOR /f "skip=%rowsToSkip% delims=" %%L in (%fileLocation%) DO (
    set "line=%%L,,,,,,,,"
    set "line=#!line:,=,#!"
    FOR /f "tokens=1,%originalCol%,%newCol% delims=," %%F IN ("!line!") DO (
        set "param1=%%F"
        set "param2=%%G"
        set "param3=%%H"
        set "param1=!param1:~1!"
        set "param2=!param2:~1!"
        set "param3=!param3:~1!"
        IF NOT #!param1!# == ## (
            SET /a lineCounter=!i!+%startRow%
            SET /a totalValues=!i!
            SET originalValuesList[!i!]=!param2!
            SET newValuesList[!i!]=!param3!
            IF !i! == %maxLines% (
                goto :copyingCSVDataComplete
            ) ELSE (
                SET /a i+=1
            )
        )
    )
)
echo.  originalValuesList [A] & echo [%originalValuesList[0]%, %originalValuesList[1]%, %originalValuesList[2]%, %originalValuesList[3]%, %originalValuesList[4]%, %originalValuesList[5]%, %originalValuesList[6]%, %originalValuesList[7]%]
echo.
echo.  originalValuesList [B] & echo [%originalValuesList[8]%]
echo.
echo.  newValuesList [A] & echo [%newValuesList[0]%, %newValuesList[1]%, %newValuesList[2]%, %newValuesList[3]%, %newValuesList[4]%, %newValuesList[5]%, %newValuesList[6]%, %newValuesList[7]%]
echo.
echo.  newValuesList [B] & echo [%newValuesList[8]%]

ACUTAL:

  originalValuesList [A]
[GOLD", GOLD2", "GOLD, "ZIRCON,  CHOCOLATE_CHIPS,  BUTTERCREAM",  BAKING_POWDER", ALEXANDRITE"]

  originalValuesList [B]
[ BUTTER"]



  newValuesList [A]
[Blank, FreeSpin, PERIDOT", TOPAZ", "EMERALD, BRONZE, BRONZE, Flour]

  newValuesList [B]
[EMERALD]

ОЖИДАЕТСЯ:

  originalValuesList [A]
[Blank, FreeSpin, Champagne, Cocoa, GoldLeaf, Sugar, IcingSugar, flour]

  originalValuesList [B]
[ChocolateChips]



  newValuesList [A]
[BLANK, FREE_SPIN, GARNET, LAPIS_LAZULI, GOLD3, EMERALD, JADE, BRONZE]

  newValuesList [B]
[ALEXANDRITE]

Итак, я хочу использовать тот же код, но вместо разделения на запятую (,) я хочу разделить на основе регулярного выражения. Что-то вроде (, "([A-Z] *),") | (,)

Можно ли использовать регулярные выражения в пакетном режиме, и если да, то как я могу использовать его для разделения строк?

1 Ответ

1 голос
/ 25 мая 2019

Во-первых, PowerShell имеет встроенную возможность разбора и обработки документов CSV, так что это будет лучшим вариантом. Но я буду придерживаться пакетной обработки.

Решение для регулярных выражений

Регулярные выражения не годятся для чистого пакетного решения по двум причинам:

  • Невозможно изменить поведение FOR / F для анализа токенов с помощью регулярных выражений - это то, что есть, - очень ограничено.
  • Чтобы проанализировать ваш файл с помощью FOR / F, вам нужно будет манипулировать каждой строкой перед анализом. Пакет не имеет никакой утилиты регулярных выражений, которая может изменить содержимое. Он имеет только FINDSTR, который может выполнять очень грубые регулярные выражения, но всегда возвращает исходную совпадающую строку. Кроме того, регулярное выражение FINDSTR настолько ограничено, что я не уверен, что вы все равно сможете правильно проанализировать CSV.

Вы можете использовать пользовательский JScript или VBScript через CSCRIPT для предварительной обработки файла с помощью поиска по регулярному выражению и замены таким образом, чтобы FOR / F мог затем проанализировать файл. Я уже написал гибридную утилиту обработки регулярных выражений JScript / batch под названием JREPL.BAT , которая хорошо работает для этого.

Поле CSV в кавычках может содержать литералы кавычек, в этом случае либералы кавычек удваиваются. Следующее регулярное выражение будет соответствовать любому токену CSV (не включая запятую) ("(?:""|[^"])*"|[^,"]*). Он ищет кавычку, за которой следует любое количество символов, не заключенных в кавычки, и / или двойные кавычки, за которыми следует заключительная кавычка или , содержащая любое количество символов, не включая кавычку или запятую. Но ваш CSV не содержит литералов с двойными кавычками, поэтому регулярное выражение можно упростить до ("[^"]*"|[^,"]*).

CSCRIPT не имеет механизма для передачи литералов кавычек в аргументах, поэтому JREPL имеет параметр / XSEQ для включения расширенной поддержки escape-последовательности, включая \q для представления ". Другой вариант - использовать стандартную последовательность \x22. JREPL "(\q[^\q]*\q|[^,\q]*)," "$1;" /XSEQ /F "test.csv" будет соответствовать любому токену (возможно, пустому), за которым следует разделитель запятых, и сохранит токен и заменит запятую точкой с запятой.

Но это все еще оставляет пустые токены, и FOR / F неправильно анализирует пустые токены. Таким образом, я могу добавить немного JSCRIPT в термин замены, чтобы удалить любые существующие кавычки, и затем окружить каждый токен кавычками (кроме последнего, где он не нужен)
JREPL "(\q[^\q]*\q|[^,\q]*)," "$txt='\q'+$1.replace(/'\q'/,'')+'\q;'" /JQ /XSEQ /F "test.csv"

Вот демонстрация, показывающая, как это можно использовать для анализа вашего CSV:

@echo off
for /f "tokens=1-11 delims=;" %%A in (
  'JREPL "(\q[^\q]*\q|[^,\q]*)," "$txt='\x22'+$1.replace(/\x22/g,'')+'\x22;'" /JQ /XSEQ /F test.csv'
) do (
  echo A=%%~A
  echo B=%%~B
  echo C=%%~C
  echo D=%%~D
  echo E=%%~E
  echo F=%%~F
  echo G=%%~G
  echo H=%%~H
  echo I=%%~I
  echo J=%%~J
  echo K=%%~K
  echo(
)

- ВЫВОД -

A=A
B=SCONE
C=Shen ring
D=SHEN_RING
E=FLOUR, BUTTER
F=BRONZE,GOLD
G=blank
H="This
I="BLANK""
J=
K=BLANK

A=A
B=STRAWBERRIES_AND_CREAM
C=Cat1
D=CAT1
E=STRAWBERRY, CREAM
F=OBSIDIAN,GOLD2
G=FS
H=FreeSpin
I=
J=FREE_SPIN
K=

A=A
B=WALNUT_TOFFEE
C=Pyramid
D=PYRAMID
E=BUTTER, SUGAR, WALNUT
F=GOLD,EMERALD,PERIDOT
G=1
H=Champagne
I=Garnet
J=GARNET
K=

A=A
B=RASPBERRY_AND_LIME_JELLY
C=Cuff bracelet
D=CUFF_BRACELET
E=RASPBERRY, JELLY, LIME
F=ZIRCON,BRONZE2,TOPAZ
G=2
H=Cocoa
I=Lapis lazuli
J=LAPIS_LAZULI
K=Blue

A=A
B=CHOCOLATE_CHIP_COOKIES
C=Nekhbet
D=NEKHBET
E=SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT
F=EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER
G=3
H=GoldLeaf
I=gold3
J=GOLD3
K=yellow

A=A
B=BUTTER_CREAM_CUP_CAKE
C=Sobek
D=SOBEK
E=ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM
F=JADE,BRONZE,GOLD,GARNET2
G=4
H=Sugar
I=emerald
J=EMERALD
K=green

A=A
B=PEANUT_BUTTER_COOKIE
C=Sekhmet
D=SEKHMET
E=PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER
F=GARNET1,BRONZE,AMAZONITE,EMERALD
G=5
H=IcingSugar
I=JADE
J=JADE
K=green

A=A
B=CHOCOLATE_MARSHMALLOWS
C=Osiris
D=OSIRIS
E=MARSHMALLOW, CHOCOLATE_CHIPS
F=PLATINUM,ALEXANDRITE
G=6
H=Flour
I=Bronze
J=BRONZE
K=yellow

A=
B=
C=
D=
E=
F=
G=7
H=Butter
I=Gold
J=GOLD
K=yellow

A=B
B=BLUEBERRY_PIE
C=Ankh
D=ANKH
E=BLUEBERRY, SUGAR, FLOUR, BUTTER
F=JADEITE,EMERALD,BRONZE,GOLD
G=8
H=ChocolateChips
I=Alexandrite
J=ALEXANDRITE
K=

Но я бы не использовал для этого регулярные выражения. Есть и другие способы.

Чистый нативный пакетный раствор

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

Есть две вещи, которые должны произойти с вашим CSV:

1) Разделители запятых без кавычек должны быть преобразованы в другой символ, который не отображается в вашем файле, оставляя запятые в кавычках в одиночку. Я могу использовать производную от метода, разработанного Джебом , чтобы различать символы в кавычках и без кавычек: когда переменные раскрываются с процентным расширением, экранированные символы, такие как ^,, обрабатываются по-разному, в зависимости от того, указаны они в кавычках или нет , Обычно ^, становится ^, а "^," остается неизменным. Но если вы используете CALL, "^," становится "^^,", а ^, остается неизменным. В любом случае, тогда можно различить символы в кавычках и в кавычках.

2) FOR / F не может анализировать пустые токены, поэтому пустые токены должны быть заключены в кавычки. Проще всего заключить все токены в кавычки.

@echo off
setlocal enableDelayedExpansion
for /f "usebackq delims=" %%A in ("test.csv") do (

  %= Print out the raw line so we can verify the end result =%
  echo %%A

  %= Preprocess the line so it is safe to parse =%
  set "ln=%%A"           %= Transfer line to environment variable =%

  %= Artifact of CALL - Convert quoted , to ^^; and unquoted , to ^;        =%
  %= Make sure unquoted SET statement does not have any trailing characters =%
  call set ln=%%ln:,=^^;%%

  set "ln=!ln:^^;=,!"    %= Convert quoted ^^; back into ,                         =%
  set "ln=!ln:^;=;!"     %= Convert unquoted ^; to ;                               =%
  set "ln=!ln:"=!"       %= Strip all quotes so we can safely do next step         =%
  set "ln="!ln:;=";"!""  %= Enclose all tokens in quotes to protect empty tokens   =%

  %= The line is now ready to parse with another FOR /F     =%
  %= I simply print the value of all 11 tokens, 1 per line. =%
  %= Adjust the loop as needed to suit your needs.          =%
  for /f "tokens=1-11 delims=;" %%A in ("!ln!") do (
    for %%a in (A B C D E F G H I J K) do call :echoToken %%a
    echo(
  )
)
exit /b

:echoToken  Char
for %%. in (.) do echo %1=%%~%1
exit /b

Вот тот же код без всех комментариев:

@echo off
setlocal enableDelayedExpansion
for /f "usebackq delims=" %%A in ("test.csv") do (
  echo %%A
  set "ln=%%A"
  call set ln=%%ln:,=^^;%%
  set "ln=!ln:^^;=,!"
  set "ln=!ln:^;=;!"
  set "ln=!ln:"=!"
  set "ln="!ln:;=";"!""
  for /f "tokens=1-11 delims=;" %%A in ("!ln!") do (
    for %%a in (A B C D E F G H I J K) do call :echoToken %%a
    echo(
  )
)
exit /b

:echoToken  Char
for %%. in (.) do echo %1=%%~%1
exit /b

- ВЫХОД ---

A,SCONE,Shen ring,SHEN_RING,"FLOUR, BUTTER","BRONZE,GOLD",blank,"This,""BLANK""",,BLANK,
A=A
B=SCONE
C=Shen ring
D=SHEN_RING
E=FLOUR, BUTTER
F=BRONZE,GOLD
G=blank
H=This,BLANK
I=
J=BLANK
K=

A,STRAWBERRIES_AND_CREAM,Cat1,CAT1,"STRAWBERRY, CREAM","OBSIDIAN,GOLD2",FS,FreeSpin,,FREE_SPIN,
A=A
B=STRAWBERRIES_AND_CREAM
C=Cat1
D=CAT1
E=STRAWBERRY, CREAM
F=OBSIDIAN,GOLD2
G=FS
H=FreeSpin
I=
J=FREE_SPIN
K=

A,WALNUT_TOFFEE,Pyramid,PYRAMID,"BUTTER, SUGAR, WALNUT","GOLD,EMERALD,PERIDOT",1,Champagne,Garnet,GARNET,
A=A
B=WALNUT_TOFFEE
C=Pyramid
D=PYRAMID
E=BUTTER, SUGAR, WALNUT
F=GOLD,EMERALD,PERIDOT
G=1
H=Champagne
I=Garnet
J=GARNET
K=

A,RASPBERRY_AND_LIME_JELLY,Cuff bracelet,CUFF_BRACELET,"RASPBERRY, JELLY, LIME","ZIRCON,BRONZE2,TOPAZ",2,Cocoa,Lapis lazuli,LAPIS_LAZULI,Blue
A=A
B=RASPBERRY_AND_LIME_JELLY
C=Cuff bracelet
D=CUFF_BRACELET
E=RASPBERRY, JELLY, LIME
F=ZIRCON,BRONZE2,TOPAZ
G=2
H=Cocoa
I=Lapis lazuli
J=LAPIS_LAZULI
K=Blue

A,CHOCOLATE_CHIP_COOKIES,Nekhbet,NEKHBET,"SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT","EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER",3,GoldLeaf,gold3,GOLD3,yellow
A=A
B=CHOCOLATE_CHIP_COOKIES
C=Nekhbet
D=NEKHBET
E=SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT
F=EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER
G=3
H=GoldLeaf
I=gold3
J=GOLD3
K=yellow

A,BUTTER_CREAM_CUP_CAKE,Sobek,SOBEK,"ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM","JADE,BRONZE,GOLD,GARNET2",4,Sugar,emerald,EMERALD,green
A=A
B=BUTTER_CREAM_CUP_CAKE
C=Sobek
D=SOBEK
E=ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM
F=JADE,BRONZE,GOLD,GARNET2
G=4
H=Sugar
I=emerald
J=EMERALD
K=green

A,PEANUT_BUTTER_COOKIE,Sekhmet,SEKHMET,"PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER","GARNET1,BRONZE,AMAZONITE,EMERALD",5,IcingSugar,JADE,JADE,green
A=A
B=PEANUT_BUTTER_COOKIE
C=Sekhmet
D=SEKHMET
E=PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER
F=GARNET1,BRONZE,AMAZONITE,EMERALD
G=5
H=IcingSugar
I=JADE
J=JADE
K=green

A,CHOCOLATE_MARSHMALLOWS,Osiris,OSIRIS,"MARSHMALLOW, CHOCOLATE_CHIPS","PLATINUM,ALEXANDRITE",6,Flour,Bronze,BRONZE,yellow
A=A
B=CHOCOLATE_MARSHMALLOWS
C=Osiris
D=OSIRIS
E=MARSHMALLOW, CHOCOLATE_CHIPS
F=PLATINUM,ALEXANDRITE
G=6
H=Flour
I=Bronze
J=BRONZE
K=yellow

,,,,,,7,Butter,Gold,GOLD,yellow
A=
B=
C=
D=
E=
F=
G=7
H=Butter
I=Gold
J=GOLD
K=yellow

B,BLUEBERRY_PIE,Ankh,ANKH,"BLUEBERRY, SUGAR, FLOUR, BUTTER","JADEITE,EMERALD,BRONZE,GOLD",8,ChocolateChips,Alexandrite,ALEXANDRITE,
A=B
B=BLUEBERRY_PIE
C=Ankh
D=ANKH
E=BLUEBERRY, SUGAR, FLOUR, BUTTER
F=JADEITE,EMERALD,BRONZE,GOLD
G=8
H=ChocolateChips
I=Alexandrite
J=ALEXANDRITE
K=

Но есть много возможных ситуаций, которые могут сделать анализ CSV намного более сложным.

  • Если отложенное расширение включено при раскрытии переменной FOR, то все неэкранированные ! будут повреждены, как и неэкранированные ^, если присутствует !.
  • Техника требует расширения в процентах.Но это не получится, если присутствуют такие ядовитые символы, как &, |, >, <, ^, если они не указаны или не экранированы.
  • Возможно, вы не знаете данные, вВ этом случае вы не можете быть уверены, что какой-либо символ будет использоваться в качестве разделителя, который не отображается в ваших данных.Таким образом, литералы-разделители в кавычках должны быть закодированы как-то еще, а затем восстановлены после анализа токенов.
  • Кавычки CSV-полей могут содержать литералы кавычек, которые удваиваются.Удвоенные кавычки должны быть удвоены после синтаксического анализа.
  • CSV также позволяет переводить строки в цитируемые поля.Я не знаю ни одного пакетного решения, которое могло бы решить эту проблему.
  • Один FOR / F не может анализировать более 32 токенов в строке.См. эту ветку DosTips о методах превышения этого предела, особенно следующие сообщения в:

Это надежное чистое пакетное решение, которое может анализировать любой CSV, если в полях нет символов новой строки, а длина строки не достигает предела пакета в 8191 байт, и выне нужно анализировать более 31 токена.Код сильно комментируется, чтобы объяснить все необходимые шаги.

@echo off
setlocal enableDelayedExpansion

:: Must use arcane FOR /F option syntax to disable both EOL and DELIMS.
for /f usebackq^ delims^=^ eol^= %%A in ("test2.csv") do call :processLine
:: I CALL out of the loop to a :subroutine because a single CALL :subroutine
:: is much faster than many CALL SET statements. It also simplifies the
:: management of delayed expansion.

exit /b


:processLine

:: Must disable delayed expansion so percent expansion does not corrupt ! or ^ literals.
setlocal disableDelayedExpansion

:: FOR variables are global - this extra FOR loop exposes %%A that would otherwise be hidden.
for %%. in (.) do set "ln=%%A"

:: Print out raw line so we can diagnose the result.
set ln

:: "Hide" quotes by doubling, making all characters safe for percent expansion when
:: entire string is quoted. Also enclose line within extra set of , delimiters.
set "ln=,%ln:"=""%,"

:: Escape poison characters so all characters are safe for unquoted percent expansion.
set "ln=%ln:^=^^^^%" %= Double escaped to account for enabled delayed expansion later on. =%
set "ln=%ln:&=^&%"
set "ln=%ln:|=^|%"
set "ln=%ln:<=^<%"
set "ln=%ln:>=^>%"

:: Double escape ! so not corrupted by later percent expansion while delayed expansion enabled.
set "ln=%ln:!=^^!%"

:: Double and escape all commas.   , -> ^,^,
set "ln=%ln:,=^,^,%"

:: Undouble quotes and unescape (originally) unquoted strings. Note that outer quotes are escaped.
set ^"ln=%ln:""="%^"

:: At this point quoted comma literals are still ^,^, whereas unquoted comma delimiters are ,,
:: Also, all quoted poison characters are still escaped, but unquoted ones are not.

:: Redouble quotes, all characters safe again for quoted percent expansion.
set "ln=%ln:"=""%"

:: Encode @ as @a and quoted comma literals ^,^, as @c
set "ln=%ln:@=@a%"
set "ln=%ln:^,^,=@c%"

:: Restore delayed expansion and undouble quotes, which unescapes (originally) quoted strings.
:: Note that outer quotes are NOT escaped this time. The ENDLOCAL and SET are on the same
:: line so that the percent expansion value is transferred across the ENDLOCAL barrier.
endlocal & set "ln=%ln:""="%" !   %= Trailing ! is ignored except forces all ^^ to become ^ =%

:: At this point no characters are escaped, and all ! and ^ are unprotected against percent or
:: FOR variable expansion while delayed expansion is enabled.

:: Remove enclosing quotes from tokens that are already quoted so we can later safely enclose
:: all tokens in quotes. This is why the extra enclosing , were added at the beginning.
set "ln=!ln:,,"=,,!"
set "ln=!ln:",,=,,!"

:: Remove outer , delimiters that were added at the beginning.
set "ln=!ln:~2,-2!"

:: Must double escape ! and ^ again to protect against delayed expansion within parsing FOR /F loop.
set "ln=!ln:^=^^^^!"
set "ln=%ln:!=^^^!%"

:: Undouble remaining quotes because quote literals are doubled within original CSV.
set "ln=!ln:""="!"

:: Restore doubled ,, delimiters to , and enclose all tokens within quotes to preserves empty tokens.
set "ln="!ln:,,=","!"" !

:: The line is now safe to parse with FOR /F, though @ and , are encoded as @a and @c

:: Parse line into tokens.
for /f "tokens=1-11 delims=," %%A in ("!ln!") do (

  %= Decode the tokens and store result in environment variables =%
  for %%a in (A B C D E F G H I J K) do call :decodeToken %%a

  %= Your processing goes here. Decoded %%A - %%K are now safely in !A! - !K! =%
  %= I will simply echo all the values, one per line =%
  for %%a in (A B C D E F G H I J K) do echo %%a=!%%a!
  echo(
)
exit /b


:decodeToken  Char
:: Converts @c and @a back into , and @
for %%. in (.) do set "%1=%%~%1" !
if defined %1 (
  set "%1=!%1:@c=,!"
  set "%1=!%1:@a=@!"
)
exit /b

Вот тот же код без всех комментариев:

@echo off
setlocal enableDelayedExpansion
for /f usebackq^ delims^=^ eol^= %%A in ("test2.csv") do call :processLine
exit /b

:processLine
setlocal disableDelayedExpansion
for %%. in (.) do set "ln=%%A"
set ln
set "ln=,%ln:"=""%,"
set "ln=%ln:^=^^^^%"
set "ln=%ln:&=^&%"
set "ln=%ln:|=^|%"
set "ln=%ln:<=^<%"
set "ln=%ln:>=^>%"
set "ln=%ln:!=^^!%"
set "ln=%ln:,=^,^,%"
set ^"ln=%ln:""="%^"
set "ln=%ln:"=""%"
set "ln=%ln:@=@a%"
set "ln=%ln:^,^,=@c%"
endlocal & set "ln=%ln:""="%" !
set "ln=!ln:,,"=,,!"
set "ln=!ln:",,=,,!"
set "ln=!ln:~2,-2!"
set "ln=!ln:^=^^^^!"
set "ln=%ln:!=^^^!%"
set "ln=!ln:""="!"
set "ln="!ln:,,=","!"" !
for /f "tokens=1-11 delims=," %%A in ("!ln!") do (
  for %%a in (A B C D E F G H I J K) do call :decodeToken %%a
  for %%a in (A B C D E F G H I J K) do echo %%a=!%%a!
  echo(
)
exit /b

:decodeToken  Char
for %%. in (.) do set "%1=%%~%1" !
if defined %1 (
  set "%1=!%1:@c=,!"
  set "%1=!%1:@a=@!"
)
exit /b

Вот ваш пример файла CSV сдобавлена ​​дополнительная строка для проверки различных сложностей:

;A!,"B!","C is ""cool""",D @^&|<>,"E @^&|<>","F ,x","G ""@^&|<>""","H ""@^&|<>!""",I,J,K
A,SCONE,Shen ring,SHEN_RING,"FLOUR, BUTTER","BRONZE,GOLD",blank,"This,""BLANK""",,BLANK,
A,STRAWBERRIES_AND_CREAM,Cat1,CAT1,"STRAWBERRY, CREAM","OBSIDIAN,GOLD2",FS,FreeSpin,,FREE_SPIN,
A,WALNUT_TOFFEE,Pyramid,PYRAMID,"BUTTER, SUGAR, WALNUT","GOLD,EMERALD,PERIDOT",1,Champagne,Garnet,GARNET,
A,RASPBERRY_AND_LIME_JELLY,Cuff bracelet,CUFF_BRACELET,"RASPBERRY, JELLY, LIME","ZIRCON,BRONZE2,TOPAZ",2,Cocoa,Lapis lazuli,LAPIS_LAZULI,Blue
A,CHOCOLATE_CHIP_COOKIES,Nekhbet,NEKHBET,"SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT","EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER",3,GoldLeaf,gold3,GOLD3,yellow
A,BUTTER_CREAM_CUP_CAKE,Sobek,SOBEK,"ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM","JADE,BRONZE,GOLD,GARNET2",4,Sugar,emerald,EMERALD,green
A,PEANUT_BUTTER_COOKIE,Sekhmet,SEKHMET,"PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER","GARNET1,BRONZE,AMAZONITE,EMERALD",5,IcingSugar,JADE,JADE,green
A,CHOCOLATE_MARSHMALLOWS,Osiris,OSIRIS,"MARSHMALLOW, CHOCOLATE_CHIPS","PLATINUM,ALEXANDRITE",6,Flour,Bronze,BRONZE,yellow
,,,,,,7,Butter,Gold,GOLD,yellow
B,BLUEBERRY_PIE,Ankh,ANKH,"BLUEBERRY, SUGAR, FLOUR, BUTTER","JADEITE,EMERALD,BRONZE,GOLD",8,ChocolateChips,Alexandrite,ALEXANDRITE,

И вот окончательный вывод:

ln=;A!,"B!","C is ""cool""",D @^&|<>,"E @^&|<>","F ,x","G ""@^&|<>""","H ""@^&|<>!""",I,J,K
A=;A!
B=B!
C=C is "cool"
D=D @^&|<>
E=E @^&|<>
F=F ,x
G=G "@^&|<>"
H=H "@^&|<>!"
I=I
J=J
K=K

ln=A,SCONE,Shen ring,SHEN_RING,"FLOUR, BUTTER","BRONZE,GOLD",blank,"This,""BLANK""",,BLANK,
A=A
B=SCONE
C=Shen ring
D=SHEN_RING
E=FLOUR, BUTTER
F=BRONZE,GOLD
G=blank
H=This,"BLANK"
I=
J=BLANK
K=

ln=A,STRAWBERRIES_AND_CREAM,Cat1,CAT1,"STRAWBERRY, CREAM","OBSIDIAN,GOLD2",FS,FreeSpin,,FREE_SPIN,
A=A
B=STRAWBERRIES_AND_CREAM
C=Cat1
D=CAT1
E=STRAWBERRY, CREAM
F=OBSIDIAN,GOLD2
G=FS
H=FreeSpin
I=
J=FREE_SPIN
K=

ln=A,WALNUT_TOFFEE,Pyramid,PYRAMID,"BUTTER, SUGAR, WALNUT","GOLD,EMERALD,PERIDOT",1,Champagne,Garnet,GARNET,
A=A
B=WALNUT_TOFFEE
C=Pyramid
D=PYRAMID
E=BUTTER, SUGAR, WALNUT
F=GOLD,EMERALD,PERIDOT
G=1
H=Champagne
I=Garnet
J=GARNET
K=

ln=A,RASPBERRY_AND_LIME_JELLY,Cuff bracelet,CUFF_BRACELET,"RASPBERRY, JELLY, LIME","ZIRCON,BRONZE2,TOPAZ",2,Cocoa,Lapis lazuli,LAPIS_LAZULI,Blue
A=A
B=RASPBERRY_AND_LIME_JELLY
C=Cuff bracelet
D=CUFF_BRACELET
E=RASPBERRY, JELLY, LIME
F=ZIRCON,BRONZE2,TOPAZ
G=2
H=Cocoa
I=Lapis lazuli
J=LAPIS_LAZULI
K=Blue

ln=A,CHOCOLATE_CHIP_COOKIES,Nekhbet,NEKHBET,"SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT","EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER",3,GoldLeaf,gold3,GOLD3,yellow
A=A
B=CHOCOLATE_CHIP_COOKIES
C=Nekhbet
D=NEKHBET
E=SUGAR, FLOUR, BUTTER, CHOCOLATE_CHIPS, SALT
F=EMERALD,BRONZE,GOLD,ALEXANDRITE,SILVER
G=3
H=GoldLeaf
I=gold3
J=GOLD3
K=yellow

ln=A,BUTTER_CREAM_CUP_CAKE,Sobek,SOBEK,"ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM","JADE,BRONZE,GOLD,GARNET2",4,Sugar,emerald,EMERALD,green
A=A
B=BUTTER_CREAM_CUP_CAKE
C=Sobek
D=SOBEK
E=ICING_SUGAR, FLOUR, BUTTER, BUTTERCREAM
F=JADE,BRONZE,GOLD,GARNET2
G=4
H=Sugar
I=emerald
J=EMERALD
K=green

ln=A,PEANUT_BUTTER_COOKIE,Sekhmet,SEKHMET,"PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER","GARNET1,BRONZE,AMAZONITE,EMERALD",5,IcingSugar,JADE,JADE,green
A=A
B=PEANUT_BUTTER_COOKIE
C=Sekhmet
D=SEKHMET
E=PEANUT_BUTTER, FLOUR, SUGAR, BAKING_POWDER
F=GARNET1,BRONZE,AMAZONITE,EMERALD
G=5
H=IcingSugar
I=JADE
J=JADE
K=green

ln=A,CHOCOLATE_MARSHMALLOWS,Osiris,OSIRIS,"MARSHMALLOW, CHOCOLATE_CHIPS","PLATINUM,ALEXANDRITE",6,Flour,Bronze,BRONZE,yellow
A=A
B=CHOCOLATE_MARSHMALLOWS
C=Osiris
D=OSIRIS
E=MARSHMALLOW, CHOCOLATE_CHIPS
F=PLATINUM,ALEXANDRITE
G=6
H=Flour
I=Bronze
J=BRONZE
K=yellow

ln=,,,,,,7,Butter,Gold,GOLD,yellow
A=
B=
C=
D=
E=
F=
G=7
H=Butter
I=Gold
J=GOLD
K=yellow

ln=B,BLUEBERRY_PIE,Ankh,ANKH,"BLUEBERRY, SUGAR, FLOUR, BUTTER","JADEITE,EMERALD,BRONZE,GOLD",8,ChocolateChips,Alexandrite,ALEXANDRITE,
A=B
B=BLUEBERRY_PIE
C=Ankh
D=ANKH
E=BLUEBERRY, SUGAR, FLOUR, BUTTER
F=JADEITE,EMERALD,BRONZE,GOLD
G=8
H=ChocolateChips
I=Alexandrite
J=ALEXANDRITE
K=

См. Этот пост DosTips для демонстрации того, какрасширить эту технику для анализа более 32 полей.

Гибридная утилита JScript / batch parseCSV.bat

Чистая партия требует много кода, который сложно создать на лету, и этоотносительно медленноЯ создал parseCSV.bat - гибридную утилиту JScript / batch, которая быстро форматирует почти любой CSV во что-то, что может быть легко проанализировано FOR / F.Он даже поддерживает новые строки внутри полей.

Конечно, parseCSV не может решить ограничение длины строки 8191, и при разборе более 32 токенов по-прежнему требуется дополнительный код.

parseCSV.bat не использует регулярные выражения.

Я не буду вдаваться в подробности о том, как это работает.Полная документация встроена в утилиту, которую можно получить, введя parseCSV /? из командной строки.Вывод справки следующий:

parseCSV  [/option]...

  Parse stdin as CSV and write it to stdout in a way that can be safely
  parsed by FOR /F. All columns will be enclosed by quotes so that empty
  columns may be preserved. It also supports delimiters, newlines, and
  escaped quotes within quoted values. Two consecutive quotes within a
  quoted value are converted into one quote by default.

  Available options:

    /I:string = Input delimiter. Default is a comma (,)

    /O:string = Output delimiter. Default is a comma (,)

         The entire option must be quoted if specifying poison character
         or whitespace literals as a delimiters for /I or /O.

         Examples:  pipe = "/I:|"
                   space = "/I: "

         Standard JScript escape sequences can also be used.

         Examples:       tab = /I:\t  or  /I:\x09
                   backslash = /I:\\

    /E = Encode output delimiter literal within value as \D
         Encode newline within value as \N
         Encode backslash within value as \S

    /D = escape exclamation point and caret for Delayed expansion
         ! becomes ^!
         ^ becomes ^^

    /L = treat all input quotes as quote Literals

    /Q:QuoteOutputFormat

       Controls output of Quotes, where QuoteOutputFormat may be any
       one of the following:

         L = all columns quoted, quote Literals output as "   (Default)
         E = all columns quoted, quote literals Escaped as ""
         N = No columns quoted, quote literals output as "

       The /Q:E and /Q:N options are useful for transforming data for
       purposes other than parsing by FOR /F

    /U = Write unix style lines with newline (\n) instead of the default
         Windows style of carriage return and linefeed (\r\n).

parseCSV  /?

  Display this help

parseCSV  /V

  Display the version of parseCSV.bat

parseCSV.bat was written by Dave Benham. Updates are available at the original
posting site: http://www.dostips.com/forum/viewtopic.php?f=3&t=5702

Вот как parseCSV.bat можно использовать с test2.csv сверху.

@echo off
setlocal enableDelayedExpansion
for /f "tokens=1-11 delims=," %%A in (
  'parseCSV /E /D ^<test2.csv'
) do (
  %= Decode Tokens =%
  for %%a in (A B C D E F G H I J K) do call :decodeToken %%a
  %= Show the results =%
  for %%a in (A B C D E F G H I J K) do echo %%a=!%%a!
  echo(
)
exit /b

:decodeToken
for %%. in (.) do set "%1=%%~%1" !
if defined %1 (
  set "%1=!%1:\D=,!"
  set "%1=!%1:\S=\!"
)
exit /b

См. Это сообщение DosTips для демонстрации того, как расширить эту технику для анализа более 32 полей.

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