[英]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
$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.