Как манипулировать CSV-файлом с помощью sed или awk?

23

Как я могу сделать следующее для файла CSV с помощью sedили awk?

  • Удалить столбец
  • Дублировать столбец
  • Переместить столбец

У меня есть большая таблица с более чем 200 строками, и я не очень знаком с ней sed.

Биной ​​Бабу
источник
1
Крест опубликован на AskUbuntu
энзотиб
@enzotib можешь опубликовать ссылку?
n0pe
@MaxMackie askubuntu.com/questions/88142/… . Я не могу получить мод там в этот час, поэтому я пометил его, попросив мигрировать, если они захотят; у него уже есть принятый ответ, так что я не уверен, будут ли они
Майкл Мрозек
@MichaelMrozek, хммм, что обычно происходит в этих ситуациях? Мы просто храним дубликаты?
n0pe
1
Если вам не нужно работать в системе, которая имеет только базовые инструменты, см. Есть ли надежный инструмент командной строки для обработки CSV-файлов?
Жиль "ТАК - перестань быть злым"

Ответы:

7

Помимо того, как вырезать и переупорядочивать поля (описанные в других ответах), существует проблема необычных полей CSV.

Если ваши данные попадают в эту «причудливую» категорию, об этом может позаботиться небольшая часть предварительной и последующей фильтрации. Фильтры , указанные ниже , требуют символы \x01, \x02, \x03, \x04чтобы не появляться в любом месте в ваших данных.

Вот фильтры, обернутые вокруг простого awkдампа поля.

Примечание: field-Five имеет недопустимый / неполный макет «заключенного в кавычки», но он доброкачествен в конце строки (в зависимости от синтаксического анализатора CSV). Но, конечно, это приведет к проблематичным неисследованным результатам, если его поменять с текущей позиции в конце строки .

Обновить; user121196 указал на ошибку, когда запятая предшествует завершающей кавычке. Вот исправление.

Данные

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

Код

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

Выход:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Вот предварительный фильтр , дополненный комментариями. Постфильтр только разворот . , ,
\x01\x02\x03\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
Peter.O
источник
Как бы вы удалили n-й столбец на основе этого фильтра?
user121196
@ user121196 - Как упомянуто в его первом предложении, этот ответ показывает способ сделать данные CSV более последовательными .. например. временно заменяя запятую, заключенную в кавычки, нейтральным символом токена ... и затем возвращая ее обратно в запятую после перемещения / вырезания / удаления. Опять же, как уже упоминалось, шаг перемещения / вырезания / удаления заменяется простым полевым дампом awk .
Peter.O
1
в данном случае это не так: «15111 N. Hayden Rd., Ste 160», «»
user121196
@ user121196: Спасибо, что указали на это. Я обновил ответ с исправлением.
Peter.O
15

Это зависит от того, использует ли ваш CSV-файл запятые только для разделителей, или если у вас есть сумасшествие, подобное:

поле одно, "поле, два", поле три

Это предполагает, что вы используете простой файл CSV:

Удаление столбца

Вы можете избавиться от одного столбца многими способами; Я использовал столбец 2 в качестве примера. Возможно, проще всего использовать этот метод cut, который позволяет вам указать разделитель -dи какие поля вы хотите напечатать -f; это говорит о том, что нужно разделить запятые и выходные поля 1 и поля 3 до конца:

$ cut -d, -f1,3- /path/to/your/file

Если вам действительно нужно использовать sed, вы можете написать регулярное выражение, которое совпадает с первыми n-1полями, nполем th и остальными, и пропустить вывод nth (здесь n2, поэтому первая группа соответствует 1time :) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Есть несколько способов сделать это awk, но ни один из них не особенно элегантен. Вы можете использовать forцикл, но иметь дело с конечной запятой - боль; игнорируя, что это будет что-то вроде:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Мне легче вывести поле 1, а затем использовать, substrчтобы вывести все после поля 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Это раздражает для колонок дальше, хотя

Дублирование столбца

В sedсущности это то же самое выражение, что и раньше, но вы также захватываете целевой столбец и включаете эту группу несколько раз в замену:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

В awkцикле for это будет что-то вроде (опять же игнорируя запятую):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

substrОбразом:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl предложил лучший метод в своем ответе )

Перемещение столбца

Я думаю, что sedрешение естественно следует из других, но оно начинает смехотворно долго

Михаил Мрозек
источник
Это загруженный ответ! +1 :)
Джайпал Сингх
Смешно долго? Тьфу !
Жиль "ТАК - перестань быть злым"
12

awkэто ваша лучшая ставка. awkпечатает поля по номеру, так что ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Чтобы удалить столбец, не печатайте его:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

Чтобы изменить порядок:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Перенаправить на выходной файл.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk Можно также отформатировать вывод.

Вывод в формате Awk

пантера
источник
Так как это CSV, вам также понадобится BEGIN { FS=","; OFS=","; }.
1
Я думаю, что даже FS = OFS = "," будет работать.
5

Дан файл с разделителями пробелами в следующем формате:

1 2 3 4 5

Вы можете удалить поле 2 с помощью awk следующим образом:

awk '{ sub($2,""); print}' file

который возвращается

1  3 4 5

Замените столбец 2 на столбец n, где это необходимо.

Чтобы дублировать столбец 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

который возвращается

1 2 2 3 4 5

Чтобы переключить столбцы 2 и 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

который возвращается

1 3 2 4 5

Как правило, awk хорошо разбирается в понятии полей . Если вы имеете дело с CSV, а не с пробелом, вы можете просто использовать

awk -F,

чтобы определить ваше поле как запятую, а не пробел (который по умолчанию). В Интернете есть несколько хороших ресурсов awk, один из которых я перечислю в качестве источника ниже.

Источник для № 3

tcdyl
источник
Я не очень разбираюсь в этом awk, но, похоже, он выводит разделенный пробелами, даже если разделитель полей (разделитель ,полей просто контролирует, как он обрабатывает ввод)
Майкл Мрозек
@MichaelMrozek: да, переменная OFS awk управляет разделителем выходного поля.
энзотиб
Да, и, как я упоминаю в своем ответе, вы можете передать опцию -F в awk, чтобы изменить разделитель (например, -F,)
tcdyl
0

Это будет работать для удаления

awk '{$2="";$0=$0;$1=$1}1'

вход

a b c d

Выход

a c d
Стивен Пенни
источник