簡體   English   中英

如何使用 Powershell 或 Batch 將 csv 中的所有值相乘?

[英]How can I multiply all values in a csv using Powershell or Batch?

我有很多個人身份證號碼,需要自動下載。 我不想在我的機器上存儲數千個唯一的個人 ID,而是想將所有 ID 乘以隨機 integer,保存結果並刪除原始文件。

這將最終出現在 Excel 中,但由於組織政策,VBA 不是一個選項。 我無法安裝任何東西——僅限於 Windows 附帶的東西。 據我了解,Excel 的電源查詢在閱讀后無法刪除源文件,但我可能是錯的 - 如果是這樣,那可能是 go 的另一個方向。

csv 樣品(ID 不是真實的)

3465549,2504526,2504566,3465552,2506339,3465551,2317719,2506451,3465547,3465550
,,,,,,,,,
,,,,,,,,,
,,,,,,,,,
3465546,3465544,2506443,3465542,3465545,2506696,3465543,2506775,,
,,,,,,,,,
,,,,,,,,,
2356880,2356779,2356828,2356944,2356915,2356917,2356928,2356783,2356850,2356781
,,,,,,,,,
3441512,2467140,2571636,2571767,2571812,2467147,3441514,2571648,3441513,3441511
,,,,,,,,,
2380632,2380582,2380629,2380586,2380613,2380537,2380601,2380636,2380539,2380471
3221426,3221424,3221425,3221429,,,,,,
2571011,2486766,1704643,2571037,2571040,2571018,2571043,2570940,2486742,2486770
2950233,2950214,2950268,3152046,3152050,3152047,3152049,3152051,3152048,2950384

特征

  • 它可以是任意數量的行,可能在幾十萬之間。
  • 許多行是空白的。 需要保留空白行,因為需要將另一列導入到 id 旁邊的 go (空白行表示沒有人與導入列中的此值對應)
  • 許多行的 id 編號少於 10 個 - 換句話說,少於最大值。

所需 output

假設隨機 integer 為 2,這將產生:

6931098,5009052,5009132,6931104,5012678,6931102,4635438,5012902,6931094,6931100
,,,,,,,,,
,,,,,,,,,
,,,,,,,,,
6931092,6931088,5012886,6931084,6931090,5013392,6931086,5013550,,
,,,,,,,,,
,,,,,,,,,
4713760,4713558,4713656,4713888,4713830,4713834,4713856,4713566,4713700,4713562
,,,,,,,,,
6883024,4934280,5143272,5143534,5143624,4934294,6883028,5143296,6883026,6883022
,,,,,,,,,
4761264,4761164,4761258,4761172,4761226,4761074,4761202,4761272,4761078,4760942
6442852,6442848,6442850,6442858,,,,,,
5142022,4973532,3409286,5142074,5142080,5142036,5142086,5141880,4973484,4973540
5900466,5900428,5900536,6304092,6304100,6304094,6304098,6304102,6304096,5900768

我試過的

這個答案看起來很有希望,但它只處理一列。 我不知道如何調整它以使其接受任意數量的列。

@echo off
setlocal enabledelayedexpansion
FOR /F "tokens=1-18* delims=," %%A IN (mycsv.csv) DO (
set sum1="%%~C"
set /a cole=!sum1! * 2
echo %%~A,%%~B,%%~C,%%~D,!cole!
) >> output.csv

我不太了解這個基於 Powershell 的答案。

$csv = Import-Csv mycsv.csv
foreach ($row in $csv) {
  [int]$row.B *= -1
  [int]$row.F *= -1
}
$csv | Export-Csv output.csv

如果我嘗試使用它,它會返回

powershell : Exception setting "B": "The property 'B' cannot be found on this object. Verify that the property exists and can be 
At line:1 char:1
+ powershell -ep Bypass .\t.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Exception setti...sts and can be :String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
set."
At C:\Users\user\OneDrive\Personal\t.ps1:3 char:3
+   [int]$row.B *= -1
+   ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting
 
Exception setting "F": "The property 'F' cannot be found on this object. Verify that the property exists and can be 
set."
At C:\Users\user\OneDrive\Personal\t.ps1:4 char:3
+   [int]$row.F *= -1
+   ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting
 
Exception setting "B": "The property 'B' cannot be found on this object. Verify that the property exists and can be 
set."
At C:\Users\user\OneDrive\Personal\t.ps1:3 char:3
+   [int]$row.B *= -1
+   ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting
 
Exception setting "F": "The property 'F' cannot be found on this object. Verify that the property exists and can be 
set."
At C:\Users\user\OneDrive\Personal\t.ps1:4 char:3
+   [int]$row.F *= -1
+   ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

我假設 B 和 F 只是指 csv 中的行,但如果我理解錯誤消息(我可能不會),它們是必須定義的名稱(如何定義?)我還需要這個在所有行中工作有值,並跳過行內的空行和空列。 所以硬編碼要相乘的行和列在這里對我不起作用。

在黑暗中摸索,替換

  [int]$row.B *= -1
  [int]$row.F *= -1

  [int]$row *= -1

返回

powershell : Cannot convert the "@{12=18}" value of type "System.Management.Automation.PSCustomObject" to type "System.Int32".
At line:1 char:1
+ powershell -ep Bypass .\t.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Cannot convert ..."System.Int32".:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
 
At C:\Users\user\OneDrive\Personal\t.ps1:3 char:3
+   [int]$row *= -1
+   ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : ConvertToFinalInvalidCastException
 
Cannot convert the "@{12=}" value of type "System.Management.Automation.PSCustomObject" to type "System.Int32".
At C:\Users\user\OneDrive\Personal\t.ps1:3 char:3
+   [int]$row *= -1
+   ~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : ConvertToFinalInvalidCastException

假設 Csv 具有固定數量的列,將 Csv 的每一行上的值相乘的邏輯可以是這樣的:

function MultiplyRows {
[cmdletbinding()]
param(
    [parameter(Mandatory, ValueFromPipeline)]
    [object]$InputObject,
    [int]$MultiplyBy = 2
)

    begin { $isFirstObject = $true }
    process {
        if($isFirstObject) {
            $headers = $InputObject.PSObject.Properties.Name
            $isFirstObject = $false
        }

        $out = [ordered]@{}
        foreach($prop in $headers) {
            if(-not ($thisValue = [int]$InputObject.$prop)) {
                $out[$prop] = $null
                continue
            }
            $out[$prop] = $thisValue * $MultiplyBy
        }
        [pscustomobject]$out
    }
}

Import-Csv ... | MultiplyRows -MultiplyBy 3 | ConvertTo-Csv

如果 Csv 沒有標題,您將需要確定它有多少列並使用Import-Csv上的-Header參數。

中可用的for /F循環將多個連續的分隔符組合為一個,因此您不能將它與逗號一起用作分隔符來讀取具有空單元格/值/字段的 CSV 文件。 但是,您可以使用它來讀取整行,並使用標准for循環以逗號分隔每行,因為沒有字符? , *< , > (因此for不需要訪問文件系統來派生匹配的文件名,因此維護給定的字符串):

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_FILE=%~dp0data.csv"         & rem // (full path to target file)
set "_SEPC=,"                     & rem // (separator character)
set "_HEAD="                      & rem // (set to something if header)
set /A "_MULT=%RANDOM%%%(1<<7)+1" & rem // (multiplicator; result < 2 Gi!)
set /A "_ZPAD=0"                  & rem // (optionally zero-pad to width)

setlocal EnableDelayedExpansion
if defined _HEAD (set "HEAD=") else (set "HEAD=#")
rem // Read file line by line:
for /F usebackq^ delims^=^ eol^= %%J in ("!_FILE!") do (
    if defined HEAD (
        set "LINE=%%J" & set "COLL=%_SEPC%"
        rem // Iterate through separated items, even blank ones:
        for %%I in ("!LINE:%_SEPC%=" "!") do (
            rem // Retain blank items in case:
            if not "%%~I"=="" (
                rem set /A "ITEM=%%~I*_MULT"
                rem // Multiply item and correctly handle zero-padded numbers:
                set "ITEM=00000000%%~I" & set "ITEM=1!ITEM:~-9!"
                set /A "ITEM%%=1000000000, ITEM*=_MULT"
                rem // Optionally zero-pad resulting number:
                if %_ZPAD% gtr 0 (
                    if "!ITEM:~,-%_ZPAD%!"=="" (
                        set "ITEM=000000000!ITEM!" & set "ITEM=!ITEM:~-10!"
                        set "ITEM=!ITEM:~-%_ZPAD%!"
                    )
                )
                rem // Append new number to new line string:
                set "COLL=!COLL!%_SEPC%!ITEM!"
            ) else set "COLL=!COLL!%_SEPC%"
        )
        rem // Return current altered line string:
        echo(!COLL:~2!
    ) else set "HEAD=#" & echo(%%J
)
endlocal

endlocal
exit /B

在頂部,將變量_MULT設置為乘法因子,以便結果值始終小於 2 31 − 1,否則可能會因溢出而導致負值。

變量_ZPAD定義了結果產品被填充到的寬度,方法是在它們前面加上足夠的零。 位數較多的數字不會被截斷。 零的最大數量為 10。當_ZPAD設置為零或更少時,不會發生填充。

變量_HEAD必須設置為某個值,以防 CSV 文件包含要保留的標題。

請注意,當 CSV 文件包含(帶引號的)字符串和/或單獨包含分隔符的帶引號的值時,此腳本將失敗。

該腳本,我們稱之為multiply.bat ,在控制台中返回結果行。 要將它們寫入文件,請使用重定向(假設 CSV 文件是當前目錄中的data.csv ):

multiply.bat "data.csv" > "data_NEW.csv"

要覆蓋原來的 CSV 文件,只需在之后執行以下命令行:

move /Y "data_NEW.csv" "data.csv"

這是我會這樣做的方式:

@echo off
setlocal EnableDelayedExpansion

rem Define the factor, use %random% here
set /A "factor=2"

(for /F "delims=" %%a in (input.txt) do (

   rem Get a line and eliminate multiple commas
   set "in=%%a"
   set "in=!in:,,=!"
   if "!in:~-1!" equ "," set "in=!in:~0,-1!"

   if not defined in (
      echo ,,,,,,,,,
   ) else (
      call :multiplyRow
      echo !out!
   )

)) > output.txt
goto :EOF


:multiplyRow

rem Multiply the numbers by the factor and count missing commas
set "out="
set /A "n=9,num=factor*%in:,=" & set "out=!out!!num!," & set /A "n-=1,num=factor*%" & set "out=!out!!num!"

rem Insert missing commas
for /L %%i in (1,1,%n%) do set "out=!out!,"

exit /B

“魔術線” in:MultiplyRow 做了幾件事:它將%in%字符串中的每個逗號分隔的數字乘以因子,並將每個結果連接到由逗號分隔out字符串。 它還為每個處理的數字遞減逗號計數器。 如果您取消@echo off (以及重定向到output.txt文件)並仔細查看執行的代碼,您可能會以更清晰的方式欣賞這種機制。

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory, destination directory
rem filenames, output filename are names that I use for testing and deliberately include names
rem which include spaces to make sure that the process works using such names.
rem These will need to be changed to suit your situation.

SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
SET "filename1=%sourcedir%\q70944027.txt"
SET "outfile=%destdir%\outfile.txt"

SET /a factor=2

SET "line="
(
FOR /f "usebackqdelims=" %%b IN ("%filename1%") DO (
 IF DEFINED line (
  SET "line=%%b"
  CALL :process
 ) ELSE (ECHO %%b&SET "line=x")
)
)>"%outfile%"

GOTO :EOF

:process
IF "%line:,=%" == "" ECHO %line%&GOTO :eof
SET "outline="
FOR %%c IN ("%line:,=" "%") DO IF "%%~c" neq "" (SET /a column=%factor% * %%~c&SET "outline=!outline!,!column!"
                                ) ELSE (SET "outline=!outline!,")
ECHO %outline:~1%
GOTO :eof

沒有提到任何行中是否可能缺少單列或數據是否可能包含前導零。

for語句加上括號允許將否則控制台 output 重定向到 output 文件。

for從源文件中讀取每一行。 如果變量line被初始化為,那么第一行只是簡單地反芻並且line設置為非空,因此可以通過 header 行。 最初將line設置為某個值將處理每一行(即沒有 header 行)。

line設置為從文件中讀取的行的文本,並由一個名為process的子例程處理。

process首先檢查line是否包含逗號,方法是用空替換每個逗號並檢查空字符串。 如果測試成功,則將逗號行反芻。

否則,將line中的每個逗號替換為" "並引用結果字符串; 用一個簡單for處理這個,所以每個數據列都將被引用,每個空列將由""表示; 全部用空格隔開。

通過將每個術語附加其處理后的形式和每個空元素附加任何內容來重建每一列

Echo顯結果,除了第一個字符是逗號。

這在往返測試中幸存下來。 源數據在 TextIn.CSV 中,結果相乘的數據存儲在 TextOut.CSV 中。 然后通過相同的過程發送TextOut.CSV除以相同的隨機數。 function ProcessFile 采用“* 2”等值乘以 2,用“/2”等值除以 2。傳遞給它的第二個值是源文件的名稱。 我在此頁面上找到了公式 SET /A test=%RANDOM% * 100 / 32768 + 1: 如何在 BATCH 腳本中使用隨機數?

有幾件事情需要考慮:

  1. 此代碼不會嘗試處理 CSV 文件中的 header 行。
  2. 如果源文件中有雙引號,我不確定結果。 可以重寫 FOR 命令以使用 USEBACKQ 參數,但這只會將其更改為引起問題的撇號。
  3. 它並不是運行速度最快的代碼。 看看它對具有 5000 行的 CSV 的作用將會很有趣。
  4. 我正在嘗試通過將 FOR 命令中的 EOL function 設置為與 DELIMS 相同的字符來禁用它。 SS64.com 的 FOR 命令文檔說 DELIMS 在 EOL 之前處理,因此理論上逗號在 EOL 看到之前被 DELIMS 抓取。 它適用於我一直在使用的代碼,但我找不到有人說你可以通過這種方式禁用 EOL。
@ECHO OFF & SETLOCAL ENABLEDELAYEDEXPANSION & GOTO :Start

:ParseLine
    SET NewLine=
    SET Line=%~1
    SET Line=!Line:,,=,$#@,!
    SET Line=!Line:,,=,$#@,!
    IF "!Line:~0,1!" EQU "," SET Line=$#@!Line!
    IF "!Line:~-1!" EQU "," SET Line=!Line!$#@
    FOR /L %%N IN (1, 1, 10000) DO (
        FOR /F "EOL=, TOKENS=1* DELIMS=," %%B IN ("!Line!") DO (
            IF "%%B" EQU "$#@" (
                SET Token=
            ) ELSE (
                SET /A Token = %%B !MathOp!
            )
            SET NewLine=!NewLine!!Token!,
            SET Line=%%C
        )
        IF "!Line!" EQU "" (
            ECHO !NewLine:~0,-1!
            GOTO :EOF
        )
    )
GOTO :EOF

:ProcessFile
    SET MathOp=%~1
    FOR /F "EOL= TOKENS=* DELIMS=" %%L IN (%~2) DO (
        if "%%L" NEQ "" (
            CALL :ParseLine "%%L"
        ) ELSE ECHO;
    )
GOTO :EOF

:Start
SET /A RandomValue=%RANDOM% * 100 / 32768 + 1
ECHO RandomValue=%RandomValue%
CALL :ProcessFile "* %RandomValue%" TextIn.CSV > TextOut.CSV
CALL :ProcessFile "/ %RandomValue%" TextOut.CSV > RoundTrip.CSV
FC TextIn.CsV RoundTrip.CSV

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM