В текстовом файле я хочу удалить ,
(запятые), а также "
(кавычки) (только если двойные кавычки содержат числа, разделенные запятыми).
56,72,"12,34,54",x,y,"foo,a,b,bar"
Ожидаемый результат
56,72,123454,x,y,"foo,a,b,bar"
Примечание:Я показываю строку выше просто как пример. Мой текстовый файл содержит много строк, подобных приведенным выше, и числа, разделенные запятыми, присутствующие в двойных кавычках, должны различаться. То есть,
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
Ожидаемый результат:
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
В двойных кавычках есть n
ряд цифр, разделенных запятыми. А также оставьте двойные кавычки, которые содержат символы, как есть.
Мне нравится sed
инструмент для обработки текста. Буду рад, если вы опубликуете какое-либо sed
решение для этого.
решение1
Если с perl все в порядке, вот короткий (и, вероятно, быстрый, хотя и не обязательно простой :) ) способ сделать это:
perl -pe 's:"(\d[\d,]+)":$1=~y/,//dr:eg' file
Флаг e
оператора s:::
(который является просто другим способом записи s///
) заставляет замену рассматриваться как выражение, которое вычисляется каждый раз. Это выражение берет $1
захват из регулярного выражения (в котором уже отсутствуют кавычки) и переводит ( y///
, что также может быть записано как tr///
) его, удаляя ( /d
) все запятые. r
Флаг y
необходим для того, чтобы получить значение, которое будет переведенной строкой, а не количеством переводов.
Для тех, кто чувствует себя каким-то образом запятнанным Perl, вот эквивалент Python. Python на самом деле не является инструментом оболочки в одну строку, но иногда его можно уговорить сотрудничать. Следующий код можно записать в одну строку (в отличие от for
циклов, которые не могут быть написаны), но горизонтальная прокрутка делает его (еще более) нечитаемым:
python -c '
import re;
import sys;
r=re.compile("\"(\d+(,\d+)*)\"");
all(not sys.stdout.write(r.sub(lambda m:m.group(1).replace(",",""),l))
for l in sys.stdin)
' < file
решение2
Это (адаптировано изздесь) должен сделать то, что вам нужно, хотя вариант @rici на Perl гораздо проще:
$ sed -r ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;ta; s/""/","/g;
s/"([0-9]*)",?/\1,/g ' file
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454,
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
Объяснение
:a
: определить метку с именемa
.s/(("[0-9,]*",?)*"[0-9,]*),/\1/
: Это нужно разобрать- Прежде всего, используя эту конструкцию:
(foo(bar))
,\1
будетfoobar
и\2
будетbar
. "[0-9,]*",?
: соответствует 0 или более0-9
или,
, за которыми следует 0 или 1,
.("[0-9,]*",?)*
: соответствует 0 или более из указанных выше."[0-9,]*
: соответствует 0 или более символов0-9
или ,,
которые идут сразу после"
- Прежде всего, используя эту конструкцию:
ta;
: вернитесь к меткеa
и запустите сноваеслизамена прошла успешно.s/""/","/g;
: постобработка. Заменить""
на","
.s/"([0-9]*)",?/\1,/g
: удалите все кавычки вокруг чисел.
Это будет легче понять на другом примере:
$ echo '"1,2,3,4"' | sed -nr ':a;s/(("[0-9,]*",?)*"[0-9,]*),/\1/;p;ta;'
"1,2,34"
"1,234"
"1234"
"1234"
Таким образом, хотя вы можете найти число, которое находится сразу после кавычек, за которым следует запятая и еще одно число, соедините два числа вместе и повторяйте процесс до тех пор, пока это не станет возможным.
На этом этапе я считаю полезным привести цитату из info sed
раздела, описывающего расширенные функции, такие как метка, использованная выше (спасибо за находку @Braiam):
В большинстве случаев использование этих команд указывает на то, что вам, вероятно, лучше программировать на чем-то вроде `awk' или Perl.
решение3
Для CSV-данных я бы использовал язык с настоящим CSV-парсером. Например, с Ruby:
ruby -rcsv -pe '
row = CSV::parse_line($_).map {|e| e.delete!(",") if e =~ /^[\d,]+$/; e}
$_ = CSV::generate_line(row)
' <<END
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
END
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
решение4
С использованиемРаку(ранее известный как Perl_6)
~$ raku -pe 's:g/ \" ~ \" (\d+) ** 2..* % "," /{$0.join}/;' file
Пример ввода:
56,72,"12,34,54",x,y,"foo,a,b,bar"
56,92,"12,34",x,y,"foo,a,b,bar"
56,72,"12,34,54,78,76,54,67",x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar","12,34,54"
56,72,x,y,"foo,a,b,bar","12,34,54","45,57,84,92","bar,foo"
Пример вывода:
56,72,123454,x,y,"foo,a,b,bar"
56,92,1234,x,y,"foo,a,b,bar"
56,72,12345478765467,x,y,"foo,a,b,bar"
56,72,x,y,"foo,a,b,bar",123454
56,72,x,y,"foo,a,b,bar",123454,45578492,"bar,foo"
Raku — это язык программирования в семействе Perl с рядом мощных функций регулярных выражений. См. URL ниже для общего обзора этого ответа:
https://unix.stackexchange.com/a/722570/227738
В коде выше цифры распознаются, а встроенные запятые удаляются. Регулярное выражение использует тот факт, чтовложенные структурыможно обозначить с помощью новой нотации Раку ~ тильда (вложенной), которая \" ~ \" [\d+]
означает «одна или несколько цифр, заключенных в двойные кавычки».
Кроме того,повторяющиеся структурыможет быть обозначено новым модифицированным квантификатором Раку %
для повторяющихся структур. Обозначение [\d+] ** 2..* % "," означает "одну или несколько цифр, разделенных запятыми, ,
причем этот шаблон повторяется ** 2..*
два или более раз. [Если в синтаксисе есть конечный разделитель (например, запятая), используйте %%
вместо %
в синтаксисе].
Это только начало. CSV-файлы с альтернативными разделителями, встроенными переносами строк, встроенными запятыми, потенциально пустыми полями и т. д. действительно должны обрабатываться настоящим CSV-парсером, таким как Text::CSV
модуль Raku. Подробности см. по ссылкам ниже.
https://docs.raku.org/language/regexes
https://raku.land/github:Tux/Text::CSV
https://raku.org