Windows批处理脚本用于解析CSV文件并输出文本文件

时间:2011-12-15 12:44:07

标签: windows csv batch-file cmd

我在另一个页面上看到了回复(Help in writing a batch script to parse CSV file and output a text file) - 精彩的代码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 个答案:

答案 0 :(得分:16)

重要更新 - 我不认为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年4月更新 - 根据DosTips用户Aacini,penpen和aGerman的调查工作,我开发了一种相对简单的方法,可以使用FOR / F同时访问数千个令牌。这项工作是this DosTips thread的一部分。实际代码可以在这3个帖子中找到:

原始答案 FOR变量仅限于一个字符,因此您的%% BL策略无法正常工作。变量区分大小写。根据微软的说法,你只能在一个FOR语句中捕获26个令牌,但如果你使用的不仅仅是alpha,那么它可能会获得更多。这很痛苦,因为你需要一个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年4月更新 几周前,我了解到以下规则(6年前编写)依赖于代码页。以下数据已针对 代码页437和850进行了验证。 更重要的是,扩展ASCII字符128-254的FOR变量序列没有匹配字节代码值,并通过代码页变化很大。事实证明,FOR / F变量映射基于底层的UTF-(16?)代码点。因此,与FOR / F一起使用时,扩展的ASCII字符用途有限。有关详细信息,请参阅http://www.dostips.com/forum/viewtopic.php?f=3&t=7703处的主题。

我进行了一些测试,并且可以报告以下(更新以回应jeb的评论)

大多数字符都可以用作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个令牌,正如我在查看Number of tokens limit in a FOR command in a Windows batch script时了解到的那样

@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

答案 1 :(得分:4)

我的回答由两部分组成。第一个是我在帮助写入 - 批处理 - 脚本 - 解析 - 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

此外,此版本不需要按顺序给出所需的字段。

修改

糟糕! for参数的东西分散了我的注意力,所以我不知道你的最后一个请求:

"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

您也可以删除标题数组的创建,因为您不需要标题! ;)

答案 2 :(得分:1)

使用%% @和%%`(此处未记录)作为起始变量,您可以获得的最大值为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
        )
    )
)

答案 3 :(得分:0)

当我再次阅读此问题并在最投票的答案中提出解决方案时,我认为可以开发一种更简单的方式来充分利用一系列嵌套的FOR / F命令。我开始编写这样一种方法,允许使用127 附加标记,将它们放在ASCII 128-254字符范围内。但是,当我的程序完成后,我发现&#34;自然&#34;中的ASCII字符。 128..254订单不能用于此目的......

然后,一群人对这个问题感兴趣,他们做了一系列的发现和发展,最终形成了一个允许在一系列中使用多个令牌(超过43,000!)的方法嵌套的FOR / F命令。您可以在this DosTips topic阅读有关此发现所涉及的研究和开发的详细说明。

最后,我使用新方法修改我的程序,因此它现在允许处理多达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

您可以从this site下载MakeForTokens.bat应用程序。