2014-09-03 92 views
0

如果給定一個包含基於行的數據的文本文件中的表,那麼建議將哪種方法轉換爲基於列的表? (例如CSV)。將基於行的記錄轉換爲基於列的記錄(CSV)

Input_data.txt:

Source =   X:\folder_abc 
Destination = Y:\Abc_folder 
Total bytes = 208,731,021 
MB per min =  256.5 
Source =   X:\folder_def 
Destination = Y:\xyz_folder 
Total bytes = 123,134,545 
MB per min =  326 
Source =   X:\folder_foo 
Destination = Y:\Baz_folder 
Total bytes = 24,344 
MB per min =  532 
...etc. 

所需的結果(只格式化標籤這裏的可讀性):

Source,    Destination,  Total bytes, MB per min 
"X:\folder_abc", "Y:\Abc_folder", "208,731,021", "256.5" 
"X:\folder_def", "Y:\xyz_folder", "123,134,545", "326" 
"X:\folder_foo", "Y:\Baz_folder", "24,344",  "532" 
... 

工具在我手上都是Windows批處理文件和PowerShell。首選.bat解決方案,因爲我在那裏更舒適,但如果那太迂迴或不透明,我們可以將它踢出去。

UPDATE,按照意見

我已經想通了如何將記錄到 & 變量,但不知道如何從點到轉操縱他們列。

for /f "tokens=1,2 delims==" %%a in ('findstr /c:"=" "%logfile%"') do (
    @echo %%a %%b 
) 

它只是發生在我,我可以做的每個文本文件一列,然後添加他們都在Excel中。原油,但也許可行

for /f "tokens=1,2 delims==" %%a in ('findstr /c:"=" "%logfile%"') do (
    @echo %%b >> %%a.csv 
) 

UPDATE-2(?):報價在期望的結果所有的值,如dbenham指出,不這樣做會引起問題。

+0

這個問題似乎是題外話,因爲它是要求別人做爲你工作。你有什麼嘗試? – 2014-09-03 19:58:52

+0

請在您的問題中顯示您嘗試的內容。 – 2014-09-03 20:06:11

+1

您的「所需格式」不正確 - 「總字節數」列中的逗號會影響格式。可能最好引用所有值。 – dbenham 2014-09-04 03:12:20

回答

1

這類似於Aacini的原來的答案,但我從來沒有在內存中存儲多個行。一個大的輸入文件會消耗大量的內存,這會降低腳本速度。只存儲一行可以避免這個問題。

另一個主要區別是我讓代碼發現啓動新行的列名,而不是硬編碼值。

我還使用不同的方法去掉標題中每個列名的尾部空格。我假設列名不包含任何以下字符::.,\/。我依賴於文件名不能以空格結尾的事實,因此~n修飾符規範化「名稱」以刪除任何尾隨空格。

我還使用"tokens=*"從值中剝離前導空格,以防萬一包含空格的值。

@echo OFF 
setlocal enableDelayedExpansion 

set "input=test.txt" 
set "output=result.csv" 

set "row=" 
set "header=" 
set "begin=" 
set "first=" 
(
    for /f "usebackq tokens=1* delims==" %%A in ("%input%") do for /f "tokens=*" %%C in ("%%B") do (
    if "!begin!" equ "%%A" (
     if not defined first (
     set first=1 
     echo !header:~1! 
    ) 
     echo !row:~1! 
     set "row=" 
    ) 
    set "row=!row!,"%%C"" 
    if not defined first for /f "delims=" %%H in ("%%A") do (
     if not defined begin set "begin=%%A" 
     set "header=!header!,"%%~nH"" 
    ) 
) 
    echo !row:~1! 
)>"%output%" 


編輯2014年12月5日

相同的算法可以在VBS或JScript更有力地實施,而且會更快。

或者你可以得到一個跳轉開始,並使用JREPL.BAT - 一個混合的JScript /批處理工具,執行正則表達式搜索和替換文本。它允許將用戶定義的JScript代碼片段合併到流程中,但在批處理上下文中執行。

整個命令可以放在一個lonnnnnggggggg行,但這將是非常醜陋的。相反,我使用batch line continuation來定義一個變量,其中包含大部分用戶定義的JScript代碼,並使用/JBEG來傳遞該變量。將雙引號文字傳遞給CSCRIPT是不可能的,所以我用'\x22'來代替。

該腳本需要將源文件作爲第一個也是唯一的參數進行傳遞,並使用相同的基本名稱將輸出寫入同一位置,擴展名爲.csv。

@echo off 
setlocal 
set beg=^ 
var begin, header='.', line='', q='\x22';^ 
function writeLn(){^ 
    if (header) output.WriteLine(header.substr(2));^ 
    header='';^ 
    if (line) output.WriteLine(line.substr(1));^ 
    line='';^ 
}^ 
function repl($1,$2){^ 
    if ($1==begin) writeLn();^ 
    if (!begin) begin=$1;^ 
    if (header) header+=','+q+$1+q;^ 
    line+=','+q+$2+q;^ 
    return false;^ 
} 
call jrepl "^(.+?) *= *(.*)" "repl($1,$2);" /jmatch /jbeg "%beg%" /jend "writeLn();" /f %1 /o "%~dpn1.csv" 
exit /b 

下面使用完全相同的JScript代碼,但是我用/JLIB選項直接從文件而不是從一個變量加載它。該腳本使用標準混合Jscript /批處理技術。這個選項允許我在代碼中使用雙引號。

@if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment 

::**** Batch code ******** 
@echo off 
call jrepl "^(.+?) *= *(.*)" "repl($1,$2);" /jmatch /jlib "%~f0" /jend "writeLn();" /f %1 /o "%~dpn1.csv" 
exit /b 

****** Jscript code ******/ 

var begin, header='.', line='', q='"'; 

function writeLn(){ 
    if (header) output.WriteLine(header.substr(2)); 
    header=''; 
    if (line) output.WriteLine(line.substr(1)); 
    line=''; 
} 

function repl($1,$2){ 
    if ($1==begin) writeLn(); 
    if (!begin) begin=$1; 
    if (header) header+=','+q+$1+q; 
    line+=','+q+$2+q; 
    return false; 
} 
+0

我接受了這個解決方案,因爲(在學習之後)我明白它在做什麼,它是唯一的.bat解決方案,它不需要對列標題有一定的瞭解。發佈的input_data.txt示例是簡化子集,並不是所有輸入文件都具有所有標題。這種方法是唯一處理所有這些問題的方法。 – 2014-09-05 20:59:00

+0

爲感興趣,我註釋的版本的dbenham的腳本是在https://github.com/maphew/Speed-test/blob/master/stats/xxcopylog_to_stats.bat – 2014-09-08 18:01:32

2

我意識到你不熟悉PowerShell,但它可能是你應該研究的東西。我在3年前就已經處於您的位置,現在90%的時間使用它來代替批處理文件。

在PowerShell中這相對簡單。您可以通過ForEach循環運行字符串數組,創建一個對象併爲每個屬性添加成員,然後在到達新的Source行時輸出前一個對象並啓動一個新對象。它會自動爲您創建一個數組,並且您可以將其傳送到Export-CSV

我會特別做的是將變量$Record設置爲空字符串。

然後我得到文件的內容,並將其傳遞到Where語句,以匹配RegEx匹配的每一行。這將創建自動變量$Matches,該變量沿着管線傳遞。該匹配將捕獲第一個冒號前的所有內容,然後是冒號後面的所有內容以及任何尾隨的空格。

這是通過管道連接到ForEach循環,每循環執行一次。它檢查是否$Matches[1](第一個冒號前的所有內容)='源'。如果是,則輸出$Record的當前內容,並創建一個新的$Record作爲具有一個屬性的自定義對象:'Source'= $Matches[2](第一個冒號和尾部空白後的所有內容)。如果$Matches[1]不等於'來源',則它將新屬性添加到$Record,其中屬性名稱爲$Matches[1],值爲$Matches[2]。爲了保持清潔,我在$Matches[2]上執行了.Trim()方法,以確保沒有前導或尾隨空格或換行符或任何奇怪的東西。

在我處理完所有事情後,我再次通過Where語句運行它以刪除空白記錄(例如我預先設置的第一個)。然後我再輸出$Record。至於你說你在一個CSV想這個我已經管道整個循環和後$RecordExport-CSV

$Record = "" 
$Output = @() 
Get-Content Input_data.txt |  Where{$_ -match "([^:]*):\s*?(\S.*)"}|Foreach{ 
    if($Matches[1] -eq "Source"){ 
     $Output += $Record 
     $Record = [PSCustomObject]@{'Source'=$Matches[2].trim()} 
    }else{ 
     $Record | Add-Member $Matches[1] $Matches[2].trim() 
    } 
}|?{![string]::IsNullOrEmpty($_)} | Export-Csv Output.csv -NoTypeInformation 
$Output += $Record 
$Output | Export-Csv Output.csv -NoTypeInformation -Append 

結果是這些內容的CSV文件:

"Source","Destination","Total bytes","MB per min" 
"X:\folder_abc","Y:\Abc_folder","208,731,021","256.5" 
"X:\folder_def","Y:\xyz_folder","123,134,545","326" 
"X:\folder_foo","Y:\Baz_folder","24,344","532" 

或者,如果你不」牛逼管使其出口CSV它只是顯示它在屏幕上:

Source     Destination    Total bytes    MB per min    
------     -----------    -----------    ----------    
X:\folder_abc    Y:\Abc_folder   208,731,021    256.5     
X:\folder_def    Y:\xyz_folder   123,134,545    326      
X:\folder_foo    Y:\Baz_folder   24,344     532 

編輯:好吧,你得到添加 - 錯誤會員使用它的方式。這意味着你有一個較舊版本的PowerShell。有2個解決方案。首先,我的建議是更新PowerShell。有時候這不是一個選項,所以沒關係,我們可以使用它。

如果您使用的是PS v1或v2,則使用添加成員的方式不起作用。我如何使用它是,如果您將對象傳遞給添加成員,然後指定2個字符串參數,它假定第一個是NotePropertyName,第二個是NotePropertyValue。你可以看到上面的樣子。因此,要怎麼做,如果不工作是使用了更詳細的語法:

Add-Member -InputObject $TargetVariable -MemberType NoteProperty -Name Name -Value Value 

在我們的情況下,它意味着我們更換添加會員行這樣的:

Add-Member -InputObject $Record -MemberType NoteProperty -Name $Matches[1] -Value $Matches[2].trim() 

你去了並改變了輸入。這很容易修復...將RegEx匹配從"([^:]*):\s*?(\S.*)"更改爲"([^=]*)=\s*?(\S.*)"。所以,把他們放在一起:

$Record = "" 
$Output = @() 
Get-Content Input_data.txt | Where{$_ -match "([^=]*)=\s*?(\S.*)"}|Foreach{ 
    if($Matches[1] -eq "Source"){ 
     If(![String]::IsNullOrEmpty($Record)){$Output += $Record} 
     $Record = [PSCustomObject]@{'Source'=$Matches[2].trim()} 
    }else{ 
     Add-Member -InputObject $Record -MemberType NoteProperty -Name $Matches[1] -Value $Matches[2].trim() 
    } 
} 
$Output += $Record 
$Output | Export-Csv C:\Temp\Output.csv -NoTypeInformation 

EDIT2:我想我忘記了-append是不是在舊版本的PowerShell的出口-CSV的選項。這可以通過收集所有數據並在最後輸出一次來實現。我已經更新了我的答案中的最後一個腳本,通過在頂部附近創建一個空數組$Output,然後在循環中,而不是僅在輸出完成時輸出$Record,我將它添加到數組中。我也修改了這一行以通過If語句來避免向數組添加空白記錄。然後在ForEach循環之後,我將最後一條記錄添加到數組中,最後將整個記錄數組輸出到CSV文件。

+1

很好解釋! – 2014-09-03 20:32:20

+0

我改變了你的意見,抱歉!第一個':'現在是'='以避免驅動器號出現問題。但即使沒有,我也無法讓你的腳本在我的機器上工作。 Input_data_colons.txt http://hastebin.com/raw/wosaligexe,腳本:http://hastebin.com/irahaqifuc.mel,錯誤信息:http://hastebin.com/vehadodore.tex,錯誤本身_「添加成員:無法找到接受參數'Destination'的位置參數。「_ – 2014-09-04 16:04:13

+0

好吧,您有一箇舊版本的PowerShell,我沒有說明這一點,但我已經更新了答案,它應該適用於您現在。 – TheMadTechnician 2014-09-04 16:27:41

2

使用純批處理文件可以輕鬆解決此問題,該文件可以創建多個數組,每個輸出文件(字段)每列一個。讀取輸入文件時,每次出現開始字段(本例中爲「源」)時,數組的索引都會增加,因此後續元素將存儲在各自陣列的正確位置。輸出只顯示同一行中每個數組的一個元素。

@echo off 
setlocal EnableDelayedExpansion 

set "header=" 
set "output=" 
set i=0 
for /F "tokens=1* delims==" %%a in (Input_data.txt) do (
    set "field=%%a" 
    set "field=!field:~0,-1!" 
    if "!field!" equ "Source" set /A i+=1 
    if !i! equ 1 (
     set "header=!header!,"!field!"" 
     set "output=!output!,"^^!!field![%%i]^^!"" 
    ) 
    for /F %%c in ("%%b") do set "!field![!i!]=%%c" 
) 

(
echo %header:~1% 
for /L %%i in (1,1,%i%) do echo %output:~1% 
) > Result.csv 

輸出例如:Arrays, linked lists and other data structures in cmd.exe (batch) script

編輯

"Source","Destination","Total bytes","MB per min" 
"X:\folder_abc","Y:\Abc_folder","208,731,021","256.5" 
"X:\folder_def","Y:\xyz_folder","123,134,545","326" 
"X:\folder_foo","Y:\Baz_folder","24,344","532" 

你可以在審查批處理文件陣列管理沒有數組新方法添加

後我讀過dbenham的評論,我意識到在這個數組中使用數組問題沒有必要,所以我相應地修改了我的原始解決方案;我還借爲了使用%%~Na在字段名的末尾,以消除空間dbenham的慣用伎倆:

@echo off 
setlocal EnableDelayedExpansion 

set "header=1" 
set "row=" 
(for /F "tokens=1* delims==" %%a in (Input_data.txt) do (
    if defined header set "header=!header!,"%%~Na"" 
    for /F "tokens=*" %%c in ("%%b") do set "row=!row!,"%%c"" 
    if "%%a" equ "MB per min " (
     if defined header echo !header:~2!& set "header=" 
     echo !row:~1! 
     set "row=" 
    ) 
)) > Result.csv 
+0

我不明白爲什麼要使用數組。如果文件很大(創建大型環境),它會減慢速度。應該在去除前導空格時使用''tokens = *「',以防萬一值包含空格。 – dbenham 2014-09-04 04:29:43

+0

@dbenham:你說得對!我相應地修改瞭解決方案。 – Aacini 2014-09-04 14:43:11

0

這工作與源數據:

@echo off 
(
for /f "usebackq tokens=1,* delims==" %%a in ("input_data.txt") do (
    if not defined header echo Source,Destination,Total bytes,MB per min&set header=1 
    for /f "tokens=*" %%c in ("%%b") do if "%%a"=="MB per min " (set/p=""%%c""<nul&echo() else (set/p=""%%c","<nul) 
) 
)>"output_data.txt" 

「output_data.txt」

Source,Destination,Total bytes,MB per min 
"X:\folder_abc","Y:\Abc_folder","208,731,021","256.5" 
"X:\folder_def","Y:\xyz_folder","123,134,545","326" 
"X:\folder_foo","Y:\Baz_folder","24,344","532" 
+1

你可能會移動'回聲源,...' FOR循環,並消除'如果沒有定義的標題'和'設置標題= 1' – Aacini 2014-09-04 14:47:17

+0

@Aacini是的,Aacini,這是真的。 – foxidrive 2014-09-04 15:08:13

+0

經過一番研究,我明白了FOR循環中發生了什麼。儘管如此,我迷失在'set/p'中。我瞭解效果 - 只在最後一個字段發出新的一行 - 而不是它如何工作。你能否詳細說明一下? – 2014-09-05 20:00:08