僅刪除 csv 檔案中雙引號數字中存在的逗號

僅刪除 csv 檔案中雙引號數字中存在的逗號

在文字檔案中,我想刪除,(逗號)和"(引號)(僅當雙引號包含以逗號分隔的數字時)。

56,72,"12,34,54",x,y,"foo,a,b,bar"

預期產出

56,72,123454,x,y,"foo,a,b,bar"

筆記:我僅將上面的行作為範例。我的文字檔案包含許多像上面這樣的行,雙引號內以逗號分隔的數字應該有所不同。那是,

56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"

預期輸出:

56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

雙引號內有許多n數字,用逗號分隔。並且保留包含字元的雙引號。

我喜歡sed文字處理工具。如果您sed為此 發布任何解決方案,我很高興。

答案1

如果 perl 沒問題,這裡有一個簡短的(可能是快速的,如果不一定簡單:))方法:

perl -pe 's:"(\d[\d,]+)":$1=~y/,//dr:eg' file

e運算子的標誌(s:::這只是另一種編寫方式s///)導致替換被視為每次都會計算的表達式。此表達式$1從正規表示式(已經缺少引號)中獲取捕獲並通過刪除 ( ) 所有逗號來翻譯 ( y///,也可以寫為) 它。為了取得翻譯字串的值(而不是翻譯的計數),必須使用標誌to 。tr////dry

對於那些感覺被 Perl 玷污的人來說,這裡是 Python 的等價物。 Python確實不是一個shell單行工具,但有時它可以被誘導進行合作。以下內容可以寫成一行(與for循環不同,循環不能如此),但水平滾動使其(甚至更)難以閱讀:

python -c '
import re;
import sys;
r=re.compile("\"(\d+(,\d+)*)\"");
all(not sys.stdout.write(r.sub(lambda m:m.group(1).replace(",",""),l))
    for l in sys.stdin)
' < file

答案2

這個(改編自這裡)應該做你需要的事情,儘管 @rici 的 Perl 更簡單:

$ sed -r ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;ta; s/""/","/g; 
          s/"([0-9]*)",?/\1,/g ' file
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454,
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

解釋

  • :a:定義一個名為 的標籤a
  • s/(("[0-9,]*",?)*"[0-9,]*),/\1/: 這個需要分解一下
    • 首先,使用這個結構:(foo(bar)), \1will befoobar\2will be bar
    • "[0-9,]*",?:符合 0 個或多個0-9,,後面跟著 0 或 1 ,
    • ("[0-9,]*",?)*:配對以上 0 個或更多。
    • "[0-9,]*:配對 0 個或多個緊隨 a 之後的0-9,"
  • ta;: 返回標籤a並再次運行如果替換成功。
  • s/""/","/g;: 後期處理。用。""","
  • s/"([0-9]*)",?/\1,/g:刪除數字周圍的所有引號。

用另一個例子可能比較容易理解:

$ echo '"1,2,3,4"' | sed -nr ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;p;ta;'
"1,2,34"
"1,234"
"1234"
"1234"

因此,雖然您可以找到緊接在引號後面且後跟逗號和另一個數字的數字,但請將這兩個數字連接在一起並重複該過程,直到不再可能為止。

在這一點上,我認為提及info sed描述高級功能的部分中出現的引用是有用的,例如上面使用的標籤(感謝查找 if @Braiam):

在大多數情況下,使用這些命令表明您最好使用“awk”或 Perl 等語言進行程式設計。

答案3

對於 CSV 數據,我會使用具有真正 CSV 解析器的語言。以 Ruby 為例:

ruby -rcsv -pe '
  row = CSV::parse_line($_).map {|e| e.delete!(",") if e =~ /^[\d,]+$/; e} 
  $_  = CSV::generate_line(row)
' <<END
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
END
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

答案4

使用(以前稱為 Perl_6)

~$ raku -pe 's:g/ \" ~ \" (\d+) ** 2..* % "," /{$0.join}/;'  file

輸入範例:

56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"

範例輸出:

56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"

Raku 是 Perl 系列中的一種程式語言,具有許多強大的正規表示式功能。請參閱下面的 URL,以了解此答案的總體概述:

https://unix.stackexchange.com/a/722570/227738

在上面的程式碼中,識別了數字並刪除了嵌入的逗號。正規表示式利用了以下事實:嵌套結構可以用 Raku 的新 ~ 波形符號(嵌套)符號表示,這\" ~ \" [\d+]意味著「一個或多個數字被「雙引號」包圍。

此外,重複結構%可以用 Raku 的新修改重複結構量詞來表示。符號 [\d+] ** 2..* % "," 表示「用,逗號分隔的一個或多個數字,此模式重複** 2..*兩次或多次。[如果碰巧有尾隨分隔符號(例如逗號),在語法中使用 a%%代替]。%

這只是一個開始。帶有備用分隔符號、嵌入換行符、嵌入逗號、可能為空白字段等的 CSV 檔案確實需要由真正的 CSV 解析器(如 RakuText::CSV模組)來處理。有關詳細信息,請參閱下面的連結。

https://docs.raku.org/language/regexes
https://raku.land/github:Tux/Text::CSV
https://raku.org

相關內容