Использование SED или AWK для удаления всех кавычек в определенном столбце CSV - PullRequest
0 голосов
/ 30 октября 2019

У меня есть файл с кучей строк CSV со значениями с кавычками и без, например:

"123","456",,17,"hello," how are you this, fine, highly caffienated morning,","2018-05-29T18:58:10-05:00","XYZ", 
"345","737",,16,"Heading to a "meeting", unprepared while trying to be "awake","2018-05-29T18:58:10-05:00","ACD",

Столбец пятый - это текстовый столбец, который экранирован или не экранирован двойнымцитаты. Я пытаюсь избавиться от всех кавычек в этом столбце, чтобы это выглядело так:

"123","456",,17,"hello, how are you this, fine, highly caffeinated morning,","2018-05-29T18:58:10-05:00","XYZ", 
"345","737",,16,"Heading to a meeting, unprepared while trying to be awake","2018-05-29T18:58:10-05:00","ACD",

Есть идеи, как этого добиться с помощью SED или AWK, или каких-либо других инструментов Unix? Очень признателен!

Ответы [ 4 ]

2 голосов
/ 30 октября 2019

С помощью awk вы можете сделать что-то подобное, чтобы избежать очень сложного регулярного выражения . Тот факт, что разбит только пятый столбец, что предыдущие столбцы не содержат запятых и что мы знаем, что существует фиксированное количество столбцов, облегчает восстановление:

Отредактировано с помощью gsubдля переносимости, предложенной Эд Мортоном

awk '
    BEGIN{FS=OFS=","}
    {
        for(i=6; i<=NF-3;i++){
            $5 = $5 FS $i
        }
    }
    {
         gsub(/"/, "", "g", $5)
    }
    {print $1,$2,$3,$4,"\""$5"\"",$(NF-2),$(NF-1),$NF}
    ' <file>

Вывод:

"123","456",,17,"hello, how are you this, fine, highly caffienated morning,","2018-05-29T18:58:10-05:00","XYZ", 
"345","737",,16,"Heading to a meeting, unprepared while trying to be awake","2018-05-29T18:58:10-05:00","ACD",

Если вы хотите экранировать кавычки, вы можете использовать это:

awk '
    BEGIN{FS=OFS=","}
    {
        for(i=6; i<=NF-3;i++){
            $5 = $5 FS $i
        }
    }
    {
         gsub(/^"|"$/,"",$5);
         gsub(/"/,"\\\"",$5);
         $5="\""$5"\"";
    }
    {print $1,$2,$3,$4,$5,$(NF-2),$(NF-1),$NF}
    ' <file>

Вывод:

"123","456",,17,"hello,\" how are you this, fine, highly caffienated morning,","2018-05-29T18:58:10-05:00","XYZ", 
"345","737",,16,"Heading to a \"meeting\", unprepared while trying to be \"awake","2018-05-29T18:58:10-05:00","ACD",
0 голосов
/ 30 октября 2019

С GNU awk для 3-го аргумента для match () и предполагая, что вы знаете, сколько полей должно быть в каждой строке:

$ cat tst.awk
BEGIN {
    numFlds  = 8
    badFldNr = 5
}
match($0,"^(([^,]*,){"badFldNr-1"})(.*)((,[^,]*){"numFlds-badFldNr"})",a) {
    gsub(/"/,"",a[3])
    print a[1] "\"" a[3] "\"" a[4]
}

$ awk -f tst.awk file
"123","456",,17,"hello, how are you this, fine, highly caffienated morning,","2018-05-29T18:58:10-05:00","XYZ",
"345","737",,16,"Heading to a meeting, unprepared while trying to be awake","2018-05-29T18:58:10-05:00","ACD",

С другими awk вы можете сделать то же самое с парой вызововto match () и переменные вместо массива.

0 голосов
/ 30 октября 2019

На ваш вопрос очень сложно ответить в общем виде. Чтобы привести пример:

 "a","b","c","d" 

Как это интерпретируется (если мы удалим кавычки из интересующих областей):

"a","b","c","d"  (4 fields)
"a,b","c","d"    (3 fields, $1 messed up)
"a","b,c","d"    (3 fields, $2 messed up)
"a","b","c,d"    (3 fields, $3 messed up)
"a,b,c","d"      (2 fields, $1 messed up)
"a,b","c,d"      (2 fields, $1 and $2 messed up)
"a","b,c,d"      (2 fields, $2 messed up)
"a,b,c,d"        (1 field , $1 messed up)

Единственный способ решить эту проблему - это иметьследующие знания:

  • Сколько полей у моего CSV есть
  • Максимальное количество полей:
  • Мы знаем, какое поле испорчено

Следующая программа awk поможет вам исправить это:

$ awk 'BEGIN{ere="[^,]*|\042[^\042]"}
       { head=tail=""; mid=$0 }
       # extract the head which is correct
       (n>1) {
          ere_h="^"
          for(i=1;i<n;++i) ere_h = ere_h (ere_h=="^" ? "",",") "(" ere ")"
          match(mid,ere_h); head=substr(mid,RSTART,RLENGTH)
          mid = substr(mid,RLENGTH+1)
       }
       # extract the tail which is correct
       (nf>n) {
          ere_t="$"
          for(i=n+1;i<=nf;++i) ere_t = "(" ere ")" (ere_h=="$" ? "",",") ere_t
          match(mid,ere_t); tail=substr(mid,RSTART,RLENGTH)
          mid = substr(mid,1,RSTART-1)
       }
       # correct the mid part
       { gsub(/\042/,"",mid)
         mid = (mid ~ /^,/) ? ( ",\042" substr(mid,2) ) : ( "\042" mid )
         mid = (mid ~ /,$/) ? ( substr(mid,1,length(mid)-1) "\042," ) : (mid "\042" )
       }
       # print the stuff
       { print head mid tail }' n=5 nf=7 file
0 голосов
/ 30 октября 2019

Попробуйте это регулярное выражение:

,\d{2}\,(.*),\"\S{25}\",\"\w{3}"

Это было сделано на основе ваших примеров. Цель просто захватить пятую колонну. Как @Jerry Jeremiah предложил использовать дату, которая всегда будет длиной 25 символов. Чтобы избежать несоответствия, я также учел 2 цифры, представленные до пятой, и 3 буквы / цифры после даты. Regex101v1

Мы также можем использовать «более сильное» регулярное выражение, ища точную дату совпадения

,\d{2}\,(.*),\"\d{4}-\d{2}-\d{2}\w\d{2}:\d{2}:\d{2}-\d{2}:\d{2}\",\"\w{3}"

Regex101v2

С помощью этих регулярных выражений вы сможете извлечь пятый столбец, используя группу. Чтобы углубиться в ваш вопрос, вы можете сделать это в bash:

regex='^(.*,[0-9]{2}\,")(.*)(",\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}\",\"[a-zA-Z]{3}".*$)'
while IFS= read -r line
do
    if [[ $line =~ $regex ]]
    then
        before=${BASH_REMATCH[1]}
        fifth=${BASH_REMATCH[2]}
        after=${BASH_REMATCH[3]}
        reworked_fifth="${fifth//\"}"
        echo ${before}${reworked_fifth}${after}
    else
        echo "Line didnt match the regex"
  fi
done < /my/file/path

Мне пришлось изменить регулярное выражение, так как мой bash не взял \d и \w. С этим ничего не нужно делать. Баш может справиться с этим один.

...