Awk рассматривает двойную кавычку как один токен и игнорирует пространство между
Файл данных - data.txt:
ABC "I am ABC" 35 DESC
DEF "I am not ABC" 42 DESC
cat data.txt | awk '{print $2}'
приведет к "I" вместо строки, цитируемой
Как сделать awk так, чтобы он игнорировал пространство внутри цитаты и думал, что это один единственный токен?
Ответы
Ответ 1
Да, это можно сделать красиво в awk. Легко получить все поля без серьезных хаков.
(Этот пример работает как в One True Awk, так и в gawk.)
{
split($0, a, "\"")
$2 = a[2]
$3 = $(NF - 1)
$4 = $NF
print "and the fields are ", $1, "+", $2, "+", $3, "+", $4
}
Ответ 2
Попробуйте следующее:
$ cat data.txt | awk -F\" '{print $2}'
I am ABC
I am not ABC
Ответ 3
Я объединил функцию, которая перераспределяет $0 в массив с именем B. Пространства между двойными кавычками не действуют как разделители полей. Работает с любым количеством полей, комбинацией цитируемых и некотируемых. Здесь:
#!/usr/bin/gawk -f
# Resplit $0 into array B. Spaces between double quotes are not separators.
# Single quotes not handled. No escaping of double quotes.
function resplit( a, l, i, j, b, k, BNF) # all are local variables
{
l=split($0, a, "\"")
BNF=0
delete B
for (i=1;i<=l;++i)
{
if (i % 2)
{
k=split(a[i], b)
for (j=1;j<=k;++j)
B[++BNF] = b[j]
}
else
{
B[++BNF] = "\""a[i]"\""
}
}
}
{
resplit()
for (i=1;i<=length(B);++i)
print i ": " B[i]
}
Надеюсь, это поможет.
Ответ 4
Верхний ответ для этого вопроса работает только для строк с одним кавычным полем. Когда я нашел этот вопрос, мне понадобилось что-то, что могло бы работать для произвольного количества цитируемых полей.
В конце концов я встретил ответ Wintermute в другом потоке, и он дал хорошее обобщенное решение этой проблемы. Я только что изменил его, чтобы удалить кавычки. Обратите внимание, что при запуске нижеприведенной программы вам необходимо вызвать awk с -F\"
.
BEGIN { OFS = "" } {
for (i = 1; i <= NF; i += 2) {
gsub(/[ \t]+/, ",", $i)
}
print
}
Это работает, наблюдая, что каждый элемент в массиве будет внутри кавычек, когда вы разделяете "-character", и поэтому он заменяет пробел, разделяющий те, что не в кавычках с запятой.
Затем вы можете легко связать другой экземпляр awk для выполнения любой необходимой вам обработки (просто используйте переключатель разделителя полей еще раз, -F,
).
Обратите внимание, что это может сломаться, если первое поле цитируется - я его не тестировал. Если это так, то это должно быть легко исправить, добавив оператор if, начинающийся с 2, а не 1, если первым символом строки является ".
Ответ 5
Другой альтернативой может быть использование переменной FPAT
, которая определяет регулярное выражение, описывающее содержимое каждого поля.
Сохраните этот AWK script как parse.awk
:
#!/bin/awk -f
BEGIN {
FPAT = "([^ ]+)|(\"[^\"]+\")"
}
{
print $2
}
Сделайте его исполняемым с помощью chmod +x ./parse.awk
и проанализируйте файл данных как ./parse.awk data.txt
:
"I am ABC"
"I am not ABC"
Ответ 6
Хорошо, если вы действительно хотите все три поля, вы можете их получить, но для этого требуется много каналов:
$ cat data.txt | awk -F\" '{print $1 "," $2 "," $3}' | awk -F' ,' '{print $1 "," $2}' | awk -F', ' '{print $1 "," $2}' | awk -F, '{print $1 "," $2 "," $3}'
ABC,I am ABC,35
DEF,I am not ABC,42
На последнем канале у вас есть все три поля, чтобы делать все, что вам нужно.
Ответ 7
Вот что-то вроде того, что я, наконец, получил работу, более общую для моего проекта.
Обратите внимание, что он не использует awk.
someText="ABC \"I am ABC\" 35 DESC '1 23' testing 456"
putItemsInLines() {
local items=""
local firstItem="true"
while test $# -gt 0; do
if [ "$firstItem" == "true" ]; then
items="$1"
firstItem="false"
else
items="$items
$1"
fi
shift
done
echo "$items"
}
count=0
while read -r valueLine; do
echo "$count: $valueLine"
count=$(( $count + 1 ))
done <<< "$(eval putItemsInLines $someText)"
Какие выходы:
0: ABC
1: I am ABC
2: 35
3: DESC
4: 1 23
5: testing
6: 456