簡體   English   中英

Bash / Regex:當某些第一個字段以引號和逗號開頭時替換 CSV 文件中的第二個字段

[英]Bash / Regex: Replacing the second field in a CSV file when some of the first fields start with quotes and commas within those

這個問題是針對用 bash 編寫的代碼,但實際上更像是一個正則表達式問題。 我有一個文件( ARyy.txt ),其中包含 CSV 值。 我想用NaN替換第二個字段。 對於簡單的情況(示例中的第 1 行和第 2 行),這完全沒有問題,但對於第一個字段中有引號且其中有逗號的少數情況,這要困難得多。 這些引號字面上僅用於表示其中包含逗號(因此,如果引號僅在逗號存在時才存在,反之亦然)。 如果第一個字段中有逗號,則引號始終是第一個和最后一個字符。

這是我迄今為止所擁有的。 注意:請嘗試使用 sed 和一般格式來回答。 據我所知,有一種方法可以將awk用於 FPAT,但我需要理想情況下使用sed (或awk簡單用例)。

#!/bin/bash
LN=1                                             #Line Number 
while read -r LIN                                #LIN is a variable containing the line
    do
    echo "$LN: $LIN"
    ((LN++))
    if [ $LN -eq 1 ]; then 
        continue                                 #header line
    elif [[ {$LIN:0:1} == "\"" ]]; then          #if the first character in the line is quote
        sed -i '${LN}s/\",/",NaN/' ARyy.txt      #replace quote followed by comma with quote followed by comma followed by NaN
    else                                         #if first character doesn't start with a quote
        sed -i '${LN}s/,[^,]*/,0/' ARyy.txt; fi  
done < ARyy.txt

其他相關信息:從來沒有雙引號或嵌套引號或任何類似的東西引號內可以有多個逗號我總是替換第二個字段第二個字段始終只是輸入的數字(絕不是單詞或引號)

輸入示例:

Fruit, Weight, Intensity, Key 
Apple, 10, 12, 343 
Banana, 5, 10, 323 
"Banana, green, 10 MG", 3, 14, 444               #Notice this line has commas in it but it has quotes to indicate this) 

期望輸出:

Fruit, Weight, Intensity, Key 
Apple, NaN, 12, 343
 Banana, NaN, 10, 323 
"Banana, green, 10 MG", NaN, 14, 444             #second field changed to NaN and first field remains in tact

試試這個:

sed -E -i '2,$ s/^("[^"]*"|[^",]*)(, *)[0-9]*,/\1\2NaN,/' ARyy.txt

說明: sed -E調用“擴展”正則表達式語法,因此更容易使用帶括號的組。

  • 2,$ = 在第 2 行到文件末尾...
  • s/ = 替換...
    • ^ = 一行的開始
    • ("[^"]*"|[^",]*) =或者一個雙引號字符串字符串不包含任何雙引號或逗號
    • (, *) = 一個逗號,可能后跟一些空格
    • [0-9]* = 一個數字
    • , = 最后一個逗號
  • / = ...與...
    • \\1 = 第一個()組(即原來的第一個字段)
    • \\2 = 第二個()組(即逗號和空格)
    • NaN, = 不是數字,后面還有逗號
  • / = 替換結束

請注意,如果第一個字段可能包含轉義的雙引號和/或轉義的逗號(不在雙引號中),則第一個模式將必須更加復雜才能處理它們。

順便說一句,原版有一個我經常看到的反模式:逐行閱讀文件以決定如何處理該行,然后運行一些處理整個文件的程序以更改該行。 因此,如果您有一個千行文件,它最終會處理整個文件一千次(總共處理一百萬行)。 這就是所謂的“二次縮放”,因為它花費的時間與問題大小的平方成正比。 正如 布魯斯·道森所說

O(n^2) 是嚴重縮放算法的最佳點:足夠快以使其投入生產,但足夠慢以使其一旦到達那里就崩潰。

鑒於您的特定格式,特別是第一個字段中永遠不會有任何轉義的雙引號:

sed -E '2,$ s/^("[^"]*"|[^,]*),[^,]*/\1,NaN/' < input.csv > output.csv

這確實需要通用但非標准的-E選項來使用 POSIX 擴展正則表達式語法而不是默認的 Basic(不支持交替)。

一個(有點冗長的) awk想法替換了問題中發布的整個代碼集:

awk -F'"' '                         # input field separator = double quotes

function print_line() {             # print array
    pfx=""
    for (i=1; i<=4; i++) {
        printf "%s%s", pfx, arr[i]
        pfx=OFS
    }
    printf "\n"
}

FNR==1 { print ; next }             # header record

NF==1  { split($0,arr,",")          # no double quotes => split line on comma
         arr[2]=" NaN"              # override arr[2] with " NaN"
       }

NF>=2  { split($3,arr,",")          # first column in from file contains double quotes
                                    # so split awk field #3 on comma; arr[2] will
                                    # be empty
         arr[1]="\"" $2 "\""        # override arr[1] with awk field #1 (the double
                                    # quoted first column from the file
         arr[2]=" NaN"              # override arr[2] " NaN"
       }
       { print_line() }             # print our array
' ARyy.txt

對於示例輸入文件,這會生成:

Fruit, Weight, Intensity, Key
Apple, NaN, 12, 343
Banana, NaN, 10, 323
"Banana, green, 10 MG", NaN, 14, 444
while read -r LIN; do
    if [ $LN -eq 1 ]; then
        ((LN++))
        continue
    elif [[ $LIN == $(echo "$LIN" | grep '"') ]]; then
        word1=$(echo "$LIN" | awk -F ',' '{print $4}')
        echo "$LIN" | sed -i "$LN"s/"$word1"/\ NaN/ ARyy2.txt

    elif [[ $LIN == $(echo "$LIN" | grep -E '[A-Z][a-z]*[,]\ [0-9]') ]]; then
        word2=$(echo "$LIN" | cut -f2 -d ',')
        echo "$LIN" | sed -i "$LN"s/"$word2"/\ NaN/ ARyy2.txt
    fi
    echo "$LN: $LIN"
    ((LN++))
done <ARyy.txt

將輸入ARyy.txt復制到ARyy2.txt並將此文本文件用作輸出。
(從ARyy.txt讀取並寫入ARyy2.txt

  • 第一個elif $(echo "$LIN" | grep '"')檢查 LINE 是否以引號"開頭返回:
    在此處輸入圖片說明 選擇后,想要使用awk -F ',' '{print $4}獲取數字 3 並保存到變量word1 -F告訴awk分隔列每次遇到,所以6列中總的和數目3是在這就是為什么塔4 {print $4}

     echo "$LIN" | sed -i "$LN"s/"$word1"/\\ NaN/ ARyy2.txt
    • 然后使用sed$LN選擇行號。 變量/$word1/的數字3 替換為/NaN/但想要向/NaN/添加一個空格所以需要用/\\ NaN/轉義\\空格
    • 總是使用echo $LIN來獲取正確的LINE
  • 第二個elif $(echo "$LIN" | grep -E '[AZ][az]*[,]\\ [0-9]')返回: 使用正則表達式返回行

    $LIN只返回一行,如下所示: 在此處輸入圖片說明
    重要的是檢查 LINE 是否有這種模式Word + space + ONE Digit
    選擇后,這次想要使用cut -f2 -d ','獲取數字 10[second column] 並將其保存到變量word2 -f2選擇第二列, -d告訴cut使用,分隔每一列。

暫無
暫無

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

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