使用 Perl 從大型 CSV 文件中刪除特定的 CSV 列和重複的行
我有一個大的 CSV 文件(300MB+),我只想使用 Perl 刪除第 2,3 和 6-8 列並刪除重複的行:
注 1:所有列都由
,
(逗號)分隔,但有時我的單元格值包含一個,
或多個,
並由分隔"
(見最後一行,第 9 列和第 10 列);因此我希望仍然能夠處理 input.csv 文件,即使它,
在單元格內:注意 2:我添加了 input.csv 和 output.csv 文件的連結:
Col1,Col2,Col3,Col4,Col5,Col6,Col7,Col8,Col9,Col10 info 1,info 2,info 3,...,info 10 address 1,address 2,....,address 10 city 1,city 2,city 3,city 4,city 5,city 6,city 7,city 8,"city 9, extra","city 10, new"
Col1,Col4,Col5,Col9,Col10 info 1,info 4,info 5,info 9,info 10 address 1,address 4,address 5,address 9,address 10 city 1,city 4,city 5,"city 9, extra","city 10, new"
我找到了一個 Perl 命令,可以使用正則表達式刪除最後一列,但不知道它是否足夠好或如何調整它以適合我的情況(任何其他建議都非常受歡迎!):
perl -pe 's/.*\K,.*//'
是否可以使用 Perl 僅刪除第 2,3 和 6-8 列並刪除任何重複的行?
PS:更新了 input.csv 文件以包含重複的行
謝謝!
最簡單的方法是使用miller aka
mlr
,它是處理 CSV、json 和其他一些輸入或輸出格式數據的好工具。例如:$ mlr --csv --implicit-csv-header --headerless-csv-output \ cut -x -f 2,3,6,7,8 \ then uniq -a input.csv Col1,Col4,Col5,Col9,Col10 info 1,info 4,5,9,info 10 address 1,4,5,9,address 10 city 1,4,5,9,city 10
同時使用
--implicit-csv-header
和--headerless-csv-output
選項可以有效地忽略標題行(即,將其與其他數據行一樣對待)並允許我指定要按數字而不是名稱剪切的欄位。我必須編輯您的範例 input.csv 文件以在缺失的欄位中添加一些垃圾數據。
mlr
否則會抱怨的。我還添加了一個重複的輸入行來測試重複消除是否有效。$ cat input.csv Col1,Col2,Col3,Col4,Col5,Col6,Col7,Col8,Col9,Col10 info 1,info 2,info 3,info 4,5,6,7,8,9,info 10 info 1,info 2,info 3,info 4,5,6,7,8,9,info 10 address 1,address 2,3,4,5,6,7,8,9,address 10 city 1, city 2,3,4,5,6,7,8,9,city 10
如果你想用 perl 來做:
- 如果您只需要處理簡單的逗號分隔輸入:
$ perl -F, -lane ' next if $seen{$_}++; splice @F,5,3; splice @F,1,2; print join ",", @F' input.csv Col1,Col4,Col5,Col9,Col10 info 1,info 4,5,9,info 10 address 1,4,5,9,address 10 city 1,4,5,9,city 10
這使用 perl 的
-a
選項將每個輸入行自動拆分為一個名為@F
. 該-F
選項告訴它使用什麼分隔符。注意 1:perl 數組從零開始,而不是一…所以數組元素 5 是第 6 列。
splice @$row, 5, 3
從元素 5 開始的數組中刪除三個元素(即第 6、7、8 列)。詳情請參閱perldoc -f splice
。注意2:我在這里以相反的順序刪除列(即編號較高的列在編號較低的列之前)。否則,如果我在刪除第 5、6、7 列之前刪除了第 2 列和第 3 列,第一次刪除將導致這些列重新編號(到 3、4、5)
- 使用Text::CSV處理任何有效的 CSV(包括包含逗號的多行引用列):
$ perl -MText::CSV -e ' my $csv = Text::CSV->new(); while (my $row = $csv->getline(*ARGV)) { next if $seen{join ",", @$row}++; splice @$row, 5, 3; splice @$row, 1, 2; $csv->say(*STDOUT, $row); }' input.csv Col1,Col4,Col5,Col9,Col10 "info 1","info 4",5,9,"info 10" "address 1",4,5,9,"address 10" "city 1",4,5,9,"city 10"
這裡有四點值得注意:
Text::CSV
不是核心 perl 模組,因此需要安裝。它為大多數(如果不是全部)Linux 發行版打包。例如在 Debian 上,您可以使用sudo apt-get install libtext-csv-perl
. 否則,您可以cpan
使用 perl 附帶的命令安裝它。- Text::CSV 的
getline()
方法($row = $csv->getline(*ARGV)
如上)返回對數組的引用,或 arrayref。這是一個指向整個數組的標量值(有關更多資訊,請參閱man perlref
和man perldata
)。$row
上面的程式碼中包含了arrayref。使用/操作 $row 對引用本身起作用,而不是它所引用的數據。因此,例如,$row2 = $row
製作參考的副本,而不是數據的副本。兩個 ref 都指向相同的數據。@$row
將 arrayref 作為數組“取消引用”,以便可以像使用任何其他數組一樣使用它。*ARGV
in是一個特殊的getline(*ARGV)
文件句柄,它從命令行上給出的所有文件名參數中讀取輸入(這些參數儲存在 perl 中名為 @ARGV 的數組中)。假設非文件名參數(例如選項,如果您的腳本有處理選項的程式碼)已經被處理並從@ARGV 中刪除。不存在或無法打開的文件名(例如由於權限)將產生錯誤消息。簡而言之,它從您提供的一個或多個文件名中讀取。的參數-
被視為標準輸入,因此它可以從文件、標準輸入或兩者讀取輸入。這是一個非常簡單且原始的範例,說明了 Text::CSV 的功能以及如何使用它。閱讀手冊頁以獲取更多詳細資訊和範例。
如您在上面的範例輸出中所見,預設情況下,Text::CSV 將引用包含空格的文本欄位。如果您不希望它這樣做,您可以通過將
quote_space
屬性設置為零來覆蓋它….當您使用以下new
方法創建 $csv 對象時:my $csv = Text::CSV->new({ quote_space => 0 });
或之後:
my $csv = Text::CSV->new(); $csv->quote_space(0);
輸出將是這樣的:
Col1,Col4,Col5,Col9,Col10 info 1,info 4,5,9,info 10 address 1,4,5,9,address 10 city 1,4,5,9,city 10