Вопрос или проблема
У меня есть файл, в котором мне нужно выяснить, пустая ли какая-то колонка. Файл разделен на части с помощью ;
Некоторые данные содержат &
в слове, а команда awk не работает с этим.
Допустим, у меня есть файл (dedup_company_output.txt
), который содержит следующие данные:
"c1";"c2";"c3";"c4";"c5"
900;"DAVIES TURNER & CO LTD";;315235;1
909;"DAVIES TURNER CO LTD";;444;1
909;"DAVIES TURNER CO LTD";;;1
Теперь в первой строке я не уверен, почему мы получаем &
вместо &
, и поэтому, когда я выполняю следующую команду, это дает мне неправильный результат.
awk -F';' '$4 == ""' dedup_company_output.txt> result.txt
В результате я получаю
900;"DAVIES TURNER & CO LTD";;315235;1
909;"DAVIES TURNER CO LTD";;;1
в то время как я ожидал только
909;"DAVIES TURNER CO LTD";;;1
Нужно ли мне что-то изменить в этой команде, чтобы включить &
как часть слова, или мне нужно изменить файл?
Проблема заключается в том, что вы имеете дело с “непростым” CSV файлом, где символ-разделитель может находиться внутри полей (которые затем будут заключены в кавычки).
В вашем примере на второй строке &
представлен как &
, и этот закрывающий ;
ошибочно интерпретируется awk как разделитель полей. Таким образом, для этой строки он считает "DAVIES TURNER &
вторым полем, CO LTD"
третьим, а четвертое пустым. Это дает ложное совпадение.
Если вы хотите использовать awk
, вам, вероятно, следует рассмотреть возможность изменения исходного файла, чтобы не экранировать специальные символы, или изменить разделитель полей на что-то отличное от ;
.
В качестве альтернативы вы можете использовать инструмент, такой как miller
, для работы с полноценными CSV файлами.
Используя mlr
:
$ mlr --csv -N --fs=";" filter '$4 == ""' file
$ mlr --csv --headerless-csv-output --fs=";" filter '$c4 == ""' file #Для фильтрации с использованием заголовков
Использование -N
для CSV файла добавит числовые заголовки, такие как 1,2 и т.д.
, как с --implicit-csv-header
, но -N
не печатает строку заголовка. С этим $4
поле может быть использовано так же, как в awk
.
Вторая команда использует заголовок $c4
для фильтрации. С --headerless-csv-output
она не выводит строку заголовка.
Как предложил @AdminBee, следует использовать инструмент, осведомленный о CSV.
&
не имеет значения, ;
внутри кавычек и является причиной сбоя вашего скрипта. Существует много способов справиться с этим, но самый простой способ, когда у вас только некоторые более ранние поля, которые могут быть в кавычках и содержать FS
, как в вашем случае, — это подсчитывать поля с конца, а не с начала, например, используя любой awk:
$ awk -F';' '$(NF-1) == ""' dedup_company_output.txt
909;"DAVIES TURNER CO LTD";;;1
Если этого недостаточно или вы хотите узнать больше о проблеме, то смотрите whats-the-most-robust-way-to-efficiently-parse-csv-using-awk для получения дополнительной информации.
Вы определили разделитель входных полей как ;
, но этот символ также появляется как данные, что заставляет awk
думать, что следующая строка действительно имеет 6 полей:
900;"DAVIES TURNER & CO LTD";;315235;1
^ ^ ^^ ^
ЗАМЕТКИ:
awk
видит 5;
символов (а не 4 как в других строках)awk
видит 3-е, 4-е и 5-е поля какCO LTD"
,>пусто<
и325235
, соответственно- поскольку 4-е поле
>пусто<
($4==""
), эта строка появляется в вашем выводе
Если вы используете GNU awk 4.0+
, вы можете использовать переменную FPAT
, чтобы определять поля по содержимому (в отличие от -F
, который определяет поля по разделителю). В этом случае вы хотите сказать (g)awk
, что поля разделены точками с запятой и обёрнуты в необязательные двойные кавычки, одновременно позволяя точке с запятой появляться как данные.
Вот одна идея для awk
для этого случая:
awk '
BEGIN { FPAT = "([^;]+)|(\"[^\"]+\")" }
$4 == ""
' input.txt
#######
# или как однострочная команда
awk 'BEGIN {FPAT = "([^;]+)|(\"[^\"]+\")"} $4==""' input.txt
Это создаёт:
909;"DAVIES TURNER CO LTD";;;1
Ответ или решение
Когда вы сталкиваетесь с проблемами в работе с утилитой awk
, особенно при обработке данных, содержащих специальные символы и кавычки, важно учитывать несколько факторов.
Проблема
Вы упомянули, что используете файл с разделителем ;
, в котором некоторые поля могут содержать сам разделитель, если они обернуты в кавычки. В вашем конкретном случае строка, содержащая &
, обрабатывается как часть данных, но приводит к неожиданному количеству полей. Как следствие, команда awk -F';' '$4 == ""' dedup_company_output.txt
возвращает больше строк, чем вы ожидали.
Анализ данных
В вашем файле есть две строки, которые приводят к путанице:
900;"DAVIES TURNER &AMP; CO LTD";;315235;1
909;"DAVIES TURNER CO LTD";;;1
Первое поле обрабатывается неверно из-за того, что awk
видит нечетное количество ;
, а именно 5, что создаёт дополнительное поле в записи. Это происходит, потому что awk
не понимает, что ;
внутри кавычек является частью данных, а не разделителем.
Решение
Для решения этой проблемы вы можете использовать переменную FPAT
, чтобы более точно указать, что является полем, а что является разделителем. Например, вы можете использовать следующий однострочник awk
, чтобы определить поля:
awk 'BEGIN { FPAT = "([^;]+)|(\"[^\"]+\")" } $4 == ""' dedup_company_output.txt
Пояснение:
BEGIN { FPAT = "([^;]+)|(\"[^\"]+\")" }
– Это устанавливает паттерн для разбиения полей, который позволяетawk
учитывать как текстовые поля без кавычек, так и текстовые поля с кавычками.$4 == ""
– Это условие проверяет, пустое ли четвертое поле.
Альтернативный подход
Если вы хотите использовать более специализированный инструмент для работы с CSV, вы можете попробовать mlr
(Miller):
mlr --csv -N --fs=";" filter '$4 == ""' dedup_company_output.txt
или, используя заголовки:
mlr --csv --headerless-csv-output --fs=";" filter '$c4 == ""' dedup_company_output.txt
Заключение
Использование стандартных инструментов, таких как awk
, может быть неэффективным при работе с более сложными данными, особенно когда поля могут содержать разделители. Альтернативные инструменты, такие как mlr
, предоставляют более мощные возможности для разбора CSV-документов. Ваша конечная цель должна заключаться в том, чтобы использовать подходящие инструменты в зависимости от сложности вашего конкретного сценария обработки данных.