在文本文件中查找数字并使用命令行更改其行数据的符号

Find a number in text file and change signs for its row data using command line

我正在学习批处理脚本,我遇到的第一个任务是创建一个超过 1000 行的文本文件,类似于:

Organization, month,acct no.,data1,data2,data3,data4
orgA,Jan,1234,78900,78900,78900,78900

我需要帮助来编写一个批处理文件,该文件应该找到特定的 acct no.(例如:3456)并在 data1, data2,data3,data4[ 之前放置一个“-” =17=]

我试过: 1) 使用批处理命令:

for /F "tokens=1 delims=," %%a in (%source%) do SET "org=%%a"   
for /F "tokens=2 delims=," %%b in (%source%) do SET "month=%%b"  
for /F "tokens=3 delims=," %%c in (%source%) do SET "acct=%%c"
for /F "tokens=4 delims=," %%d in (%source%) do SET "data1=%%d"
for /F "tokens=5 delims=," %%e in (%source%) do SET "data2=%%e"
for /F "tokens=6 delims=," %%f in (%source%) do SET "data3=%%f"
for /F "tokens=7 delims=," %%g in (%source%) do SET "data4=%%g"

set search=3456
set replace=-%data1%


FOR /F "tokens=* delims=," %%i in ("%source%") do
(set newline=%%i
IF /i %acct% EQU %search%
set newline=!newline:%org%,%month%,%acct%,%replace%! 
echo !newline!>>%target%
)  

2)VBS:

@echo objFile.WriteLine strNewText
@echo objFile.CloseConst ForReading = 
@echo Const FileIn = "test.txt"
@echo Const FileOut = "test_adhoc.txt"  
@echo Set objFSO = CreateObject("Scripting.FileSystemObject")
@echo Set objFile = objFSO.OpenTextFile(FileIn, ForReading)
@echo strText = objFile.ReadAll
@echo objFile.Close
@echo strNewText = Replace(strText, "*,*,3456,*,*,*,*", "*,*,3456,-*,-*,-*,-  *")
@echo Set objFile = objFSO.OpenTextFile(FileOut, ForWriting)
@echo objFile.WriteLine strNewText
@echo objFile.Close

这里有一个可能的方法来做你想做的事情——只针对 整数 值(请参阅代码中的解释性注释 rem):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_FILE=.\data.csv"         & rem // (path to CSV file to modify)
set "_TMPF=%TEMP%\%_FILE%.tmp" & rem // (path to temporary file)
set "_ACCT=%~1" & rem // (account number to search, taken from first argument)

rem // Write modified CSV data to temporary file:
> "%_TMPF%" (
    rem // Reset flag to indicate header (first row):
    set "SKIP="
    rem // Read CSV file line by line and extract seven tokens (columns):
    for /F "tokens= 1-7 delims=, eol=," %%A in ('type "%_FILE%"') do (
        rem // Check whether line is header, skip it from modification in case:
        if defined SKIP (
            rem // Check whether current account number matches:
            if /I "%%C"=="%_ACCT%" (
                rem // Assemble first three call values (do not modify):
                set "PREF=%%A,%%B,%%C"
                rem /* Invert sign of remaining four (numeric) cell values;
                rem    instead, you could also simply write this:
                rem    `echo(%%A,%%B,%%C,-%%D,-%%E,-%%F,-%%G`, but this
                rem    would lead to `--` if a number is already negative: */
                set /A "VAL1=-%%D, VAL2=-%%E, VAL3=-%%F, VAL4=-%%G"
                rem // Return modified line:
                setlocal EnableDelayedExpansion
                echo(!PREF!,!VAL1!,!VAL2!,!VAL3!,!VAL4!
                endlocal
            ) else (
                rem // Account number does not match, so return original line:
                echo(%%A,%%B,%%C,%%D,%%E,%%F,%%G
            )
        ) else (
            rem // Line is the header, so return original line:
            echo(%%A,%%B,%%C,%%D,%%E,%%F,%%G
            rem // Next line is certainly not a header:
            set "SKIP=#"
        )
    )
)
rem // Replace original CSV file with temporary file:
> nul move /Y "%_TMPF%" "%_FILE%"

endlocal
exit /B

这是另一种方式——对于 decimal 值,这些值实际上被视为字符串(参见备注 rem):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_FILE=.\data-dec.csv"     & rem // (path to CSV file to modify)
set "_TMPF=%TEMP%\%_FILE%.tmp" & rem // (path to temporary file)
set "_ACCT=%~1" & rem // (account number to search, taken from first argument)

rem // Write modified CSV data to temporary file:
> "%_TMPF%" (
    rem // Reset flag to indicate header (first row):
    set "SKIP="
    rem // Read CSV file line by line and extract seven tokens (columns):
    for /F "tokens= 1-7 delims=, eol=," %%A in ('type "%_FILE%"') do (
        rem // Check whether line is header, skip it from modification in case:
        if defined SKIP (
            rem // Check whether current account number matches:
            if /I "%%C"=="%_ACCT%" (
                rem // Assemble first three call values (do not modify):
                set "PREF=%%A,%%B,%%C"
                rem // Invert sign of remaining four (numeric) cell values:
                set "VAL1=-%%D" & set "VAL2=-%%E" & set "VAL3=-%%F" & set "VAL4=-%%G"
                rem // Return modified line, avoiding doubled minus-signs:
                setlocal EnableDelayedExpansion
                echo(!PREF!,!VAL1:--=!,!VAL2:--=!,!VAL3:--=!,!VAL4:--=!
                endlocal
            ) else (
                rem // Account number does not match, so return original line:
                echo(%%A,%%B,%%C,%%D,%%E,%%F,%%G
            )
        ) else (
            rem // Line is the header, so return original line:
            echo(%%A,%%B,%%C,%%D,%%E,%%F,%%G
            rem // Next line is certainly not a header:
            set "SKIP=#"
        )
    )
)
rem // Replace original CSV file with temporary file:
> nul move /Y "%_TMPF%" "%_FILE%"

endlocal
exit /B

这种管理大文件的问题是批处理文件处理本来就很慢,所以任何可以加快处理速度的方法都是好的。

编辑更改最后四个数据的符号

第二次编辑: ...当这样的数据可能有小数点时

@echo off
setlocal EnableDelayedExpansion

set search=3456

rem Find the number of lines before the target one
for /F "delims=:" %%a in ('findstr /N "^.*,.*,%search%" source.txt') do set /A lines=%%a-1

rem Reading from the source file
< source.txt (

   rem Copy the lines previous to target one
   for /L %%i in (1,1,%lines%) do set /P "line=" & echo !line!

   rem Read and process the target line
   set /P "line="
   for /F "tokens=1-7 delims=," %%a in ("!line!") do (
      set "data1=-%%d" & set "data2=-%%e" & set "data3=-%%f" & set "data4=-%%g"
      echo %%a,%%b,%%c,!data1:--=!,!data2:--=!,!data3:--=!,!data4:--=!
   )

   rem Copy the rest of lines
   findstr "^"

) > output.txt

move /Y output.txt source.txt

在此代码中,目标行是通过 findstr 正则表达式在该行的第三个逗号分隔字段中搜索所需 acct no. 的一次操作中找到的。该程序的其余部分非常简单,不言自明...

如果您对任何命令有任何疑问,您可以使用 /?范围;例如:findstr /?

(    
for /f "tokens=1-7delims=," %%a in (yourfilename.txt) do (
 if "%%c"=="3456" (echo %%a,%%b,%%c,-%%d,-%%e,-%%f,-%%g
 ) else (echo %%a,%%b,%%c,%%d,%%e,%%f,%%g)
)
)>processedfilename.txt

应该可以。请注意,整个 for 命令都用括号括起来,以确保将 echoes 的输出重定向到已处理的文件名,该文件名不得与源数据文件名相同。

当然,如果需要,3456 可以用变量替换。

这是我使用的测试批次 - 它与我发布的代码完全相同,只是文件名构造适合我的测试系统。

@ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q43354291.txt"
SET "outfile=%destdir%\outfile.txt"
(    
for /f "tokens=1-7delims=," %%a in (%filename1%) do (
 if "%%c"=="3456" (echo %%a,%%b,%%c,-%%d,-%%e,-%%f,-%%g
 ) else (echo %%a,%%b,%%c,%%d,%%e,%%f,%%g)
)
)>"%outfile%"

GOTO :EOF

这是我使用的输入文件 - 它只是您的数据,有几行被复制并固定以适合 account=3456

Organization, month,acct no.,data1,data2,data3,data4
orgA,Jan,1234,78900,78900,78900,78900
orgA,Jan,3456,78900,78900,78900,78900
orgA,Jan,6789,78900,78900,78900,78900

这是输出文件

Organization, month,acct no.,data1,data2,data3,data4
orgA,Jan,1234,78900,78900,78900,78900
orgA,Jan,3456,-78900,-78900,-78900,-78900
orgA,Jan,6789,78900,78900,78900,78900

这似乎是您需要的。

注意: 标签是很久以后才添加到问题中的,所以这个答案应该被认为是非竞争性的。

PowerShell 实现简洁而强大的解决方案:

$acctNo = 3456

Import-Csv in.csv | ForEach-Object { 
  if ($_.'acct no.' -eq $acctNo) { 
    foreach($prop in (Get-Member -InputObject $_ data*)) {
      $_.$($prop.name) = '-' + $_.$($prop.name)
    }
  }
  $_ 
} # add, e.g., | Out-File -Encoding utf8 out.csv to save to a (different) file.
  • Import-Csv file 读取输入 CSV 文件并将每一行转换为自定义 对象 ,其属性对应于每一行的列值。

  • ForEach-Object cmdlet 处理每个这样的对象:

    • 自动变量$_表示每次迭代中手头的输入对象。
    • if ($_.'acct no.' -eq $acctNo) 检查感兴趣的帐号。
    • Get-Member -InputObject $_ data* 使用反射 return 名称以 data.
    • 开头的输入对象的所有属性
    • foreach(...) 循环处理所有匹配的属性。
    • $_.$($prop.name) = '-' + $_.$($prop.name) 通过在现有值前面加上 - 来更新每个匹配的 属性。

请注意,您不能将结果直接保存回 相同的 文件 - 除非您使用 (Import-Csv in.csv) 而不是 Import-Csv in.csv,但是意味着整个输入文件将被读入内存作为一个整体.